add process and task managers #25
@ -4,7 +4,7 @@ use crate::firmware::prometeu_context::PrometeuContext;
|
||||
use prometeu_hal::cartridge::Cartridge;
|
||||
use prometeu_hal::telemetry::CertificationConfig;
|
||||
use prometeu_hal::{HardwareBridge, InputSignals};
|
||||
use prometeu_system::{PrometeuHub, VirtualMachineRuntime};
|
||||
use prometeu_system::{PrometeuHub, SystemOS};
|
||||
use prometeu_vm::VirtualMachine;
|
||||
|
||||
/// PROMETEU Firmware.
|
||||
@ -26,7 +26,7 @@ pub struct Firmware {
|
||||
/// The execution engine (PVM) for user applications.
|
||||
pub vm: VirtualMachine,
|
||||
/// The underlying OS services (Syscalls, Filesystem, Telemetry).
|
||||
pub os: VirtualMachineRuntime,
|
||||
pub os: SystemOS,
|
||||
/// The internal state of the system launcher (Hub).
|
||||
pub hub: PrometeuHub,
|
||||
/// The current operational state (e.g., Reset, SplashScreen, GameRunning).
|
||||
@ -42,7 +42,7 @@ impl Firmware {
|
||||
pub fn new(cap_config: Option<CertificationConfig>) -> Self {
|
||||
Self {
|
||||
vm: VirtualMachine::default(),
|
||||
os: VirtualMachineRuntime::new(cap_config),
|
||||
os: SystemOS::new(cap_config),
|
||||
hub: PrometeuHub::new(),
|
||||
state: FirmwareState::Reset(ResetStep),
|
||||
boot_target: BootTarget::Hub,
|
||||
@ -335,7 +335,7 @@ mod tests {
|
||||
let mut hardware = Hardware::new();
|
||||
let signals = InputSignals::default();
|
||||
|
||||
firmware.load_cartridge(valid_cartridge(AppMode::System));
|
||||
firmware.load_cartridge(valid_cartridge(AppMode::Shell));
|
||||
firmware.tick(&signals, &mut hardware);
|
||||
|
||||
assert!(matches!(firmware.state, FirmwareState::SystemRunning(_)));
|
||||
@ -349,14 +349,14 @@ mod tests {
|
||||
let mut hardware = Hardware::new();
|
||||
let signals = InputSignals::default();
|
||||
|
||||
firmware.load_cartridge(valid_cartridge(AppMode::System));
|
||||
firmware.load_cartridge(valid_cartridge(AppMode::Shell));
|
||||
firmware.tick(&signals, &mut hardware);
|
||||
|
||||
let tick_index_before_system_update = firmware.os.tick_index;
|
||||
let tick_index_before_system_update = firmware.os.vm_runtime.tick_index;
|
||||
firmware.tick(&signals, &mut hardware);
|
||||
|
||||
assert!(matches!(firmware.state, FirmwareState::SystemRunning(_)));
|
||||
assert_eq!(firmware.os.tick_index, tick_index_before_system_update + 1);
|
||||
assert_eq!(firmware.os.vm_runtime.tick_index, tick_index_before_system_update + 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@ -15,7 +15,12 @@ impl AppCrashesStep {
|
||||
}
|
||||
|
||||
pub fn on_enter(&mut self, ctx: &mut PrometeuContext) {
|
||||
ctx.os.log(LogLevel::Error, LogSource::Pos, self.report.log_tag(), self.log_message());
|
||||
ctx.os.vm_runtime.log(
|
||||
LogLevel::Error,
|
||||
LogSource::Pos,
|
||||
self.report.log_tag(),
|
||||
self.log_message(),
|
||||
);
|
||||
}
|
||||
|
||||
pub fn on_update(&mut self, ctx: &mut PrometeuContext) -> Option<FirmwareState> {
|
||||
|
||||
@ -1,19 +1,48 @@
|
||||
use crate::firmware::firmware_state::{AppCrashesStep, FirmwareState};
|
||||
use crate::firmware::prometeu_context::PrometeuContext;
|
||||
use prometeu_hal::log::{LogLevel, LogSource};
|
||||
use prometeu_system::CrashReport;
|
||||
use prometeu_system::task::{TaskId, TaskState};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct GameRunningStep;
|
||||
pub struct GameRunningStep {
|
||||
pub task_id: TaskId,
|
||||
}
|
||||
|
||||
impl GameRunningStep {
|
||||
pub fn new(task_id: TaskId) -> Self {
|
||||
Self { task_id }
|
||||
}
|
||||
|
||||
pub fn on_enter(&mut self, ctx: &mut PrometeuContext) {
|
||||
ctx.os.log(LogLevel::Info, LogSource::Pos, 0, "Entering GameRunning".to_string());
|
||||
ctx.os.vm_runtime.log(
|
||||
LogLevel::Info,
|
||||
LogSource::Pos,
|
||||
0,
|
||||
"Entering GameRunning".to_string(),
|
||||
);
|
||||
}
|
||||
|
||||
pub fn on_update(&mut self, ctx: &mut PrometeuContext) -> Option<FirmwareState> {
|
||||
let result = ctx.os.tick(ctx.vm, ctx.signals, ctx.hw);
|
||||
let Some(task) = ctx.os.task_manager.get(self.task_id) else {
|
||||
// TODO: is it panic?
|
||||
let report =
|
||||
CrashReport::VmPanic { message: "task is gone, panic!".to_string(), pc: None };
|
||||
return Some(FirmwareState::AppCrashes(AppCrashesStep { report }));
|
||||
};
|
||||
|
||||
if !ctx.os.logical_frame_active {
|
||||
if task.state != TaskState::Foreground {
|
||||
// TODO: is it panic?
|
||||
let report = CrashReport::VmPanic {
|
||||
message: "should be running as foreground, panic!".to_string(),
|
||||
pc: None,
|
||||
};
|
||||
return Some(FirmwareState::AppCrashes(AppCrashesStep { report }));
|
||||
}
|
||||
|
||||
let result = ctx.os.vm_runtime.tick(ctx.vm, ctx.signals, ctx.hw);
|
||||
|
||||
if !ctx.os.vm_runtime.logical_frame_active {
|
||||
ctx.hw.gfx_mut().present();
|
||||
}
|
||||
|
||||
|
||||
@ -7,7 +7,7 @@ pub struct HubHomeStep;
|
||||
|
||||
impl HubHomeStep {
|
||||
pub fn on_enter(&mut self, ctx: &mut PrometeuContext) {
|
||||
ctx.os.log(LogLevel::Info, LogSource::Hub, 0, "Entering HubHome".to_string());
|
||||
ctx.os.vm_runtime.log(LogLevel::Info, LogSource::Hub, 0, "Entering HubHome".to_string());
|
||||
}
|
||||
|
||||
pub fn on_update(&mut self, ctx: &mut PrometeuContext) -> Option<FirmwareState> {
|
||||
|
||||
@ -9,7 +9,7 @@ pub struct LaunchHubStep;
|
||||
|
||||
impl LaunchHubStep {
|
||||
pub fn on_enter(&mut self, ctx: &mut PrometeuContext) {
|
||||
ctx.os.log(LogLevel::Info, LogSource::Pos, 0, "Launching Hub".to_string());
|
||||
ctx.os.vm_runtime.log(LogLevel::Info, LogSource::Pos, 0, "Launching Hub".to_string());
|
||||
ctx.hub.init();
|
||||
}
|
||||
|
||||
@ -22,7 +22,7 @@ impl LaunchHubStep {
|
||||
return Some(FirmwareState::LoadCartridge(LoadCartridgeStep::new(cartridge)));
|
||||
}
|
||||
Err(e) => {
|
||||
ctx.os.log(
|
||||
ctx.os.vm_runtime.log(
|
||||
LogLevel::Error,
|
||||
LogSource::Pos,
|
||||
0,
|
||||
|
||||
@ -20,7 +20,7 @@ impl LoadCartridgeStep {
|
||||
}
|
||||
|
||||
pub fn on_enter(&mut self, ctx: &mut PrometeuContext) {
|
||||
ctx.os.log(
|
||||
ctx.os.vm_runtime.log(
|
||||
LogLevel::Info,
|
||||
LogSource::Pos,
|
||||
0,
|
||||
@ -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_runtime.initialize_vm(ctx.vm, &self.cartridge).err();
|
||||
}
|
||||
|
||||
pub fn on_update(&mut self, ctx: &mut PrometeuContext) -> Option<FirmwareState> {
|
||||
@ -42,7 +42,7 @@ impl LoadCartridgeStep {
|
||||
return Some(FirmwareState::AppCrashes(AppCrashesStep { report }));
|
||||
}
|
||||
|
||||
if self.cartridge.app_mode == AppMode::System {
|
||||
if self.cartridge.app_mode == AppMode::Shell {
|
||||
let id = ctx.hub.window_manager.add_window(
|
||||
self.cartridge.title.clone(),
|
||||
Rect { x: 40, y: 20, w: 240, h: 140 },
|
||||
@ -50,10 +50,14 @@ impl LoadCartridgeStep {
|
||||
);
|
||||
ctx.hub.window_manager.set_focus(id);
|
||||
|
||||
return Some(FirmwareState::SystemRunning(SystemRunningStep));
|
||||
let task_id =
|
||||
ctx.os.create_vm_shell_task(self.cartridge.app_id, self.cartridge.title.clone());
|
||||
return Some(FirmwareState::SystemRunning(SystemRunningStep::new(task_id)));
|
||||
}
|
||||
|
||||
Some(FirmwareState::GameRunning(GameRunningStep))
|
||||
let task_id =
|
||||
ctx.os.create_vm_game_task(self.cartridge.app_id, self.cartridge.title.clone());
|
||||
Some(FirmwareState::GameRunning(GameRunningStep::new(task_id)))
|
||||
}
|
||||
|
||||
pub fn on_exit(&mut self, _ctx: &mut PrometeuContext) {}
|
||||
|
||||
@ -8,8 +8,8 @@ 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.reset(ctx.vm);
|
||||
ctx.os.vm_runtime.log(LogLevel::Info, LogSource::Pos, 0, "Firmware Reset".to_string());
|
||||
ctx.os.vm_runtime.reset(ctx.vm);
|
||||
}
|
||||
|
||||
pub fn on_update(&mut self, ctx: &mut PrometeuContext) -> Option<FirmwareState> {
|
||||
|
||||
@ -10,7 +10,12 @@ pub struct SplashScreenStep {
|
||||
|
||||
impl SplashScreenStep {
|
||||
pub fn on_enter(&mut self, ctx: &mut PrometeuContext) {
|
||||
ctx.os.log(LogLevel::Info, LogSource::Pos, 0, "Showing SplashScreen".to_string());
|
||||
ctx.os.vm_runtime.log(
|
||||
LogLevel::Info,
|
||||
LogSource::Pos,
|
||||
0,
|
||||
"Showing SplashScreen".to_string(),
|
||||
);
|
||||
// Play sound on enter
|
||||
// ctx.hw.audio_mut().play(0, 0, 0, 255, 127, 1.0, 0, LoopMode::Off);
|
||||
}
|
||||
|
||||
@ -1,13 +1,25 @@
|
||||
use crate::firmware::firmware_state::{AppCrashesStep, FirmwareState, HubHomeStep};
|
||||
use crate::firmware::prometeu_context::PrometeuContext;
|
||||
use prometeu_hal::log::{LogLevel, LogSource};
|
||||
use prometeu_system::task::TaskId;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SystemRunningStep;
|
||||
pub struct SystemRunningStep {
|
||||
pub task_id: TaskId,
|
||||
}
|
||||
|
||||
impl SystemRunningStep {
|
||||
pub fn new(task_id: TaskId) -> Self {
|
||||
Self { task_id }
|
||||
}
|
||||
|
||||
pub fn on_enter(&mut self, ctx: &mut PrometeuContext) {
|
||||
ctx.os.log(LogLevel::Info, LogSource::Hub, 0, "Entering SystemRunning".to_string());
|
||||
ctx.os.vm_runtime.log(
|
||||
LogLevel::Info,
|
||||
LogSource::Hub,
|
||||
0,
|
||||
"Entering SystemRunning".to_string(),
|
||||
);
|
||||
}
|
||||
|
||||
pub fn on_update(&mut self, ctx: &mut PrometeuContext) -> Option<FirmwareState> {
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
use crate::firmware::boot_target::BootTarget;
|
||||
use prometeu_hal::{HardwareBridge, InputSignals};
|
||||
use prometeu_system::{PrometeuHub, VirtualMachineRuntime};
|
||||
use prometeu_system::{PrometeuHub, SystemOS};
|
||||
use prometeu_vm::VirtualMachine;
|
||||
|
||||
pub struct PrometeuContext<'a> {
|
||||
pub vm: &'a mut VirtualMachine,
|
||||
pub os: &'a mut VirtualMachineRuntime,
|
||||
pub os: &'a mut SystemOS,
|
||||
pub hub: &'a mut PrometeuHub,
|
||||
pub boot_target: &'a BootTarget,
|
||||
pub signals: &'a InputSignals,
|
||||
|
||||
@ -13,7 +13,7 @@ pub const ASSETS_PA_PRELUDE_SIZE: usize = 32;
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
|
||||
pub enum AppMode {
|
||||
Game,
|
||||
System,
|
||||
Shell,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
||||
@ -1,9 +1,13 @@
|
||||
mod crash_report;
|
||||
mod os;
|
||||
mod programs;
|
||||
mod services;
|
||||
mod virtual_machine_runtime;
|
||||
|
||||
pub use crash_report::CrashReport;
|
||||
pub use os::SystemOS;
|
||||
pub use programs::PrometeuHub;
|
||||
pub use services::fs;
|
||||
pub use services::process;
|
||||
pub use services::task;
|
||||
pub use virtual_machine_runtime::VirtualMachineRuntime;
|
||||
|
||||
3
crates/console/prometeu-system/src/os/mod.rs
Normal file
3
crates/console/prometeu-system/src/os/mod.rs
Normal file
@ -0,0 +1,3 @@
|
||||
mod system_os;
|
||||
|
||||
pub use system_os::SystemOS;
|
||||
48
crates/console/prometeu-system/src/os/system_os.rs
Normal file
48
crates/console/prometeu-system/src/os/system_os.rs
Normal file
@ -0,0 +1,48 @@
|
||||
use crate::VirtualMachineRuntime;
|
||||
use crate::process::ProcessManager;
|
||||
use crate::task::TaskId;
|
||||
use crate::task::TaskManager;
|
||||
use prometeu_hal::telemetry::CertificationConfig;
|
||||
|
||||
pub struct SystemOS {
|
||||
pub vm_runtime: VirtualMachineRuntime,
|
||||
pub process_manager: ProcessManager,
|
||||
pub task_manager: TaskManager,
|
||||
}
|
||||
|
||||
impl SystemOS {
|
||||
pub fn new(cap_config: Option<CertificationConfig>) -> Self {
|
||||
Self {
|
||||
vm_runtime: VirtualMachineRuntime::new(cap_config),
|
||||
process_manager: ProcessManager::new(),
|
||||
task_manager: TaskManager::new(),
|
||||
}
|
||||
}
|
||||
|
||||
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.task_manager.set_foreground(task_id);
|
||||
|
||||
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.task_manager.set_foreground(task_id);
|
||||
|
||||
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.task_manager.set_foreground(task_id);
|
||||
|
||||
task_id
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,5 @@
|
||||
use crate::programs::prometeu_hub::window_manager::WindowManager;
|
||||
use crate::{CrashReport, VirtualMachineRuntime};
|
||||
use crate::{CrashReport, SystemOS};
|
||||
use prometeu_hal::color::Color;
|
||||
use prometeu_hal::log::{LogLevel, LogSource};
|
||||
use prometeu_hal::window::Rect;
|
||||
@ -31,34 +31,34 @@ impl PrometeuHub {
|
||||
// Initializes the Window System and lists apps
|
||||
}
|
||||
|
||||
pub fn gui_update(&mut self, os: &mut VirtualMachineRuntime, hw: &mut dyn HardwareBridge) {
|
||||
pub fn gui_update(&mut self, os: &mut SystemOS, hw: &mut dyn HardwareBridge) {
|
||||
hw.gfx_mut().clear(Color::BLACK);
|
||||
|
||||
let mut next_window = None;
|
||||
|
||||
if hw.pad().a().pressed {
|
||||
os.log(LogLevel::Debug, LogSource::Hub, 0, "window A opened".to_string());
|
||||
os.vm_runtime.log(LogLevel::Debug, LogSource::Hub, 0, "window A opened".to_string());
|
||||
next_window = Some((
|
||||
"Green Window".to_string(),
|
||||
Rect { x: 0, y: 0, w: 160, h: 90 },
|
||||
Color::GREEN,
|
||||
));
|
||||
} else if hw.pad().b().pressed {
|
||||
os.log(LogLevel::Debug, LogSource::Hub, 0, "window B opened".to_string());
|
||||
os.vm_runtime.log(LogLevel::Debug, LogSource::Hub, 0, "window B opened".to_string());
|
||||
next_window = Some((
|
||||
"Indigo Window".to_string(),
|
||||
Rect { x: 160, y: 0, w: 160, h: 90 },
|
||||
Color::INDIGO,
|
||||
));
|
||||
} else if hw.pad().x().pressed {
|
||||
os.log(LogLevel::Debug, LogSource::Hub, 0, "window X opened".to_string());
|
||||
os.vm_runtime.log(LogLevel::Debug, LogSource::Hub, 0, "window X opened".to_string());
|
||||
next_window = Some((
|
||||
"Yellow Window".to_string(),
|
||||
Rect { x: 0, y: 90, w: 160, h: 90 },
|
||||
Color::YELLOW,
|
||||
));
|
||||
} else if hw.pad().y().pressed {
|
||||
os.log(LogLevel::Debug, LogSource::Hub, 0, "window Y opened".to_string());
|
||||
os.vm_runtime.log(LogLevel::Debug, LogSource::Hub, 0, "window Y opened".to_string());
|
||||
next_window =
|
||||
Some(("Red Window".to_string(), Rect { x: 160, y: 90, w: 160, h: 90 }, Color::RED));
|
||||
}
|
||||
@ -70,7 +70,7 @@ impl PrometeuHub {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render(&mut self, _os: &mut VirtualMachineRuntime, hw: &mut dyn HardwareBridge) {
|
||||
pub fn render(&mut self, _os: &mut SystemOS, hw: &mut dyn HardwareBridge) {
|
||||
for window in &self.window_manager.windows {
|
||||
hw.gfx_mut().fill_rect(
|
||||
window.viewport.x,
|
||||
@ -84,7 +84,7 @@ impl PrometeuHub {
|
||||
|
||||
pub fn update_system_profile(
|
||||
&mut self,
|
||||
os: &mut VirtualMachineRuntime,
|
||||
os: &mut SystemOS,
|
||||
vm: &mut VirtualMachine,
|
||||
signals: &InputSignals,
|
||||
hw: &mut dyn HardwareBridge,
|
||||
@ -97,7 +97,7 @@ impl PrometeuHub {
|
||||
if hw.pad().start().down {
|
||||
self.window_manager.remove_window(focused_id);
|
||||
} else {
|
||||
crash = os.tick(vm, signals, hw);
|
||||
crash = os.vm_runtime.tick(vm, signals, hw);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,2 +1,4 @@
|
||||
pub mod fs;
|
||||
pub mod memcard;
|
||||
pub mod process;
|
||||
pub mod task;
|
||||
|
||||
@ -0,0 +1,9 @@
|
||||
mod model;
|
||||
mod process_kind;
|
||||
pub mod process_manager;
|
||||
mod process_state;
|
||||
|
||||
pub use model::{Process, ProcessId};
|
||||
pub use process_kind::ProcessKind;
|
||||
pub use process_manager::ProcessManager;
|
||||
pub use process_state::ProcessState;
|
||||
42
crates/console/prometeu-system/src/services/process/model.rs
Normal file
42
crates/console/prometeu-system/src/services/process/model.rs
Normal file
@ -0,0 +1,42 @@
|
||||
use crate::process::{ProcessKind, ProcessState};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct ProcessId(pub u32);
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Process {
|
||||
pub id: ProcessId,
|
||||
pub app_id: u32,
|
||||
pub title: String,
|
||||
pub kind: ProcessKind,
|
||||
pub state: ProcessState,
|
||||
}
|
||||
|
||||
impl Process {
|
||||
pub fn new(id: ProcessId, app_id: u32, title: impl Into<String>, kind: ProcessKind) -> Self {
|
||||
Self { id, app_id, title: title.into(), kind, state: ProcessState::Created }
|
||||
}
|
||||
|
||||
pub fn mark_running(&mut self) {
|
||||
self.state = ProcessState::Running;
|
||||
}
|
||||
|
||||
pub fn mark_suspended(&mut self) {
|
||||
self.state = ProcessState::Suspended;
|
||||
}
|
||||
|
||||
pub fn mark_stopped(&mut self) {
|
||||
self.state = ProcessState::Stopped;
|
||||
}
|
||||
|
||||
pub fn mark_crashed(&mut self) {
|
||||
self.state = ProcessState::Crashed;
|
||||
}
|
||||
|
||||
pub fn is_alive(&self) -> bool {
|
||||
matches!(
|
||||
self.state,
|
||||
ProcessState::Created | ProcessState::Running | ProcessState::Suspended
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum ProcessKind {
|
||||
/// VM-backed fullscreen game cartridge.
|
||||
VmGame,
|
||||
|
||||
/// VM-backed app hosted by the Prometeu Shell.
|
||||
VmShell,
|
||||
|
||||
/// Native Rust app/component hosted by the Prometeu Shell.
|
||||
NativeShell,
|
||||
}
|
||||
@ -0,0 +1,180 @@
|
||||
use crate::process::{Process, ProcessId, ProcessKind, ProcessState};
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct ProcessManager {
|
||||
next_id: u32,
|
||||
processes: HashMap<ProcessId, Process>,
|
||||
}
|
||||
|
||||
impl ProcessManager {
|
||||
pub fn new() -> Self {
|
||||
Self { next_id: 1, processes: HashMap::new() }
|
||||
}
|
||||
|
||||
pub fn spawn_vm_game(&mut self, app_id: u32, title: impl Into<String>) -> ProcessId {
|
||||
self.spawn(app_id, title, ProcessKind::VmGame)
|
||||
}
|
||||
|
||||
pub fn spawn_vm_shell(&mut self, app_id: u32, title: impl Into<String>) -> ProcessId {
|
||||
self.spawn(app_id, title, ProcessKind::VmShell)
|
||||
}
|
||||
|
||||
pub fn spawn_native_shell(&mut self, app_id: u32, title: impl Into<String>) -> ProcessId {
|
||||
self.spawn(app_id, title, ProcessKind::NativeShell)
|
||||
}
|
||||
|
||||
fn spawn(&mut self, app_id: u32, title: impl Into<String>, kind: ProcessKind) -> ProcessId {
|
||||
let id = ProcessId(self.next_id);
|
||||
self.next_id += 1;
|
||||
|
||||
let mut process = Process::new(id, app_id, title, kind);
|
||||
process.mark_running();
|
||||
|
||||
self.processes.insert(id, process);
|
||||
|
||||
id
|
||||
}
|
||||
|
||||
pub fn get(&self, id: ProcessId) -> Option<&Process> {
|
||||
self.processes.get(&id)
|
||||
}
|
||||
|
||||
pub fn get_mut(&mut self, id: ProcessId) -> Option<&mut Process> {
|
||||
self.processes.get_mut(&id)
|
||||
}
|
||||
|
||||
pub fn contains(&self, id: ProcessId) -> bool {
|
||||
self.processes.contains_key(&id)
|
||||
}
|
||||
|
||||
pub fn all(&self) -> impl Iterator<Item = &Process> {
|
||||
self.processes.values()
|
||||
}
|
||||
|
||||
pub fn mark_running(&mut self, id: ProcessId) -> bool {
|
||||
self.transition(id, ProcessState::Running)
|
||||
}
|
||||
|
||||
pub fn mark_suspended(&mut self, id: ProcessId) -> bool {
|
||||
self.transition(id, ProcessState::Suspended)
|
||||
}
|
||||
|
||||
pub fn mark_stopped(&mut self, id: ProcessId) -> bool {
|
||||
self.transition(id, ProcessState::Stopped)
|
||||
}
|
||||
|
||||
pub fn mark_crashed(&mut self, id: ProcessId) -> bool {
|
||||
self.transition(id, ProcessState::Crashed)
|
||||
}
|
||||
|
||||
pub fn remove_stopped(&mut self) {
|
||||
self.processes.retain(|_, process| process.state != ProcessState::Stopped);
|
||||
}
|
||||
|
||||
fn transition(&mut self, id: ProcessId, state: ProcessState) -> bool {
|
||||
let Some(process) = self.get_mut(id) else {
|
||||
return false;
|
||||
};
|
||||
|
||||
process.state = state;
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn spawn_vm_game_creates_running_process() {
|
||||
let mut manager = ProcessManager::new();
|
||||
|
||||
let id = manager.spawn_vm_game(42, "Amazing Game");
|
||||
|
||||
let process = manager.get(id).expect("process should exist");
|
||||
|
||||
assert_eq!(process.id, id);
|
||||
assert_eq!(process.app_id, 42);
|
||||
assert_eq!(process.title, "Amazing Game");
|
||||
assert_eq!(process.kind, ProcessKind::VmGame);
|
||||
assert_eq!(process.state, ProcessState::Running);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn spawn_vm_shell_creates_running_process() {
|
||||
let mut manager = ProcessManager::new();
|
||||
|
||||
let id = manager.spawn_vm_shell(42, "Amazing Shell");
|
||||
|
||||
let process = manager.get(id).expect("process should exist");
|
||||
|
||||
assert_eq!(process.id, id);
|
||||
assert_eq!(process.app_id, 42);
|
||||
assert_eq!(process.title, "Amazing Shell");
|
||||
assert_eq!(process.kind, ProcessKind::VmShell);
|
||||
assert_eq!(process.state, ProcessState::Running);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn spawn_native_shell_creates_running_process() {
|
||||
let mut manager = ProcessManager::new();
|
||||
|
||||
let id = manager.spawn_native_shell(42, "Amazing Shell");
|
||||
|
||||
let process = manager.get(id).expect("process should exist");
|
||||
|
||||
assert_eq!(process.id, id);
|
||||
assert_eq!(process.app_id, 42);
|
||||
assert_eq!(process.title, "Amazing Shell");
|
||||
assert_eq!(process.kind, ProcessKind::NativeShell);
|
||||
assert_eq!(process.state, ProcessState::Running);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mark_suspended_updates_process_state() {
|
||||
let mut manager = ProcessManager::new();
|
||||
|
||||
let id = manager.spawn_vm_game(42, "Amazing Game");
|
||||
|
||||
assert!(manager.mark_suspended(id));
|
||||
|
||||
let process = manager.get(id).expect("process should exist");
|
||||
|
||||
assert_eq!(process.state, ProcessState::Suspended);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mark_stopped_updates_process_state() {
|
||||
let mut manager = ProcessManager::new();
|
||||
|
||||
let id = manager.spawn_vm_game(42, "Amazing Game");
|
||||
|
||||
assert!(manager.mark_stopped(id));
|
||||
|
||||
let process = manager.get(id).expect("process should exist");
|
||||
|
||||
assert_eq!(process.state, ProcessState::Stopped);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unknown_process_transition_returns_false() {
|
||||
let mut manager = ProcessManager::new();
|
||||
|
||||
assert!(!manager.mark_stopped(ProcessId(999)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn remove_stopped_keeps_running_processes() {
|
||||
let mut manager = ProcessManager::new();
|
||||
|
||||
let running = manager.spawn_vm_game(1, "Running");
|
||||
let stopped = manager.spawn_vm_game(2, "Stopped");
|
||||
|
||||
manager.mark_stopped(stopped);
|
||||
manager.remove_stopped();
|
||||
|
||||
assert!(manager.get(running).is_some());
|
||||
assert!(manager.get(stopped).is_none());
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,8 @@
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum ProcessState {
|
||||
Created,
|
||||
Running,
|
||||
Stopped,
|
||||
Suspended,
|
||||
Crashed,
|
||||
}
|
||||
10
crates/console/prometeu-system/src/services/task/mod.rs
Normal file
10
crates/console/prometeu-system/src/services/task/mod.rs
Normal file
@ -0,0 +1,10 @@
|
||||
mod model;
|
||||
mod task_kind;
|
||||
mod task_manager;
|
||||
mod task_state;
|
||||
|
||||
pub use model::Task;
|
||||
pub use model::TaskId;
|
||||
pub use task_kind::TaskKind;
|
||||
pub use task_manager::TaskManager;
|
||||
pub use task_state::TaskState;
|
||||
51
crates/console/prometeu-system/src/services/task/model.rs
Normal file
51
crates/console/prometeu-system/src/services/task/model.rs
Normal file
@ -0,0 +1,51 @@
|
||||
use crate::process::ProcessId;
|
||||
use crate::task::{TaskKind, TaskState};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct TaskId(pub u32);
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Task {
|
||||
pub id: TaskId,
|
||||
pub process_id: ProcessId,
|
||||
pub app_id: u32,
|
||||
pub title: String,
|
||||
pub kind: TaskKind,
|
||||
pub state: TaskState,
|
||||
}
|
||||
|
||||
impl Task {
|
||||
pub fn new(
|
||||
id: TaskId,
|
||||
process_id: ProcessId,
|
||||
app_id: u32,
|
||||
title: impl Into<String>,
|
||||
kind: TaskKind,
|
||||
) -> Self {
|
||||
Self { id, process_id, app_id, title: title.into(), kind, state: TaskState::Background }
|
||||
}
|
||||
|
||||
pub fn mark_foreground(&mut self) {
|
||||
self.state = TaskState::Foreground;
|
||||
}
|
||||
|
||||
pub fn mark_background(&mut self) {
|
||||
self.state = TaskState::Background;
|
||||
}
|
||||
|
||||
pub fn mark_suspended(&mut self) {
|
||||
self.state = TaskState::Suspended;
|
||||
}
|
||||
|
||||
pub fn mark_closed(&mut self) {
|
||||
self.state = TaskState::Closed;
|
||||
}
|
||||
|
||||
pub fn mark_crashed(&mut self) {
|
||||
self.state = TaskState::Crashed;
|
||||
}
|
||||
|
||||
pub fn is_active(&self) -> bool {
|
||||
matches!(self.state, TaskState::Foreground | TaskState::Background | TaskState::Suspended)
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,5 @@
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum TaskKind {
|
||||
Game,
|
||||
Shell,
|
||||
}
|
||||
282
crates/console/prometeu-system/src/services/task/task_manager.rs
Normal file
282
crates/console/prometeu-system/src/services/task/task_manager.rs
Normal file
@ -0,0 +1,282 @@
|
||||
use super::{Task, TaskId, TaskKind, TaskState};
|
||||
use crate::services::process::ProcessId;
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct TaskManager {
|
||||
next_id: u32,
|
||||
tasks: HashMap<TaskId, Task>,
|
||||
foreground_task: Option<TaskId>,
|
||||
}
|
||||
|
||||
impl TaskManager {
|
||||
pub fn new() -> Self {
|
||||
Self { next_id: 1, tasks: HashMap::new(), foreground_task: None }
|
||||
}
|
||||
|
||||
pub fn create_game_task(
|
||||
&mut self,
|
||||
process_id: ProcessId,
|
||||
app_id: u32,
|
||||
title: impl Into<String>,
|
||||
) -> TaskId {
|
||||
self.create(process_id, app_id, title, TaskKind::Game)
|
||||
}
|
||||
|
||||
pub fn create_shell_task(
|
||||
&mut self,
|
||||
process_id: ProcessId,
|
||||
app_id: u32,
|
||||
title: impl Into<String>,
|
||||
) -> TaskId {
|
||||
self.create(process_id, app_id, title, TaskKind::Shell)
|
||||
}
|
||||
|
||||
fn create(
|
||||
&mut self,
|
||||
process_id: ProcessId,
|
||||
app_id: u32,
|
||||
title: impl Into<String>,
|
||||
kind: TaskKind,
|
||||
) -> TaskId {
|
||||
let id = TaskId(self.next_id);
|
||||
self.next_id += 1;
|
||||
|
||||
let task = Task::new(id, process_id, app_id, title, kind);
|
||||
self.tasks.insert(id, task);
|
||||
|
||||
id
|
||||
}
|
||||
|
||||
pub fn get(&self, id: TaskId) -> Option<&Task> {
|
||||
self.tasks.get(&id)
|
||||
}
|
||||
|
||||
pub fn get_mut(&mut self, id: TaskId) -> Option<&mut Task> {
|
||||
self.tasks.get_mut(&id)
|
||||
}
|
||||
|
||||
pub fn contains(&self, id: TaskId) -> bool {
|
||||
self.tasks.contains_key(&id)
|
||||
}
|
||||
|
||||
pub fn all(&self) -> impl Iterator<Item = &Task> {
|
||||
self.tasks.values()
|
||||
}
|
||||
|
||||
pub fn foreground_task(&self) -> Option<TaskId> {
|
||||
self.foreground_task
|
||||
}
|
||||
|
||||
pub fn foreground(&self) -> Option<&Task> {
|
||||
self.foreground_task.and_then(|id| self.get(id))
|
||||
}
|
||||
|
||||
pub fn set_foreground(&mut self, id: TaskId) -> bool {
|
||||
if !self.tasks.contains_key(&id) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if let Some(current_id) = self.foreground_task
|
||||
&& current_id != id
|
||||
&& let Some(current) = self.tasks.get_mut(¤t_id)
|
||||
&& current.state == TaskState::Foreground
|
||||
{
|
||||
current.mark_background();
|
||||
}
|
||||
|
||||
let Some(task) = self.tasks.get_mut(&id) else {
|
||||
return false;
|
||||
};
|
||||
|
||||
task.mark_foreground();
|
||||
self.foreground_task = Some(id);
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
pub fn clear_foreground(&mut self) {
|
||||
if let Some(id) = self.foreground_task.take()
|
||||
&& let Some(task) = self.tasks.get_mut(&id)
|
||||
&& task.state == TaskState::Foreground
|
||||
{
|
||||
task.mark_background();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn mark_background(&mut self, id: TaskId) -> bool {
|
||||
let Some(task) = self.tasks.get_mut(&id) else {
|
||||
return false;
|
||||
};
|
||||
|
||||
task.mark_background();
|
||||
|
||||
if self.foreground_task == Some(id) {
|
||||
self.foreground_task = None;
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
pub fn mark_suspended(&mut self, id: TaskId) -> bool {
|
||||
let Some(task) = self.tasks.get_mut(&id) else {
|
||||
return false;
|
||||
};
|
||||
|
||||
task.mark_suspended();
|
||||
|
||||
if self.foreground_task == Some(id) {
|
||||
self.foreground_task = None;
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
pub fn close_task(&mut self, id: TaskId) -> bool {
|
||||
let Some(task) = self.tasks.get_mut(&id) else {
|
||||
return false;
|
||||
};
|
||||
|
||||
task.mark_closed();
|
||||
|
||||
if self.foreground_task == Some(id) {
|
||||
self.foreground_task = None;
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
pub fn mark_crashed(&mut self, id: TaskId) -> bool {
|
||||
let Some(task) = self.tasks.get_mut(&id) else {
|
||||
return false;
|
||||
};
|
||||
|
||||
task.mark_crashed();
|
||||
|
||||
if self.foreground_task == Some(id) {
|
||||
self.foreground_task = None;
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
pub fn remove_closed(&mut self) {
|
||||
self.tasks.retain(|id, task| {
|
||||
let should_keep = task.state != TaskState::Closed;
|
||||
if !should_keep && self.foreground_task == Some(*id) {
|
||||
self.foreground_task = None;
|
||||
}
|
||||
should_keep
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::services::process::ProcessId;
|
||||
|
||||
#[test]
|
||||
fn create_game_task_registers_task() {
|
||||
let mut manager = TaskManager::new();
|
||||
let process_id = ProcessId(7);
|
||||
|
||||
let task_id = manager.create_game_task(process_id, 42, "Sector Crawl");
|
||||
|
||||
let task = manager.get(task_id).expect("task should exist");
|
||||
|
||||
assert_eq!(task.id, task_id);
|
||||
assert_eq!(task.process_id, process_id);
|
||||
assert_eq!(task.app_id, 42);
|
||||
assert_eq!(task.title, "Sector Crawl");
|
||||
assert_eq!(task.kind, TaskKind::Game);
|
||||
assert_eq!(task.state, TaskState::Background);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_foreground_marks_task_as_foreground() {
|
||||
let mut manager = TaskManager::new();
|
||||
let task_id = manager.create_game_task(ProcessId(1), 42, "Sector Crawl");
|
||||
|
||||
assert!(manager.set_foreground(task_id));
|
||||
|
||||
let task = manager.get(task_id).expect("task should exist");
|
||||
|
||||
assert_eq!(manager.foreground_task(), Some(task_id));
|
||||
assert_eq!(task.state, TaskState::Foreground);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_foreground_moves_previous_task_to_background() {
|
||||
let mut manager = TaskManager::new();
|
||||
|
||||
let first = manager.create_game_task(ProcessId(1), 1, "First");
|
||||
let second = manager.create_game_task(ProcessId(2), 2, "Second");
|
||||
|
||||
assert!(manager.set_foreground(first));
|
||||
assert!(manager.set_foreground(second));
|
||||
|
||||
assert_eq!(manager.foreground_task(), Some(second));
|
||||
assert_eq!(
|
||||
manager.get(first).expect("first task should exist").state,
|
||||
TaskState::Background
|
||||
);
|
||||
assert_eq!(
|
||||
manager.get(second).expect("second task should exist").state,
|
||||
TaskState::Foreground
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_foreground_unknown_task_returns_false() {
|
||||
let mut manager = TaskManager::new();
|
||||
|
||||
assert!(!manager.set_foreground(TaskId(999)));
|
||||
assert_eq!(manager.foreground_task(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn close_foreground_task_clears_foreground() {
|
||||
let mut manager = TaskManager::new();
|
||||
|
||||
let task_id = manager.create_game_task(ProcessId(1), 42, "Sector Crawl");
|
||||
|
||||
assert!(manager.set_foreground(task_id));
|
||||
assert!(manager.close_task(task_id));
|
||||
|
||||
let task = manager.get(task_id).expect("task should exist");
|
||||
|
||||
assert_eq!(task.state, TaskState::Closed);
|
||||
assert_eq!(manager.foreground_task(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn suspend_foreground_task_clears_foreground() {
|
||||
let mut manager = TaskManager::new();
|
||||
|
||||
let task_id = manager.create_game_task(ProcessId(1), 42, "Sector Crawl");
|
||||
|
||||
assert!(manager.set_foreground(task_id));
|
||||
assert!(manager.mark_suspended(task_id));
|
||||
|
||||
let task = manager.get(task_id).expect("task should exist");
|
||||
|
||||
assert_eq!(task.state, TaskState::Suspended);
|
||||
assert_eq!(manager.foreground_task(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn crashed_foreground_task_clears_foreground() {
|
||||
let mut manager = TaskManager::new();
|
||||
|
||||
let task_id = manager.create_game_task(ProcessId(1), 42, "Sector Crawl");
|
||||
|
||||
assert!(manager.set_foreground(task_id));
|
||||
assert!(manager.mark_crashed(task_id));
|
||||
|
||||
let task = manager.get(task_id).expect("task should exist");
|
||||
|
||||
assert_eq!(task.state, TaskState::Crashed);
|
||||
assert_eq!(manager.foreground_task(), None);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,8 @@
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum TaskState {
|
||||
Foreground,
|
||||
Background,
|
||||
Suspended,
|
||||
Closed,
|
||||
Crashed,
|
||||
}
|
||||
@ -285,7 +285,7 @@ fn tick_system_profile_rejects_gfx_game_surface() {
|
||||
ret_slots: 0,
|
||||
}],
|
||||
);
|
||||
let cartridge = cartridge_with_program_and_mode(program, caps::GFX, AppMode::System);
|
||||
let cartridge = cartridge_with_program_and_mode(program, caps::GFX, AppMode::Shell);
|
||||
|
||||
runtime.initialize_vm(&mut vm, &cartridge).expect("runtime must initialize");
|
||||
let report = runtime.tick(&mut vm, &signals, &mut hardware).expect("system gfx call must trap");
|
||||
@ -316,7 +316,7 @@ fn tick_system_profile_rejects_composer_game_surface() {
|
||||
ret_slots: 1,
|
||||
}],
|
||||
);
|
||||
let cartridge = cartridge_with_program_and_mode(program, caps::GFX, AppMode::System);
|
||||
let cartridge = cartridge_with_program_and_mode(program, caps::GFX, AppMode::Shell);
|
||||
|
||||
runtime.initialize_vm(&mut vm, &cartridge).expect("runtime must initialize");
|
||||
let report =
|
||||
@ -350,7 +350,7 @@ fn tick_system_profile_rejects_bank_game_surface() {
|
||||
ret_slots: 2,
|
||||
}],
|
||||
);
|
||||
let cartridge = cartridge_with_program_and_mode(program, caps::BANK, AppMode::System);
|
||||
let cartridge = cartridge_with_program_and_mode(program, caps::BANK, AppMode::Shell);
|
||||
|
||||
runtime.initialize_vm(&mut vm, &cartridge).expect("runtime must initialize");
|
||||
let report =
|
||||
@ -383,7 +383,7 @@ fn tick_system_profile_can_use_shared_log_transport() {
|
||||
ret_slots: 0,
|
||||
}],
|
||||
);
|
||||
let cartridge = cartridge_with_program_and_mode(program, caps::LOG, AppMode::System);
|
||||
let cartridge = cartridge_with_program_and_mode(program, caps::LOG, AppMode::Shell);
|
||||
|
||||
runtime.initialize_vm(&mut vm, &cartridge).expect("runtime must initialize");
|
||||
let report = runtime.tick(&mut vm, &signals, &mut hardware);
|
||||
@ -632,7 +632,7 @@ fn reset_clears_cartridge_scoped_runtime_state() {
|
||||
runtime.current_app_id = 42;
|
||||
runtime.current_cartridge_title = "Cart".into();
|
||||
runtime.current_cartridge_app_version = "1.2.3".into();
|
||||
runtime.current_cartridge_app_mode = AppMode::System;
|
||||
runtime.current_cartridge_app_mode = AppMode::Shell;
|
||||
runtime.logs_written_this_frame.insert(42, 3);
|
||||
runtime.atomic_telemetry.logs_count.store(5, Ordering::Relaxed);
|
||||
runtime.atomic_telemetry.current_logs_count.store(8, Ordering::Relaxed);
|
||||
|
||||
@ -81,7 +81,7 @@ fn tick_memcard_access_is_denied_for_non_game_profile() {
|
||||
app_id: 101,
|
||||
title: "System App".into(),
|
||||
app_version: "1.0.0".into(),
|
||||
app_mode: AppMode::System,
|
||||
app_mode: AppMode::Shell,
|
||||
capabilities: caps::FS,
|
||||
program,
|
||||
assets: AssetsPayloadSource::empty(),
|
||||
|
||||
@ -58,7 +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.initialize_vm(&mut firmware.vm, &cartridge);
|
||||
let _ = firmware.os.vm_runtime.initialize_vm(&mut firmware.vm, &cartridge);
|
||||
}
|
||||
|
||||
match TcpListener::bind(format!("127.0.0.1:{}", debug_port)) {
|
||||
@ -117,10 +117,10 @@ impl HostDebugger {
|
||||
protocol_version: DEVTOOLS_PROTOCOL_VERSION,
|
||||
runtime_version: "0.1".to_string(),
|
||||
cartridge: HandshakeCartridge {
|
||||
app_id: firmware.os.current_app_id,
|
||||
title: firmware.os.current_cartridge_title.clone(),
|
||||
app_version: firmware.os.current_cartridge_app_version.clone(),
|
||||
app_mode: firmware.os.current_cartridge_app_mode,
|
||||
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,
|
||||
},
|
||||
};
|
||||
self.send_response(handshake);
|
||||
@ -138,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.paused = false;
|
||||
firmware.os.vm_runtime.paused = false;
|
||||
self.waiting_for_start = false;
|
||||
}
|
||||
Ok(n) => {
|
||||
@ -165,7 +165,7 @@ impl HostDebugger {
|
||||
Err(e) => {
|
||||
eprintln!("[Debugger] Connection error: {}", e);
|
||||
self.stream = None;
|
||||
firmware.os.paused = false;
|
||||
firmware.os.vm_runtime.paused = false;
|
||||
self.waiting_for_start = false;
|
||||
}
|
||||
}
|
||||
@ -190,23 +190,23 @@ impl HostDebugger {
|
||||
println!("[Debugger] Starting execution...");
|
||||
self.waiting_for_start = false;
|
||||
}
|
||||
firmware.os.paused = false;
|
||||
firmware.os.vm_runtime.paused = false;
|
||||
}
|
||||
DebugCommand::Pause => {
|
||||
firmware.os.paused = true;
|
||||
firmware.os.vm_runtime.paused = true;
|
||||
}
|
||||
DebugCommand::Resume => {
|
||||
firmware.os.paused = false;
|
||||
firmware.os.vm_runtime.paused = false;
|
||||
}
|
||||
DebugCommand::Step => {
|
||||
// Execute exactly one instruction and keep paused.
|
||||
firmware.os.paused = true;
|
||||
let _ = firmware.os.debug_step_instruction(&mut firmware.vm, hardware);
|
||||
firmware.os.vm_runtime.paused = true;
|
||||
let _ = firmware.os.vm_runtime.debug_step_instruction(&mut firmware.vm, hardware);
|
||||
}
|
||||
DebugCommand::StepFrame => {
|
||||
// Execute until the end of the current logical frame.
|
||||
firmware.os.paused = false;
|
||||
firmware.os.debug_step_request = true;
|
||||
firmware.os.vm_runtime.paused = false;
|
||||
firmware.os.vm_runtime.debug_step_request = true;
|
||||
}
|
||||
DebugCommand::GetState => {
|
||||
// Return detailed VM register and stack state.
|
||||
@ -215,8 +215,8 @@ impl HostDebugger {
|
||||
let resp = DebugResponse::GetState {
|
||||
pc: firmware.vm.pc(),
|
||||
stack_top,
|
||||
frame_index: firmware.os.logical_frame_index,
|
||||
app_id: firmware.os.current_app_id,
|
||||
frame_index: firmware.os.vm_runtime.logical_frame_index,
|
||||
app_id: firmware.os.vm_runtime.current_app_id,
|
||||
};
|
||||
self.send_response(resp);
|
||||
}
|
||||
@ -301,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.last_crash_report.as_ref() {
|
||||
if let Some(report) = firmware.os.vm_runtime.last_crash_report.as_ref() {
|
||||
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.vm_runtime.log_service.get_after(self.last_log_seq);
|
||||
for event in new_events {
|
||||
self.last_log_seq = event.seq;
|
||||
|
||||
@ -316,16 +316,16 @@ impl HostDebugger {
|
||||
if event.tag == 0xDEB1 {
|
||||
self.send_event(DebugEvent::BreakpointHit {
|
||||
pc: firmware.vm.pc(),
|
||||
frame_index: firmware.os.logical_frame_index,
|
||||
frame_index: firmware.os.vm_runtime.logical_frame_index,
|
||||
});
|
||||
}
|
||||
|
||||
if (0xCA01..=0xCA07).contains(&event.tag)
|
||||
&& let Some(cert_event) = Self::cert_event_from_snapshot(
|
||||
event.tag,
|
||||
firmware.os.atomic_telemetry.snapshot(),
|
||||
&firmware.os.certifier.config,
|
||||
firmware.os.logical_frame_index,
|
||||
firmware.os.vm_runtime.atomic_telemetry.snapshot(),
|
||||
&firmware.os.vm_runtime.certifier.config,
|
||||
firmware.os.vm_runtime.logical_frame_index,
|
||||
)
|
||||
{
|
||||
self.send_event(cert_event);
|
||||
@ -339,9 +339,9 @@ impl HostDebugger {
|
||||
}
|
||||
|
||||
// 2. Send telemetry snapshots at the completion of every frame.
|
||||
let current_frame = firmware.os.logical_frame_index;
|
||||
let current_frame = firmware.os.vm_runtime.logical_frame_index;
|
||||
if current_frame > self.last_telemetry_frame {
|
||||
let tel = firmware.os.atomic_telemetry.snapshot();
|
||||
let tel = firmware.os.vm_runtime.atomic_telemetry.snapshot();
|
||||
self.send_event(Self::telemetry_event_from_snapshot(tel));
|
||||
self.last_telemetry_frame = current_frame;
|
||||
}
|
||||
@ -399,14 +399,14 @@ mod tests {
|
||||
let mut hardware = Hardware::new();
|
||||
|
||||
debugger.handle_command(DebugCommand::Pause, &mut firmware, &mut hardware);
|
||||
assert!(firmware.os.paused);
|
||||
assert!(firmware.os.vm_runtime.paused);
|
||||
|
||||
debugger.handle_command(DebugCommand::Resume, &mut firmware, &mut hardware);
|
||||
assert!(!firmware.os.paused);
|
||||
assert!(!firmware.os.vm_runtime.paused);
|
||||
|
||||
debugger.handle_command(DebugCommand::StepFrame, &mut firmware, &mut hardware);
|
||||
assert!(!firmware.os.paused);
|
||||
assert!(firmware.os.debug_step_request);
|
||||
assert!(!firmware.os.vm_runtime.paused);
|
||||
assert!(firmware.os.vm_runtime.debug_step_request);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -416,11 +416,11 @@ mod tests {
|
||||
let mut hardware = Hardware::new();
|
||||
|
||||
debugger.waiting_for_start = true;
|
||||
firmware.os.paused = true;
|
||||
firmware.os.vm_runtime.paused = true;
|
||||
|
||||
debugger.handle_command(DebugCommand::Start, &mut firmware, &mut hardware);
|
||||
|
||||
assert!(!debugger.waiting_for_start);
|
||||
assert!(!firmware.os.paused);
|
||||
assert!(!firmware.os.vm_runtime.paused);
|
||||
}
|
||||
}
|
||||
|
||||
@ -58,8 +58,8 @@ struct FrameCanvas<'a> {
|
||||
}
|
||||
|
||||
pub(crate) fn capture_snapshot(stats: &HostStats, firmware: &Firmware) -> OverlaySnapshot {
|
||||
let tel = firmware.os.atomic_telemetry.snapshot();
|
||||
let recent_logs = firmware.os.log_service.get_recent(10);
|
||||
let tel = firmware.os.vm_runtime.atomic_telemetry.snapshot();
|
||||
let recent_logs = firmware.os.vm_runtime.log_service.get_recent(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.last_crash_report.as_ref() {
|
||||
if let Some(report) = firmware.os.vm_runtime.last_crash_report.as_ref() {
|
||||
footer.push(OverlayMetric {
|
||||
label: "CRASH",
|
||||
value: truncate_value(&report.summary(), 28),
|
||||
@ -86,6 +86,7 @@ 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
|
||||
.max_heap_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.vm_runtime.mount_fs(Box::new(backend));
|
||||
}
|
||||
|
||||
Self {
|
||||
@ -204,7 +204,7 @@ impl HostRunner {
|
||||
}
|
||||
|
||||
fn machine_running(&self) -> bool {
|
||||
!self.firmware.os.paused && !self.debugger.waiting_for_start
|
||||
!self.firmware.os.vm_runtime.paused && !self.debugger.waiting_for_start
|
||||
}
|
||||
}
|
||||
|
||||
@ -320,23 +320,26 @@ 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.paused;
|
||||
let was_paused = self.firmware.os.vm_runtime.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.inspection_active = self.overlay_enabled || self.debugger.stream.is_some();
|
||||
self.firmware.os.vm_runtime.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()
|
||||
if matches!(
|
||||
self.firmware.os.vm_runtime.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));
|
||||
self.firmware.os.vm_runtime.mount_fs(Box::new(backend));
|
||||
}
|
||||
}
|
||||
|
||||
@ -361,13 +364,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.last_frame_cpu_time_us);
|
||||
self.stats.record_host_cpu_time(self.firmware.os.vm_runtime.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.paused || self.debugger.waiting_for_start;
|
||||
let is_paused = self.firmware.os.vm_runtime.paused || self.debugger.waiting_for_start;
|
||||
if is_paused != self.last_paused_state {
|
||||
self.last_paused_state = is_paused;
|
||||
let cmd =
|
||||
@ -382,11 +385,11 @@ impl ApplicationHandler for HostRunner {
|
||||
self.stats.record_frame();
|
||||
}
|
||||
|
||||
self.presentation.note_published_frame(self.firmware.os.logical_frame_index);
|
||||
self.presentation.note_published_frame(self.firmware.os.vm_runtime.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.paused
|
||||
|| was_paused != self.firmware.os.vm_runtime.paused
|
||||
{
|
||||
self.invalidate_host_surface();
|
||||
}
|
||||
@ -400,9 +403,9 @@ impl ApplicationHandler for HostRunner {
|
||||
// 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.vm_runtime.log_service.get_recent(4096)
|
||||
} else {
|
||||
self.firmware.os.log_service.get_after(last_seq)
|
||||
self.firmware.os.vm_runtime.log_service.get_after(last_seq)
|
||||
};
|
||||
self.log_sink.process_events(new_events);
|
||||
|
||||
@ -812,7 +815,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.paused, "VM should be paused");
|
||||
assert!(runner.firmware.os.vm_runtime.paused, "VM should be paused");
|
||||
}
|
||||
|
||||
// 2. Disconnect (stream goes out of scope)
|
||||
@ -820,7 +823,7 @@ mod tests {
|
||||
runner.debugger.check_commands(&mut runner.firmware, &mut runner.hardware);
|
||||
|
||||
// 3. Check if unpaused
|
||||
assert!(!runner.firmware.os.paused, "VM should have unpaused after disconnect");
|
||||
assert!(!runner.firmware.os.vm_runtime.paused, "VM should have unpaused after disconnect");
|
||||
assert!(!runner.debugger.waiting_for_start, "VM should have left waiting_for_start state");
|
||||
}
|
||||
}
|
||||
|
||||
@ -88,10 +88,11 @@ impl HostStats {
|
||||
self.current_fps,
|
||||
cpu_load_core,
|
||||
cpu_load_audio,
|
||||
firmware.os.tick_index,
|
||||
firmware.os.logical_frame_index,
|
||||
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),
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user