diff --git a/.gitignore b/.gitignore index 00b87bf0..26622bca 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,7 @@ # RustRover / IntelliJ .idea/ *.iml +.junie # VS Code .vscode/ diff --git a/crates/console/prometeu-firmware/src/firmware/firmware.rs b/crates/console/prometeu-firmware/src/firmware/firmware.rs index 3d6bf562..cd9233dc 100644 --- a/crates/console/prometeu-firmware/src/firmware/firmware.rs +++ b/crates/console/prometeu-firmware/src/firmware/firmware.rs @@ -308,12 +308,11 @@ mod tests { other => panic!("expected AppCrashes state, got {:?}", other), } - let task = firmware.os.task_manager.get(task_id).expect("task should exist"); - let process = - firmware.os.process_manager.get(task.process_id).expect("process should exist"); - - assert_eq!(task.state, TaskState::Crashed); - assert_eq!(process.state, ProcessState::Crashed); + assert_eq!(firmware.os.lifecycle().task_state(task_id), Some(TaskState::Crashed)); + assert_eq!( + firmware.os.lifecycle().process_state_for_task(task_id), + Ok(ProcessState::Crashed) + ); } #[test] @@ -351,8 +350,8 @@ mod tests { firmware.tick(&signals, &mut hardware); assert!(matches!(firmware.state, FirmwareState::SystemRunning(_))); - assert!(firmware.os.window_manager.focused.is_some()); - assert_eq!(firmware.os.window_manager.windows.len(), 1); + assert!(firmware.os.window().focused_window().is_some()); + assert_eq!(firmware.os.window().window_count(), 1); } #[test] @@ -364,11 +363,11 @@ mod tests { firmware.load_cartridge(valid_cartridge(AppMode::Shell)); firmware.tick(&signals, &mut hardware); - let tick_index_before_system_update = firmware.os.vm_runtime.tick_index; + let tick_index_before_system_update = firmware.os.vm().tick_index(); firmware.tick(&signals, &mut hardware); assert!(matches!(firmware.state, FirmwareState::SystemRunning(_))); - assert_eq!(firmware.os.vm_runtime.tick_index, tick_index_before_system_update + 1); + assert_eq!(firmware.os.vm().tick_index(), tick_index_before_system_update + 1); } #[test] diff --git a/crates/console/prometeu-firmware/src/firmware/firmware_step_game_running.rs b/crates/console/prometeu-firmware/src/firmware/firmware_step_game_running.rs index 43d7ae63..5bd9579f 100644 --- a/crates/console/prometeu-firmware/src/firmware/firmware_step_game_running.rs +++ b/crates/console/prometeu-firmware/src/firmware/firmware_step_game_running.rs @@ -19,7 +19,7 @@ impl GameRunningStep { } pub fn on_update(&mut self, ctx: &mut PrometeuContext) -> Option { - let Some(task_state) = ctx.os.task_manager.get(self.task_id).map(|task| task.state) else { + let Some(task_state) = ctx.os.lifecycle().task_state(self.task_id) else { // TODO: is it panic? let report = CrashReport::VmPanic { message: "task is gone, panic!".to_string(), pc: None }; @@ -32,18 +32,18 @@ impl GameRunningStep { message: "should be running as foreground, panic!".to_string(), pc: None, }; - let _ = ctx.os.crash_task(self.task_id, Some(&report)); + let _ = ctx.os.lifecycle().crash_task(self.task_id, Some(&report)); return Some(FirmwareState::AppCrashes(AppCrashesStep { report })); } - let result = ctx.os.tick_vm(ctx.vm, ctx.signals, ctx.hw); + let result = ctx.os.vm().tick(ctx.vm, ctx.signals, ctx.hw); - if !ctx.os.vm_runtime.logical_frame_active { + if !ctx.os.vm().logical_frame_active() { ctx.hw.gfx_mut().present(); } if let Some(report) = result { - let _ = ctx.os.crash_task(self.task_id, Some(&report)); + let _ = ctx.os.lifecycle().crash_task(self.task_id, Some(&report)); return Some(FirmwareState::AppCrashes(AppCrashesStep { report })); } diff --git a/crates/console/prometeu-firmware/src/firmware/firmware_step_load_cartridge.rs b/crates/console/prometeu-firmware/src/firmware/firmware_step_load_cartridge.rs index fdc4ae7a..28b21a63 100644 --- a/crates/console/prometeu-firmware/src/firmware/firmware_step_load_cartridge.rs +++ b/crates/console/prometeu-firmware/src/firmware/firmware_step_load_cartridge.rs @@ -34,7 +34,7 @@ impl LoadCartridgeStep { self.cartridge.assets.clone(), ); - self.init_error = ctx.os.initialize_vm(ctx.vm, &self.cartridge).err(); + self.init_error = ctx.os.vm().initialize(ctx.vm, &self.cartridge).err(); } pub fn on_update(&mut self, ctx: &mut PrometeuContext) -> Option { @@ -43,20 +43,24 @@ impl LoadCartridgeStep { } if self.cartridge.app_mode == AppMode::Shell { - let id = ctx.os.window_manager.add_window( + let id = ctx.os.window().add_window( self.cartridge.title.clone(), Rect { x: 40, y: 20, w: 240, h: 140 }, Color::WHITE, ); - ctx.os.window_manager.set_focus(id); + ctx.os.window().set_focus(id); - let task_id = - ctx.os.create_vm_shell_task(self.cartridge.app_id, self.cartridge.title.clone()); + let task_id = ctx + .os + .sessions() + .create_vm_shell_task(self.cartridge.app_id, self.cartridge.title.clone()); return Some(FirmwareState::SystemRunning(SystemRunningStep::new(task_id))); } - let task_id = - ctx.os.create_vm_game_task(self.cartridge.app_id, self.cartridge.title.clone()); + let task_id = ctx + .os + .sessions() + .create_vm_game_task(self.cartridge.app_id, self.cartridge.title.clone()); Some(FirmwareState::GameRunning(GameRunningStep::new(task_id))) } diff --git a/crates/console/prometeu-firmware/src/firmware/firmware_step_reset.rs b/crates/console/prometeu-firmware/src/firmware/firmware_step_reset.rs index 20cde51d..586e3026 100644 --- a/crates/console/prometeu-firmware/src/firmware/firmware_step_reset.rs +++ b/crates/console/prometeu-firmware/src/firmware/firmware_step_reset.rs @@ -9,7 +9,7 @@ pub struct ResetStep; impl ResetStep { pub fn on_enter(&mut self, ctx: &mut PrometeuContext) { ctx.os.log(LogLevel::Info, LogSource::Pos, 0, "Firmware Reset".to_string()); - ctx.os.vm_runtime.reset(ctx.vm); + ctx.os.vm().reset(ctx.vm); } pub fn on_update(&mut self, ctx: &mut PrometeuContext) -> Option { diff --git a/crates/console/prometeu-firmware/src/firmware/firmware_step_system_running.rs b/crates/console/prometeu-firmware/src/firmware/firmware_step_system_running.rs index bcbccdf8..25bfc402 100644 --- a/crates/console/prometeu-firmware/src/firmware/firmware_step_system_running.rs +++ b/crates/console/prometeu-firmware/src/firmware/firmware_step_system_running.rs @@ -20,7 +20,7 @@ impl SystemRunningStep { pub fn on_update(&mut self, ctx: &mut PrometeuContext) -> Option { let outcome = ctx.hub.update_system_profile(ctx.os, ctx.vm, ctx.signals, ctx.hw); if let Some(report) = outcome.crash { - let _ = ctx.os.crash_task(self.task_id, Some(&report)); + let _ = ctx.os.lifecycle().crash_task(self.task_id, Some(&report)); return Some(FirmwareState::AppCrashes(AppCrashesStep { report })); } diff --git a/crates/console/prometeu-system/src/os/facades/fs.rs b/crates/console/prometeu-system/src/os/facades/fs.rs new file mode 100644 index 00000000..8d40665b --- /dev/null +++ b/crates/console/prometeu-system/src/os/facades/fs.rs @@ -0,0 +1,26 @@ +use crate::fs::{FsBackend, FsState}; +use crate::os::SystemOS; + +pub struct FsFacade<'a> { + pub(in crate::os) os: &'a mut SystemOS, +} + +impl<'a> FsFacade<'a> { + pub fn needs_mount(&self) -> bool { + matches!(self.os.fs_state, FsState::Unmounted | FsState::Error(_)) + } + + pub fn mount(&mut self, backend: Box) { + self.os.vm_runtime.mount_fs( + &mut self.os.log_service, + &mut self.os.fs, + &mut self.os.fs_state, + backend, + ); + } + + pub fn unmount(&mut self) { + self.os.fs.unmount(); + self.os.fs_state = FsState::Unmounted; + } +} diff --git a/crates/console/prometeu-system/src/os/facades/lifecycle.rs b/crates/console/prometeu-system/src/os/facades/lifecycle.rs new file mode 100644 index 00000000..7fe687b8 --- /dev/null +++ b/crates/console/prometeu-system/src/os/facades/lifecycle.rs @@ -0,0 +1,94 @@ +use crate::CrashReport; +use crate::os::SystemOS; +use crate::os::{LifecycleError, LifecycleOperation}; +use crate::process::{ProcessId, ProcessState}; +use crate::task::{TaskId, TaskState}; + +pub struct LifecycleFacade<'a> { + pub(in crate::os) os: &'a mut SystemOS, +} + +impl<'a> LifecycleFacade<'a> { + pub fn task_state(&self, task_id: TaskId) -> Option { + self.os.task_manager.get(task_id).map(|task| task.state) + } + + pub fn process_state_for_task(&self, task_id: TaskId) -> Result { + let process_id = process_id_for_task(self.os, task_id)?; + self.os + .process_manager + .get(process_id) + .map(|process| process.state) + .ok_or(LifecycleError::ProcessNotFound(process_id)) + } + + pub fn set_foreground_task(&mut self, task_id: TaskId) -> Result<(), LifecycleError> { + let process_id = process_id_for_task(self.os, task_id)?; + + self.os.task_manager.set_foreground(task_id); + self.os.process_manager.mark_running(process_id); + + Ok(()) + } + + pub fn suspend_task(&mut self, task_id: TaskId) -> Result<(), LifecycleError> { + let process_id = process_id_for_task(self.os, task_id)?; + + self.os.task_manager.mark_suspended(task_id); + self.os.process_manager.mark_suspended(process_id); + + Ok(()) + } + + pub fn resume_task(&mut self, task_id: TaskId) -> Result<(), LifecycleError> { + let task = + self.os.task_manager.get(task_id).ok_or(LifecycleError::TaskNotFound(task_id))?; + + if task.state != TaskState::Suspended { + return Err(LifecycleError::InvalidTransition { + task_id, + from: task.state, + operation: LifecycleOperation::Resume, + }); + } + + let process_id = process_id_for_task(self.os, task_id)?; + + self.os.task_manager.set_foreground(task_id); + self.os.process_manager.mark_running(process_id); + + Ok(()) + } + + pub fn close_task(&mut self, task_id: TaskId) -> Result<(), LifecycleError> { + let process_id = process_id_for_task(self.os, task_id)?; + + self.os.task_manager.close_task(task_id); + self.os.process_manager.mark_stopped(process_id); + + Ok(()) + } + + pub fn crash_task( + &mut self, + task_id: TaskId, + _report: Option<&CrashReport>, + ) -> Result<(), LifecycleError> { + let process_id = process_id_for_task(self.os, task_id)?; + + self.os.task_manager.mark_crashed(task_id); + self.os.process_manager.mark_crashed(process_id); + + Ok(()) + } +} + +fn process_id_for_task(os: &SystemOS, task_id: TaskId) -> Result { + let task = os.task_manager.get(task_id).ok_or(LifecycleError::TaskNotFound(task_id))?; + + if !os.process_manager.contains(task.process_id) { + return Err(LifecycleError::ProcessNotFound(task.process_id)); + } + + Ok(task.process_id) +} diff --git a/crates/console/prometeu-system/src/os/facades/mod.rs b/crates/console/prometeu-system/src/os/facades/mod.rs new file mode 100644 index 00000000..be54cf66 --- /dev/null +++ b/crates/console/prometeu-system/src/os/facades/mod.rs @@ -0,0 +1,11 @@ +mod fs; +mod lifecycle; +mod sessions; +mod vm; +mod window; + +pub use fs::FsFacade; +pub use lifecycle::LifecycleFacade; +pub use sessions::SessionsFacade; +pub use vm::VmFacade; +pub use window::WindowFacade; diff --git a/crates/console/prometeu-system/src/os/facades/sessions.rs b/crates/console/prometeu-system/src/os/facades/sessions.rs new file mode 100644 index 00000000..42f8f3fe --- /dev/null +++ b/crates/console/prometeu-system/src/os/facades/sessions.rs @@ -0,0 +1,44 @@ +use crate::os::SystemOS; +use crate::task::TaskId; + +pub struct SessionsFacade<'a> { + pub(in crate::os) os: &'a mut SystemOS, +} + +impl<'a> SessionsFacade<'a> { + pub fn create_vm_game_task(&mut self, app_id: u32, title: impl Into) -> TaskId { + let title = title.into(); + let process_id = self.os.process_manager.spawn_vm_game(app_id, title.clone()); + let task_id = self.os.task_manager.create_game_task(process_id, app_id, title); + self.os + .lifecycle() + .set_foreground_task(task_id) + .expect("newly created game task should have an associated process"); + + task_id + } + + pub fn create_vm_shell_task(&mut self, app_id: u32, title: impl Into) -> TaskId { + let title = title.into(); + let process_id = self.os.process_manager.spawn_vm_shell(app_id, title.clone()); + let task_id = self.os.task_manager.create_shell_task(process_id, app_id, title); + self.os + .lifecycle() + .set_foreground_task(task_id) + .expect("newly created shell task should have an associated process"); + + task_id + } + + pub fn create_native_shell_task(&mut self, app_id: u32, title: impl Into) -> TaskId { + let title = title.into(); + let process_id = self.os.process_manager.spawn_native_shell(app_id, title.clone()); + let task_id = self.os.task_manager.create_shell_task(process_id, app_id, title); + self.os + .lifecycle() + .set_foreground_task(task_id) + .expect("newly created native shell task should have an associated process"); + + task_id + } +} diff --git a/crates/console/prometeu-system/src/os/facades/vm.rs b/crates/console/prometeu-system/src/os/facades/vm.rs new file mode 100644 index 00000000..4f38d314 --- /dev/null +++ b/crates/console/prometeu-system/src/os/facades/vm.rs @@ -0,0 +1,121 @@ +use crate::CrashReport; +use crate::os::SystemOS; +use prometeu_hal::cartridge::{AppMode, Cartridge}; +use prometeu_hal::telemetry::{CertificationConfig, TelemetryFrame}; +use prometeu_hal::{HardwareBridge, InputSignals}; +use prometeu_vm::VirtualMachine; +use std::sync::atomic::Ordering; + +pub struct VmFacade<'a> { + pub(in crate::os) os: &'a mut SystemOS, +} + +impl<'a> VmFacade<'a> { + pub fn initialize( + &mut self, + vm: &mut VirtualMachine, + cartridge: &Cartridge, + ) -> Result<(), CrashReport> { + self.os.clear_cartridge_service_state(); + self.os.vm_runtime.initialize_vm(&mut self.os.log_service, vm, cartridge) + } + + pub fn tick( + &mut self, + vm: &mut VirtualMachine, + signals: &InputSignals, + hw: &mut dyn HardwareBridge, + ) -> Option { + self.os.vm_runtime.tick( + &mut self.os.log_service, + &mut self.os.fs, + &mut self.os.fs_state, + &mut self.os.memcard, + &mut self.os.open_files, + &mut self.os.next_handle, + vm, + signals, + hw, + ) + } + + pub fn reset(&mut self, vm: &mut VirtualMachine) { + self.os.vm_runtime.reset(vm); + } + + pub fn debug_step_instruction( + &mut self, + vm: &mut VirtualMachine, + hw: &mut dyn HardwareBridge, + ) -> Option { + self.os.vm_runtime.debug_step_instruction(&mut self.os.log_service, vm, hw) + } + + pub fn paused(&self) -> bool { + self.os.vm_runtime.paused + } + + pub fn set_paused(&mut self, paused: bool) { + self.os.vm_runtime.paused = paused; + } + + pub fn set_inspection_active(&mut self, active: bool) { + self.os.vm_runtime.inspection_active = active; + } + + pub fn request_debug_step(&mut self) { + self.os.vm_runtime.debug_step_request = true; + } + + pub fn debug_step_requested(&self) -> bool { + self.os.vm_runtime.debug_step_request + } + + pub fn logical_frame_active(&self) -> bool { + self.os.vm_runtime.logical_frame_active + } + + pub fn tick_index(&self) -> u64 { + self.os.vm_runtime.tick_index + } + + pub fn logical_frame_index(&self) -> u64 { + self.os.vm_runtime.logical_frame_index + } + + pub fn last_frame_cpu_time_us(&self) -> u64 { + self.os.vm_runtime.last_frame_cpu_time_us + } + + pub fn completed_logical_frames(&self) -> u64 { + self.os.vm_runtime.atomic_telemetry.completed_logical_frames.load(Ordering::Relaxed).into() + } + + pub fn telemetry_snapshot(&self) -> TelemetryFrame { + self.os.vm_runtime.atomic_telemetry.snapshot() + } + + pub fn cert_config(&self) -> &CertificationConfig { + &self.os.vm_runtime.certifier.config + } + + pub fn last_crash_report(&self) -> Option<&CrashReport> { + self.os.vm_runtime.last_crash_report.as_ref() + } + + pub fn current_app_id(&self) -> u32 { + self.os.vm_runtime.current_app_id + } + + pub fn current_cartridge_title(&self) -> String { + self.os.vm_runtime.current_cartridge_title.clone() + } + + pub fn current_cartridge_app_version(&self) -> String { + self.os.vm_runtime.current_cartridge_app_version.clone() + } + + pub fn current_cartridge_app_mode(&self) -> AppMode { + self.os.vm_runtime.current_cartridge_app_mode + } +} diff --git a/crates/console/prometeu-system/src/os/facades/window.rs b/crates/console/prometeu-system/src/os/facades/window.rs new file mode 100644 index 00000000..c22e76e0 --- /dev/null +++ b/crates/console/prometeu-system/src/os/facades/window.rs @@ -0,0 +1,37 @@ +use crate::os::SystemOS; +use prometeu_hal::color::Color; +use prometeu_hal::window::{Rect, Window, WindowId}; + +pub struct WindowFacade<'a> { + pub(in crate::os) os: &'a mut SystemOS, +} + +impl<'a> WindowFacade<'a> { + pub fn add_window(&mut self, title: String, viewport: Rect, color: Color) -> WindowId { + self.os.window_manager.add_window(title, viewport, color) + } + + pub fn set_focus(&mut self, id: WindowId) { + self.os.window_manager.set_focus(id); + } + + pub fn focused_window(&self) -> Option { + self.os.window_manager.focused + } + + pub fn close_window(&mut self, id: WindowId) { + self.os.window_manager.remove_window(id); + } + + pub fn remove_all_windows(&mut self) { + self.os.window_manager.remove_all_windows(); + } + + pub fn windows(&self) -> &[Window] { + &self.os.window_manager.windows + } + + pub fn window_count(&self) -> usize { + self.os.window_manager.windows.len() + } +} diff --git a/crates/console/prometeu-system/src/os/mod.rs b/crates/console/prometeu-system/src/os/mod.rs index 5a2e6d76..a438d5cc 100644 --- a/crates/console/prometeu-system/src/os/mod.rs +++ b/crates/console/prometeu-system/src/os/mod.rs @@ -1,5 +1,7 @@ +mod facades; mod lifecycle; mod system_os; +pub use facades::{FsFacade, LifecycleFacade, SessionsFacade, VmFacade, WindowFacade}; pub use lifecycle::{LifecycleError, LifecycleOperation}; pub use system_os::SystemOS; diff --git a/crates/console/prometeu-system/src/os/system_os.rs b/crates/console/prometeu-system/src/os/system_os.rs index ae536400..d804409b 100644 --- a/crates/console/prometeu-system/src/os/system_os.rs +++ b/crates/console/prometeu-system/src/os/system_os.rs @@ -1,31 +1,26 @@ -use crate::CrashReport; use crate::VirtualMachineRuntime; -use crate::fs::{FsBackend, FsState, VirtualFS}; -use crate::os::{LifecycleError, LifecycleOperation}; +use crate::fs::{FsState, VirtualFS}; +use crate::os::{FsFacade, LifecycleFacade, SessionsFacade, VmFacade, WindowFacade}; use crate::process::ProcessManager; use crate::services::memcard::MemcardService; use crate::services::window_manager::WindowManager; use crate::task::TaskManager; -use crate::task::{TaskId, TaskState}; -use prometeu_hal::cartridge::Cartridge; -use prometeu_hal::log::{LogLevel, LogService, LogSource}; +use prometeu_hal::log::{LogEvent, LogLevel, LogService, LogSource}; use prometeu_hal::telemetry::CertificationConfig; -use prometeu_hal::{HardwareBridge, InputSignals}; -use prometeu_vm::VirtualMachine; use std::collections::HashMap; use std::sync::Arc; pub struct SystemOS { - pub vm_runtime: VirtualMachineRuntime, - pub process_manager: ProcessManager, - pub task_manager: TaskManager, - pub window_manager: WindowManager, - pub log_service: LogService, - pub fs: VirtualFS, - pub fs_state: FsState, - pub memcard: MemcardService, - pub open_files: HashMap, - pub next_handle: u32, + pub(super) vm_runtime: VirtualMachineRuntime, + pub(super) process_manager: ProcessManager, + pub(super) task_manager: TaskManager, + pub(super) window_manager: WindowManager, + pub(super) log_service: LogService, + pub(super) fs: VirtualFS, + pub(super) fs_state: FsState, + pub(super) memcard: MemcardService, + pub(super) open_files: HashMap, + pub(super) next_handle: u32, } impl SystemOS { @@ -56,156 +51,47 @@ impl SystemOS { self.vm_runtime.log(&mut self.log_service, level, source, tag, msg); } - pub fn initialize_vm( - &mut self, - vm: &mut VirtualMachine, - cartridge: &Cartridge, - ) -> Result<(), CrashReport> { - self.clear_cartridge_service_state(); - self.vm_runtime.initialize_vm(&mut self.log_service, vm, cartridge) + pub fn recent_logs(&self, n: usize) -> Vec { + self.log_service.get_recent(n) } - pub fn tick_vm( - &mut self, - vm: &mut VirtualMachine, - signals: &InputSignals, - hw: &mut dyn HardwareBridge, - ) -> Option { - self.vm_runtime.tick( - &mut self.log_service, - &mut self.fs, - &mut self.fs_state, - &mut self.memcard, - &mut self.open_files, - &mut self.next_handle, - vm, - signals, - hw, - ) + pub fn logs_after(&self, seq: u64) -> Vec { + self.log_service.get_after(seq) } - pub fn mount_fs(&mut self, backend: Box) { - self.vm_runtime.mount_fs(&mut self.log_service, &mut self.fs, &mut self.fs_state, backend); + pub fn lifecycle(&mut self) -> LifecycleFacade<'_> { + LifecycleFacade { os: self } } - pub fn unmount_fs(&mut self) { - self.fs.unmount(); - self.fs_state = FsState::Unmounted; + pub fn sessions(&mut self) -> SessionsFacade<'_> { + SessionsFacade { os: self } } - fn clear_cartridge_service_state(&mut self) { + pub fn vm(&mut self) -> VmFacade<'_> { + VmFacade { os: self } + } + + pub fn fs(&mut self) -> FsFacade<'_> { + FsFacade { os: self } + } + + pub fn window(&mut self) -> WindowFacade<'_> { + WindowFacade { os: self } + } + + pub(super) fn clear_cartridge_service_state(&mut self) { self.open_files.clear(); self.next_handle = 1; self.memcard.clear_all_staging(); } - - pub fn create_vm_game_task(&mut self, app_id: u32, title: impl Into) -> TaskId { - let title = title.into(); - let process_id = self.process_manager.spawn_vm_game(app_id, title.clone()); - let task_id = self.task_manager.create_game_task(process_id, app_id, title); - self.set_foreground_task(task_id) - .expect("newly created game task should have an associated process"); - - task_id - } - - pub fn create_vm_shell_task(&mut self, app_id: u32, title: impl Into) -> TaskId { - let title = title.into(); - let process_id = self.process_manager.spawn_vm_shell(app_id, title.clone()); - let task_id = self.task_manager.create_shell_task(process_id, app_id, title); - self.set_foreground_task(task_id) - .expect("newly created shell task should have an associated process"); - - task_id - } - - pub fn create_native_shell_task(&mut self, app_id: u32, title: impl Into) -> TaskId { - let title = title.into(); - let process_id = self.process_manager.spawn_native_shell(app_id, title.clone()); - let task_id = self.task_manager.create_shell_task(process_id, app_id, title); - self.set_foreground_task(task_id) - .expect("newly created native shell task should have an associated process"); - - task_id - } - - pub fn set_foreground_task(&mut self, task_id: TaskId) -> Result<(), LifecycleError> { - let process_id = self.process_id_for_task(task_id)?; - - self.task_manager.set_foreground(task_id); - self.process_manager.mark_running(process_id); - - Ok(()) - } - - pub fn suspend_task(&mut self, task_id: TaskId) -> Result<(), LifecycleError> { - let process_id = self.process_id_for_task(task_id)?; - - self.task_manager.mark_suspended(task_id); - self.process_manager.mark_suspended(process_id); - - Ok(()) - } - - pub fn resume_task(&mut self, task_id: TaskId) -> Result<(), LifecycleError> { - let task = self.task_manager.get(task_id).ok_or(LifecycleError::TaskNotFound(task_id))?; - - if task.state != TaskState::Suspended { - return Err(LifecycleError::InvalidTransition { - task_id, - from: task.state, - operation: LifecycleOperation::Resume, - }); - } - - let process_id = self.process_id_for_task(task_id)?; - - self.task_manager.set_foreground(task_id); - self.process_manager.mark_running(process_id); - - Ok(()) - } - - pub fn close_task(&mut self, task_id: TaskId) -> Result<(), LifecycleError> { - let process_id = self.process_id_for_task(task_id)?; - - self.task_manager.close_task(task_id); - self.process_manager.mark_stopped(process_id); - - Ok(()) - } - - pub fn crash_task( - &mut self, - task_id: TaskId, - _report: Option<&CrashReport>, - ) -> Result<(), LifecycleError> { - let process_id = self.process_id_for_task(task_id)?; - - self.task_manager.mark_crashed(task_id); - self.process_manager.mark_crashed(process_id); - - Ok(()) - } - - fn process_id_for_task( - &self, - task_id: TaskId, - ) -> Result { - let task = self.task_manager.get(task_id).ok_or(LifecycleError::TaskNotFound(task_id))?; - - if !self.process_manager.contains(task.process_id) { - return Err(LifecycleError::ProcessNotFound(task.process_id)); - } - - Ok(task.process_id) - } } #[cfg(test)] mod tests { use super::*; + use crate::os::{LifecycleError, LifecycleOperation}; use crate::process::ProcessState; + use crate::task::{TaskId, TaskState}; fn process_state_for_task(os: &SystemOS, task_id: TaskId) -> ProcessState { let task = os.task_manager.get(task_id).expect("task should exist"); @@ -215,10 +101,10 @@ mod tests { #[test] fn set_foreground_task_marks_task_and_process_running() { let mut os = SystemOS::new(None); - let task_id = os.create_vm_game_task(42, "Sector Crawl"); + let task_id = os.sessions().create_vm_game_task(42, "Sector Crawl"); - os.suspend_task(task_id).expect("suspend should succeed"); - os.set_foreground_task(task_id).expect("foreground should succeed"); + os.lifecycle().suspend_task(task_id).expect("suspend should succeed"); + os.lifecycle().set_foreground_task(task_id).expect("foreground should succeed"); assert_eq!( os.task_manager.get(task_id).expect("task should exist").state, @@ -230,9 +116,9 @@ mod tests { #[test] fn suspend_task_marks_task_and_process_suspended() { let mut os = SystemOS::new(None); - let task_id = os.create_vm_game_task(42, "Sector Crawl"); + let task_id = os.sessions().create_vm_game_task(42, "Sector Crawl"); - os.suspend_task(task_id).expect("suspend should succeed"); + os.lifecycle().suspend_task(task_id).expect("suspend should succeed"); assert_eq!( os.task_manager.get(task_id).expect("task should exist").state, @@ -244,10 +130,10 @@ mod tests { #[test] fn resume_task_marks_suspended_task_foreground_and_process_running() { let mut os = SystemOS::new(None); - let task_id = os.create_vm_game_task(42, "Sector Crawl"); + let task_id = os.sessions().create_vm_game_task(42, "Sector Crawl"); - os.suspend_task(task_id).expect("suspend should succeed"); - os.resume_task(task_id).expect("resume should succeed"); + os.lifecycle().suspend_task(task_id).expect("suspend should succeed"); + os.lifecycle().resume_task(task_id).expect("resume should succeed"); assert_eq!( os.task_manager.get(task_id).expect("task should exist").state, @@ -259,10 +145,10 @@ mod tests { #[test] fn close_task_marks_task_closed_and_process_stopped_without_removing_entities() { let mut os = SystemOS::new(None); - let task_id = os.create_vm_game_task(42, "Sector Crawl"); + let task_id = os.sessions().create_vm_game_task(42, "Sector Crawl"); let process_id = os.task_manager.get(task_id).expect("task should exist").process_id; - os.close_task(task_id).expect("close should succeed"); + os.lifecycle().close_task(task_id).expect("close should succeed"); assert_eq!( os.task_manager.get(task_id).expect("task should remain").state, @@ -277,9 +163,9 @@ mod tests { #[test] fn crash_task_marks_task_and_process_crashed() { let mut os = SystemOS::new(None); - let task_id = os.create_vm_game_task(42, "Sector Crawl"); + let task_id = os.sessions().create_vm_game_task(42, "Sector Crawl"); - os.crash_task(task_id, None).expect("crash should succeed"); + os.lifecycle().crash_task(task_id, None).expect("crash should succeed"); assert_eq!( os.task_manager.get(task_id).expect("task should exist").state, @@ -292,28 +178,34 @@ mod tests { fn lifecycle_operation_missing_task_returns_typed_error() { let mut os = SystemOS::new(None); - assert_eq!(os.suspend_task(TaskId(999)), Err(LifecycleError::TaskNotFound(TaskId(999)))); + assert_eq!( + os.lifecycle().suspend_task(TaskId(999)), + Err(LifecycleError::TaskNotFound(TaskId(999))) + ); } #[test] fn lifecycle_operation_missing_process_returns_typed_error() { let mut os = SystemOS::new(None); - let task_id = os.create_vm_game_task(42, "Sector Crawl"); + let task_id = os.sessions().create_vm_game_task(42, "Sector Crawl"); let process_id = os.task_manager.get(task_id).expect("task should exist").process_id; os.process_manager.mark_stopped(process_id); os.process_manager.remove_stopped(); - assert_eq!(os.suspend_task(task_id), Err(LifecycleError::ProcessNotFound(process_id))); + assert_eq!( + os.lifecycle().suspend_task(task_id), + Err(LifecycleError::ProcessNotFound(process_id)) + ); } #[test] fn resume_task_from_non_suspended_state_returns_invalid_transition() { let mut os = SystemOS::new(None); - let task_id = os.create_vm_game_task(42, "Sector Crawl"); + let task_id = os.sessions().create_vm_game_task(42, "Sector Crawl"); assert_eq!( - os.resume_task(task_id), + os.lifecycle().resume_task(task_id), Err(LifecycleError::InvalidTransition { task_id, from: TaskState::Foreground, diff --git a/crates/console/prometeu-system/src/programs/prometeu_hub/prometeu_hub.rs b/crates/console/prometeu-system/src/programs/prometeu_hub/prometeu_hub.rs index 333ff302..3619a4bd 100644 --- a/crates/console/prometeu-system/src/programs/prometeu_hub/prometeu_hub.rs +++ b/crates/console/prometeu-system/src/programs/prometeu_hub/prometeu_hub.rs @@ -61,14 +61,15 @@ impl PrometeuHub { } if let Some((title, rect, color)) = next_window { - os.window_manager.remove_all_windows(); - let id = os.window_manager.add_window(title, rect, color); - os.window_manager.set_focus(id); + let mut window = os.window(); + window.remove_all_windows(); + let id = window.add_window(title, rect, color); + window.set_focus(id); } } pub fn render(&mut self, os: &mut SystemOS, hw: &mut dyn HardwareBridge) { - for window in &os.window_manager.windows { + for window in os.window().windows() { hw.gfx_mut().fill_rect( window.viewport.x, window.viewport.y, @@ -90,17 +91,17 @@ impl PrometeuHub { self.gui_update(os, hw); - if let Some(focused_id) = os.window_manager.focused { + if let Some(focused_id) = os.window().focused_window() { if hw.pad().start().down { - os.window_manager.remove_window(focused_id); + os.window().close_window(focused_id); } else { - crash = os.tick_vm(vm, signals, hw); + crash = os.vm().tick(vm, signals, hw); } } self.render(os, hw); hw.gfx_mut().present(); - SystemProfileUpdate { crash, focused_window_active: os.window_manager.focused.is_some() } + SystemProfileUpdate { crash, focused_window_active: os.window().focused_window().is_some() } } } diff --git a/crates/host/prometeu-host-desktop-winit/src/debugger.rs b/crates/host/prometeu-host-desktop-winit/src/debugger.rs index a1867789..c635a4f6 100644 --- a/crates/host/prometeu-host-desktop-winit/src/debugger.rs +++ b/crates/host/prometeu-host-desktop-winit/src/debugger.rs @@ -58,11 +58,7 @@ impl HostDebugger { // Pre-load cartridge metadata so the Handshake message can contain // valid information about the App being debugged. if let Ok(cartridge) = CartridgeLoader::load(path) { - let _ = firmware.os.vm_runtime.initialize_vm( - &mut firmware.os.log_service, - &mut firmware.vm, - &cartridge, - ); + let _ = firmware.os.vm().initialize(&mut firmware.vm, &cartridge); } match TcpListener::bind(format!("127.0.0.1:{}", debug_port)) { @@ -121,10 +117,10 @@ impl HostDebugger { protocol_version: DEVTOOLS_PROTOCOL_VERSION, runtime_version: "0.1".to_string(), cartridge: HandshakeCartridge { - app_id: firmware.os.vm_runtime.current_app_id, - title: firmware.os.vm_runtime.current_cartridge_title.clone(), - app_version: firmware.os.vm_runtime.current_cartridge_app_version.clone(), - app_mode: firmware.os.vm_runtime.current_cartridge_app_mode, + app_id: firmware.os.vm().current_app_id(), + title: firmware.os.vm().current_cartridge_title(), + app_version: firmware.os.vm().current_cartridge_app_version(), + app_mode: firmware.os.vm().current_cartridge_app_mode(), }, }; self.send_response(handshake); @@ -142,7 +138,7 @@ impl HostDebugger { println!("[Debugger] Connection closed by remote."); self.stream = None; // Resume VM execution if it was paused waiting for the debugger. - firmware.os.vm_runtime.paused = false; + firmware.os.vm().set_paused(false); self.waiting_for_start = false; } Ok(n) => { @@ -169,7 +165,7 @@ impl HostDebugger { Err(e) => { eprintln!("[Debugger] Connection error: {}", e); self.stream = None; - firmware.os.vm_runtime.paused = false; + firmware.os.vm().set_paused(false); self.waiting_for_start = false; } } @@ -194,27 +190,23 @@ impl HostDebugger { println!("[Debugger] Starting execution..."); self.waiting_for_start = false; } - firmware.os.vm_runtime.paused = false; + firmware.os.vm().set_paused(false); } DebugCommand::Pause => { - firmware.os.vm_runtime.paused = true; + firmware.os.vm().set_paused(true); } DebugCommand::Resume => { - firmware.os.vm_runtime.paused = false; + firmware.os.vm().set_paused(false); } DebugCommand::Step => { // Execute exactly one instruction and keep paused. - firmware.os.vm_runtime.paused = true; - let _ = firmware.os.vm_runtime.debug_step_instruction( - &mut firmware.os.log_service, - &mut firmware.vm, - hardware, - ); + firmware.os.vm().set_paused(true); + let _ = firmware.os.vm().debug_step_instruction(&mut firmware.vm, hardware); } DebugCommand::StepFrame => { // Execute until the end of the current logical frame. - firmware.os.vm_runtime.paused = false; - firmware.os.vm_runtime.debug_step_request = true; + firmware.os.vm().set_paused(false); + firmware.os.vm().request_debug_step(); } DebugCommand::GetState => { // Return detailed VM register and stack state. @@ -223,8 +215,8 @@ impl HostDebugger { let resp = DebugResponse::GetState { pc: firmware.vm.pc(), stack_top, - frame_index: firmware.os.vm_runtime.logical_frame_index, - app_id: firmware.os.vm_runtime.current_app_id, + frame_index: firmware.os.vm().logical_frame_index(), + app_id: firmware.os.vm().current_app_id(), }; self.send_response(resp); } @@ -309,14 +301,14 @@ impl HostDebugger { /// Scans the system for new information to push to the debugger client. fn stream_events(&mut self, firmware: &mut Firmware) { - if let Some(report) = firmware.os.vm_runtime.last_crash_report.as_ref() { + if let Some(report) = firmware.os.vm().last_crash_report() { self.stream_fault(report); } else { self.last_fault_summary = None; } // 1. Process and send new log entries. - let new_events = firmware.os.log_service.get_after(self.last_log_seq); + let new_events = firmware.os.logs_after(self.last_log_seq); for event in new_events { self.last_log_seq = event.seq; @@ -324,17 +316,17 @@ impl HostDebugger { if event.tag == 0xDEB1 { self.send_event(DebugEvent::BreakpointHit { pc: firmware.vm.pc(), - frame_index: firmware.os.vm_runtime.logical_frame_index, + frame_index: firmware.os.vm().logical_frame_index(), }); } if (0xCA01..=0xCA07).contains(&event.tag) - && let Some(cert_event) = Self::cert_event_from_snapshot( - event.tag, - firmware.os.vm_runtime.atomic_telemetry.snapshot(), - &firmware.os.vm_runtime.certifier.config, - firmware.os.vm_runtime.logical_frame_index, - ) + && let Some(cert_event) = { + let telemetry = firmware.os.vm().telemetry_snapshot(); + let cert_config = *firmware.os.vm().cert_config(); + let frame_index = firmware.os.vm().logical_frame_index(); + Self::cert_event_from_snapshot(event.tag, telemetry, &cert_config, frame_index) + } { self.send_event(cert_event); } @@ -347,9 +339,9 @@ impl HostDebugger { } // 2. Send telemetry snapshots at the completion of every frame. - let current_frame = firmware.os.vm_runtime.logical_frame_index; + let current_frame = firmware.os.vm().logical_frame_index(); if current_frame > self.last_telemetry_frame { - let tel = firmware.os.vm_runtime.atomic_telemetry.snapshot(); + let tel = firmware.os.vm().telemetry_snapshot(); self.send_event(Self::telemetry_event_from_snapshot(tel)); self.last_telemetry_frame = current_frame; } @@ -407,14 +399,14 @@ mod tests { let mut hardware = Hardware::new(); debugger.handle_command(DebugCommand::Pause, &mut firmware, &mut hardware); - assert!(firmware.os.vm_runtime.paused); + assert!(firmware.os.vm().paused()); debugger.handle_command(DebugCommand::Resume, &mut firmware, &mut hardware); - assert!(!firmware.os.vm_runtime.paused); + assert!(!firmware.os.vm().paused()); debugger.handle_command(DebugCommand::StepFrame, &mut firmware, &mut hardware); - assert!(!firmware.os.vm_runtime.paused); - assert!(firmware.os.vm_runtime.debug_step_request); + assert!(!firmware.os.vm().paused()); + assert!(firmware.os.vm().debug_step_requested()); } #[test] @@ -424,11 +416,11 @@ mod tests { let mut hardware = Hardware::new(); debugger.waiting_for_start = true; - firmware.os.vm_runtime.paused = true; + firmware.os.vm().set_paused(true); debugger.handle_command(DebugCommand::Start, &mut firmware, &mut hardware); assert!(!debugger.waiting_for_start); - assert!(!firmware.os.vm_runtime.paused); + assert!(!firmware.os.vm().paused()); } } diff --git a/crates/host/prometeu-host-desktop-winit/src/overlay.rs b/crates/host/prometeu-host-desktop-winit/src/overlay.rs index ec1ae313..a18795c0 100644 --- a/crates/host/prometeu-host-desktop-winit/src/overlay.rs +++ b/crates/host/prometeu-host-desktop-winit/src/overlay.rs @@ -57,9 +57,9 @@ struct FrameCanvas<'a> { height: usize, } -pub(crate) fn capture_snapshot(stats: &HostStats, firmware: &Firmware) -> OverlaySnapshot { - let tel = firmware.os.vm_runtime.atomic_telemetry.snapshot(); - let recent_logs = firmware.os.log_service.get_recent(10); +pub(crate) fn capture_snapshot(stats: &HostStats, firmware: &mut Firmware) -> OverlaySnapshot { + let tel = firmware.os.vm().telemetry_snapshot(); + let recent_logs = firmware.os.recent_logs(10); let violations_count = recent_logs.iter().filter(|e| e.tag >= 0xCA01 && e.tag <= 0xCA07).count(); @@ -75,7 +75,7 @@ pub(crate) fn capture_snapshot(stats: &HostStats, firmware: &Firmware) -> Overla }); } - if let Some(report) = firmware.os.vm_runtime.last_crash_report.as_ref() { + if let Some(report) = firmware.os.vm().last_crash_report() { footer.push(OverlayMetric { label: "CRASH", value: truncate_value(&report.summary(), 28), @@ -86,9 +86,8 @@ pub(crate) fn capture_snapshot(stats: &HostStats, firmware: &Firmware) -> Overla let cycles_ratio = ratio(tel.cycles_used, tel.cycles_budget); let heap_total_bytes = firmware .os - .vm_runtime - .certifier - .config + .vm() + .cert_config() .max_heap_bytes .or(if tel.heap_max_bytes > 0 { Some(tel.heap_max_bytes) } else { None }) .unwrap_or(OVERLAY_HEAP_FALLBACK_BYTES); diff --git a/crates/host/prometeu-host-desktop-winit/src/runner.rs b/crates/host/prometeu-host-desktop-winit/src/runner.rs index e7f61988..4d2a3b65 100644 --- a/crates/host/prometeu-host-desktop-winit/src/runner.rs +++ b/crates/host/prometeu-host-desktop-winit/src/runner.rs @@ -153,7 +153,7 @@ impl HostRunner { let mut firmware = Firmware::new(cap_config); if let Some(root) = &fs_root { let backend = HostDirBackend::new(root); - firmware.os.mount_fs(Box::new(backend)); + firmware.os.fs().mount(Box::new(backend)); } Self { @@ -203,8 +203,8 @@ impl HostRunner { } } - fn machine_running(&self) -> bool { - !self.firmware.os.vm_runtime.paused && !self.debugger.waiting_for_start + fn machine_running(&mut self) -> bool { + !self.firmware.os.vm().paused() && !self.debugger.waiting_for_start } } @@ -266,7 +266,7 @@ impl ApplicationHandler for HostRunner { WindowEvent::RedrawRequested => { let overlay_snapshot = - self.overlay_enabled.then(|| capture_snapshot(&self.stats, &self.firmware)); + self.overlay_enabled.then(|| capture_snapshot(&self.stats, &mut self.firmware)); // Get Pixels directly from the field (not via helper that gets the entire &mut self) let pixels = self.pixels.as_mut().expect("pixels not initialized"); @@ -320,25 +320,25 @@ impl ApplicationHandler for HostRunner { fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) { let was_debugger_connected = self.debugger.stream.is_some(); let was_waiting_for_start = self.debugger.waiting_for_start; - let was_paused = self.firmware.os.vm_runtime.paused; + let was_paused = self.firmware.os.vm().paused(); // 1. Process pending debug commands from the network. self.debugger.check_commands(&mut self.firmware, &mut self.hardware); // Sync inspection mode state. This is host-owned overlay/debugger control, // not a guest-visible debug ABI switch. - self.firmware.os.vm_runtime.inspection_active = - self.overlay_enabled || self.debugger.stream.is_some(); + self.firmware + .os + .vm() + .set_inspection_active(self.overlay_enabled || self.debugger.stream.is_some()); - // 2. Maintain filesystem connection if it was lost (e.g., directory removed). - if let Some(root) = &self.fs_root { - use prometeu_system::fs::FsState; - if matches!(self.firmware.os.fs_state, FsState::Unmounted | FsState::Error(_)) - && std::path::Path::new(root).exists() - { - let backend = HostDirBackend::new(root); - self.firmware.os.mount_fs(Box::new(backend)); - } + // 2. Maintain a filesystem connection if it was lost (e.g., directory removed). + if let Some(root) = &self.fs_root + && self.firmware.os.fs().needs_mount() + && std::path::Path::new(root).exists() + { + let backend = HostDirBackend::new(root); + self.firmware.os.fs().mount(Box::new(backend)); } // 3. Timing Management (The heart of determinism). @@ -362,13 +362,13 @@ impl ApplicationHandler for HostRunner { // Unless the debugger is waiting for a 'start' command, advance the system. if !self.debugger.waiting_for_start { self.firmware.tick(&self.input.signals, &mut self.hardware); - self.stats.record_host_cpu_time(self.firmware.os.vm_runtime.last_frame_cpu_time_us); + self.stats.record_host_cpu_time(self.firmware.os.vm().last_frame_cpu_time_us()); } // Sync pause state with audio. // We do this AFTER firmware.tick to avoid MasterPause/Resume commands // being cleared by the OS if a new logical frame starts in this tick. - let is_paused = self.firmware.os.vm_runtime.paused || self.debugger.waiting_for_start; + let is_paused = self.firmware.os.vm().paused() || self.debugger.waiting_for_start; if is_paused != self.last_paused_state { self.last_paused_state = is_paused; let cmd = @@ -383,11 +383,11 @@ impl ApplicationHandler for HostRunner { self.stats.record_frame(); } - self.presentation.note_published_frame(self.firmware.os.vm_runtime.logical_frame_index); + self.presentation.note_published_frame(self.firmware.os.vm().logical_frame_index()); if was_debugger_connected != self.debugger.stream.is_some() || was_waiting_for_start != self.debugger.waiting_for_start - || was_paused != self.firmware.os.vm_runtime.paused + || was_paused != self.firmware.os.vm().paused() { self.invalidate_host_surface(); } @@ -396,14 +396,14 @@ impl ApplicationHandler for HostRunner { self.audio.update_stats(&mut self.stats); // Update technical statistics displayed in the window title. - self.stats.update(now, self.window, &self.hardware, &self.firmware); + self.stats.update(now, self.window, &self.hardware, &mut self.firmware); // Synchronize system logs to the host console. let last_seq = self.log_sink.last_seq().unwrap_or(u64::MAX); let new_events = if last_seq == u64::MAX { - self.firmware.os.log_service.get_recent(4096) + self.firmware.os.recent_logs(4096) } else { - self.firmware.os.log_service.get_after(last_seq) + self.firmware.os.logs_after(last_seq) }; self.log_sink.process_events(new_events); @@ -813,7 +813,7 @@ mod tests { stream.write_all(b"{\"type\":\"pause\"}\n").expect("Should write pause"); std::thread::sleep(std::time::Duration::from_millis(50)); runner.debugger.check_commands(&mut runner.firmware, &mut runner.hardware); - assert!(runner.firmware.os.vm_runtime.paused, "VM should be paused"); + assert!(runner.firmware.os.vm().paused(), "VM should be paused"); } // 2. Disconnect (stream goes out of scope) @@ -821,7 +821,7 @@ mod tests { runner.debugger.check_commands(&mut runner.firmware, &mut runner.hardware); // 3. Check if unpaused - assert!(!runner.firmware.os.vm_runtime.paused, "VM should have unpaused after disconnect"); + assert!(!runner.firmware.os.vm().paused(), "VM should have unpaused after disconnect"); assert!(!runner.debugger.waiting_for_start, "VM should have left waiting_for_start state"); } } diff --git a/crates/host/prometeu-host-desktop-winit/src/stats.rs b/crates/host/prometeu-host-desktop-winit/src/stats.rs index a14b4994..fb20f9c7 100644 --- a/crates/host/prometeu-host-desktop-winit/src/stats.rs +++ b/crates/host/prometeu-host-desktop-winit/src/stats.rs @@ -65,7 +65,7 @@ impl HostStats { now: Instant, window: Option<&Window>, _hardware: &Hardware, - firmware: &Firmware, + firmware: &mut Firmware, ) { let stats_elapsed = now.duration_since(self.last_stats_update); if stats_elapsed >= Duration::from_secs(1) { @@ -88,14 +88,9 @@ impl HostStats { self.current_fps, cpu_load_core, cpu_load_audio, - firmware.os.vm_runtime.tick_index, - firmware.os.vm_runtime.logical_frame_index, - firmware - .os - .vm_runtime - .atomic_telemetry - .completed_logical_frames - .load(std::sync::atomic::Ordering::Relaxed), + firmware.os.vm().tick_index(), + firmware.os.vm().logical_frame_index(), + firmware.os.vm().completed_logical_frames(), ); window.set_title(&title); }