implements PLN-0058
This commit is contained in:
parent
bfe900797f
commit
4d2ea6400d
1
.gitignore
vendored
1
.gitignore
vendored
@ -11,6 +11,7 @@
|
||||
# RustRover / IntelliJ
|
||||
.idea/
|
||||
*.iml
|
||||
.junie
|
||||
|
||||
# VS Code
|
||||
.vscode/
|
||||
|
||||
@ -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]
|
||||
|
||||
@ -19,7 +19,7 @@ impl GameRunningStep {
|
||||
}
|
||||
|
||||
pub fn on_update(&mut self, ctx: &mut PrometeuContext) -> Option<FirmwareState> {
|
||||
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 }));
|
||||
}
|
||||
|
||||
|
||||
@ -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<FirmwareState> {
|
||||
@ -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)))
|
||||
}
|
||||
|
||||
|
||||
@ -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<FirmwareState> {
|
||||
|
||||
@ -20,7 +20,7 @@ impl SystemRunningStep {
|
||||
pub fn on_update(&mut self, ctx: &mut PrometeuContext) -> Option<FirmwareState> {
|
||||
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 }));
|
||||
}
|
||||
|
||||
|
||||
26
crates/console/prometeu-system/src/os/facades/fs.rs
Normal file
26
crates/console/prometeu-system/src/os/facades/fs.rs
Normal file
@ -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<dyn FsBackend>) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
94
crates/console/prometeu-system/src/os/facades/lifecycle.rs
Normal file
94
crates/console/prometeu-system/src/os/facades/lifecycle.rs
Normal file
@ -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<TaskState> {
|
||||
self.os.task_manager.get(task_id).map(|task| task.state)
|
||||
}
|
||||
|
||||
pub fn process_state_for_task(&self, task_id: TaskId) -> Result<ProcessState, LifecycleError> {
|
||||
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<ProcessId, LifecycleError> {
|
||||
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)
|
||||
}
|
||||
11
crates/console/prometeu-system/src/os/facades/mod.rs
Normal file
11
crates/console/prometeu-system/src/os/facades/mod.rs
Normal file
@ -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;
|
||||
44
crates/console/prometeu-system/src/os/facades/sessions.rs
Normal file
44
crates/console/prometeu-system/src/os/facades/sessions.rs
Normal file
@ -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<String>) -> 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<String>) -> 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<String>) -> 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
|
||||
}
|
||||
}
|
||||
121
crates/console/prometeu-system/src/os/facades/vm.rs
Normal file
121
crates/console/prometeu-system/src/os/facades/vm.rs
Normal file
@ -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<CrashReport> {
|
||||
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<CrashReport> {
|
||||
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
|
||||
}
|
||||
}
|
||||
37
crates/console/prometeu-system/src/os/facades/window.rs
Normal file
37
crates/console/prometeu-system/src/os/facades/window.rs
Normal file
@ -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<WindowId> {
|
||||
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()
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
|
||||
@ -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<u32, String>,
|
||||
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<u32, String>,
|
||||
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<LogEvent> {
|
||||
self.log_service.get_recent(n)
|
||||
}
|
||||
|
||||
pub fn tick_vm(
|
||||
&mut self,
|
||||
vm: &mut VirtualMachine,
|
||||
signals: &InputSignals,
|
||||
hw: &mut dyn HardwareBridge,
|
||||
) -> Option<CrashReport> {
|
||||
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<LogEvent> {
|
||||
self.log_service.get_after(seq)
|
||||
}
|
||||
|
||||
pub fn mount_fs(&mut self, backend: Box<dyn FsBackend>) {
|
||||
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<String>) -> 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<String>) -> 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<String>) -> 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<crate::process::ProcessId, LifecycleError> {
|
||||
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,
|
||||
|
||||
@ -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() }
|
||||
}
|
||||
}
|
||||
|
||||
@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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(_))
|
||||
// 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.mount_fs(Box::new(backend));
|
||||
}
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user