Merge pull request 'dev/system-os-service-ownership-and-module-layout' (#26) from dev/system-os-service-ownership-and-module-layout into master
All checks were successful
Intrepid/Prometeu/Runtime/pipeline/head This commit looks good
All checks were successful
Intrepid/Prometeu/Runtime/pipeline/head This commit looks good
Reviewed-on: #26
This commit is contained in:
commit
5282089c7d
@ -339,8 +339,8 @@ mod tests {
|
|||||||
firmware.tick(&signals, &mut hardware);
|
firmware.tick(&signals, &mut hardware);
|
||||||
|
|
||||||
assert!(matches!(firmware.state, FirmwareState::SystemRunning(_)));
|
assert!(matches!(firmware.state, FirmwareState::SystemRunning(_)));
|
||||||
assert!(firmware.hub.window_manager.focused.is_some());
|
assert!(firmware.os.window_manager.focused.is_some());
|
||||||
assert_eq!(firmware.hub.window_manager.windows.len(), 1);
|
assert_eq!(firmware.os.window_manager.windows.len(), 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@ -15,12 +15,7 @@ impl AppCrashesStep {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn on_enter(&mut self, ctx: &mut PrometeuContext) {
|
pub fn on_enter(&mut self, ctx: &mut PrometeuContext) {
|
||||||
ctx.os.vm_runtime.log(
|
ctx.os.log(LogLevel::Error, LogSource::Pos, self.report.log_tag(), self.log_message());
|
||||||
LogLevel::Error,
|
|
||||||
LogSource::Pos,
|
|
||||||
self.report.log_tag(),
|
|
||||||
self.log_message(),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn on_update(&mut self, ctx: &mut PrometeuContext) -> Option<FirmwareState> {
|
pub fn on_update(&mut self, ctx: &mut PrometeuContext) -> Option<FirmwareState> {
|
||||||
|
|||||||
@ -15,12 +15,7 @@ impl GameRunningStep {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn on_enter(&mut self, ctx: &mut PrometeuContext) {
|
pub fn on_enter(&mut self, ctx: &mut PrometeuContext) {
|
||||||
ctx.os.vm_runtime.log(
|
ctx.os.log(LogLevel::Info, LogSource::Pos, 0, "Entering GameRunning".to_string());
|
||||||
LogLevel::Info,
|
|
||||||
LogSource::Pos,
|
|
||||||
0,
|
|
||||||
"Entering GameRunning".to_string(),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn on_update(&mut self, ctx: &mut PrometeuContext) -> Option<FirmwareState> {
|
pub fn on_update(&mut self, ctx: &mut PrometeuContext) -> Option<FirmwareState> {
|
||||||
@ -40,7 +35,7 @@ impl GameRunningStep {
|
|||||||
return Some(FirmwareState::AppCrashes(AppCrashesStep { report }));
|
return Some(FirmwareState::AppCrashes(AppCrashesStep { report }));
|
||||||
}
|
}
|
||||||
|
|
||||||
let result = ctx.os.vm_runtime.tick(ctx.vm, ctx.signals, ctx.hw);
|
let result = ctx.os.tick_vm(ctx.vm, ctx.signals, ctx.hw);
|
||||||
|
|
||||||
if !ctx.os.vm_runtime.logical_frame_active {
|
if !ctx.os.vm_runtime.logical_frame_active {
|
||||||
ctx.hw.gfx_mut().present();
|
ctx.hw.gfx_mut().present();
|
||||||
|
|||||||
@ -7,7 +7,7 @@ pub struct HubHomeStep;
|
|||||||
|
|
||||||
impl HubHomeStep {
|
impl HubHomeStep {
|
||||||
pub fn on_enter(&mut self, ctx: &mut PrometeuContext) {
|
pub fn on_enter(&mut self, ctx: &mut PrometeuContext) {
|
||||||
ctx.os.vm_runtime.log(LogLevel::Info, LogSource::Hub, 0, "Entering HubHome".to_string());
|
ctx.os.log(LogLevel::Info, LogSource::Hub, 0, "Entering HubHome".to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn on_update(&mut self, ctx: &mut PrometeuContext) -> Option<FirmwareState> {
|
pub fn on_update(&mut self, ctx: &mut PrometeuContext) -> Option<FirmwareState> {
|
||||||
|
|||||||
@ -9,7 +9,7 @@ pub struct LaunchHubStep;
|
|||||||
|
|
||||||
impl LaunchHubStep {
|
impl LaunchHubStep {
|
||||||
pub fn on_enter(&mut self, ctx: &mut PrometeuContext) {
|
pub fn on_enter(&mut self, ctx: &mut PrometeuContext) {
|
||||||
ctx.os.vm_runtime.log(LogLevel::Info, LogSource::Pos, 0, "Launching Hub".to_string());
|
ctx.os.log(LogLevel::Info, LogSource::Pos, 0, "Launching Hub".to_string());
|
||||||
ctx.hub.init();
|
ctx.hub.init();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -22,7 +22,7 @@ impl LaunchHubStep {
|
|||||||
return Some(FirmwareState::LoadCartridge(LoadCartridgeStep::new(cartridge)));
|
return Some(FirmwareState::LoadCartridge(LoadCartridgeStep::new(cartridge)));
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
ctx.os.vm_runtime.log(
|
ctx.os.log(
|
||||||
LogLevel::Error,
|
LogLevel::Error,
|
||||||
LogSource::Pos,
|
LogSource::Pos,
|
||||||
0,
|
0,
|
||||||
|
|||||||
@ -20,7 +20,7 @@ impl LoadCartridgeStep {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn on_enter(&mut self, ctx: &mut PrometeuContext) {
|
pub fn on_enter(&mut self, ctx: &mut PrometeuContext) {
|
||||||
ctx.os.vm_runtime.log(
|
ctx.os.log(
|
||||||
LogLevel::Info,
|
LogLevel::Info,
|
||||||
LogSource::Pos,
|
LogSource::Pos,
|
||||||
0,
|
0,
|
||||||
@ -34,7 +34,7 @@ impl LoadCartridgeStep {
|
|||||||
self.cartridge.assets.clone(),
|
self.cartridge.assets.clone(),
|
||||||
);
|
);
|
||||||
|
|
||||||
self.init_error = ctx.os.vm_runtime.initialize_vm(ctx.vm, &self.cartridge).err();
|
self.init_error = ctx.os.initialize_vm(ctx.vm, &self.cartridge).err();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn on_update(&mut self, ctx: &mut PrometeuContext) -> Option<FirmwareState> {
|
pub fn on_update(&mut self, ctx: &mut PrometeuContext) -> Option<FirmwareState> {
|
||||||
@ -43,12 +43,12 @@ impl LoadCartridgeStep {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if self.cartridge.app_mode == AppMode::Shell {
|
if self.cartridge.app_mode == AppMode::Shell {
|
||||||
let id = ctx.hub.window_manager.add_window(
|
let id = ctx.os.window_manager.add_window(
|
||||||
self.cartridge.title.clone(),
|
self.cartridge.title.clone(),
|
||||||
Rect { x: 40, y: 20, w: 240, h: 140 },
|
Rect { x: 40, y: 20, w: 240, h: 140 },
|
||||||
Color::WHITE,
|
Color::WHITE,
|
||||||
);
|
);
|
||||||
ctx.hub.window_manager.set_focus(id);
|
ctx.os.window_manager.set_focus(id);
|
||||||
|
|
||||||
let task_id =
|
let task_id =
|
||||||
ctx.os.create_vm_shell_task(self.cartridge.app_id, self.cartridge.title.clone());
|
ctx.os.create_vm_shell_task(self.cartridge.app_id, self.cartridge.title.clone());
|
||||||
|
|||||||
@ -8,7 +8,7 @@ pub struct ResetStep;
|
|||||||
|
|
||||||
impl ResetStep {
|
impl ResetStep {
|
||||||
pub fn on_enter(&mut self, ctx: &mut PrometeuContext) {
|
pub fn on_enter(&mut self, ctx: &mut PrometeuContext) {
|
||||||
ctx.os.vm_runtime.log(LogLevel::Info, LogSource::Pos, 0, "Firmware Reset".to_string());
|
ctx.os.log(LogLevel::Info, LogSource::Pos, 0, "Firmware Reset".to_string());
|
||||||
ctx.os.vm_runtime.reset(ctx.vm);
|
ctx.os.vm_runtime.reset(ctx.vm);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -10,12 +10,7 @@ pub struct SplashScreenStep {
|
|||||||
|
|
||||||
impl SplashScreenStep {
|
impl SplashScreenStep {
|
||||||
pub fn on_enter(&mut self, ctx: &mut PrometeuContext) {
|
pub fn on_enter(&mut self, ctx: &mut PrometeuContext) {
|
||||||
ctx.os.vm_runtime.log(
|
ctx.os.log(LogLevel::Info, LogSource::Pos, 0, "Showing SplashScreen".to_string());
|
||||||
LogLevel::Info,
|
|
||||||
LogSource::Pos,
|
|
||||||
0,
|
|
||||||
"Showing SplashScreen".to_string(),
|
|
||||||
);
|
|
||||||
// Play sound on enter
|
// Play sound on enter
|
||||||
// ctx.hw.audio_mut().play(0, 0, 0, 255, 127, 1.0, 0, LoopMode::Off);
|
// ctx.hw.audio_mut().play(0, 0, 0, 255, 127, 1.0, 0, LoopMode::Off);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -14,12 +14,7 @@ impl SystemRunningStep {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn on_enter(&mut self, ctx: &mut PrometeuContext) {
|
pub fn on_enter(&mut self, ctx: &mut PrometeuContext) {
|
||||||
ctx.os.vm_runtime.log(
|
ctx.os.log(LogLevel::Info, LogSource::Hub, 0, "Entering SystemRunning".to_string());
|
||||||
LogLevel::Info,
|
|
||||||
LogSource::Hub,
|
|
||||||
0,
|
|
||||||
"Entering SystemRunning".to_string(),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn on_update(&mut self, ctx: &mut PrometeuContext) -> Option<FirmwareState> {
|
pub fn on_update(&mut self, ctx: &mut PrometeuContext) -> Option<FirmwareState> {
|
||||||
|
|||||||
@ -2,7 +2,6 @@ mod crash_report;
|
|||||||
mod os;
|
mod os;
|
||||||
mod programs;
|
mod programs;
|
||||||
mod services;
|
mod services;
|
||||||
mod virtual_machine_runtime;
|
|
||||||
|
|
||||||
pub use crash_report::CrashReport;
|
pub use crash_report::CrashReport;
|
||||||
pub use os::SystemOS;
|
pub use os::SystemOS;
|
||||||
@ -10,4 +9,4 @@ pub use programs::PrometeuHub;
|
|||||||
pub use services::fs;
|
pub use services::fs;
|
||||||
pub use services::process;
|
pub use services::process;
|
||||||
pub use services::task;
|
pub use services::task;
|
||||||
pub use virtual_machine_runtime::VirtualMachineRuntime;
|
pub use services::vm_runtime::VirtualMachineRuntime;
|
||||||
|
|||||||
@ -1,22 +1,101 @@
|
|||||||
|
use crate::CrashReport;
|
||||||
use crate::VirtualMachineRuntime;
|
use crate::VirtualMachineRuntime;
|
||||||
|
use crate::fs::{FsBackend, FsState, VirtualFS};
|
||||||
use crate::process::ProcessManager;
|
use crate::process::ProcessManager;
|
||||||
|
use crate::services::memcard::MemcardService;
|
||||||
|
use crate::services::window_manager::WindowManager;
|
||||||
use crate::task::TaskId;
|
use crate::task::TaskId;
|
||||||
use crate::task::TaskManager;
|
use crate::task::TaskManager;
|
||||||
|
use prometeu_hal::cartridge::Cartridge;
|
||||||
|
use prometeu_hal::log::{LogLevel, LogService, LogSource};
|
||||||
use prometeu_hal::telemetry::CertificationConfig;
|
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 struct SystemOS {
|
||||||
pub vm_runtime: VirtualMachineRuntime,
|
pub vm_runtime: VirtualMachineRuntime,
|
||||||
pub process_manager: ProcessManager,
|
pub process_manager: ProcessManager,
|
||||||
pub task_manager: TaskManager,
|
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,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SystemOS {
|
impl SystemOS {
|
||||||
pub fn new(cap_config: Option<CertificationConfig>) -> Self {
|
pub fn new(cap_config: Option<CertificationConfig>) -> Self {
|
||||||
Self {
|
let log_service = LogService::new(4096);
|
||||||
vm_runtime: VirtualMachineRuntime::new(cap_config),
|
let mut os = Self {
|
||||||
|
vm_runtime: VirtualMachineRuntime::new_with_log_counter(
|
||||||
|
cap_config,
|
||||||
|
Arc::clone(&log_service.logs_count),
|
||||||
|
),
|
||||||
process_manager: ProcessManager::new(),
|
process_manager: ProcessManager::new(),
|
||||||
task_manager: TaskManager::new(),
|
task_manager: TaskManager::new(),
|
||||||
}
|
window_manager: WindowManager::new(),
|
||||||
|
log_service,
|
||||||
|
fs: VirtualFS::new(),
|
||||||
|
fs_state: FsState::Unmounted,
|
||||||
|
memcard: MemcardService::new(),
|
||||||
|
open_files: HashMap::new(),
|
||||||
|
next_handle: 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
os.log(LogLevel::Info, LogSource::Pos, 0, "PrometeuOS starting...".to_string());
|
||||||
|
|
||||||
|
os
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn log(&mut self, level: LogLevel, source: LogSource, tag: u16, msg: String) {
|
||||||
|
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 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 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 unmount_fs(&mut self) {
|
||||||
|
self.fs.unmount();
|
||||||
|
self.fs_state = FsState::Unmounted;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
pub fn create_vm_game_task(&mut self, app_id: u32, title: impl Into<String>) -> TaskId {
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
#[allow(clippy::module_inception)]
|
#[allow(clippy::module_inception)]
|
||||||
mod prometeu_hub;
|
mod prometeu_hub;
|
||||||
mod window_manager;
|
|
||||||
|
|
||||||
pub use prometeu_hub::PrometeuHub;
|
pub use prometeu_hub::PrometeuHub;
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
use crate::programs::prometeu_hub::window_manager::WindowManager;
|
|
||||||
use crate::{CrashReport, SystemOS};
|
use crate::{CrashReport, SystemOS};
|
||||||
use prometeu_hal::color::Color;
|
use prometeu_hal::color::Color;
|
||||||
use prometeu_hal::log::{LogLevel, LogSource};
|
use prometeu_hal::log::{LogLevel, LogSource};
|
||||||
@ -7,9 +6,7 @@ use prometeu_hal::{HardwareBridge, InputSignals};
|
|||||||
use prometeu_vm::VirtualMachine;
|
use prometeu_vm::VirtualMachine;
|
||||||
|
|
||||||
/// PrometeuHub: Launcher and system UI environment.
|
/// PrometeuHub: Launcher and system UI environment.
|
||||||
pub struct PrometeuHub {
|
pub struct PrometeuHub;
|
||||||
pub window_manager: WindowManager,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct SystemProfileUpdate {
|
pub struct SystemProfileUpdate {
|
||||||
pub crash: Option<CrashReport>,
|
pub crash: Option<CrashReport>,
|
||||||
@ -24,7 +21,7 @@ impl Default for PrometeuHub {
|
|||||||
|
|
||||||
impl PrometeuHub {
|
impl PrometeuHub {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self { window_manager: WindowManager::new() }
|
Self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn init(&mut self) {
|
pub fn init(&mut self) {
|
||||||
@ -37,41 +34,41 @@ impl PrometeuHub {
|
|||||||
let mut next_window = None;
|
let mut next_window = None;
|
||||||
|
|
||||||
if hw.pad().a().pressed {
|
if hw.pad().a().pressed {
|
||||||
os.vm_runtime.log(LogLevel::Debug, LogSource::Hub, 0, "window A opened".to_string());
|
os.log(LogLevel::Debug, LogSource::Hub, 0, "window A opened".to_string());
|
||||||
next_window = Some((
|
next_window = Some((
|
||||||
"Green Window".to_string(),
|
"Green Window".to_string(),
|
||||||
Rect { x: 0, y: 0, w: 160, h: 90 },
|
Rect { x: 0, y: 0, w: 160, h: 90 },
|
||||||
Color::GREEN,
|
Color::GREEN,
|
||||||
));
|
));
|
||||||
} else if hw.pad().b().pressed {
|
} else if hw.pad().b().pressed {
|
||||||
os.vm_runtime.log(LogLevel::Debug, LogSource::Hub, 0, "window B opened".to_string());
|
os.log(LogLevel::Debug, LogSource::Hub, 0, "window B opened".to_string());
|
||||||
next_window = Some((
|
next_window = Some((
|
||||||
"Indigo Window".to_string(),
|
"Indigo Window".to_string(),
|
||||||
Rect { x: 160, y: 0, w: 160, h: 90 },
|
Rect { x: 160, y: 0, w: 160, h: 90 },
|
||||||
Color::INDIGO,
|
Color::INDIGO,
|
||||||
));
|
));
|
||||||
} else if hw.pad().x().pressed {
|
} else if hw.pad().x().pressed {
|
||||||
os.vm_runtime.log(LogLevel::Debug, LogSource::Hub, 0, "window X opened".to_string());
|
os.log(LogLevel::Debug, LogSource::Hub, 0, "window X opened".to_string());
|
||||||
next_window = Some((
|
next_window = Some((
|
||||||
"Yellow Window".to_string(),
|
"Yellow Window".to_string(),
|
||||||
Rect { x: 0, y: 90, w: 160, h: 90 },
|
Rect { x: 0, y: 90, w: 160, h: 90 },
|
||||||
Color::YELLOW,
|
Color::YELLOW,
|
||||||
));
|
));
|
||||||
} else if hw.pad().y().pressed {
|
} else if hw.pad().y().pressed {
|
||||||
os.vm_runtime.log(LogLevel::Debug, LogSource::Hub, 0, "window Y opened".to_string());
|
os.log(LogLevel::Debug, LogSource::Hub, 0, "window Y opened".to_string());
|
||||||
next_window =
|
next_window =
|
||||||
Some(("Red Window".to_string(), Rect { x: 160, y: 90, w: 160, h: 90 }, Color::RED));
|
Some(("Red Window".to_string(), Rect { x: 160, y: 90, w: 160, h: 90 }, Color::RED));
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some((title, rect, color)) = next_window {
|
if let Some((title, rect, color)) = next_window {
|
||||||
self.window_manager.remove_all_windows();
|
os.window_manager.remove_all_windows();
|
||||||
let id = self.window_manager.add_window(title, rect, color);
|
let id = os.window_manager.add_window(title, rect, color);
|
||||||
self.window_manager.set_focus(id);
|
os.window_manager.set_focus(id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn render(&mut self, _os: &mut SystemOS, hw: &mut dyn HardwareBridge) {
|
pub fn render(&mut self, os: &mut SystemOS, hw: &mut dyn HardwareBridge) {
|
||||||
for window in &self.window_manager.windows {
|
for window in &os.window_manager.windows {
|
||||||
hw.gfx_mut().fill_rect(
|
hw.gfx_mut().fill_rect(
|
||||||
window.viewport.x,
|
window.viewport.x,
|
||||||
window.viewport.y,
|
window.viewport.y,
|
||||||
@ -93,17 +90,17 @@ impl PrometeuHub {
|
|||||||
|
|
||||||
self.gui_update(os, hw);
|
self.gui_update(os, hw);
|
||||||
|
|
||||||
if let Some(focused_id) = self.window_manager.focused {
|
if let Some(focused_id) = os.window_manager.focused {
|
||||||
if hw.pad().start().down {
|
if hw.pad().start().down {
|
||||||
self.window_manager.remove_window(focused_id);
|
os.window_manager.remove_window(focused_id);
|
||||||
} else {
|
} else {
|
||||||
crash = os.vm_runtime.tick(vm, signals, hw);
|
crash = os.tick_vm(vm, signals, hw);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.render(os, hw);
|
self.render(os, hw);
|
||||||
hw.gfx_mut().present();
|
hw.gfx_mut().present();
|
||||||
|
|
||||||
SystemProfileUpdate { crash, focused_window_active: self.window_manager.focused.is_some() }
|
SystemProfileUpdate { crash, focused_window_active: os.window_manager.focused.is_some() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,3 +2,5 @@ pub mod fs;
|
|||||||
pub mod memcard;
|
pub mod memcard;
|
||||||
pub mod process;
|
pub mod process;
|
||||||
pub mod task;
|
pub mod task;
|
||||||
|
pub mod vm_runtime;
|
||||||
|
pub mod window_manager;
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
use super::*;
|
use super::*;
|
||||||
use crate::services::memcard::{MemcardSlotState, MemcardStatus};
|
use crate::fs::{FsState, VirtualFS};
|
||||||
|
use crate::services::memcard::{MemcardService, MemcardSlotState, MemcardStatus};
|
||||||
use prometeu_bytecode::{TRAP_INVALID_SYSCALL, TRAP_OOB, TRAP_TYPE, Value};
|
use prometeu_bytecode::{TRAP_INVALID_SYSCALL, TRAP_OOB, TRAP_TYPE, Value};
|
||||||
use prometeu_hal::asset::{AssetId, AssetOpStatus, BankType, SlotRef};
|
use prometeu_hal::asset::{AssetId, AssetOpStatus, BankType, SlotRef};
|
||||||
use prometeu_hal::cartridge::AppMode;
|
use prometeu_hal::cartridge::AppMode;
|
||||||
@ -13,10 +14,40 @@ use prometeu_hal::{
|
|||||||
AudioOpStatus, ComposerOpStatus, HostContext, HostReturn, NativeInterface, SyscallId,
|
AudioOpStatus, ComposerOpStatus, HostContext, HostReturn, NativeInterface, SyscallId,
|
||||||
expect_bool, expect_int,
|
expect_bool, expect_int,
|
||||||
};
|
};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::ops::{Deref, DerefMut};
|
||||||
use std::panic::{AssertUnwindSafe, catch_unwind};
|
use std::panic::{AssertUnwindSafe, catch_unwind};
|
||||||
use std::sync::atomic::Ordering;
|
use std::sync::atomic::Ordering;
|
||||||
|
|
||||||
impl VirtualMachineRuntime {
|
pub(crate) struct VmRuntimeHost<'a> {
|
||||||
|
pub runtime: &'a mut VirtualMachineRuntime,
|
||||||
|
pub log_service: &'a mut LogService,
|
||||||
|
pub fs: &'a mut VirtualFS,
|
||||||
|
pub fs_state: &'a mut FsState,
|
||||||
|
pub memcard: &'a mut MemcardService,
|
||||||
|
pub open_files: &'a mut HashMap<u32, String>,
|
||||||
|
pub next_handle: &'a mut u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Deref for VmRuntimeHost<'_> {
|
||||||
|
type Target = VirtualMachineRuntime;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
self.runtime
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DerefMut for VmRuntimeHost<'_> {
|
||||||
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
|
self.runtime
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VmRuntimeHost<'_> {
|
||||||
|
fn log(&mut self, level: LogLevel, source: LogSource, tag: u16, msg: String) {
|
||||||
|
self.runtime.log(self.log_service, level, source, tag, msg);
|
||||||
|
}
|
||||||
|
|
||||||
fn panic_payload_to_string(payload: Box<dyn std::any::Any + Send>) -> String {
|
fn panic_payload_to_string(payload: Box<dyn std::any::Any + Send>) -> String {
|
||||||
if let Some(message) = payload.downcast_ref::<String>() {
|
if let Some(message) = payload.downcast_ref::<String>() {
|
||||||
return message.clone();
|
return message.clone();
|
||||||
@ -40,8 +71,8 @@ impl VirtualMachineRuntime {
|
|||||||
let app_id = self.current_app_id;
|
let app_id = self.current_app_id;
|
||||||
let count = *self.logs_written_this_frame.get(&app_id).unwrap_or(&0);
|
let count = *self.logs_written_this_frame.get(&app_id).unwrap_or(&0);
|
||||||
|
|
||||||
if count >= Self::MAX_LOGS_PER_FRAME {
|
if count >= VirtualMachineRuntime::MAX_LOGS_PER_FRAME {
|
||||||
if count == Self::MAX_LOGS_PER_FRAME {
|
if count == VirtualMachineRuntime::MAX_LOGS_PER_FRAME {
|
||||||
self.logs_written_this_frame.insert(app_id, count + 1);
|
self.logs_written_this_frame.insert(app_id, count + 1);
|
||||||
self.log(
|
self.log(
|
||||||
LogLevel::Warn,
|
LogLevel::Warn,
|
||||||
@ -56,8 +87,8 @@ impl VirtualMachineRuntime {
|
|||||||
self.logs_written_this_frame.insert(app_id, count + 1);
|
self.logs_written_this_frame.insert(app_id, count + 1);
|
||||||
|
|
||||||
let mut final_msg = msg;
|
let mut final_msg = msg;
|
||||||
if final_msg.len() > Self::MAX_LOG_LEN {
|
if final_msg.len() > VirtualMachineRuntime::MAX_LOG_LEN {
|
||||||
final_msg.truncate(Self::MAX_LOG_LEN);
|
final_msg.truncate(VirtualMachineRuntime::MAX_LOG_LEN);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.log(level, LogSource::App { app_id }, tag, final_msg);
|
self.log(level, LogSource::App { app_id }, tag, final_msg);
|
||||||
@ -122,7 +153,7 @@ impl VirtualMachineRuntime {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl NativeInterface for VirtualMachineRuntime {
|
impl NativeInterface for VmRuntimeHost<'_> {
|
||||||
fn syscall(
|
fn syscall(
|
||||||
&mut self,
|
&mut self,
|
||||||
id: SyscallId,
|
id: SyscallId,
|
||||||
@ -395,13 +426,13 @@ impl NativeInterface for VirtualMachineRuntime {
|
|||||||
}
|
}
|
||||||
Syscall::FsOpen => {
|
Syscall::FsOpen => {
|
||||||
let path = expect_string(args, 0, "path")?;
|
let path = expect_string(args, 0, "path")?;
|
||||||
if self.fs_state != FsState::Mounted {
|
if *self.fs_state != FsState::Mounted {
|
||||||
ret.push_int(-1);
|
ret.push_int(-1);
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
let handle = self.next_handle;
|
let handle = *self.next_handle;
|
||||||
self.open_files.insert(handle, path);
|
self.open_files.insert(handle, path);
|
||||||
self.next_handle += 1;
|
*self.next_handle += 1;
|
||||||
ret.push_int(handle as i64);
|
ret.push_int(handle as i64);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -410,8 +441,9 @@ impl NativeInterface for VirtualMachineRuntime {
|
|||||||
let path = self
|
let path = self
|
||||||
.open_files
|
.open_files
|
||||||
.get(&handle)
|
.get(&handle)
|
||||||
.ok_or_else(|| VmFault::Panic("Invalid handle".into()))?;
|
.ok_or_else(|| VmFault::Panic("Invalid handle".into()))?
|
||||||
match self.fs.read_file(path) {
|
.clone();
|
||||||
|
match self.fs.read_file(&path) {
|
||||||
Ok(data) => ret.push_string(String::from_utf8_lossy(&data).into_owned()),
|
Ok(data) => ret.push_string(String::from_utf8_lossy(&data).into_owned()),
|
||||||
Err(_) => ret.push_null(),
|
Err(_) => ret.push_null(),
|
||||||
}
|
}
|
||||||
@ -423,8 +455,9 @@ impl NativeInterface for VirtualMachineRuntime {
|
|||||||
let path = self
|
let path = self
|
||||||
.open_files
|
.open_files
|
||||||
.get(&handle)
|
.get(&handle)
|
||||||
.ok_or_else(|| VmFault::Panic("Invalid handle".into()))?;
|
.ok_or_else(|| VmFault::Panic("Invalid handle".into()))?
|
||||||
match self.fs.write_file(path, &content) {
|
.clone();
|
||||||
|
match self.fs.write_file(&path, &content) {
|
||||||
Ok(_) => ret.push_bool(true),
|
Ok(_) => ret.push_bool(true),
|
||||||
Err(_) => ret.push_bool(false),
|
Err(_) => ret.push_bool(false),
|
||||||
}
|
}
|
||||||
@ -476,7 +509,7 @@ impl NativeInterface for VirtualMachineRuntime {
|
|||||||
ret.push_int(0);
|
ret.push_int(0);
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
let stat = self.memcard.slot_stat(&self.fs, self.current_app_id, slot);
|
let stat = self.memcard.slot_stat(self.fs, self.current_app_id, slot);
|
||||||
let status = if stat.state == MemcardSlotState::Corrupt {
|
let status = if stat.state == MemcardSlotState::Corrupt {
|
||||||
MemcardStatus::Corrupt
|
MemcardStatus::Corrupt
|
||||||
} else {
|
} else {
|
||||||
@ -500,7 +533,7 @@ impl NativeInterface for VirtualMachineRuntime {
|
|||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
let read =
|
let read =
|
||||||
self.memcard.slot_read(&self.fs, self.current_app_id, slot, offset, max_bytes);
|
self.memcard.slot_read(self.fs, self.current_app_id, slot, offset, max_bytes);
|
||||||
ret.push_int(read.status as i64);
|
ret.push_int(read.status as i64);
|
||||||
ret.push_string(hex_encode(&read.bytes));
|
ret.push_string(hex_encode(&read.bytes));
|
||||||
ret.push_int(read.bytes_read as i64);
|
ret.push_int(read.bytes_read as i64);
|
||||||
@ -516,8 +549,8 @@ impl NativeInterface for VirtualMachineRuntime {
|
|||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
let payload = hex_decode(&payload_hex)?;
|
let payload = hex_decode(&payload_hex)?;
|
||||||
let write =
|
let app_id = self.current_app_id;
|
||||||
self.memcard.slot_write(&self.fs, self.current_app_id, slot, offset, &payload);
|
let write = self.memcard.slot_write(self.fs, app_id, slot, offset, &payload);
|
||||||
ret.push_int(write.status as i64);
|
ret.push_int(write.status as i64);
|
||||||
ret.push_int(write.bytes_written as i64);
|
ret.push_int(write.bytes_written as i64);
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -529,9 +562,10 @@ impl NativeInterface for VirtualMachineRuntime {
|
|||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
let status = {
|
let status = {
|
||||||
|
let app_id = self.current_app_id;
|
||||||
let memcard = &mut self.memcard;
|
let memcard = &mut self.memcard;
|
||||||
let fs = &mut self.fs;
|
let fs = &mut self.fs;
|
||||||
memcard.slot_commit(fs, self.current_app_id, slot)
|
memcard.slot_commit(fs, app_id, slot)
|
||||||
};
|
};
|
||||||
ret.push_int(status as i64);
|
ret.push_int(status as i64);
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -543,9 +577,10 @@ impl NativeInterface for VirtualMachineRuntime {
|
|||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
let status = {
|
let status = {
|
||||||
|
let app_id = self.current_app_id;
|
||||||
let memcard = &mut self.memcard;
|
let memcard = &mut self.memcard;
|
||||||
let fs = &mut self.fs;
|
let fs = &mut self.fs;
|
||||||
memcard.slot_clear(fs, self.current_app_id, slot)
|
memcard.slot_clear(fs, app_id, slot)
|
||||||
};
|
};
|
||||||
ret.push_int(status as i64);
|
ret.push_int(status as i64);
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -1,26 +1,23 @@
|
|||||||
use super::*;
|
use super::*;
|
||||||
use crate::CrashReport;
|
use crate::CrashReport;
|
||||||
use crate::fs::FsBackend;
|
use crate::fs::{FsBackend, FsState, VirtualFS};
|
||||||
use prometeu_hal::cartridge::Cartridge;
|
use prometeu_hal::cartridge::Cartridge;
|
||||||
use prometeu_hal::log::{LogLevel, LogSource};
|
use prometeu_hal::log::{LogLevel, LogSource};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
impl VirtualMachineRuntime {
|
impl VirtualMachineRuntime {
|
||||||
pub fn new(cap_config: Option<CertificationConfig>) -> Self {
|
pub fn new_with_log_counter(
|
||||||
|
cap_config: Option<CertificationConfig>,
|
||||||
|
logs_count: Arc<AtomicU32>,
|
||||||
|
) -> Self {
|
||||||
let boot_time = Instant::now();
|
let boot_time = Instant::now();
|
||||||
let log_service = LogService::new(4096);
|
let atomic_telemetry = Arc::new(AtomicTelemetry::new(logs_count));
|
||||||
let atomic_telemetry = Arc::new(AtomicTelemetry::new(Arc::clone(&log_service.logs_count)));
|
Self {
|
||||||
let mut os = Self {
|
|
||||||
tick_index: 0,
|
tick_index: 0,
|
||||||
logical_frame_index: 0,
|
logical_frame_index: 0,
|
||||||
logical_frame_active: false,
|
logical_frame_active: false,
|
||||||
logical_frame_remaining_cycles: 0,
|
logical_frame_remaining_cycles: 0,
|
||||||
last_frame_cpu_time_us: 0,
|
last_frame_cpu_time_us: 0,
|
||||||
fs: VirtualFS::new(),
|
|
||||||
fs_state: FsState::Unmounted,
|
|
||||||
memcard: MemcardService::new(),
|
|
||||||
open_files: HashMap::new(),
|
|
||||||
next_handle: 1,
|
|
||||||
log_service,
|
|
||||||
current_app_id: 0,
|
current_app_id: 0,
|
||||||
current_cartridge_title: String::new(),
|
current_cartridge_title: String::new(),
|
||||||
current_cartridge_app_version: String::new(),
|
current_cartridge_app_version: String::new(),
|
||||||
@ -36,25 +33,41 @@ impl VirtualMachineRuntime {
|
|||||||
frame_start_string_materializations: 0,
|
frame_start_string_materializations: 0,
|
||||||
needs_prepare_entry_call: false,
|
needs_prepare_entry_call: false,
|
||||||
boot_time,
|
boot_time,
|
||||||
};
|
}
|
||||||
|
|
||||||
os.log(LogLevel::Info, LogSource::Pos, 0, "PrometeuOS starting...".to_string());
|
|
||||||
|
|
||||||
os
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn log(&mut self, level: LogLevel, source: LogSource, tag: u16, msg: String) {
|
pub fn log(
|
||||||
|
&self,
|
||||||
|
log_service: &mut LogService,
|
||||||
|
level: LogLevel,
|
||||||
|
source: LogSource,
|
||||||
|
tag: u16,
|
||||||
|
msg: String,
|
||||||
|
) {
|
||||||
let ts_ms = self.boot_time.elapsed().as_millis() as u64;
|
let ts_ms = self.boot_time.elapsed().as_millis() as u64;
|
||||||
let frame = self.logical_frame_index;
|
let frame = self.logical_frame_index;
|
||||||
self.log_service.log(ts_ms, frame, level, source, tag, msg);
|
log_service.log(ts_ms, frame, level, source, tag, msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn mount_fs(&mut self, backend: Box<dyn FsBackend>) {
|
pub fn mount_fs(
|
||||||
self.log(LogLevel::Info, LogSource::Fs, 0, "Attempting to mount filesystem".to_string());
|
&mut self,
|
||||||
match self.fs.mount(backend) {
|
log_service: &mut LogService,
|
||||||
|
fs: &mut VirtualFS,
|
||||||
|
fs_state: &mut FsState,
|
||||||
|
backend: Box<dyn FsBackend>,
|
||||||
|
) {
|
||||||
|
self.log(
|
||||||
|
log_service,
|
||||||
|
LogLevel::Info,
|
||||||
|
LogSource::Fs,
|
||||||
|
0,
|
||||||
|
"Attempting to mount filesystem".to_string(),
|
||||||
|
);
|
||||||
|
match fs.mount(backend) {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
self.fs_state = FsState::Mounted;
|
*fs_state = FsState::Mounted;
|
||||||
self.log(
|
self.log(
|
||||||
|
log_service,
|
||||||
LogLevel::Info,
|
LogLevel::Info,
|
||||||
LogSource::Fs,
|
LogSource::Fs,
|
||||||
0,
|
0,
|
||||||
@ -63,26 +76,32 @@ impl VirtualMachineRuntime {
|
|||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
let err_msg = format!("Failed to mount filesystem: {:?}", e);
|
let err_msg = format!("Failed to mount filesystem: {:?}", e);
|
||||||
self.log(LogLevel::Error, LogSource::Fs, 0, err_msg);
|
self.log(log_service, LogLevel::Error, LogSource::Fs, 0, err_msg);
|
||||||
self.fs_state = FsState::Error(e);
|
*fs_state = FsState::Error(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn unmount_fs(&mut self) {
|
pub fn unmount_fs(&mut self, fs: &mut VirtualFS, fs_state: &mut FsState) {
|
||||||
self.fs.unmount();
|
fs.unmount();
|
||||||
self.fs_state = FsState::Unmounted;
|
*fs_state = FsState::Unmounted;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn update_fs(&mut self) {
|
pub(crate) fn update_fs(
|
||||||
if self.fs_state == FsState::Mounted && !self.fs.is_healthy() {
|
&mut self,
|
||||||
|
log_service: &mut LogService,
|
||||||
|
fs: &mut VirtualFS,
|
||||||
|
fs_state: &mut FsState,
|
||||||
|
) {
|
||||||
|
if *fs_state == FsState::Mounted && !fs.is_healthy() {
|
||||||
self.log(
|
self.log(
|
||||||
|
log_service,
|
||||||
LogLevel::Error,
|
LogLevel::Error,
|
||||||
LogSource::Fs,
|
LogSource::Fs,
|
||||||
0,
|
0,
|
||||||
"Filesystem became unhealthy, unmounting".to_string(),
|
"Filesystem became unhealthy, unmounting".to_string(),
|
||||||
);
|
);
|
||||||
self.unmount_fs();
|
self.unmount_fs(fs, fs_state);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -93,10 +112,6 @@ impl VirtualMachineRuntime {
|
|||||||
self.last_frame_cpu_time_us = 0;
|
self.last_frame_cpu_time_us = 0;
|
||||||
self.atomic_telemetry.reset();
|
self.atomic_telemetry.reset();
|
||||||
|
|
||||||
self.open_files.clear();
|
|
||||||
self.next_handle = 1;
|
|
||||||
self.memcard.clear_all_staging();
|
|
||||||
|
|
||||||
self.current_app_id = 0;
|
self.current_app_id = 0;
|
||||||
self.current_cartridge_title.clear();
|
self.current_cartridge_title.clear();
|
||||||
self.current_cartridge_app_version.clear();
|
self.current_cartridge_app_version.clear();
|
||||||
@ -120,6 +135,7 @@ impl VirtualMachineRuntime {
|
|||||||
|
|
||||||
pub fn initialize_vm(
|
pub fn initialize_vm(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
log_service: &mut LogService,
|
||||||
vm: &mut VirtualMachine,
|
vm: &mut VirtualMachine,
|
||||||
cartridge: &Cartridge,
|
cartridge: &Cartridge,
|
||||||
) -> Result<(), CrashReport> {
|
) -> Result<(), CrashReport> {
|
||||||
@ -138,6 +154,7 @@ impl VirtualMachineRuntime {
|
|||||||
let report = CrashReport::VmInit { error: e };
|
let report = CrashReport::VmInit { error: e };
|
||||||
self.last_crash_report = Some(report.clone());
|
self.last_crash_report = Some(report.clone());
|
||||||
self.log(
|
self.log(
|
||||||
|
log_service,
|
||||||
LogLevel::Error,
|
LogLevel::Error,
|
||||||
LogSource::Vm,
|
LogSource::Vm,
|
||||||
report.log_tag(),
|
report.log_tag(),
|
||||||
@ -5,8 +5,6 @@ mod tests;
|
|||||||
mod tick;
|
mod tick;
|
||||||
|
|
||||||
use crate::CrashReport;
|
use crate::CrashReport;
|
||||||
use crate::fs::{FsState, VirtualFS};
|
|
||||||
use crate::services::memcard::MemcardService;
|
|
||||||
use prometeu_bytecode::string_materialization_count;
|
use prometeu_bytecode::string_materialization_count;
|
||||||
use prometeu_hal::cartridge::AppMode;
|
use prometeu_hal::cartridge::AppMode;
|
||||||
use prometeu_hal::log::LogService;
|
use prometeu_hal::log::LogService;
|
||||||
@ -14,6 +12,7 @@ use prometeu_hal::telemetry::{AtomicTelemetry, CertificationConfig, Certifier};
|
|||||||
use prometeu_vm::VirtualMachine;
|
use prometeu_vm::VirtualMachine;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
use std::sync::atomic::AtomicU32;
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
|
|
||||||
pub struct VirtualMachineRuntime {
|
pub struct VirtualMachineRuntime {
|
||||||
@ -22,12 +21,6 @@ pub struct VirtualMachineRuntime {
|
|||||||
pub logical_frame_active: bool,
|
pub logical_frame_active: bool,
|
||||||
pub logical_frame_remaining_cycles: u64,
|
pub logical_frame_remaining_cycles: u64,
|
||||||
pub last_frame_cpu_time_us: u64,
|
pub last_frame_cpu_time_us: u64,
|
||||||
pub fs: VirtualFS,
|
|
||||||
pub fs_state: FsState,
|
|
||||||
pub memcard: MemcardService,
|
|
||||||
pub open_files: HashMap<u32, String>,
|
|
||||||
pub next_handle: u32,
|
|
||||||
pub log_service: LogService,
|
|
||||||
pub current_app_id: u32,
|
pub current_app_id: u32,
|
||||||
pub current_cartridge_title: String,
|
pub current_cartridge_title: String,
|
||||||
pub current_cartridge_app_version: String,
|
pub current_cartridge_app_version: String,
|
||||||
@ -50,4 +43,8 @@ impl VirtualMachineRuntime {
|
|||||||
pub const SLICE_PER_TICK: u64 = 1_500_000;
|
pub const SLICE_PER_TICK: u64 = 1_500_000;
|
||||||
pub const MAX_LOG_LEN: usize = 256;
|
pub const MAX_LOG_LEN: usize = 256;
|
||||||
pub const MAX_LOGS_PER_FRAME: u32 = 10;
|
pub const MAX_LOGS_PER_FRAME: u32 = 10;
|
||||||
|
|
||||||
|
pub fn new(cap_config: Option<CertificationConfig>) -> Self {
|
||||||
|
Self::new_with_log_counter(cap_config, Arc::new(AtomicU32::new(0)))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -1,5 +1,6 @@
|
|||||||
use super::*;
|
use super::*;
|
||||||
use crate::fs::{FsBackend, FsEntry, FsError};
|
use crate::fs::{FsBackend, FsEntry, FsError, FsState, VirtualFS};
|
||||||
|
use crate::services::memcard::MemcardService;
|
||||||
use prometeu_bytecode::TRAP_TYPE;
|
use prometeu_bytecode::TRAP_TYPE;
|
||||||
use prometeu_bytecode::Value;
|
use prometeu_bytecode::Value;
|
||||||
use prometeu_bytecode::assembler::assemble;
|
use prometeu_bytecode::assembler::assemble;
|
||||||
@ -188,6 +189,7 @@ mod fs_memcard;
|
|||||||
#[test]
|
#[test]
|
||||||
fn initialize_vm_applies_cartridge_capabilities_before_loader_resolution() {
|
fn initialize_vm_applies_cartridge_capabilities_before_loader_resolution() {
|
||||||
let mut runtime = VirtualMachineRuntime::new(None);
|
let mut runtime = VirtualMachineRuntime::new(None);
|
||||||
|
let mut log_service = LogService::new(4096);
|
||||||
let mut vm = VirtualMachine::default();
|
let mut vm = VirtualMachine::default();
|
||||||
let code = assemble("PUSH_I32 0\nHOSTCALL 0\nHALT").expect("assemble");
|
let code = assemble("PUSH_I32 0\nHOSTCALL 0\nHALT").expect("assemble");
|
||||||
let program = serialized_single_function_module(
|
let program = serialized_single_function_module(
|
||||||
@ -202,7 +204,7 @@ fn initialize_vm_applies_cartridge_capabilities_before_loader_resolution() {
|
|||||||
);
|
);
|
||||||
let cartridge = cartridge_with_program(program, caps::NONE);
|
let cartridge = cartridge_with_program(program, caps::NONE);
|
||||||
|
|
||||||
let res = runtime.initialize_vm(&mut vm, &cartridge);
|
let res = runtime.initialize_vm(&mut log_service, &mut vm, &cartridge);
|
||||||
|
|
||||||
assert!(matches!(res, Err(CrashReport::VmInit { error: VmInitError::LoaderPatchFailed(_) })));
|
assert!(matches!(res, Err(CrashReport::VmInit { error: VmInitError::LoaderPatchFailed(_) })));
|
||||||
assert_eq!(runtime.current_app_id, 0);
|
assert_eq!(runtime.current_app_id, 0);
|
||||||
@ -213,6 +215,7 @@ fn initialize_vm_applies_cartridge_capabilities_before_loader_resolution() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn initialize_vm_succeeds_when_cartridge_capabilities_cover_hostcalls() {
|
fn initialize_vm_succeeds_when_cartridge_capabilities_cover_hostcalls() {
|
||||||
let mut runtime = VirtualMachineRuntime::new(None);
|
let mut runtime = VirtualMachineRuntime::new(None);
|
||||||
|
let mut log_service = LogService::new(4096);
|
||||||
let mut vm = VirtualMachine::default();
|
let mut vm = VirtualMachine::default();
|
||||||
let code = assemble("PUSH_I32 0\nHOSTCALL 0\nHALT").expect("assemble");
|
let code = assemble("PUSH_I32 0\nHOSTCALL 0\nHALT").expect("assemble");
|
||||||
let program = serialized_single_function_module(
|
let program = serialized_single_function_module(
|
||||||
@ -227,7 +230,7 @@ fn initialize_vm_succeeds_when_cartridge_capabilities_cover_hostcalls() {
|
|||||||
);
|
);
|
||||||
let cartridge = cartridge_with_program(program, caps::GFX);
|
let cartridge = cartridge_with_program(program, caps::GFX);
|
||||||
|
|
||||||
let res = runtime.initialize_vm(&mut vm, &cartridge);
|
let res = runtime.initialize_vm(&mut log_service, &mut vm, &cartridge);
|
||||||
|
|
||||||
assert!(res.is_ok());
|
assert!(res.is_ok());
|
||||||
assert_eq!(runtime.current_app_id, 42);
|
assert_eq!(runtime.current_app_id, 42);
|
||||||
@ -237,6 +240,12 @@ fn initialize_vm_succeeds_when_cartridge_capabilities_cover_hostcalls() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn tick_returns_error_when_vm_ends_slice_with_trap() {
|
fn tick_returns_error_when_vm_ends_slice_with_trap() {
|
||||||
let mut runtime = VirtualMachineRuntime::new(None);
|
let mut runtime = VirtualMachineRuntime::new(None);
|
||||||
|
let mut log_service = LogService::new(4096);
|
||||||
|
let mut fs = VirtualFS::new();
|
||||||
|
let mut fs_state = FsState::Unmounted;
|
||||||
|
let mut memcard = MemcardService::new();
|
||||||
|
let mut open_files: HashMap<u32, String> = HashMap::new();
|
||||||
|
let mut next_handle = 1;
|
||||||
let mut vm = VirtualMachine::default();
|
let mut vm = VirtualMachine::default();
|
||||||
let mut hardware = Hardware::new();
|
let mut hardware = Hardware::new();
|
||||||
let signals = InputSignals::default();
|
let signals = InputSignals::default();
|
||||||
@ -253,10 +262,21 @@ fn tick_returns_error_when_vm_ends_slice_with_trap() {
|
|||||||
);
|
);
|
||||||
let cartridge = cartridge_with_program(program, caps::GFX);
|
let cartridge = cartridge_with_program(program, caps::GFX);
|
||||||
|
|
||||||
runtime.initialize_vm(&mut vm, &cartridge).expect("runtime must initialize");
|
runtime.initialize_vm(&mut log_service, &mut vm, &cartridge).expect("runtime must initialize");
|
||||||
|
|
||||||
let report =
|
let report = runtime
|
||||||
runtime.tick(&mut vm, &signals, &mut hardware).expect("trap must surface as runtime error");
|
.tick(
|
||||||
|
&mut log_service,
|
||||||
|
&mut fs,
|
||||||
|
&mut fs_state,
|
||||||
|
&mut memcard,
|
||||||
|
&mut open_files,
|
||||||
|
&mut next_handle,
|
||||||
|
&mut vm,
|
||||||
|
&signals,
|
||||||
|
&mut hardware,
|
||||||
|
)
|
||||||
|
.expect("trap must surface as runtime error");
|
||||||
|
|
||||||
match report {
|
match report {
|
||||||
CrashReport::VmTrap { trap } => {
|
CrashReport::VmTrap { trap } => {
|
||||||
@ -271,6 +291,12 @@ fn tick_returns_error_when_vm_ends_slice_with_trap() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn tick_system_profile_rejects_gfx_game_surface() {
|
fn tick_system_profile_rejects_gfx_game_surface() {
|
||||||
let mut runtime = VirtualMachineRuntime::new(None);
|
let mut runtime = VirtualMachineRuntime::new(None);
|
||||||
|
let mut log_service = LogService::new(4096);
|
||||||
|
let mut fs = VirtualFS::new();
|
||||||
|
let mut fs_state = FsState::Unmounted;
|
||||||
|
let mut memcard = MemcardService::new();
|
||||||
|
let mut open_files: HashMap<u32, String> = HashMap::new();
|
||||||
|
let mut next_handle = 1;
|
||||||
let mut vm = VirtualMachine::default();
|
let mut vm = VirtualMachine::default();
|
||||||
let mut hardware = Hardware::new();
|
let mut hardware = Hardware::new();
|
||||||
let signals = InputSignals::default();
|
let signals = InputSignals::default();
|
||||||
@ -287,8 +313,20 @@ fn tick_system_profile_rejects_gfx_game_surface() {
|
|||||||
);
|
);
|
||||||
let cartridge = cartridge_with_program_and_mode(program, caps::GFX, AppMode::Shell);
|
let cartridge = cartridge_with_program_and_mode(program, caps::GFX, AppMode::Shell);
|
||||||
|
|
||||||
runtime.initialize_vm(&mut vm, &cartridge).expect("runtime must initialize");
|
runtime.initialize_vm(&mut log_service, &mut vm, &cartridge).expect("runtime must initialize");
|
||||||
let report = runtime.tick(&mut vm, &signals, &mut hardware).expect("system gfx call must trap");
|
let report = runtime
|
||||||
|
.tick(
|
||||||
|
&mut log_service,
|
||||||
|
&mut fs,
|
||||||
|
&mut fs_state,
|
||||||
|
&mut memcard,
|
||||||
|
&mut open_files,
|
||||||
|
&mut next_handle,
|
||||||
|
&mut vm,
|
||||||
|
&signals,
|
||||||
|
&mut hardware,
|
||||||
|
)
|
||||||
|
.expect("system gfx call must trap");
|
||||||
|
|
||||||
match report {
|
match report {
|
||||||
CrashReport::VmTrap { trap } => {
|
CrashReport::VmTrap { trap } => {
|
||||||
@ -302,6 +340,12 @@ fn tick_system_profile_rejects_gfx_game_surface() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn tick_system_profile_rejects_composer_game_surface() {
|
fn tick_system_profile_rejects_composer_game_surface() {
|
||||||
let mut runtime = VirtualMachineRuntime::new(None);
|
let mut runtime = VirtualMachineRuntime::new(None);
|
||||||
|
let mut log_service = LogService::new(4096);
|
||||||
|
let mut fs = VirtualFS::new();
|
||||||
|
let mut fs_state = FsState::Unmounted;
|
||||||
|
let mut memcard = MemcardService::new();
|
||||||
|
let mut open_files: HashMap<u32, String> = HashMap::new();
|
||||||
|
let mut next_handle = 1;
|
||||||
let mut vm = VirtualMachine::default();
|
let mut vm = VirtualMachine::default();
|
||||||
let mut hardware = Hardware::new();
|
let mut hardware = Hardware::new();
|
||||||
let signals = InputSignals::default();
|
let signals = InputSignals::default();
|
||||||
@ -318,9 +362,20 @@ fn tick_system_profile_rejects_composer_game_surface() {
|
|||||||
);
|
);
|
||||||
let cartridge = cartridge_with_program_and_mode(program, caps::GFX, AppMode::Shell);
|
let cartridge = cartridge_with_program_and_mode(program, caps::GFX, AppMode::Shell);
|
||||||
|
|
||||||
runtime.initialize_vm(&mut vm, &cartridge).expect("runtime must initialize");
|
runtime.initialize_vm(&mut log_service, &mut vm, &cartridge).expect("runtime must initialize");
|
||||||
let report =
|
let report = runtime
|
||||||
runtime.tick(&mut vm, &signals, &mut hardware).expect("system composer call must trap");
|
.tick(
|
||||||
|
&mut log_service,
|
||||||
|
&mut fs,
|
||||||
|
&mut fs_state,
|
||||||
|
&mut memcard,
|
||||||
|
&mut open_files,
|
||||||
|
&mut next_handle,
|
||||||
|
&mut vm,
|
||||||
|
&signals,
|
||||||
|
&mut hardware,
|
||||||
|
)
|
||||||
|
.expect("system composer call must trap");
|
||||||
|
|
||||||
match report {
|
match report {
|
||||||
CrashReport::VmTrap { trap } => {
|
CrashReport::VmTrap { trap } => {
|
||||||
@ -336,6 +391,12 @@ fn tick_system_profile_rejects_composer_game_surface() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn tick_system_profile_rejects_bank_game_surface() {
|
fn tick_system_profile_rejects_bank_game_surface() {
|
||||||
let mut runtime = VirtualMachineRuntime::new(None);
|
let mut runtime = VirtualMachineRuntime::new(None);
|
||||||
|
let mut log_service = LogService::new(4096);
|
||||||
|
let mut fs = VirtualFS::new();
|
||||||
|
let mut fs_state = FsState::Unmounted;
|
||||||
|
let mut memcard = MemcardService::new();
|
||||||
|
let mut open_files: HashMap<u32, String> = HashMap::new();
|
||||||
|
let mut next_handle = 1;
|
||||||
let mut vm = VirtualMachine::default();
|
let mut vm = VirtualMachine::default();
|
||||||
let mut hardware = Hardware::new();
|
let mut hardware = Hardware::new();
|
||||||
let signals = InputSignals::default();
|
let signals = InputSignals::default();
|
||||||
@ -352,9 +413,20 @@ fn tick_system_profile_rejects_bank_game_surface() {
|
|||||||
);
|
);
|
||||||
let cartridge = cartridge_with_program_and_mode(program, caps::BANK, AppMode::Shell);
|
let cartridge = cartridge_with_program_and_mode(program, caps::BANK, AppMode::Shell);
|
||||||
|
|
||||||
runtime.initialize_vm(&mut vm, &cartridge).expect("runtime must initialize");
|
runtime.initialize_vm(&mut log_service, &mut vm, &cartridge).expect("runtime must initialize");
|
||||||
let report =
|
let report = runtime
|
||||||
runtime.tick(&mut vm, &signals, &mut hardware).expect("system bank call must trap");
|
.tick(
|
||||||
|
&mut log_service,
|
||||||
|
&mut fs,
|
||||||
|
&mut fs_state,
|
||||||
|
&mut memcard,
|
||||||
|
&mut open_files,
|
||||||
|
&mut next_handle,
|
||||||
|
&mut vm,
|
||||||
|
&signals,
|
||||||
|
&mut hardware,
|
||||||
|
)
|
||||||
|
.expect("system bank call must trap");
|
||||||
|
|
||||||
match report {
|
match report {
|
||||||
CrashReport::VmTrap { trap } => {
|
CrashReport::VmTrap { trap } => {
|
||||||
@ -368,6 +440,12 @@ fn tick_system_profile_rejects_bank_game_surface() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn tick_system_profile_can_use_shared_log_transport() {
|
fn tick_system_profile_can_use_shared_log_transport() {
|
||||||
let mut runtime = VirtualMachineRuntime::new(None);
|
let mut runtime = VirtualMachineRuntime::new(None);
|
||||||
|
let mut log_service = LogService::new(4096);
|
||||||
|
let mut fs = VirtualFS::new();
|
||||||
|
let mut fs_state = FsState::Unmounted;
|
||||||
|
let mut memcard = MemcardService::new();
|
||||||
|
let mut open_files: HashMap<u32, String> = HashMap::new();
|
||||||
|
let mut next_handle = 1;
|
||||||
let mut vm = VirtualMachine::default();
|
let mut vm = VirtualMachine::default();
|
||||||
let mut hardware = Hardware::new();
|
let mut hardware = Hardware::new();
|
||||||
let signals = InputSignals::default();
|
let signals = InputSignals::default();
|
||||||
@ -385,8 +463,18 @@ fn tick_system_profile_can_use_shared_log_transport() {
|
|||||||
);
|
);
|
||||||
let cartridge = cartridge_with_program_and_mode(program, caps::LOG, AppMode::Shell);
|
let cartridge = cartridge_with_program_and_mode(program, caps::LOG, AppMode::Shell);
|
||||||
|
|
||||||
runtime.initialize_vm(&mut vm, &cartridge).expect("runtime must initialize");
|
runtime.initialize_vm(&mut log_service, &mut vm, &cartridge).expect("runtime must initialize");
|
||||||
let report = runtime.tick(&mut vm, &signals, &mut hardware);
|
let report = runtime.tick(
|
||||||
|
&mut log_service,
|
||||||
|
&mut fs,
|
||||||
|
&mut fs_state,
|
||||||
|
&mut memcard,
|
||||||
|
&mut open_files,
|
||||||
|
&mut next_handle,
|
||||||
|
&mut vm,
|
||||||
|
&signals,
|
||||||
|
&mut hardware,
|
||||||
|
);
|
||||||
|
|
||||||
assert!(report.is_none(), "system log transport must remain internally shared");
|
assert!(report.is_none(), "system log transport must remain internally shared");
|
||||||
assert!(vm.is_halted());
|
assert!(vm.is_halted());
|
||||||
@ -395,12 +483,28 @@ fn tick_system_profile_can_use_shared_log_transport() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn tick_returns_panic_report_distinct_from_trap() {
|
fn tick_returns_panic_report_distinct_from_trap() {
|
||||||
let mut runtime = VirtualMachineRuntime::new(None);
|
let mut runtime = VirtualMachineRuntime::new(None);
|
||||||
|
let mut log_service = LogService::new(4096);
|
||||||
|
let mut fs = VirtualFS::new();
|
||||||
|
let mut fs_state = FsState::Unmounted;
|
||||||
|
let mut memcard = MemcardService::new();
|
||||||
|
let mut open_files: HashMap<u32, String> = HashMap::new();
|
||||||
|
let mut next_handle = 1;
|
||||||
let mut vm = VirtualMachine::new(assemble("HOSTCALL 0\nHALT").expect("assemble"), vec![]);
|
let mut vm = VirtualMachine::new(assemble("HOSTCALL 0\nHALT").expect("assemble"), vec![]);
|
||||||
let mut hardware = Hardware::new();
|
let mut hardware = Hardware::new();
|
||||||
let signals = InputSignals::default();
|
let signals = InputSignals::default();
|
||||||
|
|
||||||
let report = runtime
|
let report = runtime
|
||||||
.tick(&mut vm, &signals, &mut hardware)
|
.tick(
|
||||||
|
&mut log_service,
|
||||||
|
&mut fs,
|
||||||
|
&mut fs_state,
|
||||||
|
&mut memcard,
|
||||||
|
&mut open_files,
|
||||||
|
&mut next_handle,
|
||||||
|
&mut vm,
|
||||||
|
&signals,
|
||||||
|
&mut hardware,
|
||||||
|
)
|
||||||
.expect("panic must surface as runtime error");
|
.expect("panic must surface as runtime error");
|
||||||
|
|
||||||
match report {
|
match report {
|
||||||
@ -416,6 +520,12 @@ fn tick_returns_panic_report_distinct_from_trap() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn tick_renders_bound_eight_pixel_scene_through_frame_composer_path() {
|
fn tick_renders_bound_eight_pixel_scene_through_frame_composer_path() {
|
||||||
let mut runtime = VirtualMachineRuntime::new(None);
|
let mut runtime = VirtualMachineRuntime::new(None);
|
||||||
|
let mut log_service = LogService::new(4096);
|
||||||
|
let mut fs = VirtualFS::new();
|
||||||
|
let mut fs_state = FsState::Unmounted;
|
||||||
|
let mut memcard = MemcardService::new();
|
||||||
|
let mut open_files: HashMap<u32, String> = HashMap::new();
|
||||||
|
let mut next_handle = 1;
|
||||||
let mut vm = VirtualMachine::default();
|
let mut vm = VirtualMachine::default();
|
||||||
let signals = InputSignals::default();
|
let signals = InputSignals::default();
|
||||||
let program =
|
let program =
|
||||||
@ -432,8 +542,18 @@ fn tick_renders_bound_eight_pixel_scene_through_frame_composer_path() {
|
|||||||
glyph_slot_index.rebuild_from_slots(&slots);
|
glyph_slot_index.rebuild_from_slots(&slots);
|
||||||
assert!(hardware.frame_composer.bind_scene(0));
|
assert!(hardware.frame_composer.bind_scene(0));
|
||||||
|
|
||||||
runtime.initialize_vm(&mut vm, &cartridge).expect("runtime must initialize");
|
runtime.initialize_vm(&mut log_service, &mut vm, &cartridge).expect("runtime must initialize");
|
||||||
let report = runtime.tick(&mut vm, &signals, &mut hardware);
|
let report = runtime.tick(
|
||||||
|
&mut log_service,
|
||||||
|
&mut fs,
|
||||||
|
&mut fs_state,
|
||||||
|
&mut memcard,
|
||||||
|
&mut open_files,
|
||||||
|
&mut next_handle,
|
||||||
|
&mut vm,
|
||||||
|
&signals,
|
||||||
|
&mut hardware,
|
||||||
|
);
|
||||||
|
|
||||||
assert!(report.is_none(), "frame render path must not crash");
|
assert!(report.is_none(), "frame render path must not crash");
|
||||||
hardware.gfx.present();
|
hardware.gfx.present();
|
||||||
@ -443,6 +563,12 @@ fn tick_renders_bound_eight_pixel_scene_through_frame_composer_path() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn tick_renders_scene_through_public_composer_syscalls() {
|
fn tick_renders_scene_through_public_composer_syscalls() {
|
||||||
let mut runtime = VirtualMachineRuntime::new(None);
|
let mut runtime = VirtualMachineRuntime::new(None);
|
||||||
|
let mut log_service = LogService::new(4096);
|
||||||
|
let mut fs = VirtualFS::new();
|
||||||
|
let mut fs_state = FsState::Unmounted;
|
||||||
|
let mut memcard = MemcardService::new();
|
||||||
|
let mut open_files: HashMap<u32, String> = HashMap::new();
|
||||||
|
let mut next_handle = 1;
|
||||||
let mut vm = VirtualMachine::default();
|
let mut vm = VirtualMachine::default();
|
||||||
let signals = InputSignals::default();
|
let signals = InputSignals::default();
|
||||||
let code = assemble(
|
let code = assemble(
|
||||||
@ -491,8 +617,18 @@ fn tick_renders_scene_through_public_composer_syscalls() {
|
|||||||
hardware.gfx.scene_fade_level = 31;
|
hardware.gfx.scene_fade_level = 31;
|
||||||
hardware.gfx.hud_fade_level = 31;
|
hardware.gfx.hud_fade_level = 31;
|
||||||
|
|
||||||
runtime.initialize_vm(&mut vm, &cartridge).expect("runtime must initialize");
|
runtime.initialize_vm(&mut log_service, &mut vm, &cartridge).expect("runtime must initialize");
|
||||||
let report = runtime.tick(&mut vm, &signals, &mut hardware);
|
let report = runtime.tick(
|
||||||
|
&mut log_service,
|
||||||
|
&mut fs,
|
||||||
|
&mut fs_state,
|
||||||
|
&mut memcard,
|
||||||
|
&mut open_files,
|
||||||
|
&mut next_handle,
|
||||||
|
&mut vm,
|
||||||
|
&signals,
|
||||||
|
&mut hardware,
|
||||||
|
);
|
||||||
|
|
||||||
assert!(report.is_none(), "public composer path must not crash");
|
assert!(report.is_none(), "public composer path must not crash");
|
||||||
assert_eq!(vm.operand_stack_top(1), vec![Value::Int64(ComposerOpStatus::Ok as i64)]);
|
assert_eq!(vm.operand_stack_top(1), vec![Value::Int64(ComposerOpStatus::Ok as i64)]);
|
||||||
@ -503,6 +639,12 @@ fn tick_renders_scene_through_public_composer_syscalls() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn tick_draw_text_survives_scene_backed_frame_composition() {
|
fn tick_draw_text_survives_scene_backed_frame_composition() {
|
||||||
let mut runtime = VirtualMachineRuntime::new(None);
|
let mut runtime = VirtualMachineRuntime::new(None);
|
||||||
|
let mut log_service = LogService::new(4096);
|
||||||
|
let mut fs = VirtualFS::new();
|
||||||
|
let mut fs_state = FsState::Unmounted;
|
||||||
|
let mut memcard = MemcardService::new();
|
||||||
|
let mut open_files: HashMap<u32, String> = HashMap::new();
|
||||||
|
let mut next_handle = 1;
|
||||||
let mut vm = VirtualMachine::default();
|
let mut vm = VirtualMachine::default();
|
||||||
let signals = InputSignals::default();
|
let signals = InputSignals::default();
|
||||||
let code = assemble(
|
let code = assemble(
|
||||||
@ -552,8 +694,18 @@ fn tick_draw_text_survives_scene_backed_frame_composition() {
|
|||||||
hardware.gfx.scene_fade_level = 31;
|
hardware.gfx.scene_fade_level = 31;
|
||||||
hardware.gfx.hud_fade_level = 31;
|
hardware.gfx.hud_fade_level = 31;
|
||||||
|
|
||||||
runtime.initialize_vm(&mut vm, &cartridge).expect("runtime must initialize");
|
runtime.initialize_vm(&mut log_service, &mut vm, &cartridge).expect("runtime must initialize");
|
||||||
let report = runtime.tick(&mut vm, &signals, &mut hardware);
|
let report = runtime.tick(
|
||||||
|
&mut log_service,
|
||||||
|
&mut fs,
|
||||||
|
&mut fs_state,
|
||||||
|
&mut memcard,
|
||||||
|
&mut open_files,
|
||||||
|
&mut next_handle,
|
||||||
|
&mut vm,
|
||||||
|
&signals,
|
||||||
|
&mut hardware,
|
||||||
|
);
|
||||||
|
|
||||||
assert!(report.is_none(), "scene-backed overlay text must not crash");
|
assert!(report.is_none(), "scene-backed overlay text must not crash");
|
||||||
hardware.gfx.present();
|
hardware.gfx.present();
|
||||||
@ -563,6 +715,12 @@ fn tick_draw_text_survives_scene_backed_frame_composition() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn tick_draw_text_survives_no_scene_frame_path() {
|
fn tick_draw_text_survives_no_scene_frame_path() {
|
||||||
let mut runtime = VirtualMachineRuntime::new(None);
|
let mut runtime = VirtualMachineRuntime::new(None);
|
||||||
|
let mut log_service = LogService::new(4096);
|
||||||
|
let mut fs = VirtualFS::new();
|
||||||
|
let mut fs_state = FsState::Unmounted;
|
||||||
|
let mut memcard = MemcardService::new();
|
||||||
|
let mut open_files: HashMap<u32, String> = HashMap::new();
|
||||||
|
let mut next_handle = 1;
|
||||||
let mut vm = VirtualMachine::default();
|
let mut vm = VirtualMachine::default();
|
||||||
let signals = InputSignals::default();
|
let signals = InputSignals::default();
|
||||||
let code = assemble(
|
let code = assemble(
|
||||||
@ -585,8 +743,18 @@ fn tick_draw_text_survives_no_scene_frame_path() {
|
|||||||
hardware.gfx.scene_fade_level = 31;
|
hardware.gfx.scene_fade_level = 31;
|
||||||
hardware.gfx.hud_fade_level = 31;
|
hardware.gfx.hud_fade_level = 31;
|
||||||
|
|
||||||
runtime.initialize_vm(&mut vm, &cartridge).expect("runtime must initialize");
|
runtime.initialize_vm(&mut log_service, &mut vm, &cartridge).expect("runtime must initialize");
|
||||||
let report = runtime.tick(&mut vm, &signals, &mut hardware);
|
let report = runtime.tick(
|
||||||
|
&mut log_service,
|
||||||
|
&mut fs,
|
||||||
|
&mut fs_state,
|
||||||
|
&mut memcard,
|
||||||
|
&mut open_files,
|
||||||
|
&mut next_handle,
|
||||||
|
&mut vm,
|
||||||
|
&signals,
|
||||||
|
&mut hardware,
|
||||||
|
);
|
||||||
|
|
||||||
assert!(report.is_none(), "no-scene overlay text must not crash");
|
assert!(report.is_none(), "no-scene overlay text must not crash");
|
||||||
hardware.gfx.present();
|
hardware.gfx.present();
|
||||||
@ -596,6 +764,7 @@ fn tick_draw_text_survives_no_scene_frame_path() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn initialize_vm_success_clears_previous_crash_report() {
|
fn initialize_vm_success_clears_previous_crash_report() {
|
||||||
let mut runtime = VirtualMachineRuntime::new(None);
|
let mut runtime = VirtualMachineRuntime::new(None);
|
||||||
|
let mut log_service = LogService::new(4096);
|
||||||
runtime.last_crash_report = Some(CrashReport::VmPanic { message: "stale".into(), pc: Some(1) });
|
runtime.last_crash_report = Some(CrashReport::VmPanic { message: "stale".into(), pc: Some(1) });
|
||||||
|
|
||||||
let mut vm = VirtualMachine::default();
|
let mut vm = VirtualMachine::default();
|
||||||
@ -612,7 +781,7 @@ fn initialize_vm_success_clears_previous_crash_report() {
|
|||||||
);
|
);
|
||||||
let cartridge = cartridge_with_program(program, caps::GFX);
|
let cartridge = cartridge_with_program(program, caps::GFX);
|
||||||
|
|
||||||
runtime.initialize_vm(&mut vm, &cartridge).expect("runtime must initialize");
|
runtime.initialize_vm(&mut log_service, &mut vm, &cartridge).expect("runtime must initialize");
|
||||||
assert!(runtime.last_crash_report.is_none());
|
assert!(runtime.last_crash_report.is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -620,15 +789,16 @@ fn initialize_vm_success_clears_previous_crash_report() {
|
|||||||
fn reset_clears_cartridge_scoped_runtime_state() {
|
fn reset_clears_cartridge_scoped_runtime_state() {
|
||||||
let mut runtime = VirtualMachineRuntime::new(None);
|
let mut runtime = VirtualMachineRuntime::new(None);
|
||||||
let mut vm = VirtualMachine::default();
|
let mut vm = VirtualMachine::default();
|
||||||
|
let fs_state = FsState::Mounted;
|
||||||
|
let mut open_files: HashMap<u32, String> = HashMap::new();
|
||||||
|
let next_handle = 9;
|
||||||
|
|
||||||
runtime.tick_index = 77;
|
runtime.tick_index = 77;
|
||||||
runtime.logical_frame_index = 12;
|
runtime.logical_frame_index = 12;
|
||||||
runtime.logical_frame_active = true;
|
runtime.logical_frame_active = true;
|
||||||
runtime.logical_frame_remaining_cycles = 345;
|
runtime.logical_frame_remaining_cycles = 345;
|
||||||
runtime.last_frame_cpu_time_us = 678;
|
runtime.last_frame_cpu_time_us = 678;
|
||||||
runtime.fs_state = FsState::Mounted;
|
open_files.insert(7, "/save.dat".into());
|
||||||
runtime.open_files.insert(7, "/save.dat".into());
|
|
||||||
runtime.next_handle = 9;
|
|
||||||
runtime.current_app_id = 42;
|
runtime.current_app_id = 42;
|
||||||
runtime.current_cartridge_title = "Cart".into();
|
runtime.current_cartridge_title = "Cart".into();
|
||||||
runtime.current_cartridge_app_version = "1.2.3".into();
|
runtime.current_cartridge_app_version = "1.2.3".into();
|
||||||
@ -650,13 +820,13 @@ fn reset_clears_cartridge_scoped_runtime_state() {
|
|||||||
runtime.reset(&mut vm);
|
runtime.reset(&mut vm);
|
||||||
|
|
||||||
assert_eq!(runtime.tick_index, 77);
|
assert_eq!(runtime.tick_index, 77);
|
||||||
assert_eq!(runtime.fs_state, FsState::Mounted);
|
assert_eq!(fs_state, FsState::Mounted);
|
||||||
assert_eq!(runtime.logical_frame_index, 0);
|
assert_eq!(runtime.logical_frame_index, 0);
|
||||||
assert!(!runtime.logical_frame_active);
|
assert!(!runtime.logical_frame_active);
|
||||||
assert_eq!(runtime.logical_frame_remaining_cycles, 0);
|
assert_eq!(runtime.logical_frame_remaining_cycles, 0);
|
||||||
assert_eq!(runtime.last_frame_cpu_time_us, 0);
|
assert_eq!(runtime.last_frame_cpu_time_us, 0);
|
||||||
assert!(runtime.open_files.is_empty());
|
assert_eq!(open_files.len(), 1);
|
||||||
assert_eq!(runtime.next_handle, 1);
|
assert_eq!(next_handle, 9);
|
||||||
assert_eq!(runtime.current_app_id, 0);
|
assert_eq!(runtime.current_app_id, 0);
|
||||||
assert!(runtime.current_cartridge_title.is_empty());
|
assert!(runtime.current_cartridge_title.is_empty());
|
||||||
assert!(runtime.current_cartridge_app_version.is_empty());
|
assert!(runtime.current_cartridge_app_version.is_empty());
|
||||||
@ -679,6 +849,12 @@ fn reset_clears_cartridge_scoped_runtime_state() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn tick_numeric_happy_path_records_zero_internal_allocations() {
|
fn tick_numeric_happy_path_records_zero_internal_allocations() {
|
||||||
let mut runtime = VirtualMachineRuntime::new(None);
|
let mut runtime = VirtualMachineRuntime::new(None);
|
||||||
|
let mut log_service = LogService::new(4096);
|
||||||
|
let mut fs = VirtualFS::new();
|
||||||
|
let mut fs_state = FsState::Unmounted;
|
||||||
|
let mut memcard = MemcardService::new();
|
||||||
|
let mut open_files: HashMap<u32, String> = HashMap::new();
|
||||||
|
let mut next_handle = 1;
|
||||||
let mut vm = VirtualMachine::default();
|
let mut vm = VirtualMachine::default();
|
||||||
let mut hardware = Hardware::new();
|
let mut hardware = Hardware::new();
|
||||||
let signals = InputSignals::default();
|
let signals = InputSignals::default();
|
||||||
@ -687,8 +863,18 @@ fn tick_numeric_happy_path_records_zero_internal_allocations() {
|
|||||||
let program = serialized_single_function_module(code, vec![]);
|
let program = serialized_single_function_module(code, vec![]);
|
||||||
let cartridge = cartridge_with_program(program, caps::NONE);
|
let cartridge = cartridge_with_program(program, caps::NONE);
|
||||||
|
|
||||||
runtime.initialize_vm(&mut vm, &cartridge).expect("runtime must initialize");
|
runtime.initialize_vm(&mut log_service, &mut vm, &cartridge).expect("runtime must initialize");
|
||||||
let report = runtime.tick(&mut vm, &signals, &mut hardware);
|
let report = runtime.tick(
|
||||||
|
&mut log_service,
|
||||||
|
&mut fs,
|
||||||
|
&mut fs_state,
|
||||||
|
&mut memcard,
|
||||||
|
&mut open_files,
|
||||||
|
&mut next_handle,
|
||||||
|
&mut vm,
|
||||||
|
&signals,
|
||||||
|
&mut hardware,
|
||||||
|
);
|
||||||
|
|
||||||
assert!(report.is_none());
|
assert!(report.is_none());
|
||||||
let snapshot = runtime.atomic_telemetry.snapshot();
|
let snapshot = runtime.atomic_telemetry.snapshot();
|
||||||
@ -699,6 +885,12 @@ fn tick_numeric_happy_path_records_zero_internal_allocations() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn tick_already_materialized_string_path_records_zero_internal_allocations() {
|
fn tick_already_materialized_string_path_records_zero_internal_allocations() {
|
||||||
let mut runtime = VirtualMachineRuntime::new(None);
|
let mut runtime = VirtualMachineRuntime::new(None);
|
||||||
|
let mut log_service = LogService::new(4096);
|
||||||
|
let mut fs = VirtualFS::new();
|
||||||
|
let mut fs_state = FsState::Unmounted;
|
||||||
|
let mut memcard = MemcardService::new();
|
||||||
|
let mut open_files: HashMap<u32, String> = HashMap::new();
|
||||||
|
let mut next_handle = 1;
|
||||||
let mut vm = VirtualMachine::default();
|
let mut vm = VirtualMachine::default();
|
||||||
let mut hardware = Hardware::new();
|
let mut hardware = Hardware::new();
|
||||||
let signals = InputSignals::default();
|
let signals = InputSignals::default();
|
||||||
@ -711,8 +903,18 @@ fn tick_already_materialized_string_path_records_zero_internal_allocations() {
|
|||||||
);
|
);
|
||||||
let cartridge = cartridge_with_program(program, caps::NONE);
|
let cartridge = cartridge_with_program(program, caps::NONE);
|
||||||
|
|
||||||
runtime.initialize_vm(&mut vm, &cartridge).expect("runtime must initialize");
|
runtime.initialize_vm(&mut log_service, &mut vm, &cartridge).expect("runtime must initialize");
|
||||||
let report = runtime.tick(&mut vm, &signals, &mut hardware);
|
let report = runtime.tick(
|
||||||
|
&mut log_service,
|
||||||
|
&mut fs,
|
||||||
|
&mut fs_state,
|
||||||
|
&mut memcard,
|
||||||
|
&mut open_files,
|
||||||
|
&mut next_handle,
|
||||||
|
&mut vm,
|
||||||
|
&signals,
|
||||||
|
&mut hardware,
|
||||||
|
);
|
||||||
|
|
||||||
assert!(report.is_none());
|
assert!(report.is_none());
|
||||||
let snapshot = runtime.atomic_telemetry.snapshot();
|
let snapshot = runtime.atomic_telemetry.snapshot();
|
||||||
@ -723,6 +925,8 @@ fn tick_already_materialized_string_path_records_zero_internal_allocations() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn initialize_vm_failure_clears_previous_identity_and_handles() {
|
fn initialize_vm_failure_clears_previous_identity_and_handles() {
|
||||||
let mut runtime = VirtualMachineRuntime::new(None);
|
let mut runtime = VirtualMachineRuntime::new(None);
|
||||||
|
let mut log_service = LogService::new(4096);
|
||||||
|
let mut open_files: HashMap<u32, String> = HashMap::new();
|
||||||
let mut vm = VirtualMachine::default();
|
let mut vm = VirtualMachine::default();
|
||||||
|
|
||||||
let good_program = serialized_single_function_module(
|
let good_program = serialized_single_function_module(
|
||||||
@ -736,10 +940,13 @@ fn initialize_vm_failure_clears_previous_identity_and_handles() {
|
|||||||
}],
|
}],
|
||||||
);
|
);
|
||||||
let good_cartridge = cartridge_with_program(good_program, caps::GFX);
|
let good_cartridge = cartridge_with_program(good_program, caps::GFX);
|
||||||
runtime.initialize_vm(&mut vm, &good_cartridge).expect("runtime must initialize");
|
runtime
|
||||||
|
.initialize_vm(&mut log_service, &mut vm, &good_cartridge)
|
||||||
|
.expect("runtime must initialize");
|
||||||
|
|
||||||
runtime.open_files.insert(5, "/save.dat".into());
|
open_files.insert(5, "/save.dat".into());
|
||||||
runtime.next_handle = 6;
|
|
||||||
|
let next_handle = 6;
|
||||||
runtime.paused = true;
|
runtime.paused = true;
|
||||||
runtime.debug_step_request = true;
|
runtime.debug_step_request = true;
|
||||||
runtime.atomic_telemetry.cycles_used.store(123, Ordering::Relaxed);
|
runtime.atomic_telemetry.cycles_used.store(123, Ordering::Relaxed);
|
||||||
@ -756,15 +963,15 @@ fn initialize_vm_failure_clears_previous_identity_and_handles() {
|
|||||||
);
|
);
|
||||||
let bad_cartridge = cartridge_with_program(bad_program, caps::NONE);
|
let bad_cartridge = cartridge_with_program(bad_program, caps::NONE);
|
||||||
|
|
||||||
let res = runtime.initialize_vm(&mut vm, &bad_cartridge);
|
let res = runtime.initialize_vm(&mut log_service, &mut vm, &bad_cartridge);
|
||||||
|
|
||||||
assert!(matches!(res, Err(CrashReport::VmInit { error: VmInitError::LoaderPatchFailed(_) })));
|
assert!(matches!(res, Err(CrashReport::VmInit { error: VmInitError::LoaderPatchFailed(_) })));
|
||||||
assert_eq!(runtime.current_app_id, 0);
|
assert_eq!(runtime.current_app_id, 0);
|
||||||
assert!(runtime.current_cartridge_title.is_empty());
|
assert!(runtime.current_cartridge_title.is_empty());
|
||||||
assert!(runtime.current_cartridge_app_version.is_empty());
|
assert!(runtime.current_cartridge_app_version.is_empty());
|
||||||
assert_eq!(runtime.current_cartridge_app_mode, AppMode::Game);
|
assert_eq!(runtime.current_cartridge_app_mode, AppMode::Game);
|
||||||
assert!(runtime.open_files.is_empty());
|
assert_eq!(open_files.len(), 1);
|
||||||
assert_eq!(runtime.next_handle, 1);
|
assert_eq!(next_handle, 6);
|
||||||
assert!(!runtime.paused);
|
assert!(!runtime.paused);
|
||||||
assert!(!runtime.debug_step_request);
|
assert!(!runtime.debug_step_request);
|
||||||
assert_eq!(runtime.atomic_telemetry.cycles_used.load(Ordering::Relaxed), 0);
|
assert_eq!(runtime.atomic_telemetry.cycles_used.load(Ordering::Relaxed), 0);
|
||||||
@ -774,6 +981,12 @@ fn initialize_vm_failure_clears_previous_identity_and_handles() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn tick_composer_bind_scene_operational_error_returns_status_not_crash() {
|
fn tick_composer_bind_scene_operational_error_returns_status_not_crash() {
|
||||||
let mut runtime = VirtualMachineRuntime::new(None);
|
let mut runtime = VirtualMachineRuntime::new(None);
|
||||||
|
let mut log_service = LogService::new(4096);
|
||||||
|
let mut fs = VirtualFS::new();
|
||||||
|
let mut fs_state = FsState::Unmounted;
|
||||||
|
let mut memcard = MemcardService::new();
|
||||||
|
let mut open_files: HashMap<u32, String> = HashMap::new();
|
||||||
|
let mut next_handle = 1;
|
||||||
let mut vm = VirtualMachine::default();
|
let mut vm = VirtualMachine::default();
|
||||||
let mut hardware = Hardware::new();
|
let mut hardware = Hardware::new();
|
||||||
let signals = InputSignals::default();
|
let signals = InputSignals::default();
|
||||||
@ -790,8 +1003,18 @@ fn tick_composer_bind_scene_operational_error_returns_status_not_crash() {
|
|||||||
);
|
);
|
||||||
let cartridge = cartridge_with_program(program, caps::GFX);
|
let cartridge = cartridge_with_program(program, caps::GFX);
|
||||||
|
|
||||||
runtime.initialize_vm(&mut vm, &cartridge).expect("runtime must initialize");
|
runtime.initialize_vm(&mut log_service, &mut vm, &cartridge).expect("runtime must initialize");
|
||||||
let report = runtime.tick(&mut vm, &signals, &mut hardware);
|
let report = runtime.tick(
|
||||||
|
&mut log_service,
|
||||||
|
&mut fs,
|
||||||
|
&mut fs_state,
|
||||||
|
&mut memcard,
|
||||||
|
&mut open_files,
|
||||||
|
&mut next_handle,
|
||||||
|
&mut vm,
|
||||||
|
&signals,
|
||||||
|
&mut hardware,
|
||||||
|
);
|
||||||
assert!(report.is_none(), "operational error must not crash");
|
assert!(report.is_none(), "operational error must not crash");
|
||||||
assert!(vm.is_halted());
|
assert!(vm.is_halted());
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@ -803,6 +1026,12 @@ fn tick_composer_bind_scene_operational_error_returns_status_not_crash() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn tick_composer_emit_sprite_operational_error_returns_status_not_crash() {
|
fn tick_composer_emit_sprite_operational_error_returns_status_not_crash() {
|
||||||
let mut runtime = VirtualMachineRuntime::new(None);
|
let mut runtime = VirtualMachineRuntime::new(None);
|
||||||
|
let mut log_service = LogService::new(4096);
|
||||||
|
let mut fs = VirtualFS::new();
|
||||||
|
let mut fs_state = FsState::Unmounted;
|
||||||
|
let mut memcard = MemcardService::new();
|
||||||
|
let mut open_files: HashMap<u32, String> = HashMap::new();
|
||||||
|
let mut next_handle = 1;
|
||||||
let mut vm = VirtualMachine::default();
|
let mut vm = VirtualMachine::default();
|
||||||
let mut hardware = Hardware::new();
|
let mut hardware = Hardware::new();
|
||||||
let signals = InputSignals::default();
|
let signals = InputSignals::default();
|
||||||
@ -822,8 +1051,18 @@ fn tick_composer_emit_sprite_operational_error_returns_status_not_crash() {
|
|||||||
);
|
);
|
||||||
let cartridge = cartridge_with_program(program, caps::GFX);
|
let cartridge = cartridge_with_program(program, caps::GFX);
|
||||||
|
|
||||||
runtime.initialize_vm(&mut vm, &cartridge).expect("runtime must initialize");
|
runtime.initialize_vm(&mut log_service, &mut vm, &cartridge).expect("runtime must initialize");
|
||||||
let report = runtime.tick(&mut vm, &signals, &mut hardware);
|
let report = runtime.tick(
|
||||||
|
&mut log_service,
|
||||||
|
&mut fs,
|
||||||
|
&mut fs_state,
|
||||||
|
&mut memcard,
|
||||||
|
&mut open_files,
|
||||||
|
&mut next_handle,
|
||||||
|
&mut vm,
|
||||||
|
&signals,
|
||||||
|
&mut hardware,
|
||||||
|
);
|
||||||
assert!(report.is_none(), "operational error must not crash");
|
assert!(report.is_none(), "operational error must not crash");
|
||||||
assert!(vm.is_halted());
|
assert!(vm.is_halted());
|
||||||
assert_eq!(vm.operand_stack_top(1), vec![Value::Int64(ComposerOpStatus::BankInvalid as i64)]);
|
assert_eq!(vm.operand_stack_top(1), vec![Value::Int64(ComposerOpStatus::BankInvalid as i64)]);
|
||||||
@ -832,6 +1071,12 @@ fn tick_composer_emit_sprite_operational_error_returns_status_not_crash() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn tick_composer_emit_sprite_invalid_layer_returns_status_not_crash() {
|
fn tick_composer_emit_sprite_invalid_layer_returns_status_not_crash() {
|
||||||
let mut runtime = VirtualMachineRuntime::new(None);
|
let mut runtime = VirtualMachineRuntime::new(None);
|
||||||
|
let mut log_service = LogService::new(4096);
|
||||||
|
let mut fs = VirtualFS::new();
|
||||||
|
let mut fs_state = FsState::Unmounted;
|
||||||
|
let mut memcard = MemcardService::new();
|
||||||
|
let mut open_files: HashMap<u32, String> = HashMap::new();
|
||||||
|
let mut next_handle = 1;
|
||||||
let mut vm = VirtualMachine::default();
|
let mut vm = VirtualMachine::default();
|
||||||
let mut hardware = Hardware::new();
|
let mut hardware = Hardware::new();
|
||||||
let signals = InputSignals::default();
|
let signals = InputSignals::default();
|
||||||
@ -851,8 +1096,18 @@ fn tick_composer_emit_sprite_invalid_layer_returns_status_not_crash() {
|
|||||||
);
|
);
|
||||||
let cartridge = cartridge_with_program(program, caps::GFX);
|
let cartridge = cartridge_with_program(program, caps::GFX);
|
||||||
|
|
||||||
runtime.initialize_vm(&mut vm, &cartridge).expect("runtime must initialize");
|
runtime.initialize_vm(&mut log_service, &mut vm, &cartridge).expect("runtime must initialize");
|
||||||
let report = runtime.tick(&mut vm, &signals, &mut hardware);
|
let report = runtime.tick(
|
||||||
|
&mut log_service,
|
||||||
|
&mut fs,
|
||||||
|
&mut fs_state,
|
||||||
|
&mut memcard,
|
||||||
|
&mut open_files,
|
||||||
|
&mut next_handle,
|
||||||
|
&mut vm,
|
||||||
|
&signals,
|
||||||
|
&mut hardware,
|
||||||
|
);
|
||||||
assert!(report.is_none(), "invalid layer must not crash");
|
assert!(report.is_none(), "invalid layer must not crash");
|
||||||
assert!(vm.is_halted());
|
assert!(vm.is_halted());
|
||||||
assert_eq!(vm.operand_stack_top(1), vec![Value::Int64(ComposerOpStatus::LayerInvalid as i64)]);
|
assert_eq!(vm.operand_stack_top(1), vec![Value::Int64(ComposerOpStatus::LayerInvalid as i64)]);
|
||||||
@ -861,6 +1116,12 @@ fn tick_composer_emit_sprite_invalid_layer_returns_status_not_crash() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn tick_composer_emit_sprite_invalid_range_returns_status_not_crash() {
|
fn tick_composer_emit_sprite_invalid_range_returns_status_not_crash() {
|
||||||
let mut runtime = VirtualMachineRuntime::new(None);
|
let mut runtime = VirtualMachineRuntime::new(None);
|
||||||
|
let mut log_service = LogService::new(4096);
|
||||||
|
let mut fs = VirtualFS::new();
|
||||||
|
let mut fs_state = FsState::Unmounted;
|
||||||
|
let mut memcard = MemcardService::new();
|
||||||
|
let mut open_files: HashMap<u32, String> = HashMap::new();
|
||||||
|
let mut next_handle = 1;
|
||||||
let mut vm = VirtualMachine::default();
|
let mut vm = VirtualMachine::default();
|
||||||
let mut hardware = Hardware::new();
|
let mut hardware = Hardware::new();
|
||||||
let signals = InputSignals::default();
|
let signals = InputSignals::default();
|
||||||
@ -887,8 +1148,18 @@ fn tick_composer_emit_sprite_invalid_range_returns_status_not_crash() {
|
|||||||
AssetsPayloadSource::from_bytes(asset_data),
|
AssetsPayloadSource::from_bytes(asset_data),
|
||||||
);
|
);
|
||||||
|
|
||||||
runtime.initialize_vm(&mut vm, &cartridge).expect("runtime must initialize");
|
runtime.initialize_vm(&mut log_service, &mut vm, &cartridge).expect("runtime must initialize");
|
||||||
let report = runtime.tick(&mut vm, &signals, &mut hardware);
|
let report = runtime.tick(
|
||||||
|
&mut log_service,
|
||||||
|
&mut fs,
|
||||||
|
&mut fs_state,
|
||||||
|
&mut memcard,
|
||||||
|
&mut open_files,
|
||||||
|
&mut next_handle,
|
||||||
|
&mut vm,
|
||||||
|
&signals,
|
||||||
|
&mut hardware,
|
||||||
|
);
|
||||||
assert!(report.is_none(), "invalid composer parameter range must not crash");
|
assert!(report.is_none(), "invalid composer parameter range must not crash");
|
||||||
assert!(vm.is_halted());
|
assert!(vm.is_halted());
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@ -900,6 +1171,12 @@ fn tick_composer_emit_sprite_invalid_range_returns_status_not_crash() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn tick_audio_play_sample_operational_error_returns_status_not_crash() {
|
fn tick_audio_play_sample_operational_error_returns_status_not_crash() {
|
||||||
let mut runtime = VirtualMachineRuntime::new(None);
|
let mut runtime = VirtualMachineRuntime::new(None);
|
||||||
|
let mut log_service = LogService::new(4096);
|
||||||
|
let mut fs = VirtualFS::new();
|
||||||
|
let mut fs_state = FsState::Unmounted;
|
||||||
|
let mut memcard = MemcardService::new();
|
||||||
|
let mut open_files: HashMap<u32, String> = HashMap::new();
|
||||||
|
let mut next_handle = 1;
|
||||||
let mut vm = VirtualMachine::default();
|
let mut vm = VirtualMachine::default();
|
||||||
let mut hardware = Hardware::new();
|
let mut hardware = Hardware::new();
|
||||||
let signals = InputSignals::default();
|
let signals = InputSignals::default();
|
||||||
@ -919,8 +1196,18 @@ fn tick_audio_play_sample_operational_error_returns_status_not_crash() {
|
|||||||
);
|
);
|
||||||
let cartridge = cartridge_with_program(program, caps::AUDIO);
|
let cartridge = cartridge_with_program(program, caps::AUDIO);
|
||||||
|
|
||||||
runtime.initialize_vm(&mut vm, &cartridge).expect("runtime must initialize");
|
runtime.initialize_vm(&mut log_service, &mut vm, &cartridge).expect("runtime must initialize");
|
||||||
let report = runtime.tick(&mut vm, &signals, &mut hardware);
|
let report = runtime.tick(
|
||||||
|
&mut log_service,
|
||||||
|
&mut fs,
|
||||||
|
&mut fs_state,
|
||||||
|
&mut memcard,
|
||||||
|
&mut open_files,
|
||||||
|
&mut next_handle,
|
||||||
|
&mut vm,
|
||||||
|
&signals,
|
||||||
|
&mut hardware,
|
||||||
|
);
|
||||||
assert!(report.is_none(), "operational error must not crash");
|
assert!(report.is_none(), "operational error must not crash");
|
||||||
assert!(vm.is_halted());
|
assert!(vm.is_halted());
|
||||||
assert_eq!(vm.operand_stack_top(1), vec![Value::Int64(AudioOpStatus::ArgRangeInvalid as i64)]);
|
assert_eq!(vm.operand_stack_top(1), vec![Value::Int64(AudioOpStatus::ArgRangeInvalid as i64)]);
|
||||||
@ -929,6 +1216,12 @@ fn tick_audio_play_sample_operational_error_returns_status_not_crash() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn tick_audio_play_voice_invalid_returns_status_not_crash() {
|
fn tick_audio_play_voice_invalid_returns_status_not_crash() {
|
||||||
let mut runtime = VirtualMachineRuntime::new(None);
|
let mut runtime = VirtualMachineRuntime::new(None);
|
||||||
|
let mut log_service = LogService::new(4096);
|
||||||
|
let mut fs = VirtualFS::new();
|
||||||
|
let mut fs_state = FsState::Unmounted;
|
||||||
|
let mut memcard = MemcardService::new();
|
||||||
|
let mut open_files: HashMap<u32, String> = HashMap::new();
|
||||||
|
let mut next_handle = 1;
|
||||||
let mut vm = VirtualMachine::default();
|
let mut vm = VirtualMachine::default();
|
||||||
let mut hardware = Hardware::new();
|
let mut hardware = Hardware::new();
|
||||||
let signals = InputSignals::default();
|
let signals = InputSignals::default();
|
||||||
@ -948,8 +1241,18 @@ fn tick_audio_play_voice_invalid_returns_status_not_crash() {
|
|||||||
);
|
);
|
||||||
let cartridge = cartridge_with_program(program, caps::AUDIO);
|
let cartridge = cartridge_with_program(program, caps::AUDIO);
|
||||||
|
|
||||||
runtime.initialize_vm(&mut vm, &cartridge).expect("runtime must initialize");
|
runtime.initialize_vm(&mut log_service, &mut vm, &cartridge).expect("runtime must initialize");
|
||||||
let report = runtime.tick(&mut vm, &signals, &mut hardware);
|
let report = runtime.tick(
|
||||||
|
&mut log_service,
|
||||||
|
&mut fs,
|
||||||
|
&mut fs_state,
|
||||||
|
&mut memcard,
|
||||||
|
&mut open_files,
|
||||||
|
&mut next_handle,
|
||||||
|
&mut vm,
|
||||||
|
&signals,
|
||||||
|
&mut hardware,
|
||||||
|
);
|
||||||
assert!(report.is_none(), "invalid voice must not crash");
|
assert!(report.is_none(), "invalid voice must not crash");
|
||||||
assert!(vm.is_halted());
|
assert!(vm.is_halted());
|
||||||
assert_eq!(vm.operand_stack_top(1), vec![Value::Int64(AudioOpStatus::VoiceInvalid as i64)]);
|
assert_eq!(vm.operand_stack_top(1), vec![Value::Int64(AudioOpStatus::VoiceInvalid as i64)]);
|
||||||
@ -958,6 +1261,12 @@ fn tick_audio_play_voice_invalid_returns_status_not_crash() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn tick_audio_play_missing_asset_returns_status_not_crash() {
|
fn tick_audio_play_missing_asset_returns_status_not_crash() {
|
||||||
let mut runtime = VirtualMachineRuntime::new(None);
|
let mut runtime = VirtualMachineRuntime::new(None);
|
||||||
|
let mut log_service = LogService::new(4096);
|
||||||
|
let mut fs = VirtualFS::new();
|
||||||
|
let mut fs_state = FsState::Unmounted;
|
||||||
|
let mut memcard = MemcardService::new();
|
||||||
|
let mut open_files: HashMap<u32, String> = HashMap::new();
|
||||||
|
let mut next_handle = 1;
|
||||||
let mut vm = VirtualMachine::default();
|
let mut vm = VirtualMachine::default();
|
||||||
let mut hardware = Hardware::new();
|
let mut hardware = Hardware::new();
|
||||||
let signals = InputSignals::default();
|
let signals = InputSignals::default();
|
||||||
@ -977,8 +1286,18 @@ fn tick_audio_play_missing_asset_returns_status_not_crash() {
|
|||||||
);
|
);
|
||||||
let cartridge = cartridge_with_program(program, caps::AUDIO);
|
let cartridge = cartridge_with_program(program, caps::AUDIO);
|
||||||
|
|
||||||
runtime.initialize_vm(&mut vm, &cartridge).expect("runtime must initialize");
|
runtime.initialize_vm(&mut log_service, &mut vm, &cartridge).expect("runtime must initialize");
|
||||||
let report = runtime.tick(&mut vm, &signals, &mut hardware);
|
let report = runtime.tick(
|
||||||
|
&mut log_service,
|
||||||
|
&mut fs,
|
||||||
|
&mut fs_state,
|
||||||
|
&mut memcard,
|
||||||
|
&mut open_files,
|
||||||
|
&mut next_handle,
|
||||||
|
&mut vm,
|
||||||
|
&signals,
|
||||||
|
&mut hardware,
|
||||||
|
);
|
||||||
assert!(report.is_none(), "missing audio asset must not crash");
|
assert!(report.is_none(), "missing audio asset must not crash");
|
||||||
assert!(vm.is_halted());
|
assert!(vm.is_halted());
|
||||||
assert_eq!(vm.operand_stack_top(1), vec![Value::Int64(AudioOpStatus::BankInvalid as i64)]);
|
assert_eq!(vm.operand_stack_top(1), vec![Value::Int64(AudioOpStatus::BankInvalid as i64)]);
|
||||||
@ -987,6 +1306,12 @@ fn tick_audio_play_missing_asset_returns_status_not_crash() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn tick_audio_play_type_mismatch_surfaces_trap_not_panic() {
|
fn tick_audio_play_type_mismatch_surfaces_trap_not_panic() {
|
||||||
let mut runtime = VirtualMachineRuntime::new(None);
|
let mut runtime = VirtualMachineRuntime::new(None);
|
||||||
|
let mut log_service = LogService::new(4096);
|
||||||
|
let mut fs = VirtualFS::new();
|
||||||
|
let mut fs_state = FsState::Unmounted;
|
||||||
|
let mut memcard = MemcardService::new();
|
||||||
|
let mut open_files: HashMap<u32, String> = HashMap::new();
|
||||||
|
let mut next_handle = 1;
|
||||||
let mut vm = VirtualMachine::default();
|
let mut vm = VirtualMachine::default();
|
||||||
let mut hardware = Hardware::new();
|
let mut hardware = Hardware::new();
|
||||||
let signals = InputSignals::default();
|
let signals = InputSignals::default();
|
||||||
@ -1006,9 +1331,20 @@ fn tick_audio_play_type_mismatch_surfaces_trap_not_panic() {
|
|||||||
);
|
);
|
||||||
let cartridge = cartridge_with_program(program, caps::AUDIO);
|
let cartridge = cartridge_with_program(program, caps::AUDIO);
|
||||||
|
|
||||||
runtime.initialize_vm(&mut vm, &cartridge).expect("runtime must initialize");
|
runtime.initialize_vm(&mut log_service, &mut vm, &cartridge).expect("runtime must initialize");
|
||||||
let report =
|
let report = runtime
|
||||||
runtime.tick(&mut vm, &signals, &mut hardware).expect("type mismatch must surface as trap");
|
.tick(
|
||||||
|
&mut log_service,
|
||||||
|
&mut fs,
|
||||||
|
&mut fs_state,
|
||||||
|
&mut memcard,
|
||||||
|
&mut open_files,
|
||||||
|
&mut next_handle,
|
||||||
|
&mut vm,
|
||||||
|
&signals,
|
||||||
|
&mut hardware,
|
||||||
|
)
|
||||||
|
.expect("type mismatch must surface as trap");
|
||||||
match report {
|
match report {
|
||||||
CrashReport::VmTrap { trap } => {
|
CrashReport::VmTrap { trap } => {
|
||||||
assert_eq!(trap.code, TRAP_TYPE);
|
assert_eq!(trap.code, TRAP_TYPE);
|
||||||
@ -1022,6 +1358,12 @@ fn tick_audio_play_type_mismatch_surfaces_trap_not_panic() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn tick_composer_emit_sprite_type_mismatch_surfaces_trap_not_panic() {
|
fn tick_composer_emit_sprite_type_mismatch_surfaces_trap_not_panic() {
|
||||||
let mut runtime = VirtualMachineRuntime::new(None);
|
let mut runtime = VirtualMachineRuntime::new(None);
|
||||||
|
let mut log_service = LogService::new(4096);
|
||||||
|
let mut fs = VirtualFS::new();
|
||||||
|
let mut fs_state = FsState::Unmounted;
|
||||||
|
let mut memcard = MemcardService::new();
|
||||||
|
let mut open_files: HashMap<u32, String> = HashMap::new();
|
||||||
|
let mut next_handle = 1;
|
||||||
let mut vm = VirtualMachine::default();
|
let mut vm = VirtualMachine::default();
|
||||||
let mut hardware = Hardware::new();
|
let mut hardware = Hardware::new();
|
||||||
let signals = InputSignals::default();
|
let signals = InputSignals::default();
|
||||||
@ -1041,9 +1383,20 @@ fn tick_composer_emit_sprite_type_mismatch_surfaces_trap_not_panic() {
|
|||||||
);
|
);
|
||||||
let cartridge = cartridge_with_program(program, caps::GFX);
|
let cartridge = cartridge_with_program(program, caps::GFX);
|
||||||
|
|
||||||
runtime.initialize_vm(&mut vm, &cartridge).expect("runtime must initialize");
|
runtime.initialize_vm(&mut log_service, &mut vm, &cartridge).expect("runtime must initialize");
|
||||||
let report =
|
let report = runtime
|
||||||
runtime.tick(&mut vm, &signals, &mut hardware).expect("type mismatch must surface as trap");
|
.tick(
|
||||||
|
&mut log_service,
|
||||||
|
&mut fs,
|
||||||
|
&mut fs_state,
|
||||||
|
&mut memcard,
|
||||||
|
&mut open_files,
|
||||||
|
&mut next_handle,
|
||||||
|
&mut vm,
|
||||||
|
&signals,
|
||||||
|
&mut hardware,
|
||||||
|
)
|
||||||
|
.expect("type mismatch must surface as trap");
|
||||||
match report {
|
match report {
|
||||||
CrashReport::VmTrap { trap } => {
|
CrashReport::VmTrap { trap } => {
|
||||||
assert_eq!(trap.code, TRAP_TYPE);
|
assert_eq!(trap.code, TRAP_TYPE);
|
||||||
@ -3,6 +3,12 @@ use super::*;
|
|||||||
#[test]
|
#[test]
|
||||||
fn tick_asset_commit_operational_error_returns_status_not_crash() {
|
fn tick_asset_commit_operational_error_returns_status_not_crash() {
|
||||||
let mut runtime = VirtualMachineRuntime::new(None);
|
let mut runtime = VirtualMachineRuntime::new(None);
|
||||||
|
let mut log_service = LogService::new(4096);
|
||||||
|
let mut fs = VirtualFS::new();
|
||||||
|
let mut fs_state = FsState::Unmounted;
|
||||||
|
let mut memcard = MemcardService::new();
|
||||||
|
let mut open_files: HashMap<u32, String> = HashMap::new();
|
||||||
|
let mut next_handle = 1;
|
||||||
let mut vm = VirtualMachine::default();
|
let mut vm = VirtualMachine::default();
|
||||||
let mut hardware = Hardware::new();
|
let mut hardware = Hardware::new();
|
||||||
let signals = InputSignals::default();
|
let signals = InputSignals::default();
|
||||||
@ -19,8 +25,18 @@ fn tick_asset_commit_operational_error_returns_status_not_crash() {
|
|||||||
);
|
);
|
||||||
let cartridge = cartridge_with_program(program, caps::ASSET);
|
let cartridge = cartridge_with_program(program, caps::ASSET);
|
||||||
|
|
||||||
runtime.initialize_vm(&mut vm, &cartridge).expect("runtime must initialize");
|
runtime.initialize_vm(&mut log_service, &mut vm, &cartridge).expect("runtime must initialize");
|
||||||
let report = runtime.tick(&mut vm, &signals, &mut hardware);
|
let report = runtime.tick(
|
||||||
|
&mut log_service,
|
||||||
|
&mut fs,
|
||||||
|
&mut fs_state,
|
||||||
|
&mut memcard,
|
||||||
|
&mut open_files,
|
||||||
|
&mut next_handle,
|
||||||
|
&mut vm,
|
||||||
|
&signals,
|
||||||
|
&mut hardware,
|
||||||
|
);
|
||||||
assert!(report.is_none(), "operational error must not crash");
|
assert!(report.is_none(), "operational error must not crash");
|
||||||
assert!(vm.is_halted());
|
assert!(vm.is_halted());
|
||||||
assert_eq!(vm.operand_stack_top(1), vec![Value::Int64(AssetOpStatus::UnknownHandle as i64)]);
|
assert_eq!(vm.operand_stack_top(1), vec![Value::Int64(AssetOpStatus::UnknownHandle as i64)]);
|
||||||
@ -29,6 +45,12 @@ fn tick_asset_commit_operational_error_returns_status_not_crash() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn tick_asset_load_missing_asset_returns_status_and_zero_handle() {
|
fn tick_asset_load_missing_asset_returns_status_and_zero_handle() {
|
||||||
let mut runtime = VirtualMachineRuntime::new(None);
|
let mut runtime = VirtualMachineRuntime::new(None);
|
||||||
|
let mut log_service = LogService::new(4096);
|
||||||
|
let mut fs = VirtualFS::new();
|
||||||
|
let mut fs_state = FsState::Unmounted;
|
||||||
|
let mut memcard = MemcardService::new();
|
||||||
|
let mut open_files: HashMap<u32, String> = HashMap::new();
|
||||||
|
let mut next_handle = 1;
|
||||||
let mut vm = VirtualMachine::default();
|
let mut vm = VirtualMachine::default();
|
||||||
let mut hardware = Hardware::new();
|
let mut hardware = Hardware::new();
|
||||||
let signals = InputSignals::default();
|
let signals = InputSignals::default();
|
||||||
@ -45,8 +67,18 @@ fn tick_asset_load_missing_asset_returns_status_and_zero_handle() {
|
|||||||
);
|
);
|
||||||
let cartridge = cartridge_with_program(program, caps::ASSET);
|
let cartridge = cartridge_with_program(program, caps::ASSET);
|
||||||
|
|
||||||
runtime.initialize_vm(&mut vm, &cartridge).expect("runtime must initialize");
|
runtime.initialize_vm(&mut log_service, &mut vm, &cartridge).expect("runtime must initialize");
|
||||||
let report = runtime.tick(&mut vm, &signals, &mut hardware);
|
let report = runtime.tick(
|
||||||
|
&mut log_service,
|
||||||
|
&mut fs,
|
||||||
|
&mut fs_state,
|
||||||
|
&mut memcard,
|
||||||
|
&mut open_files,
|
||||||
|
&mut next_handle,
|
||||||
|
&mut vm,
|
||||||
|
&signals,
|
||||||
|
&mut hardware,
|
||||||
|
);
|
||||||
assert!(report.is_none(), "missing asset must not crash");
|
assert!(report.is_none(), "missing asset must not crash");
|
||||||
assert!(vm.is_halted());
|
assert!(vm.is_halted());
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@ -58,6 +90,12 @@ fn tick_asset_load_missing_asset_returns_status_and_zero_handle() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn tick_asset_load_invalid_slot_returns_status_and_zero_handle() {
|
fn tick_asset_load_invalid_slot_returns_status_and_zero_handle() {
|
||||||
let mut runtime = VirtualMachineRuntime::new(None);
|
let mut runtime = VirtualMachineRuntime::new(None);
|
||||||
|
let mut log_service = LogService::new(4096);
|
||||||
|
let mut fs = VirtualFS::new();
|
||||||
|
let mut fs_state = FsState::Unmounted;
|
||||||
|
let mut memcard = MemcardService::new();
|
||||||
|
let mut open_files: HashMap<u32, String> = HashMap::new();
|
||||||
|
let mut next_handle = 1;
|
||||||
let mut vm = VirtualMachine::default();
|
let mut vm = VirtualMachine::default();
|
||||||
let mut hardware = Hardware::new();
|
let mut hardware = Hardware::new();
|
||||||
let signals = InputSignals::default();
|
let signals = InputSignals::default();
|
||||||
@ -80,8 +118,18 @@ fn tick_asset_load_invalid_slot_returns_status_and_zero_handle() {
|
|||||||
);
|
);
|
||||||
let cartridge = cartridge_with_program(program, caps::ASSET);
|
let cartridge = cartridge_with_program(program, caps::ASSET);
|
||||||
|
|
||||||
runtime.initialize_vm(&mut vm, &cartridge).expect("runtime must initialize");
|
runtime.initialize_vm(&mut log_service, &mut vm, &cartridge).expect("runtime must initialize");
|
||||||
let report = runtime.tick(&mut vm, &signals, &mut hardware);
|
let report = runtime.tick(
|
||||||
|
&mut log_service,
|
||||||
|
&mut fs,
|
||||||
|
&mut fs_state,
|
||||||
|
&mut memcard,
|
||||||
|
&mut open_files,
|
||||||
|
&mut next_handle,
|
||||||
|
&mut vm,
|
||||||
|
&signals,
|
||||||
|
&mut hardware,
|
||||||
|
);
|
||||||
assert!(report.is_none(), "invalid slot must not crash");
|
assert!(report.is_none(), "invalid slot must not crash");
|
||||||
assert!(vm.is_halted());
|
assert!(vm.is_halted());
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@ -93,6 +141,12 @@ fn tick_asset_load_invalid_slot_returns_status_and_zero_handle() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn tick_asset_status_unknown_handle_returns_status_not_crash() {
|
fn tick_asset_status_unknown_handle_returns_status_not_crash() {
|
||||||
let mut runtime = VirtualMachineRuntime::new(None);
|
let mut runtime = VirtualMachineRuntime::new(None);
|
||||||
|
let mut log_service = LogService::new(4096);
|
||||||
|
let mut fs = VirtualFS::new();
|
||||||
|
let mut fs_state = FsState::Unmounted;
|
||||||
|
let mut memcard = MemcardService::new();
|
||||||
|
let mut open_files: HashMap<u32, String> = HashMap::new();
|
||||||
|
let mut next_handle = 1;
|
||||||
let mut vm = VirtualMachine::default();
|
let mut vm = VirtualMachine::default();
|
||||||
let mut hardware = Hardware::new();
|
let mut hardware = Hardware::new();
|
||||||
let signals = InputSignals::default();
|
let signals = InputSignals::default();
|
||||||
@ -109,8 +163,18 @@ fn tick_asset_status_unknown_handle_returns_status_not_crash() {
|
|||||||
);
|
);
|
||||||
let cartridge = cartridge_with_program(program, caps::ASSET);
|
let cartridge = cartridge_with_program(program, caps::ASSET);
|
||||||
|
|
||||||
runtime.initialize_vm(&mut vm, &cartridge).expect("runtime must initialize");
|
runtime.initialize_vm(&mut log_service, &mut vm, &cartridge).expect("runtime must initialize");
|
||||||
let report = runtime.tick(&mut vm, &signals, &mut hardware);
|
let report = runtime.tick(
|
||||||
|
&mut log_service,
|
||||||
|
&mut fs,
|
||||||
|
&mut fs_state,
|
||||||
|
&mut memcard,
|
||||||
|
&mut open_files,
|
||||||
|
&mut next_handle,
|
||||||
|
&mut vm,
|
||||||
|
&signals,
|
||||||
|
&mut hardware,
|
||||||
|
);
|
||||||
assert!(report.is_none(), "unknown asset handle must not crash");
|
assert!(report.is_none(), "unknown asset handle must not crash");
|
||||||
assert!(vm.is_halted());
|
assert!(vm.is_halted());
|
||||||
assert_eq!(vm.operand_stack_top(1), vec![Value::Int64(LoadStatus::UnknownHandle as i64)]);
|
assert_eq!(vm.operand_stack_top(1), vec![Value::Int64(LoadStatus::UnknownHandle as i64)]);
|
||||||
@ -119,6 +183,12 @@ fn tick_asset_status_unknown_handle_returns_status_not_crash() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn tick_bank_info_returns_slot_summary_not_json() {
|
fn tick_bank_info_returns_slot_summary_not_json() {
|
||||||
let mut runtime = VirtualMachineRuntime::new(None);
|
let mut runtime = VirtualMachineRuntime::new(None);
|
||||||
|
let mut log_service = LogService::new(4096);
|
||||||
|
let mut fs = VirtualFS::new();
|
||||||
|
let mut fs_state = FsState::Unmounted;
|
||||||
|
let mut memcard = MemcardService::new();
|
||||||
|
let mut open_files: HashMap<u32, String> = HashMap::new();
|
||||||
|
let mut next_handle = 1;
|
||||||
let mut vm = VirtualMachine::default();
|
let mut vm = VirtualMachine::default();
|
||||||
let mut hardware = Hardware::new();
|
let mut hardware = Hardware::new();
|
||||||
let signals = InputSignals::default();
|
let signals = InputSignals::default();
|
||||||
@ -141,8 +211,18 @@ fn tick_bank_info_returns_slot_summary_not_json() {
|
|||||||
);
|
);
|
||||||
let cartridge = cartridge_with_program(program, caps::BANK);
|
let cartridge = cartridge_with_program(program, caps::BANK);
|
||||||
|
|
||||||
runtime.initialize_vm(&mut vm, &cartridge).expect("runtime must initialize");
|
runtime.initialize_vm(&mut log_service, &mut vm, &cartridge).expect("runtime must initialize");
|
||||||
let report = runtime.tick(&mut vm, &signals, &mut hardware);
|
let report = runtime.tick(
|
||||||
|
&mut log_service,
|
||||||
|
&mut fs,
|
||||||
|
&mut fs_state,
|
||||||
|
&mut memcard,
|
||||||
|
&mut open_files,
|
||||||
|
&mut next_handle,
|
||||||
|
&mut vm,
|
||||||
|
&signals,
|
||||||
|
&mut hardware,
|
||||||
|
);
|
||||||
assert!(report.is_none(), "bank summary must not crash");
|
assert!(report.is_none(), "bank summary must not crash");
|
||||||
assert!(vm.is_halted());
|
assert!(vm.is_halted());
|
||||||
assert_eq!(vm.operand_stack_top(2), vec![Value::Int64(16), Value::Int64(1)]);
|
assert_eq!(vm.operand_stack_top(2), vec![Value::Int64(16), Value::Int64(1)]);
|
||||||
@ -151,6 +231,7 @@ fn tick_bank_info_returns_slot_summary_not_json() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn initialize_vm_rejects_removed_bank_slot_info_syscall_identity() {
|
fn initialize_vm_rejects_removed_bank_slot_info_syscall_identity() {
|
||||||
let mut runtime = VirtualMachineRuntime::new(None);
|
let mut runtime = VirtualMachineRuntime::new(None);
|
||||||
|
let mut log_service = LogService::new(4096);
|
||||||
let mut vm = VirtualMachine::default();
|
let mut vm = VirtualMachine::default();
|
||||||
let code = assemble("PUSH_I32 0\nPUSH_I32 0\nHOSTCALL 0\nHALT").expect("assemble");
|
let code = assemble("PUSH_I32 0\nPUSH_I32 0\nHOSTCALL 0\nHALT").expect("assemble");
|
||||||
let program = serialized_single_function_module(
|
let program = serialized_single_function_module(
|
||||||
@ -165,7 +246,7 @@ fn initialize_vm_rejects_removed_bank_slot_info_syscall_identity() {
|
|||||||
);
|
);
|
||||||
let cartridge = cartridge_with_program(program, caps::BANK);
|
let cartridge = cartridge_with_program(program, caps::BANK);
|
||||||
|
|
||||||
let res = runtime.initialize_vm(&mut vm, &cartridge);
|
let res = runtime.initialize_vm(&mut log_service, &mut vm, &cartridge);
|
||||||
|
|
||||||
assert!(matches!(res, Err(CrashReport::VmInit { error: VmInitError::LoaderPatchFailed(_) })));
|
assert!(matches!(res, Err(CrashReport::VmInit { error: VmInitError::LoaderPatchFailed(_) })));
|
||||||
}
|
}
|
||||||
@ -173,6 +254,12 @@ fn initialize_vm_rejects_removed_bank_slot_info_syscall_identity() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn tick_asset_commit_invalid_transition_returns_status_not_crash() {
|
fn tick_asset_commit_invalid_transition_returns_status_not_crash() {
|
||||||
let mut runtime = VirtualMachineRuntime::new(None);
|
let mut runtime = VirtualMachineRuntime::new(None);
|
||||||
|
let mut log_service = LogService::new(4096);
|
||||||
|
let mut fs = VirtualFS::new();
|
||||||
|
let mut fs_state = FsState::Unmounted;
|
||||||
|
let mut memcard = MemcardService::new();
|
||||||
|
let mut open_files: HashMap<u32, String> = HashMap::new();
|
||||||
|
let mut next_handle = 1;
|
||||||
let mut vm = VirtualMachine::default();
|
let mut vm = VirtualMachine::default();
|
||||||
let mut hardware = Hardware::new();
|
let mut hardware = Hardware::new();
|
||||||
let signals = InputSignals::default();
|
let signals = InputSignals::default();
|
||||||
@ -207,8 +294,18 @@ fn tick_asset_commit_invalid_transition_returns_status_not_crash() {
|
|||||||
);
|
);
|
||||||
let handle = hardware.assets.load(7, 0).expect("asset handle must be allocated");
|
let handle = hardware.assets.load(7, 0).expect("asset handle must be allocated");
|
||||||
|
|
||||||
runtime.initialize_vm(&mut vm, &cartridge).expect("runtime must initialize");
|
runtime.initialize_vm(&mut log_service, &mut vm, &cartridge).expect("runtime must initialize");
|
||||||
let report = runtime.tick(&mut vm, &signals, &mut hardware);
|
let report = runtime.tick(
|
||||||
|
&mut log_service,
|
||||||
|
&mut fs,
|
||||||
|
&mut fs_state,
|
||||||
|
&mut memcard,
|
||||||
|
&mut open_files,
|
||||||
|
&mut next_handle,
|
||||||
|
&mut vm,
|
||||||
|
&signals,
|
||||||
|
&mut hardware,
|
||||||
|
);
|
||||||
assert!(report.is_none(), "invalid transition must not crash");
|
assert!(report.is_none(), "invalid transition must not crash");
|
||||||
assert!(vm.is_halted());
|
assert!(vm.is_halted());
|
||||||
assert_eq!(handle, 1);
|
assert_eq!(handle, 1);
|
||||||
@ -218,6 +315,12 @@ fn tick_asset_commit_invalid_transition_returns_status_not_crash() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn tick_asset_cancel_unknown_handle_returns_status_not_crash() {
|
fn tick_asset_cancel_unknown_handle_returns_status_not_crash() {
|
||||||
let mut runtime = VirtualMachineRuntime::new(None);
|
let mut runtime = VirtualMachineRuntime::new(None);
|
||||||
|
let mut log_service = LogService::new(4096);
|
||||||
|
let mut fs = VirtualFS::new();
|
||||||
|
let mut fs_state = FsState::Unmounted;
|
||||||
|
let mut memcard = MemcardService::new();
|
||||||
|
let mut open_files: HashMap<u32, String> = HashMap::new();
|
||||||
|
let mut next_handle = 1;
|
||||||
let mut vm = VirtualMachine::default();
|
let mut vm = VirtualMachine::default();
|
||||||
let mut hardware = Hardware::new();
|
let mut hardware = Hardware::new();
|
||||||
let signals = InputSignals::default();
|
let signals = InputSignals::default();
|
||||||
@ -234,8 +337,18 @@ fn tick_asset_cancel_unknown_handle_returns_status_not_crash() {
|
|||||||
);
|
);
|
||||||
let cartridge = cartridge_with_program(program, caps::ASSET);
|
let cartridge = cartridge_with_program(program, caps::ASSET);
|
||||||
|
|
||||||
runtime.initialize_vm(&mut vm, &cartridge).expect("runtime must initialize");
|
runtime.initialize_vm(&mut log_service, &mut vm, &cartridge).expect("runtime must initialize");
|
||||||
let report = runtime.tick(&mut vm, &signals, &mut hardware);
|
let report = runtime.tick(
|
||||||
|
&mut log_service,
|
||||||
|
&mut fs,
|
||||||
|
&mut fs_state,
|
||||||
|
&mut memcard,
|
||||||
|
&mut open_files,
|
||||||
|
&mut next_handle,
|
||||||
|
&mut vm,
|
||||||
|
&signals,
|
||||||
|
&mut hardware,
|
||||||
|
);
|
||||||
assert!(report.is_none(), "unknown handle cancel must not crash");
|
assert!(report.is_none(), "unknown handle cancel must not crash");
|
||||||
assert!(vm.is_halted());
|
assert!(vm.is_halted());
|
||||||
assert_eq!(vm.operand_stack_top(1), vec![Value::Int64(AssetOpStatus::UnknownHandle as i64)]);
|
assert_eq!(vm.operand_stack_top(1), vec![Value::Int64(AssetOpStatus::UnknownHandle as i64)]);
|
||||||
@ -244,6 +357,12 @@ fn tick_asset_cancel_unknown_handle_returns_status_not_crash() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn tick_asset_cancel_invalid_transition_returns_status_not_crash() {
|
fn tick_asset_cancel_invalid_transition_returns_status_not_crash() {
|
||||||
let mut runtime = VirtualMachineRuntime::new(None);
|
let mut runtime = VirtualMachineRuntime::new(None);
|
||||||
|
let mut log_service = LogService::new(4096);
|
||||||
|
let mut fs = VirtualFS::new();
|
||||||
|
let mut fs_state = FsState::Unmounted;
|
||||||
|
let mut memcard = MemcardService::new();
|
||||||
|
let mut open_files: HashMap<u32, String> = HashMap::new();
|
||||||
|
let mut next_handle = 1;
|
||||||
let mut vm = VirtualMachine::default();
|
let mut vm = VirtualMachine::default();
|
||||||
let mut hardware = Hardware::new();
|
let mut hardware = Hardware::new();
|
||||||
let signals = InputSignals::default();
|
let signals = InputSignals::default();
|
||||||
@ -268,7 +387,7 @@ fn tick_asset_cancel_invalid_transition_returns_status_not_crash() {
|
|||||||
);
|
);
|
||||||
let handle = hardware.assets.load(7, 0).expect("asset handle must be allocated");
|
let handle = hardware.assets.load(7, 0).expect("asset handle must be allocated");
|
||||||
|
|
||||||
runtime.initialize_vm(&mut vm, &cartridge).expect("runtime must initialize");
|
runtime.initialize_vm(&mut log_service, &mut vm, &cartridge).expect("runtime must initialize");
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
match hardware.assets.status(handle) {
|
match hardware.assets.status(handle) {
|
||||||
@ -284,7 +403,17 @@ fn tick_asset_cancel_invalid_transition_returns_status_not_crash() {
|
|||||||
hardware.assets.apply_commits();
|
hardware.assets.apply_commits();
|
||||||
assert_eq!(hardware.assets.status(handle), LoadStatus::COMMITTED);
|
assert_eq!(hardware.assets.status(handle), LoadStatus::COMMITTED);
|
||||||
|
|
||||||
let report = runtime.tick(&mut vm, &signals, &mut hardware);
|
let report = runtime.tick(
|
||||||
|
&mut log_service,
|
||||||
|
&mut fs,
|
||||||
|
&mut fs_state,
|
||||||
|
&mut memcard,
|
||||||
|
&mut open_files,
|
||||||
|
&mut next_handle,
|
||||||
|
&mut vm,
|
||||||
|
&signals,
|
||||||
|
&mut hardware,
|
||||||
|
);
|
||||||
assert!(report.is_none(), "cancel after commit must not crash");
|
assert!(report.is_none(), "cancel after commit must not crash");
|
||||||
assert!(vm.is_halted());
|
assert!(vm.is_halted());
|
||||||
assert_eq!(vm.operand_stack_top(1), vec![Value::Int64(AssetOpStatus::InvalidState as i64)]);
|
assert_eq!(vm.operand_stack_top(1), vec![Value::Int64(AssetOpStatus::InvalidState as i64)]);
|
||||||
@ -293,6 +422,12 @@ fn tick_asset_cancel_invalid_transition_returns_status_not_crash() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn tick_status_first_surface_smoke_across_composer_audio_and_asset() {
|
fn tick_status_first_surface_smoke_across_composer_audio_and_asset() {
|
||||||
let mut runtime = VirtualMachineRuntime::new(None);
|
let mut runtime = VirtualMachineRuntime::new(None);
|
||||||
|
let mut log_service = LogService::new(4096);
|
||||||
|
let mut fs = VirtualFS::new();
|
||||||
|
let mut fs_state = FsState::Unmounted;
|
||||||
|
let mut memcard = MemcardService::new();
|
||||||
|
let mut open_files: HashMap<u32, String> = HashMap::new();
|
||||||
|
let mut next_handle = 1;
|
||||||
let mut vm = VirtualMachine::default();
|
let mut vm = VirtualMachine::default();
|
||||||
let mut hardware = Hardware::new();
|
let mut hardware = Hardware::new();
|
||||||
let signals = InputSignals::default();
|
let signals = InputSignals::default();
|
||||||
@ -331,8 +466,18 @@ fn tick_status_first_surface_smoke_across_composer_audio_and_asset() {
|
|||||||
);
|
);
|
||||||
let cartridge = cartridge_with_program(program, caps::GFX | caps::AUDIO | caps::ASSET);
|
let cartridge = cartridge_with_program(program, caps::GFX | caps::AUDIO | caps::ASSET);
|
||||||
|
|
||||||
runtime.initialize_vm(&mut vm, &cartridge).expect("runtime must initialize");
|
runtime.initialize_vm(&mut log_service, &mut vm, &cartridge).expect("runtime must initialize");
|
||||||
let report = runtime.tick(&mut vm, &signals, &mut hardware);
|
let report = runtime.tick(
|
||||||
|
&mut log_service,
|
||||||
|
&mut fs,
|
||||||
|
&mut fs_state,
|
||||||
|
&mut memcard,
|
||||||
|
&mut open_files,
|
||||||
|
&mut next_handle,
|
||||||
|
&mut vm,
|
||||||
|
&signals,
|
||||||
|
&mut hardware,
|
||||||
|
);
|
||||||
assert!(report.is_none(), "mixed status-first surface must not crash");
|
assert!(report.is_none(), "mixed status-first surface must not crash");
|
||||||
assert!(vm.is_halted());
|
assert!(vm.is_halted());
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@ -3,7 +3,13 @@ use super::*;
|
|||||||
#[test]
|
#[test]
|
||||||
fn tick_memcard_slot_roundtrip_for_game_profile() {
|
fn tick_memcard_slot_roundtrip_for_game_profile() {
|
||||||
let mut runtime = VirtualMachineRuntime::new(None);
|
let mut runtime = VirtualMachineRuntime::new(None);
|
||||||
runtime.mount_fs(Box::new(MemFsBackend::default()));
|
let mut log_service = LogService::new(4096);
|
||||||
|
let mut fs = VirtualFS::new();
|
||||||
|
let mut fs_state = FsState::Unmounted;
|
||||||
|
let mut memcard = MemcardService::new();
|
||||||
|
let mut open_files: HashMap<u32, String> = HashMap::new();
|
||||||
|
let mut next_handle = 1;
|
||||||
|
runtime.mount_fs(&mut log_service, &mut fs, &mut fs_state, Box::new(MemFsBackend::default()));
|
||||||
let mut vm = VirtualMachine::default();
|
let mut vm = VirtualMachine::default();
|
||||||
let mut hardware = Hardware::new();
|
let mut hardware = Hardware::new();
|
||||||
let signals = InputSignals::default();
|
let signals = InputSignals::default();
|
||||||
@ -50,8 +56,18 @@ fn tick_memcard_slot_roundtrip_for_game_profile() {
|
|||||||
preload: vec![],
|
preload: vec![],
|
||||||
};
|
};
|
||||||
|
|
||||||
runtime.initialize_vm(&mut vm, &cartridge).expect("runtime must initialize");
|
runtime.initialize_vm(&mut log_service, &mut vm, &cartridge).expect("runtime must initialize");
|
||||||
let report = runtime.tick(&mut vm, &signals, &mut hardware);
|
let report = runtime.tick(
|
||||||
|
&mut log_service,
|
||||||
|
&mut fs,
|
||||||
|
&mut fs_state,
|
||||||
|
&mut memcard,
|
||||||
|
&mut open_files,
|
||||||
|
&mut next_handle,
|
||||||
|
&mut vm,
|
||||||
|
&signals,
|
||||||
|
&mut hardware,
|
||||||
|
);
|
||||||
assert!(report.is_none(), "memcard roundtrip must not crash");
|
assert!(report.is_none(), "memcard roundtrip must not crash");
|
||||||
assert!(vm.is_halted());
|
assert!(vm.is_halted());
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@ -63,6 +79,12 @@ fn tick_memcard_slot_roundtrip_for_game_profile() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn tick_memcard_access_is_denied_for_non_game_profile() {
|
fn tick_memcard_access_is_denied_for_non_game_profile() {
|
||||||
let mut runtime = VirtualMachineRuntime::new(None);
|
let mut runtime = VirtualMachineRuntime::new(None);
|
||||||
|
let mut log_service = LogService::new(4096);
|
||||||
|
let mut fs = VirtualFS::new();
|
||||||
|
let mut fs_state = FsState::Unmounted;
|
||||||
|
let mut memcard = MemcardService::new();
|
||||||
|
let mut open_files: HashMap<u32, String> = HashMap::new();
|
||||||
|
let mut next_handle = 1;
|
||||||
let mut vm = VirtualMachine::default();
|
let mut vm = VirtualMachine::default();
|
||||||
let mut hardware = Hardware::new();
|
let mut hardware = Hardware::new();
|
||||||
let signals = InputSignals::default();
|
let signals = InputSignals::default();
|
||||||
@ -89,8 +111,18 @@ fn tick_memcard_access_is_denied_for_non_game_profile() {
|
|||||||
preload: vec![],
|
preload: vec![],
|
||||||
};
|
};
|
||||||
|
|
||||||
runtime.initialize_vm(&mut vm, &cartridge).expect("runtime must initialize");
|
runtime.initialize_vm(&mut log_service, &mut vm, &cartridge).expect("runtime must initialize");
|
||||||
let report = runtime.tick(&mut vm, &signals, &mut hardware);
|
let report = runtime.tick(
|
||||||
|
&mut log_service,
|
||||||
|
&mut fs,
|
||||||
|
&mut fs_state,
|
||||||
|
&mut memcard,
|
||||||
|
&mut open_files,
|
||||||
|
&mut next_handle,
|
||||||
|
&mut vm,
|
||||||
|
&signals,
|
||||||
|
&mut hardware,
|
||||||
|
);
|
||||||
assert!(report.is_none(), "non-game memcard call must return status");
|
assert!(report.is_none(), "non-game memcard call must return status");
|
||||||
assert!(vm.is_halted());
|
assert!(vm.is_halted());
|
||||||
assert_eq!(vm.operand_stack_top(2), vec![Value::Int64(0), Value::Int64(4)]);
|
assert_eq!(vm.operand_stack_top(2), vec![Value::Int64(0), Value::Int64(4)]);
|
||||||
@ -1,9 +1,13 @@
|
|||||||
|
use super::dispatch::VmRuntimeHost;
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::CrashReport;
|
use crate::CrashReport;
|
||||||
|
use crate::fs::{FsState, VirtualFS};
|
||||||
|
use crate::services::memcard::MemcardService;
|
||||||
use prometeu_hal::asset::{BankTelemetry, BankType};
|
use prometeu_hal::asset::{BankTelemetry, BankType};
|
||||||
use prometeu_hal::log::{LogLevel, LogSource};
|
use prometeu_hal::log::{LogLevel, LogService, LogSource};
|
||||||
use prometeu_hal::{HardwareBridge, HostContext, InputSignals};
|
use prometeu_hal::{HardwareBridge, HostContext, InputSignals};
|
||||||
use prometeu_vm::LogicalFrameEndingReason;
|
use prometeu_vm::LogicalFrameEndingReason;
|
||||||
|
use std::collections::HashMap;
|
||||||
use std::panic::{AssertUnwindSafe, catch_unwind};
|
use std::panic::{AssertUnwindSafe, catch_unwind};
|
||||||
use std::sync::atomic::Ordering;
|
use std::sync::atomic::Ordering;
|
||||||
|
|
||||||
@ -52,11 +56,30 @@ impl VirtualMachineRuntime {
|
|||||||
|
|
||||||
pub fn debug_step_instruction(
|
pub fn debug_step_instruction(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
log_service: &mut LogService,
|
||||||
vm: &mut VirtualMachine,
|
vm: &mut VirtualMachine,
|
||||||
hw: &mut dyn HardwareBridge,
|
hw: &mut dyn HardwareBridge,
|
||||||
) -> Option<CrashReport> {
|
) -> Option<CrashReport> {
|
||||||
let mut ctx = HostContext::new(Some(hw));
|
let step_result = {
|
||||||
match vm.step(self, &mut ctx) {
|
let mut ctx = HostContext::new(Some(hw));
|
||||||
|
let mut fs = VirtualFS::new();
|
||||||
|
let mut fs_state = FsState::Unmounted;
|
||||||
|
let mut memcard = MemcardService::new();
|
||||||
|
let mut open_files = HashMap::new();
|
||||||
|
let mut next_handle = 1;
|
||||||
|
let mut host = VmRuntimeHost {
|
||||||
|
runtime: self,
|
||||||
|
log_service,
|
||||||
|
fs: &mut fs,
|
||||||
|
fs_state: &mut fs_state,
|
||||||
|
memcard: &mut memcard,
|
||||||
|
open_files: &mut open_files,
|
||||||
|
next_handle: &mut next_handle,
|
||||||
|
};
|
||||||
|
vm.step(&mut host, &mut ctx)
|
||||||
|
};
|
||||||
|
|
||||||
|
match step_result {
|
||||||
Ok(_) => None,
|
Ok(_) => None,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
let report = match e {
|
let report = match e {
|
||||||
@ -70,6 +93,7 @@ impl VirtualMachineRuntime {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
self.log(
|
self.log(
|
||||||
|
log_service,
|
||||||
LogLevel::Error,
|
LogLevel::Error,
|
||||||
LogSource::Vm,
|
LogSource::Vm,
|
||||||
report.log_tag(),
|
report.log_tag(),
|
||||||
@ -83,6 +107,12 @@ impl VirtualMachineRuntime {
|
|||||||
|
|
||||||
pub fn tick(
|
pub fn tick(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
log_service: &mut LogService,
|
||||||
|
fs: &mut VirtualFS,
|
||||||
|
fs_state: &mut FsState,
|
||||||
|
mem_card: &mut MemcardService,
|
||||||
|
open_files: &mut HashMap<u32, String>,
|
||||||
|
next_handle: &mut u32,
|
||||||
vm: &mut VirtualMachine,
|
vm: &mut VirtualMachine,
|
||||||
signals: &InputSignals,
|
signals: &InputSignals,
|
||||||
hw: &mut dyn HardwareBridge,
|
hw: &mut dyn HardwareBridge,
|
||||||
@ -94,7 +124,7 @@ impl VirtualMachineRuntime {
|
|||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.update_fs();
|
self.update_fs(log_service, fs, fs_state);
|
||||||
|
|
||||||
if !self.logical_frame_active {
|
if !self.logical_frame_active {
|
||||||
self.logical_frame_active = true;
|
self.logical_frame_active = true;
|
||||||
@ -128,7 +158,16 @@ impl VirtualMachineRuntime {
|
|||||||
if budget > 0 {
|
if budget > 0 {
|
||||||
let run_result = {
|
let run_result = {
|
||||||
let mut ctx = HostContext::new(Some(hw));
|
let mut ctx = HostContext::new(Some(hw));
|
||||||
vm.run_budget(budget, self, &mut ctx)
|
let mut host = VmRuntimeHost {
|
||||||
|
runtime: self,
|
||||||
|
log_service,
|
||||||
|
fs,
|
||||||
|
fs_state,
|
||||||
|
memcard: mem_card,
|
||||||
|
open_files,
|
||||||
|
next_handle,
|
||||||
|
};
|
||||||
|
vm.run_budget(budget, &mut host, &mut ctx)
|
||||||
};
|
};
|
||||||
|
|
||||||
match run_result {
|
match run_result {
|
||||||
@ -143,6 +182,7 @@ impl VirtualMachineRuntime {
|
|||||||
self.paused = true;
|
self.paused = true;
|
||||||
self.debug_step_request = false;
|
self.debug_step_request = false;
|
||||||
self.log(
|
self.log(
|
||||||
|
log_service,
|
||||||
LogLevel::Info,
|
LogLevel::Info,
|
||||||
LogSource::Vm,
|
LogSource::Vm,
|
||||||
0xDEB1,
|
0xDEB1,
|
||||||
@ -154,6 +194,7 @@ impl VirtualMachineRuntime {
|
|||||||
let report =
|
let report =
|
||||||
CrashReport::VmPanic { message: err, pc: Some(vm.pc() as u32) };
|
CrashReport::VmPanic { message: err, pc: Some(vm.pc() as u32) };
|
||||||
self.log(
|
self.log(
|
||||||
|
log_service,
|
||||||
LogLevel::Error,
|
LogLevel::Error,
|
||||||
LogSource::Vm,
|
LogSource::Vm,
|
||||||
report.log_tag(),
|
report.log_tag(),
|
||||||
@ -166,6 +207,7 @@ impl VirtualMachineRuntime {
|
|||||||
if let LogicalFrameEndingReason::Trap(trap) = &run.reason {
|
if let LogicalFrameEndingReason::Trap(trap) = &run.reason {
|
||||||
let report = CrashReport::VmTrap { trap: trap.clone() };
|
let report = CrashReport::VmTrap { trap: trap.clone() };
|
||||||
self.log(
|
self.log(
|
||||||
|
log_service,
|
||||||
LogLevel::Error,
|
LogLevel::Error,
|
||||||
LogSource::Vm,
|
LogSource::Vm,
|
||||||
report.log_tag(),
|
report.log_tag(),
|
||||||
@ -182,6 +224,7 @@ impl VirtualMachineRuntime {
|
|||||||
let message = Self::host_panic_payload_to_string(payload);
|
let message = Self::host_panic_payload_to_string(payload);
|
||||||
let report = CrashReport::VmPanic { message, pc: Some(vm.pc() as u32) };
|
let report = CrashReport::VmPanic { message, pc: Some(vm.pc() as u32) };
|
||||||
self.log(
|
self.log(
|
||||||
|
log_service,
|
||||||
LogLevel::Error,
|
LogLevel::Error,
|
||||||
LogSource::Vm,
|
LogSource::Vm,
|
||||||
report.log_tag(),
|
report.log_tag(),
|
||||||
@ -229,18 +272,15 @@ impl VirtualMachineRuntime {
|
|||||||
let ts_ms = self.boot_time.elapsed().as_millis() as u64;
|
let ts_ms = self.boot_time.elapsed().as_millis() as u64;
|
||||||
let telemetry_snapshot = self.atomic_telemetry.snapshot();
|
let telemetry_snapshot = self.atomic_telemetry.snapshot();
|
||||||
|
|
||||||
let violations = self.certifier.evaluate(
|
let violations =
|
||||||
&telemetry_snapshot,
|
self.certifier.evaluate(&telemetry_snapshot, log_service, ts_ms) as u32;
|
||||||
&mut self.log_service,
|
|
||||||
ts_ms,
|
|
||||||
) as u32;
|
|
||||||
|
|
||||||
self.atomic_telemetry.violations.store(violations, Ordering::Relaxed);
|
self.atomic_telemetry.violations.store(violations, Ordering::Relaxed);
|
||||||
self.atomic_telemetry
|
self.atomic_telemetry
|
||||||
.completed_logical_frames
|
.completed_logical_frames
|
||||||
.fetch_add(1, Ordering::Relaxed);
|
.fetch_add(1, Ordering::Relaxed);
|
||||||
|
|
||||||
self.log_service.reset_count();
|
log_service.reset_count();
|
||||||
|
|
||||||
self.logical_frame_index += 1;
|
self.logical_frame_index += 1;
|
||||||
self.logical_frame_active = false;
|
self.logical_frame_active = false;
|
||||||
@ -258,7 +298,13 @@ impl VirtualMachineRuntime {
|
|||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
let report = CrashReport::VmPanic { message: e, pc: Some(vm.pc() as u32) };
|
let report = CrashReport::VmPanic { message: e, pc: Some(vm.pc() as u32) };
|
||||||
self.log(LogLevel::Error, LogSource::Vm, report.log_tag(), report.summary());
|
self.log(
|
||||||
|
log_service,
|
||||||
|
LogLevel::Error,
|
||||||
|
LogSource::Vm,
|
||||||
|
report.log_tag(),
|
||||||
|
report.summary(),
|
||||||
|
);
|
||||||
self.last_crash_report = Some(report.clone());
|
self.last_crash_report = Some(report.clone());
|
||||||
return Some(report);
|
return Some(report);
|
||||||
}
|
}
|
||||||
@ -58,7 +58,11 @@ impl HostDebugger {
|
|||||||
// Pre-load cartridge metadata so the Handshake message can contain
|
// Pre-load cartridge metadata so the Handshake message can contain
|
||||||
// valid information about the App being debugged.
|
// valid information about the App being debugged.
|
||||||
if let Ok(cartridge) = CartridgeLoader::load(path) {
|
if let Ok(cartridge) = CartridgeLoader::load(path) {
|
||||||
let _ = firmware.os.vm_runtime.initialize_vm(&mut firmware.vm, &cartridge);
|
let _ = firmware.os.vm_runtime.initialize_vm(
|
||||||
|
&mut firmware.os.log_service,
|
||||||
|
&mut firmware.vm,
|
||||||
|
&cartridge,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
match TcpListener::bind(format!("127.0.0.1:{}", debug_port)) {
|
match TcpListener::bind(format!("127.0.0.1:{}", debug_port)) {
|
||||||
@ -201,7 +205,11 @@ impl HostDebugger {
|
|||||||
DebugCommand::Step => {
|
DebugCommand::Step => {
|
||||||
// Execute exactly one instruction and keep paused.
|
// Execute exactly one instruction and keep paused.
|
||||||
firmware.os.vm_runtime.paused = true;
|
firmware.os.vm_runtime.paused = true;
|
||||||
let _ = firmware.os.vm_runtime.debug_step_instruction(&mut firmware.vm, hardware);
|
let _ = firmware.os.vm_runtime.debug_step_instruction(
|
||||||
|
&mut firmware.os.log_service,
|
||||||
|
&mut firmware.vm,
|
||||||
|
hardware,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
DebugCommand::StepFrame => {
|
DebugCommand::StepFrame => {
|
||||||
// Execute until the end of the current logical frame.
|
// Execute until the end of the current logical frame.
|
||||||
@ -308,7 +316,7 @@ impl HostDebugger {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 1. Process and send new log entries.
|
// 1. Process and send new log entries.
|
||||||
let new_events = firmware.os.vm_runtime.log_service.get_after(self.last_log_seq);
|
let new_events = firmware.os.log_service.get_after(self.last_log_seq);
|
||||||
for event in new_events {
|
for event in new_events {
|
||||||
self.last_log_seq = event.seq;
|
self.last_log_seq = event.seq;
|
||||||
|
|
||||||
|
|||||||
@ -59,7 +59,7 @@ struct FrameCanvas<'a> {
|
|||||||
|
|
||||||
pub(crate) fn capture_snapshot(stats: &HostStats, firmware: &Firmware) -> OverlaySnapshot {
|
pub(crate) fn capture_snapshot(stats: &HostStats, firmware: &Firmware) -> OverlaySnapshot {
|
||||||
let tel = firmware.os.vm_runtime.atomic_telemetry.snapshot();
|
let tel = firmware.os.vm_runtime.atomic_telemetry.snapshot();
|
||||||
let recent_logs = firmware.os.vm_runtime.log_service.get_recent(10);
|
let recent_logs = firmware.os.log_service.get_recent(10);
|
||||||
let violations_count =
|
let violations_count =
|
||||||
recent_logs.iter().filter(|e| e.tag >= 0xCA01 && e.tag <= 0xCA07).count();
|
recent_logs.iter().filter(|e| e.tag >= 0xCA01 && e.tag <= 0xCA07).count();
|
||||||
|
|
||||||
|
|||||||
@ -153,7 +153,7 @@ impl HostRunner {
|
|||||||
let mut firmware = Firmware::new(cap_config);
|
let mut firmware = Firmware::new(cap_config);
|
||||||
if let Some(root) = &fs_root {
|
if let Some(root) = &fs_root {
|
||||||
let backend = HostDirBackend::new(root);
|
let backend = HostDirBackend::new(root);
|
||||||
firmware.os.vm_runtime.mount_fs(Box::new(backend));
|
firmware.os.mount_fs(Box::new(backend));
|
||||||
}
|
}
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
@ -333,13 +333,11 @@ impl ApplicationHandler for HostRunner {
|
|||||||
// 2. Maintain filesystem connection if it was lost (e.g., directory removed).
|
// 2. Maintain filesystem connection if it was lost (e.g., directory removed).
|
||||||
if let Some(root) = &self.fs_root {
|
if let Some(root) = &self.fs_root {
|
||||||
use prometeu_system::fs::FsState;
|
use prometeu_system::fs::FsState;
|
||||||
if matches!(
|
if matches!(self.firmware.os.fs_state, FsState::Unmounted | FsState::Error(_))
|
||||||
self.firmware.os.vm_runtime.fs_state,
|
&& std::path::Path::new(root).exists()
|
||||||
FsState::Unmounted | FsState::Error(_)
|
|
||||||
) && std::path::Path::new(root).exists()
|
|
||||||
{
|
{
|
||||||
let backend = HostDirBackend::new(root);
|
let backend = HostDirBackend::new(root);
|
||||||
self.firmware.os.vm_runtime.mount_fs(Box::new(backend));
|
self.firmware.os.mount_fs(Box::new(backend));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -403,9 +401,9 @@ impl ApplicationHandler for HostRunner {
|
|||||||
// Synchronize system logs to the host console.
|
// Synchronize system logs to the host console.
|
||||||
let last_seq = self.log_sink.last_seq().unwrap_or(u64::MAX);
|
let last_seq = self.log_sink.last_seq().unwrap_or(u64::MAX);
|
||||||
let new_events = if last_seq == u64::MAX {
|
let new_events = if last_seq == u64::MAX {
|
||||||
self.firmware.os.vm_runtime.log_service.get_recent(4096)
|
self.firmware.os.log_service.get_recent(4096)
|
||||||
} else {
|
} else {
|
||||||
self.firmware.os.vm_runtime.log_service.get_after(last_seq)
|
self.firmware.os.log_service.get_after(last_seq)
|
||||||
};
|
};
|
||||||
self.log_sink.process_events(new_events);
|
self.log_sink.process_events(new_events);
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
{"type":"meta","next_id":{"DSC":32,"AGD":32,"DEC":24,"PLN":51,"LSN":41,"CLSN":1}}
|
{"type":"meta","next_id":{"DSC":34,"AGD":34,"DEC":25,"PLN":55,"LSN":41,"CLSN":1}}
|
||||||
{"type":"discussion","id":"DSC-0023","status":"done","ticket":"perf-full-migration-to-atomic-telemetry","title":"Agenda - [PERF] Full Migration to Atomic Telemetry","created_at":"2026-04-10","updated_at":"2026-04-10","tags":["perf","runtime","telemetry"],"agendas":[{"id":"AGD-0021","file":"AGD-0021-full-migration-to-atomic-telemetry.md","status":"done","created_at":"2026-04-10","updated_at":"2026-04-10"}],"decisions":[{"id":"DEC-0008","file":"DEC-0008-full-migration-to-atomic-telemetry.md","status":"accepted","created_at":"2026-04-10","updated_at":"2026-04-10"}],"plans":[{"id":"PLN-0007","file":"PLN-0007-full-migration-to-atomic-telemetry.md","status":"done","created_at":"2026-04-10","updated_at":"2026-04-10"}],"lessons":[{"id":"LSN-0028","file":"discussion/lessons/DSC-0023-perf-full-migration-to-atomic-telemetry/LSN-0028-converging-to-single-atomic-telemetry-source.md","status":"done","created_at":"2026-04-10","updated_at":"2026-04-10"}]}
|
{"type":"discussion","id":"DSC-0023","status":"done","ticket":"perf-full-migration-to-atomic-telemetry","title":"Agenda - [PERF] Full Migration to Atomic Telemetry","created_at":"2026-04-10","updated_at":"2026-04-10","tags":["perf","runtime","telemetry"],"agendas":[{"id":"AGD-0021","file":"AGD-0021-full-migration-to-atomic-telemetry.md","status":"done","created_at":"2026-04-10","updated_at":"2026-04-10"}],"decisions":[{"id":"DEC-0008","file":"DEC-0008-full-migration-to-atomic-telemetry.md","status":"accepted","created_at":"2026-04-10","updated_at":"2026-04-10"}],"plans":[{"id":"PLN-0007","file":"PLN-0007-full-migration-to-atomic-telemetry.md","status":"done","created_at":"2026-04-10","updated_at":"2026-04-10"}],"lessons":[{"id":"LSN-0028","file":"discussion/lessons/DSC-0023-perf-full-migration-to-atomic-telemetry/LSN-0028-converging-to-single-atomic-telemetry-source.md","status":"done","created_at":"2026-04-10","updated_at":"2026-04-10"}]}
|
||||||
{"type":"discussion","id":"DSC-0020","status":"done","ticket":"jenkins-gitea-integration","title":"Jenkins Gitea Integration and Relocation","created_at":"2026-04-07","updated_at":"2026-04-07","tags":["ci","jenkins","gitea"],"agendas":[{"id":"AGD-0018","file":"AGD-0018-jenkins-gitea-integration-and-relocation.md","status":"done","created_at":"2026-04-07","updated_at":"2026-04-07"}],"decisions":[{"id":"DEC-0003","file":"DEC-0003-jenkins-gitea-strategy.md","status":"accepted","created_at":"2026-04-07","updated_at":"2026-04-07"}],"plans":[{"id":"PLN-0003","file":"PLN-0003-jenkins-gitea-execution.md","status":"done","created_at":"2026-04-07","updated_at":"2026-04-07"}],"lessons":[{"id":"LSN-0021","file":"discussion/lessons/DSC-0020-jenkins-gitea-integration/LSN-0021-jenkins-gitea-integration.md","status":"done","created_at":"2026-04-07","updated_at":"2026-04-07"}]}
|
{"type":"discussion","id":"DSC-0020","status":"done","ticket":"jenkins-gitea-integration","title":"Jenkins Gitea Integration and Relocation","created_at":"2026-04-07","updated_at":"2026-04-07","tags":["ci","jenkins","gitea"],"agendas":[{"id":"AGD-0018","file":"AGD-0018-jenkins-gitea-integration-and-relocation.md","status":"done","created_at":"2026-04-07","updated_at":"2026-04-07"}],"decisions":[{"id":"DEC-0003","file":"DEC-0003-jenkins-gitea-strategy.md","status":"accepted","created_at":"2026-04-07","updated_at":"2026-04-07"}],"plans":[{"id":"PLN-0003","file":"PLN-0003-jenkins-gitea-execution.md","status":"done","created_at":"2026-04-07","updated_at":"2026-04-07"}],"lessons":[{"id":"LSN-0021","file":"discussion/lessons/DSC-0020-jenkins-gitea-integration/LSN-0021-jenkins-gitea-integration.md","status":"done","created_at":"2026-04-07","updated_at":"2026-04-07"}]}
|
||||||
{"type":"discussion","id":"DSC-0021","status":"done","ticket":"asset-entry-codec-enum-with-metadata","title":"Asset Entry Codec Enum Contract","created_at":"2026-04-09","updated_at":"2026-04-09","tags":["asset","runtime","codec","metadata"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0024","file":"discussion/lessons/DSC-0021-asset-entry-codec-enum-contract/LSN-0024-string-on-the-wire-enum-in-runtime.md","status":"done","created_at":"2026-04-09","updated_at":"2026-04-09"}]}
|
{"type":"discussion","id":"DSC-0021","status":"done","ticket":"asset-entry-codec-enum-with-metadata","title":"Asset Entry Codec Enum Contract","created_at":"2026-04-09","updated_at":"2026-04-09","tags":["asset","runtime","codec","metadata"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0024","file":"discussion/lessons/DSC-0021-asset-entry-codec-enum-contract/LSN-0024-string-on-the-wire-enum-in-runtime.md","status":"done","created_at":"2026-04-09","updated_at":"2026-04-09"}]}
|
||||||
@ -30,3 +30,5 @@
|
|||||||
{"type":"discussion","id":"DSC-0019","status":"done","ticket":"jenkinsfile-correction","title":"Jenkinsfile Correction and Relocation","created_at":"2026-04-07","updated_at":"2026-04-07","tags":["ci","jenkins"],"agendas":[{"id":"AGD-0017","file":"AGD-0017-jenkinsfile-correction.md","status":"done","created_at":"2026-04-07","updated_at":"2026-04-07"}],"decisions":[{"id":"DEC-0002","file":"DEC-0002-jenkinsfile-strategy.md","status":"accepted","created_at":"2026-04-07","updated_at":"2026-04-07"}],"plans":[{"id":"PLN-0002","file":"PLN-0002-jenkinsfile-execution.md","status":"done","created_at":"2026-04-07","updated_at":"2026-04-07"}],"lessons":[{"id":"LSN-0020","file":"discussion/lessons/DSC-0019-jenkins-ci-standardization/LSN-0020-jenkins-standard-relocation.md","status":"done","created_at":"2026-04-07","updated_at":"2026-04-07"}]}
|
{"type":"discussion","id":"DSC-0019","status":"done","ticket":"jenkinsfile-correction","title":"Jenkinsfile Correction and Relocation","created_at":"2026-04-07","updated_at":"2026-04-07","tags":["ci","jenkins"],"agendas":[{"id":"AGD-0017","file":"AGD-0017-jenkinsfile-correction.md","status":"done","created_at":"2026-04-07","updated_at":"2026-04-07"}],"decisions":[{"id":"DEC-0002","file":"DEC-0002-jenkinsfile-strategy.md","status":"accepted","created_at":"2026-04-07","updated_at":"2026-04-07"}],"plans":[{"id":"PLN-0002","file":"PLN-0002-jenkinsfile-execution.md","status":"done","created_at":"2026-04-07","updated_at":"2026-04-07"}],"lessons":[{"id":"LSN-0020","file":"discussion/lessons/DSC-0019-jenkins-ci-standardization/LSN-0020-jenkins-standard-relocation.md","status":"done","created_at":"2026-04-07","updated_at":"2026-04-07"}]}
|
||||||
{"type":"discussion","id":"DSC-0030","status":"done","ticket":"internal-viewport-270p","title":"Agenda - Internal Viewport 270p (480x270)","created_at":"2026-04-27","updated_at":"2026-04-28","tags":["gfx","runtime","viewport","resolution","frame-composer","host"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0039","file":"discussion/lessons/DSC-0030-internal-viewport-270p/LSN-0039-resolution-baseline-changes-are-runtime-wide-contracts.md","status":"done","created_at":"2026-04-28","updated_at":"2026-04-28"}]}
|
{"type":"discussion","id":"DSC-0030","status":"done","ticket":"internal-viewport-270p","title":"Agenda - Internal Viewport 270p (480x270)","created_at":"2026-04-27","updated_at":"2026-04-28","tags":["gfx","runtime","viewport","resolution","frame-composer","host"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0039","file":"discussion/lessons/DSC-0030-internal-viewport-270p/LSN-0039-resolution-baseline-changes-are-runtime-wide-contracts.md","status":"done","created_at":"2026-04-28","updated_at":"2026-04-28"}]}
|
||||||
{"type":"discussion","id":"DSC-0031","status":"done","ticket":"runtime-mode-separation-game-system","title":"Agenda - Runtime Mode Separation: Game and System","created_at":"2026-05-11","updated_at":"2026-05-14","tags":["runtime","firmware","hub","system-apps","game-mode","scheduler"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0040","file":"discussion/lessons/DSC-0031-runtime-mode-separation-game-system/LSN-0040-system-pipeline-separation.md","status":"done","created_at":"2026-05-14","updated_at":"2026-05-14"}]}
|
{"type":"discussion","id":"DSC-0031","status":"done","ticket":"runtime-mode-separation-game-system","title":"Agenda - Runtime Mode Separation: Game and System","created_at":"2026-05-11","updated_at":"2026-05-14","tags":["runtime","firmware","hub","system-apps","game-mode","scheduler"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0040","file":"discussion/lessons/DSC-0031-runtime-mode-separation-game-system/LSN-0040-system-pipeline-separation.md","status":"done","created_at":"2026-05-14","updated_at":"2026-05-14"}]}
|
||||||
|
{"type":"discussion","id":"DSC-0032","status":"open","ticket":"system-os-lifecycle-process-task-contract","title":"Agenda - SystemOS Lifecycle, Process and Task Contract","created_at":"2026-05-14","updated_at":"2026-05-14","tags":["runtime","os","lifecycle","process","task","shell","firmware"],"agendas":[{"id":"AGD-0032","file":"AGD-0032-system-os-lifecycle-process-task-contract.md","status":"open","created_at":"2026-05-14","updated_at":"2026-05-14"}],"decisions":[],"plans":[],"lessons":[]}
|
||||||
|
{"type":"discussion","id":"DSC-0033","status":"in_progress","ticket":"system-os-service-ownership-and-module-layout","title":"Agenda - SystemOS Service Ownership and Module Layout","created_at":"2026-05-14","updated_at":"2026-05-14","tags":["runtime","os","services","module-layout","vm","window-manager","logging"],"agendas":[{"id":"AGD-0033","file":"AGD-0033-system-os-service-ownership-and-module-layout.md","status":"accepted","created_at":"2026-05-14","updated_at":"2026-05-14"}],"decisions":[{"id":"DEC-0024","file":"DEC-0024-systemos-service-ownership-and-module-layout.md","status":"accepted","created_at":"2026-05-14","updated_at":"2026-05-14","ref_agenda":"AGD-0033"}],"plans":[{"id":"PLN-0051","file":"PLN-0051-move-vm-runtime-into-services.md","status":"done","created_at":"2026-05-14","updated_at":"2026-05-14","ref_decisions":["DEC-0024"]},{"id":"PLN-0052","file":"PLN-0052-promote-window-manager-to-systemos-service.md","status":"done","created_at":"2026-05-14","updated_at":"2026-05-14","ref_decisions":["DEC-0024"]},{"id":"PLN-0053","file":"PLN-0053-move-logging-ownership-to-systemos.md","status":"done","created_at":"2026-05-14","updated_at":"2026-05-14","ref_decisions":["DEC-0024"]},{"id":"PLN-0054","file":"PLN-0054-move-fs-and-memcard-ownership-to-systemos.md","status":"done","created_at":"2026-05-14","updated_at":"2026-05-14","ref_decisions":["DEC-0024"]}],"lessons":[]}
|
||||||
|
|||||||
@ -0,0 +1,302 @@
|
|||||||
|
---
|
||||||
|
id: AGD-0032
|
||||||
|
ticket: system-os-lifecycle-process-task-contract
|
||||||
|
title: Agenda - SystemOS Lifecycle, Process and Task Contract
|
||||||
|
status: open
|
||||||
|
created: 2026-05-14
|
||||||
|
resolved:
|
||||||
|
decision:
|
||||||
|
tags: [runtime, os, lifecycle, process, task, shell, firmware]
|
||||||
|
---
|
||||||
|
|
||||||
|
# Agenda - SystemOS Lifecycle, Process and Task Contract
|
||||||
|
|
||||||
|
## Contexto
|
||||||
|
|
||||||
|
Prometeu deu o primeiro passo concreto para deixar de ser apenas um firmware que
|
||||||
|
roda cartuchos e passar a ter um modelo de sistema operacional de console.
|
||||||
|
|
||||||
|
Antes, o cartucho era representado quase exclusivamente pelo fluxo do firmware:
|
||||||
|
|
||||||
|
```text
|
||||||
|
LoadCartridgeStep
|
||||||
|
-> initialize_vm
|
||||||
|
-> GameRunningStep / SystemRunningStep
|
||||||
|
```
|
||||||
|
|
||||||
|
Esse modelo permitia executar jogos e apps de sistema, mas não dava ao OS uma
|
||||||
|
entidade própria para representar processo, task, foreground, background,
|
||||||
|
suspensão, fechamento ou crash. O firmware sabia que algo estava rodando; o
|
||||||
|
Prometeu OS ainda não possuía um contrato próprio de execução e presença de
|
||||||
|
usuário.
|
||||||
|
|
||||||
|
Agora existe um `SystemOS` em `prometeu-system`, agregando:
|
||||||
|
|
||||||
|
```text
|
||||||
|
SystemOS
|
||||||
|
VirtualMachineRuntime
|
||||||
|
ProcessManager
|
||||||
|
TaskManager
|
||||||
|
```
|
||||||
|
|
||||||
|
Isso estabelece uma fronteira importante: a VM continua sendo a VM, e não a dona
|
||||||
|
do sistema. O `SystemOS` passa a ser o lugar natural para concentrar serviços de
|
||||||
|
OS, lifecycle, permissões e coordenação entre execução técnica e experiência de
|
||||||
|
usuário.
|
||||||
|
|
||||||
|
Os conceitos iniciais são:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Process
|
||||||
|
execução técnica:
|
||||||
|
VmGame, VmShell, NativeShell
|
||||||
|
|
||||||
|
Task
|
||||||
|
presença navegável do usuário:
|
||||||
|
Game ou Shell em Foreground, Background, Suspended, Closed ou Crashed
|
||||||
|
```
|
||||||
|
|
||||||
|
O carregamento de cartucho começou a registrar entidades de sistema:
|
||||||
|
|
||||||
|
```text
|
||||||
|
AppMode::Game
|
||||||
|
-> cria processo VmGame
|
||||||
|
-> cria task Game
|
||||||
|
-> marca task como Foreground
|
||||||
|
-> entra em GameRunningStep com TaskId
|
||||||
|
|
||||||
|
AppMode::System
|
||||||
|
-> cria processo VmShell
|
||||||
|
-> cria task Shell
|
||||||
|
-> marca task como Foreground
|
||||||
|
-> entra em SystemRunningStep com TaskId
|
||||||
|
```
|
||||||
|
|
||||||
|
`GameRunningStep` agora executa uma task específica e valida se ela ainda está em
|
||||||
|
`Foreground` antes de avançar o frame. Essa é a primeira ponte real entre o
|
||||||
|
firmware atual e um OS com lifecycle.
|
||||||
|
|
||||||
|
## Problema
|
||||||
|
|
||||||
|
O código já tem os primeiros tipos e fluxos, mas o contrato arquitetural ainda
|
||||||
|
não está fechado:
|
||||||
|
|
||||||
|
- quem é a autoridade para mover uma task entre `Foreground`, `Background`,
|
||||||
|
`Suspended`, `Closed` e `Crashed`;
|
||||||
|
- como `TaskState` e `ProcessState` devem evoluir juntos;
|
||||||
|
- quais transições pertencem ao `SystemOS` e quais continuam nos managers
|
||||||
|
internos;
|
||||||
|
- como o firmware deve reagir quando uma task deixa de estar executável;
|
||||||
|
- quando o `WindowManager` deixa de ser parte do `PrometeuHub` e vira serviço
|
||||||
|
real do OS;
|
||||||
|
- se o nome público do perfil de sistema deve continuar `System` ou convergir
|
||||||
|
para `Shell` no contrato de cartucho/runtime.
|
||||||
|
|
||||||
|
Sem esse contrato, o risco é o sistema crescer em duas direções ruins:
|
||||||
|
|
||||||
|
1. o firmware continuar acumulando decisões de lifecycle que pertencem ao OS;
|
||||||
|
2. `TaskManager`, `ProcessManager`, `PrometeuHub` e `VirtualMachineRuntime`
|
||||||
|
criarem regras paralelas e inconsistentes.
|
||||||
|
|
||||||
|
## Pontos Criticos
|
||||||
|
|
||||||
|
- `SystemOS` deve ser a API de coordenação de lifecycle, não apenas um struct
|
||||||
|
agregador de managers.
|
||||||
|
- `ProcessManager` e `TaskManager` devem continuar guardando estado local, mas
|
||||||
|
não devem, sozinhos, definir o contrato semântico completo entre task e
|
||||||
|
processo.
|
||||||
|
- `Task` representa presença navegável e foco do usuário; `Process` representa
|
||||||
|
execução técnica.
|
||||||
|
- `Foreground` não é apenas uma flag visual: no modelo atual, ele autoriza o
|
||||||
|
`GameRunningStep` a executar frame.
|
||||||
|
- `Suspended`, `Closed` e `Crashed` precisam mover `TaskState` e `ProcessState`
|
||||||
|
de forma coordenada.
|
||||||
|
- `Background` precisa ser distinguido de `Suspended`: background pode existir
|
||||||
|
como presença navegável não focada, mas ainda não implica que o processo possa
|
||||||
|
executar.
|
||||||
|
- `PrometeuHub` deve evoluir para Shell/Home visual, não permanecer como
|
||||||
|
sinônimo de OS.
|
||||||
|
- `WindowManager` provavelmente deve virar serviço de `SystemOS`, mas talvez
|
||||||
|
isso precise esperar a primeira API de lifecycle estabilizar.
|
||||||
|
- A inspiração de UX é console/Switch; a inspiração interna é mais Android-like:
|
||||||
|
processos, tasks, lifecycle, surfaces/janelas, serviços e permissões.
|
||||||
|
|
||||||
|
## Dependencia / Agenda Paralela
|
||||||
|
|
||||||
|
`AGD-0033` trata a limpeza de ownership e layout de serviços do `SystemOS` antes
|
||||||
|
ou em paralelo a esta agenda.
|
||||||
|
|
||||||
|
Essa separação é intencional: `AGD-0032` deve decidir lifecycle de
|
||||||
|
`Task`/`Process`, enquanto `AGD-0033` deve decidir se `VirtualMachineRuntime`,
|
||||||
|
`LogService`, `WindowManager` e outros serviços pertencem ao root, ao Hub ou à
|
||||||
|
camada `services` do `SystemOS`.
|
||||||
|
|
||||||
|
Se `AGD-0033` concluir que `LogService` e outros serviços precisam sair da VM
|
||||||
|
antes do lifecycle, a decisão de `AGD-0032` deve tratar essa mudança como
|
||||||
|
pré-requisito de implementação, não como parte do contrato de lifecycle em si.
|
||||||
|
|
||||||
|
## Opcoes
|
||||||
|
|
||||||
|
### Opcao A - Manter lifecycle distribuido nos managers e no firmware
|
||||||
|
|
||||||
|
**Abordagem:**
|
||||||
|
`TaskManager` muda tasks, `ProcessManager` muda processos, e o firmware chama
|
||||||
|
ambos diretamente quando precisar.
|
||||||
|
|
||||||
|
**Vantagens:**
|
||||||
|
- menor mudança imediata;
|
||||||
|
- preserva managers simples;
|
||||||
|
- fácil de encaixar incrementalmente no código atual.
|
||||||
|
|
||||||
|
**Custos / Riscos:**
|
||||||
|
- espalha regra de lifecycle por vários chamadores;
|
||||||
|
- torna mais provável esquecer de mover `ProcessState` junto com `TaskState`;
|
||||||
|
- mantém firmware como conhecedor de detalhes internos do OS;
|
||||||
|
- dificulta introduzir Shell/Home, suspensão e retomada sem duplicação.
|
||||||
|
|
||||||
|
**Manutenibilidade:**
|
||||||
|
Aceitável no curto prazo, fraca como modelo de OS.
|
||||||
|
|
||||||
|
### Opcao B - Centralizar lifecycle no SystemOS
|
||||||
|
|
||||||
|
**Abordagem:**
|
||||||
|
`SystemOS` vira a API semântica para operações como:
|
||||||
|
|
||||||
|
```text
|
||||||
|
suspend_task
|
||||||
|
resume_task
|
||||||
|
close_task
|
||||||
|
crash_task
|
||||||
|
foreground_task
|
||||||
|
background_task
|
||||||
|
```
|
||||||
|
|
||||||
|
Essas operações coordenam `TaskManager` e `ProcessManager` juntas.
|
||||||
|
|
||||||
|
Exemplos de contrato:
|
||||||
|
|
||||||
|
```text
|
||||||
|
jogo rodando:
|
||||||
|
TaskState::Foreground
|
||||||
|
ProcessState::Running
|
||||||
|
|
||||||
|
jogo suspenso:
|
||||||
|
TaskState::Suspended
|
||||||
|
ProcessState::Suspended
|
||||||
|
|
||||||
|
jogo fechado:
|
||||||
|
TaskState::Closed
|
||||||
|
ProcessState::Stopped
|
||||||
|
|
||||||
|
jogo crashado:
|
||||||
|
TaskState::Crashed
|
||||||
|
ProcessState::Crashed
|
||||||
|
```
|
||||||
|
|
||||||
|
**Vantagens:**
|
||||||
|
- estabelece `SystemOS` como autoridade de lifecycle;
|
||||||
|
- mantém firmware mais fino;
|
||||||
|
- reduz inconsistência entre task e processo;
|
||||||
|
- cria base natural para Home/Shell, suspend/resume, close/crash e permissões.
|
||||||
|
|
||||||
|
**Custos / Riscos:**
|
||||||
|
- exige definir transições inválidas e retorno/erro de cada operação;
|
||||||
|
- pode expor API cedo demais se tentarmos modelar todas as regras finais agora;
|
||||||
|
- precisa decidir se `Background` implica processo running, suspended ou apenas
|
||||||
|
presença não focada.
|
||||||
|
|
||||||
|
**Manutenibilidade:**
|
||||||
|
Forte. É o caminho mais alinhado com transformar Prometeu em console OS.
|
||||||
|
|
||||||
|
### Opcao C - Promover WindowManager a servico do OS antes do lifecycle
|
||||||
|
|
||||||
|
**Abordagem:**
|
||||||
|
Mover primeiro o `WindowManager` para dentro de `SystemOS` e depois organizar
|
||||||
|
tasks/processos em torno de janelas/surfaces.
|
||||||
|
|
||||||
|
**Vantagens:**
|
||||||
|
- aproxima rapidamente o modelo de Shell/Home e apps gerenciados;
|
||||||
|
- evita `PrometeuHub` continuar parecendo o sistema inteiro;
|
||||||
|
- abre caminho para surfaces e composição de apps de sistema.
|
||||||
|
|
||||||
|
**Custos / Riscos:**
|
||||||
|
- pode misturar lifecycle de execução com lifecycle visual;
|
||||||
|
- força decisões de janela/surface antes de fechar Process/Task;
|
||||||
|
- pode congelar um WindowManager ainda experimental.
|
||||||
|
|
||||||
|
**Manutenibilidade:**
|
||||||
|
Boa como passo futuro, mas arriscada como próximo passo se o lifecycle base
|
||||||
|
ainda não estiver normativo.
|
||||||
|
|
||||||
|
## Sugestao / Recomendacao
|
||||||
|
|
||||||
|
A recomendação inicial é seguir a **Opção B**.
|
||||||
|
|
||||||
|
O próximo contrato deve declarar `SystemOS` como autoridade para lifecycle de
|
||||||
|
tasks e processos. `TaskManager` e `ProcessManager` continuam existindo, mas
|
||||||
|
devem ser mecanismos internos de armazenamento/transição, não a API semântica
|
||||||
|
principal do sistema.
|
||||||
|
|
||||||
|
O primeiro corte deve ser pequeno:
|
||||||
|
|
||||||
|
- adicionar métodos de lifecycle em `SystemOS`;
|
||||||
|
- coordenar `TaskState` e `ProcessState` nesses métodos;
|
||||||
|
- definir comportamento para task inexistente e process inexistente;
|
||||||
|
- preservar `GameRunningStep` como consumidor de `TaskId`, validando
|
||||||
|
foreground;
|
||||||
|
- evitar mover `WindowManager` neste mesmo corte.
|
||||||
|
|
||||||
|
O `WindowManager` deve ser a agenda/decisão seguinte: promovê-lo a serviço real
|
||||||
|
do sistema quando a base `Task/Process/Lifecycle` estiver fechada.
|
||||||
|
|
||||||
|
Também precisamos resolver a nomenclatura `System` versus `Shell`. O código e o
|
||||||
|
contrato anterior usam `AppMode::System`; a narrativa nova fala em
|
||||||
|
`AppMode::Shell`. A agenda deve decidir se:
|
||||||
|
|
||||||
|
- mantemos `AppMode::System` como perfil formal e usamos `Shell` como tipo de
|
||||||
|
task/processo/UI; ou
|
||||||
|
- renomeamos o perfil público para `Shell`, com migração explícita.
|
||||||
|
|
||||||
|
Minha inclinação inicial é manter `AppMode::System` por compatibilidade de
|
||||||
|
contrato e usar `Shell` para a camada de UI/app gerenciado dentro do OS.
|
||||||
|
|
||||||
|
## Perguntas em Aberto
|
||||||
|
|
||||||
|
- [ ] `SystemOS` deve expor `suspend_task`, `resume_task`, `close_task` e
|
||||||
|
`crash_task` já no próximo corte?
|
||||||
|
- [ ] `foreground_task` e `background_task` devem ser operações explícitas do
|
||||||
|
`SystemOS`, ou apenas detalhe do `TaskManager`?
|
||||||
|
- [ ] Qual é a semântica exata de `Background`: processo ainda `Running`,
|
||||||
|
processo `Suspended`, ou estado de presença sem garantia de execução?
|
||||||
|
- [ ] `resume_task` deve sempre mover a task para `Foreground`, ou pode retomar
|
||||||
|
para `Background`?
|
||||||
|
- [ ] O que acontece quando a task existe mas o processo associado não existe?
|
||||||
|
- [ ] `close_task` remove imediatamente entidades fechadas ou apenas marca
|
||||||
|
`Closed`/`Stopped` para coleta posterior?
|
||||||
|
- [ ] `crash_task` deve receber/armazenar `CrashReport`, ou isso fica em outro
|
||||||
|
serviço de diagnóstico?
|
||||||
|
- [ ] `GameRunningStep` deve transicionar para Hub/Home quando sua task deixa de
|
||||||
|
estar `Foreground`, ou apenas não executar frame?
|
||||||
|
- [ ] O perfil de manifesto continua `AppMode::System`, ou abrimos uma migração
|
||||||
|
formal para `AppMode::Shell`?
|
||||||
|
- [ ] A decisão de ownership/layout em `AGD-0033` deve ser pré-requisito para o
|
||||||
|
primeiro plan de lifecycle, ou pode ser executada em paralelo?
|
||||||
|
|
||||||
|
## Criterio para Encerrar
|
||||||
|
|
||||||
|
Esta agenda pode virar decisão quando houver resposta para:
|
||||||
|
|
||||||
|
- qual entidade é autoridade normativa de lifecycle;
|
||||||
|
- quais métodos mínimos entram no primeiro contrato de `SystemOS`;
|
||||||
|
- como cada método move `TaskState` e `ProcessState`;
|
||||||
|
- qual semântica inicial de `Background` e `Suspended`;
|
||||||
|
- como o firmware deve reagir a task não-foreground/não-executável;
|
||||||
|
- qual nomenclatura será usada para `System` versus `Shell`;
|
||||||
|
- como a dependência com `AGD-0033` será tratada nos plans.
|
||||||
|
|
||||||
|
## Proximo Passo
|
||||||
|
|
||||||
|
Revisar esta agenda e fechar as perguntas principais. Se a direção for aceita,
|
||||||
|
o próximo estágio deve ser uma decisão normativa para `SystemOS` como autoridade
|
||||||
|
de lifecycle de `Task`/`Process`, seguida de uma família curta de plans para
|
||||||
|
implementação.
|
||||||
@ -0,0 +1,199 @@
|
|||||||
|
---
|
||||||
|
id: AGD-0033
|
||||||
|
ticket: system-os-service-ownership-and-module-layout
|
||||||
|
title: Agenda - SystemOS Service Ownership and Module Layout
|
||||||
|
status: accepted
|
||||||
|
created: 2026-05-14
|
||||||
|
resolved:
|
||||||
|
decision:
|
||||||
|
tags: [runtime, os, services, module-layout, vm, window-manager, logging]
|
||||||
|
---
|
||||||
|
|
||||||
|
# Agenda - SystemOS Service Ownership and Module Layout
|
||||||
|
|
||||||
|
## Contexto
|
||||||
|
|
||||||
|
Antes de avançar o contrato de lifecycle do `SystemOS`, há uma limpeza de
|
||||||
|
fronteira que precisa ser discutida separadamente.
|
||||||
|
|
||||||
|
O `SystemOS` começou como agregador de:
|
||||||
|
|
||||||
|
```text
|
||||||
|
SystemOS
|
||||||
|
VirtualMachineRuntime
|
||||||
|
ProcessManager
|
||||||
|
TaskManager
|
||||||
|
```
|
||||||
|
|
||||||
|
Mas o código ainda carrega sinais do modelo anterior, em que a VM concentrava
|
||||||
|
coisas que hoje parecem serviços de sistema. O exemplo mais claro é
|
||||||
|
`LogService` dentro de `VirtualMachineRuntime`. A mesma dúvida aparece em torno
|
||||||
|
de outros serviços e estados associados a filesystem, memcard, diagnóstico,
|
||||||
|
telemetria e identidade do cartucho.
|
||||||
|
|
||||||
|
Há também uma questão de layout:
|
||||||
|
|
||||||
|
- `VirtualMachineRuntime` está no root de `prometeu-system`, embora conceitualmente
|
||||||
|
agora seja um serviço do OS;
|
||||||
|
- `WindowManager` está dentro de `programs/prometeu_hub`, embora a direção de
|
||||||
|
arquitetura seja transformar o Hub em Shell/Home visual, não no sistema;
|
||||||
|
- `ProcessManager` e `TaskManager` já vivem em `services`, o que torna a posição
|
||||||
|
da VM e do WindowManager inconsistente com o desenho novo.
|
||||||
|
|
||||||
|
## Problema
|
||||||
|
|
||||||
|
Misturar ownership de serviços com execução da VM dificulta a evolução do OS.
|
||||||
|
Se `VirtualMachineRuntime` continuar parecendo dona de logging, filesystem,
|
||||||
|
memcard, diagnóstico e janela, o `SystemOS` vira apenas um wrapper, e não a
|
||||||
|
autoridade natural para coordenar serviços do console.
|
||||||
|
|
||||||
|
Ao mesmo tempo, nem tudo aqui é puramente mecânico:
|
||||||
|
|
||||||
|
- mover arquivos para `services` pode ser uma alteração sem mudança funcional;
|
||||||
|
- mover `LogService` para `SystemOS` altera ownership e pode exigir uma API de
|
||||||
|
serviço ou passagem explícita de dependência para a VM;
|
||||||
|
- promover `WindowManager` a serviço do OS antecipa uma fronteira arquitetural,
|
||||||
|
mesmo que a implementação continue mínima.
|
||||||
|
|
||||||
|
Por isso, este assunto não deve ser escondido dentro da agenda de lifecycle
|
||||||
|
(`AGD-0032`). Ele é uma agenda de pré-requisito ou paralela: primeiro precisamos
|
||||||
|
limpar a fronteira entre "VM como executor" e "OS como dono de serviços".
|
||||||
|
|
||||||
|
## Pontos Criticos
|
||||||
|
|
||||||
|
- `VirtualMachineRuntime` deve representar execução VM, não ser o container
|
||||||
|
geral de serviços do sistema.
|
||||||
|
- `SystemOS` deve ser o lugar natural para serviços compartilhados por jogos,
|
||||||
|
Shell/Home, firmware e processos nativos futuros.
|
||||||
|
- `LogService` provavelmente pertence ao `SystemOS`, mas a forma de acesso pela
|
||||||
|
VM precisa respeitar as restrições de borrow e o desenho de `NativeInterface`.
|
||||||
|
- `VirtualFS` e `MemcardService` também parecem serviços de OS, mas movê-los no
|
||||||
|
mesmo corte pode aumentar demais o risco.
|
||||||
|
- `WindowManager` deve sair de `PrometeuHub` porque o Hub deve virar consumidor
|
||||||
|
visual do OS, não proprietário de serviço de janela.
|
||||||
|
- A mudança de layout de módulo deve preservar comportamento, mas não precisa
|
||||||
|
preservar caminhos antigos de import; callsites devem ser atualizados
|
||||||
|
diretamente.
|
||||||
|
- A decisão precisa separar explicitamente "relocação de módulo" de "mudança de
|
||||||
|
ownership".
|
||||||
|
|
||||||
|
## Opcoes
|
||||||
|
|
||||||
|
### Opcao A - Agregar tudo na agenda de lifecycle
|
||||||
|
|
||||||
|
**Abordagem:**
|
||||||
|
Usar `AGD-0032` para decidir lifecycle, ownership de serviços, layout de módulos
|
||||||
|
e promoção do `WindowManager`.
|
||||||
|
|
||||||
|
**Vantagens:**
|
||||||
|
- uma única discussão cobre toda a direção do `SystemOS`;
|
||||||
|
- menos artefatos de workflow.
|
||||||
|
|
||||||
|
**Custos / Riscos:**
|
||||||
|
- mistura semântica de lifecycle com limpeza estrutural;
|
||||||
|
- aumenta a chance de uma decisão grande demais;
|
||||||
|
- dificulta transformar a discussão em plans pequenos e verificáveis;
|
||||||
|
- pode bloquear lifecycle por causa de detalhes de módulo e borrow.
|
||||||
|
|
||||||
|
### Opcao B - Separar uma agenda de service ownership e layout
|
||||||
|
|
||||||
|
**Abordagem:**
|
||||||
|
Manter `AGD-0032` focada em lifecycle de `Task`/`Process` e usar esta agenda
|
||||||
|
para decidir onde vivem os serviços do OS, quais módulos devem ir para
|
||||||
|
`services`, e quais extrações são mecânicas ou semânticas.
|
||||||
|
|
||||||
|
**Vantagens:**
|
||||||
|
- mantém a agenda principal limpa;
|
||||||
|
- permite um primeiro plan mecânico de relocação sem reabrir lifecycle;
|
||||||
|
- torna explícito que `LogService` sair da VM é mudança de ownership;
|
||||||
|
- cria base melhor para promover `WindowManager` sem confundir Hub e OS.
|
||||||
|
|
||||||
|
**Custos / Riscos:**
|
||||||
|
- cria uma dependência a mais antes da implementação do lifecycle;
|
||||||
|
- pode revelar que alguns serviços exigem desenho de API antes do refactor;
|
||||||
|
- exige cuidado para atualizar callsites diretamente sem deixar caminhos antigos
|
||||||
|
exportados.
|
||||||
|
|
||||||
|
### Opcao C - Fazer apenas os moves mecanicos direto no codigo
|
||||||
|
|
||||||
|
**Abordagem:**
|
||||||
|
Mover `VirtualMachineRuntime` e `WindowManager` para `services` sem decisão
|
||||||
|
formal, deixando `LogService` para depois.
|
||||||
|
|
||||||
|
**Vantagens:**
|
||||||
|
- rápido;
|
||||||
|
- baixo risco se for realmente só path/reexport;
|
||||||
|
- reduz ruído visual do layout atual.
|
||||||
|
|
||||||
|
**Custos / Riscos:**
|
||||||
|
- não resolve a dúvida central de ownership;
|
||||||
|
- pode mascarar uma decisão arquitetural como refactor mecânico;
|
||||||
|
- viola a política do repositório se a mudança alterar fronteiras sem agenda e
|
||||||
|
decisão.
|
||||||
|
|
||||||
|
## Sugestao / Recomendacao
|
||||||
|
|
||||||
|
A recomendação é seguir a **Opção B**.
|
||||||
|
|
||||||
|
Esta agenda deve ser separada da `AGD-0032`, mas tratada como pré-requisito ou
|
||||||
|
agenda paralela para ela. O primeiro contrato deve dividir o trabalho em dois
|
||||||
|
tipos:
|
||||||
|
|
||||||
|
1. **Relocação mecânica sem mudança funcional**
|
||||||
|
- mover `VirtualMachineRuntime` para `services`;
|
||||||
|
- mover `WindowManager` para `services`;
|
||||||
|
- não manter reexports ou aliases de compatibilidade para os caminhos antigos;
|
||||||
|
- atualizar callsites diretamente para os caminhos novos;
|
||||||
|
- provar que o comportamento não mudou.
|
||||||
|
|
||||||
|
2. **Correção de ownership de serviços**
|
||||||
|
- mover `LogService` para `SystemOS` se aceitarmos que logging é serviço do
|
||||||
|
OS;
|
||||||
|
- decidir se `VirtualFS`, `MemcardService`, diagnóstico, telemetria e
|
||||||
|
certificação ficam na VM, no OS, ou em serviços próprios;
|
||||||
|
- definir como a VM acessa serviços de OS sem voltar a ser dona deles.
|
||||||
|
|
||||||
|
Para o primeiro corte, a inclinação é:
|
||||||
|
|
||||||
|
- `VirtualMachineRuntime` deve ficar em `services`, como serviço de execução VM;
|
||||||
|
- `WindowManager` deve ficar em `services`, mesmo que ainda seja mínimo;
|
||||||
|
- `LogService` deve ser tratado como serviço de `SystemOS`, não detalhe interno
|
||||||
|
da VM;
|
||||||
|
- `VirtualFS` e `MemcardService` devem ser avaliados em seguida, sem mover tudo
|
||||||
|
junto automaticamente.
|
||||||
|
|
||||||
|
## Perguntas em Aberto
|
||||||
|
|
||||||
|
- [x] O path deve ser `services/vm_runtime` ou
|
||||||
|
`services/virtual_machine_runtime`? vm_runtime
|
||||||
|
- [x] `VirtualMachineRuntime` continua sendo o nome correto depois de virar
|
||||||
|
serviço, ou devemos introduzir um nome menor apenas no módulo? Podemos manter o mesmo nome
|
||||||
|
- [x] `LogService` deve sair da VM no mesmo plan do move mecânico, ou em um
|
||||||
|
plan separado de ownership? pode ser em um plan separado
|
||||||
|
- [x] A VM deve receber referência explícita ao logging do `SystemOS`, ou deve
|
||||||
|
chamar uma API de serviço mais estreita? A VM pode ter uma referência explícita ao logging do `SystemOS` para seu uso
|
||||||
|
- [x] `VirtualFS` e `MemcardService` pertencem ao `SystemOS` já, ou continuam na
|
||||||
|
VM até existir uma camada de permissões e mount por processo? podemos move-los para systemos jah
|
||||||
|
- [x] `WindowManager` deve virar campo do `SystemOS` imediatamente, ou primeiro
|
||||||
|
apenas sair de `PrometeuHub` em termos de módulo? eu diria que ele jah deve nascer em system os a partir de jah, mesmo que sem implementar nada
|
||||||
|
- [x] O `PrometeuHub` deve continuar expondo `window_manager` enquanto a Shell
|
||||||
|
visual ainda depende dele, ou isso deve ser mediado pelo `SystemOS` desde já? o prometeu hub eh um shell especial, ou seja, o window manager deve mediado pelo system os desde jah
|
||||||
|
|
||||||
|
## Criterio para Encerrar
|
||||||
|
|
||||||
|
Esta agenda pode virar decisão quando houver resposta para:
|
||||||
|
|
||||||
|
- quais serviços pertencem ao `SystemOS` no primeiro corte;
|
||||||
|
- qual layout canônico de módulo será usado em `services`;
|
||||||
|
- quais mudanças são relocação mecânica e quais são ownership semântico;
|
||||||
|
- como `VirtualMachineRuntime` acessa `LogService` depois da extração;
|
||||||
|
- se `WindowManager` será apenas movido de módulo ou também promovido a campo do
|
||||||
|
`SystemOS`;
|
||||||
|
- como os callsites serão atualizados sem reexports temporários.
|
||||||
|
|
||||||
|
## Proximo Passo
|
||||||
|
|
||||||
|
Revisar esta agenda antes de fechar `AGD-0032`. Se a direção for aceita, o
|
||||||
|
próximo estágio deve ser uma decisão curta para fronteira de serviços do
|
||||||
|
`SystemOS`, seguida de plans pequenos: primeiro relocação mecânica, depois
|
||||||
|
extração de ownership onde houver contrato suficiente.
|
||||||
@ -0,0 +1,133 @@
|
|||||||
|
---
|
||||||
|
id: DEC-0024
|
||||||
|
ticket: system-os-service-ownership-and-module-layout
|
||||||
|
title: SystemOS Service Ownership and Module Layout
|
||||||
|
status: accepted
|
||||||
|
created: 2026-05-14
|
||||||
|
ref_agenda: AGD-0033
|
||||||
|
tags: [runtime, os, services, module-layout, vm, window-manager, logging]
|
||||||
|
---
|
||||||
|
|
||||||
|
## Status
|
||||||
|
|
||||||
|
Accepted. Esta decisão foi aceita em 2026-05-14 e passa a ser referência
|
||||||
|
normativa para os plans de ownership e layout de serviços do `SystemOS`.
|
||||||
|
|
||||||
|
## Contexto
|
||||||
|
|
||||||
|
`SystemOS` está se tornando o dono dos serviços de console em
|
||||||
|
`prometeu-system`. O formato atual ainda deixa responsabilidades de OS dentro ou
|
||||||
|
perto de `VirtualMachineRuntime` e `PrometeuHub`:
|
||||||
|
|
||||||
|
- `VirtualMachineRuntime` vive no root do crate, embora agora seja um serviço de
|
||||||
|
execução VM do OS;
|
||||||
|
- `LogService` é owned por `VirtualMachineRuntime`, embora logging seja serviço
|
||||||
|
de observabilidade do OS;
|
||||||
|
- `VirtualFS` e `MemcardService` também ficam dentro de
|
||||||
|
`VirtualMachineRuntime`, embora representem serviços do sistema;
|
||||||
|
- `WindowManager` vive em `programs/prometeu_hub`, embora `PrometeuHub` deva ser
|
||||||
|
Shell/Home visual, não dono de serviços do OS.
|
||||||
|
|
||||||
|
Esta decisão é separada da `AGD-0032`, que trata lifecycle de `Task`/`Process`.
|
||||||
|
Aqui o contrato é ownership de serviços e layout de módulos.
|
||||||
|
|
||||||
|
## Decisao
|
||||||
|
|
||||||
|
`SystemOS` SHALL ser o owner dos serviços do Prometeu OS.
|
||||||
|
|
||||||
|
`VirtualMachineRuntime` SHALL representar somente execução VM. Ele MUST NOT ser
|
||||||
|
usado como container geral de serviços do sistema.
|
||||||
|
|
||||||
|
O layout canônico da VM runtime SHALL ser:
|
||||||
|
|
||||||
|
```text
|
||||||
|
crates/console/prometeu-system/src/services/vm_runtime/
|
||||||
|
```
|
||||||
|
|
||||||
|
O nome público do tipo SHALL continuar `VirtualMachineRuntime`.
|
||||||
|
|
||||||
|
`WindowManager` SHALL sair de `programs/prometeu_hub` e ir para a árvore
|
||||||
|
`services`. Ele SHALL ser owned ou mediado por `SystemOS` desde este estágio,
|
||||||
|
mesmo que a implementação continue mínima.
|
||||||
|
|
||||||
|
`PrometeuHub` SHALL ser tratado como uma Shell/Home especial. Ele MUST NOT ser
|
||||||
|
owner do window manager como serviço de OS. O acesso do Hub a window management
|
||||||
|
MUST passar por `SystemOS` ou por uma superfície mediada pelo OS.
|
||||||
|
|
||||||
|
`LogService` SHALL sair de `VirtualMachineRuntime` e virar serviço owned por
|
||||||
|
`SystemOS`. `VirtualMachineRuntime` MAY receber referência ou handle explícito
|
||||||
|
para o logging do OS quando precisar registrar eventos de execução VM, mas esse
|
||||||
|
acesso MUST NOT transformar a VM em owner de logging.
|
||||||
|
|
||||||
|
`VirtualFS` e `MemcardService` SHALL mover para `SystemOS` neste cleanup de
|
||||||
|
ownership. A VM pode continuar usando capacidades de filesystem e memcard, mas
|
||||||
|
o ownership pertence ao boundary do OS.
|
||||||
|
|
||||||
|
Esta decisão MUST NOT introduzir reexports ou aliases de compatibilidade para
|
||||||
|
os paths antigos. Os plans de implementação MUST atualizar callsites
|
||||||
|
diretamente. Novos imports MUST usar os paths canônicos de `services` e o
|
||||||
|
boundary de ownership do `SystemOS`.
|
||||||
|
|
||||||
|
## Rationale
|
||||||
|
|
||||||
|
O runtime está saindo de um runner de firmware para um console OS. Nesse modelo,
|
||||||
|
execução VM é um serviço do sistema, não o centro proprietário do sistema. Se
|
||||||
|
logging, filesystem, memcard, window management e futuramente lifecycle
|
||||||
|
continuarem dentro ou ao redor de `VirtualMachineRuntime`, a VM permanece como
|
||||||
|
owner de fato do OS.
|
||||||
|
|
||||||
|
Mover `VirtualMachineRuntime` para `services/vm_runtime` alinha layout com
|
||||||
|
arquitetura: execução VM é serviço, não root.
|
||||||
|
|
||||||
|
Mover `WindowManager` para `services` evita que `PrometeuHub` continue parecendo
|
||||||
|
o sistema inteiro. O Hub é Shell/Home e deve consumir serviços do OS.
|
||||||
|
|
||||||
|
Mover `LogService`, `VirtualFS` e `MemcardService` para `SystemOS` cria uma base
|
||||||
|
mais limpa para permissões, processo/task ownership, mount policy, diagnóstico e
|
||||||
|
mediação da Shell.
|
||||||
|
|
||||||
|
Não manter compatibilidade por reexport é aceitável agora porque esses paths são
|
||||||
|
internos e não há necessidade de janela longa de migração. Atualizar callsites
|
||||||
|
diretamente mantém o boundary claro.
|
||||||
|
|
||||||
|
## Invariantes / Contrato
|
||||||
|
|
||||||
|
- `SystemOS` MUST ser o ponto de ownership para serviços compartilhados do OS.
|
||||||
|
- `VirtualMachineRuntime` MUST NOT own `LogService`, `VirtualFS`,
|
||||||
|
`MemcardService` ou `WindowManager` depois dos plans correspondentes.
|
||||||
|
- `VirtualMachineRuntime` MAY usar serviços do OS por referência, handle ou API
|
||||||
|
estreita, mas MUST permanecer semanticamente executor VM.
|
||||||
|
- `PrometeuHub` MUST ser consumidor de OS services, não owner desses serviços.
|
||||||
|
- Paths antigos MUST NOT permanecer disponíveis por reexport ou alias.
|
||||||
|
- Mudanças de path MUST preservar comportamento observável.
|
||||||
|
- Esta decisão MUST NOT definir lifecycle de `Task`/`Process`; isso pertence à
|
||||||
|
`AGD-0032` e à decisão futura correspondente.
|
||||||
|
|
||||||
|
## Impactos
|
||||||
|
|
||||||
|
- `prometeu-system` deve ganhar layout de serviço mais coerente em `services`.
|
||||||
|
- `SystemOS` deve concentrar ownership de `LogService`, `VirtualFS`,
|
||||||
|
`MemcardService` e `WindowManager`.
|
||||||
|
- `VirtualMachineRuntime` deve reduzir escopo para execução VM.
|
||||||
|
- `PrometeuHub` deve começar a depender de mediação do OS para window
|
||||||
|
management.
|
||||||
|
- Firmware e testes podem precisar atualizar imports e construtores.
|
||||||
|
- Não há mudança exigida de ABI pública de cartucho.
|
||||||
|
|
||||||
|
## Referencias
|
||||||
|
|
||||||
|
- Agenda: `AGD-0033`
|
||||||
|
- Agenda relacionada: `AGD-0032`
|
||||||
|
|
||||||
|
## Propagacao Necessaria
|
||||||
|
|
||||||
|
- Plans: criar plans pequenos para relocação da VM runtime, promoção do
|
||||||
|
WindowManager e extração de ownership de serviços.
|
||||||
|
- Code: atualizar `prometeu-system`, `SystemOS`, `VirtualMachineRuntime`,
|
||||||
|
`PrometeuHub`, callsites de firmware e testes afetados.
|
||||||
|
- Tests: rodar a suíte Rust afetada e adicionar cobertura se ownership mudar
|
||||||
|
construtores ou caminhos de acesso.
|
||||||
|
- Specs: sem update obrigatório neste momento, salvo se plans futuros expuserem
|
||||||
|
o boundary em contrato público.
|
||||||
|
- Discussion: manter `AGD-0032` dependente desta decisão antes de planejar
|
||||||
|
lifecycle.
|
||||||
@ -0,0 +1,103 @@
|
|||||||
|
---
|
||||||
|
id: PLN-0051
|
||||||
|
ticket: system-os-service-ownership-and-module-layout
|
||||||
|
title: Move VM Runtime Into Services
|
||||||
|
status: done
|
||||||
|
created: 2026-05-14
|
||||||
|
ref_decisions: [DEC-0024]
|
||||||
|
tags: [runtime, os, services, module-layout, vm, window-manager, logging]
|
||||||
|
---
|
||||||
|
|
||||||
|
## Briefing
|
||||||
|
|
||||||
|
Implement the mechanical module relocation required by `DEC-0024`: move
|
||||||
|
`VirtualMachineRuntime` from the `prometeu-system` crate root into the services
|
||||||
|
tree at `services/vm_runtime`.
|
||||||
|
|
||||||
|
This plan is intentionally behavior-preserving. It prepares the service layout
|
||||||
|
for later ownership plans without moving logging, filesystem, or memcard state.
|
||||||
|
|
||||||
|
## Source Decisions
|
||||||
|
|
||||||
|
- `DEC-0024`: `VirtualMachineRuntime` SHALL move to
|
||||||
|
`services/vm_runtime`; old root paths MUST NOT remain available through
|
||||||
|
compatibility reexports.
|
||||||
|
|
||||||
|
## Target
|
||||||
|
|
||||||
|
Make `VirtualMachineRuntime` a service module while keeping the public type name
|
||||||
|
unchanged and preserving current VM behavior.
|
||||||
|
|
||||||
|
The canonical implementation path after this plan is:
|
||||||
|
|
||||||
|
```text
|
||||||
|
crates/console/prometeu-system/src/services/vm_runtime/
|
||||||
|
```
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
|
||||||
|
- Source decision: `DEC-0024`.
|
||||||
|
- No prior plan is required.
|
||||||
|
- This plan should run before `PLN-0052`, `PLN-0053`, and `PLN-0054`.
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
- Move `crates/console/prometeu-system/src/virtual_machine_runtime.rs` to
|
||||||
|
`crates/console/prometeu-system/src/services/vm_runtime.rs` or equivalent
|
||||||
|
module root under `services/vm_runtime`.
|
||||||
|
- Move the existing `virtual_machine_runtime/` child modules under
|
||||||
|
`services/vm_runtime/`.
|
||||||
|
- Update `crates/console/prometeu-system/src/services/mod.rs` to expose
|
||||||
|
`vm_runtime`.
|
||||||
|
- Update `crates/console/prometeu-system/src/lib.rs` to export
|
||||||
|
`VirtualMachineRuntime` from `services::vm_runtime`.
|
||||||
|
- Update internal module paths and tests directly.
|
||||||
|
|
||||||
|
## Out of Scope
|
||||||
|
|
||||||
|
- Do not move `LogService` ownership.
|
||||||
|
- Do not move `VirtualFS` or `MemcardService` ownership.
|
||||||
|
- Do not move `WindowManager`.
|
||||||
|
- Do not introduce reexports or aliases for the old root module path.
|
||||||
|
- Do not change VM runtime behavior, syscalls, telemetry, logging semantics, or
|
||||||
|
cartridge execution behavior.
|
||||||
|
|
||||||
|
## Execution Plan
|
||||||
|
|
||||||
|
1. Move the VM runtime module files into `src/services/vm_runtime/`.
|
||||||
|
2. Register `pub mod vm_runtime;` from `src/services/mod.rs`.
|
||||||
|
3. Remove the root `mod virtual_machine_runtime;` declaration from `src/lib.rs`.
|
||||||
|
4. Export `VirtualMachineRuntime` through the services path.
|
||||||
|
5. Update `SystemOS`, tests, and any internal imports to use the new module
|
||||||
|
location.
|
||||||
|
6. Run formatting and the affected Rust tests.
|
||||||
|
|
||||||
|
## Acceptance Criteria
|
||||||
|
|
||||||
|
- `VirtualMachineRuntime` implementation lives under `services/vm_runtime`.
|
||||||
|
- No root `virtual_machine_runtime` module remains in `prometeu-system`.
|
||||||
|
- No compatibility reexport preserves the old root module path.
|
||||||
|
- Public type name `VirtualMachineRuntime` remains available where the crate API
|
||||||
|
intentionally exposes it.
|
||||||
|
- Runtime behavior remains unchanged.
|
||||||
|
|
||||||
|
## Tests / Validation
|
||||||
|
|
||||||
|
- Run `cargo test -p prometeu-system`.
|
||||||
|
- If workspace constraints make that too broad, run the narrowest available
|
||||||
|
`prometeu-system` test target and record the exact command in implementation
|
||||||
|
evidence.
|
||||||
|
|
||||||
|
## Risks
|
||||||
|
|
||||||
|
- Module move churn can hide accidental import compatibility. Review must check
|
||||||
|
that no old root module remains.
|
||||||
|
- Test modules may need path updates because they currently live under the VM
|
||||||
|
runtime module tree.
|
||||||
|
|
||||||
|
## Affected Artifacts
|
||||||
|
|
||||||
|
- `crates/console/prometeu-system/src/lib.rs`
|
||||||
|
- `crates/console/prometeu-system/src/services/mod.rs`
|
||||||
|
- `crates/console/prometeu-system/src/services/vm_runtime/**`
|
||||||
|
- Existing VM runtime tests moved with the module
|
||||||
@ -0,0 +1,85 @@
|
|||||||
|
---
|
||||||
|
id: PLN-0052
|
||||||
|
ticket: system-os-service-ownership-and-module-layout
|
||||||
|
title: Promote Window Manager To SystemOS Service
|
||||||
|
status: done
|
||||||
|
created: 2026-05-14
|
||||||
|
ref_decisions: [DEC-0024]
|
||||||
|
tags: [runtime, os, services, module-layout, vm, window-manager, logging]
|
||||||
|
---
|
||||||
|
|
||||||
|
## Briefing
|
||||||
|
|
||||||
|
Promote `WindowManager` from a `PrometeuHub` private module into an OS service
|
||||||
|
owned or mediated by `SystemOS`, as required by `DEC-0024`.
|
||||||
|
|
||||||
|
## Source Decisions
|
||||||
|
|
||||||
|
- `DEC-0024`: `WindowManager` SHALL move to the services tree and SHALL be
|
||||||
|
owned or mediated by `SystemOS`; `PrometeuHub` MUST NOT own it as an OS
|
||||||
|
service.
|
||||||
|
|
||||||
|
## Target
|
||||||
|
|
||||||
|
Make `WindowManager` part of the `services` tree and stop treating
|
||||||
|
`PrometeuHub` as the owner of window management.
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
|
||||||
|
- Source decision: `DEC-0024`.
|
||||||
|
- Depends on `PLN-0051` only for the general service layout convention.
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
- Move `crates/console/prometeu-system/src/programs/prometeu_hub/window_manager.rs`
|
||||||
|
into the `services` tree.
|
||||||
|
- Expose the service from `crates/console/prometeu-system/src/services/mod.rs`.
|
||||||
|
- Add a `WindowManager` field or OS-mediated access path on `SystemOS`.
|
||||||
|
- Remove `window_manager` ownership from `PrometeuHub`.
|
||||||
|
- Update `PrometeuHub` to access window management through `SystemOS`.
|
||||||
|
- Update tests and imports directly.
|
||||||
|
|
||||||
|
## Out of Scope
|
||||||
|
|
||||||
|
- Do not implement new windowing behavior.
|
||||||
|
- Do not define surface/window lifecycle beyond the existing minimal behavior.
|
||||||
|
- Do not introduce old-path compatibility reexports.
|
||||||
|
- Do not change task/process lifecycle semantics.
|
||||||
|
|
||||||
|
## Execution Plan
|
||||||
|
|
||||||
|
1. Move the `WindowManager` module into `src/services/`.
|
||||||
|
2. Register the new service module in `services/mod.rs`.
|
||||||
|
3. Add `WindowManager` initialization to `SystemOS::new`.
|
||||||
|
4. Remove `WindowManager` storage from `PrometeuHub`.
|
||||||
|
5. Update Hub methods so their window manager access is mediated by `SystemOS`.
|
||||||
|
6. Update tests for the new module path and ownership location.
|
||||||
|
7. Run formatting and the affected tests.
|
||||||
|
|
||||||
|
## Acceptance Criteria
|
||||||
|
|
||||||
|
- `WindowManager` no longer lives under `programs/prometeu_hub`.
|
||||||
|
- `PrometeuHub` does not own a `WindowManager` field.
|
||||||
|
- `SystemOS` owns or mediates access to `WindowManager`.
|
||||||
|
- No old `programs::prometeu_hub::window_manager` path remains available.
|
||||||
|
- Existing Hub behavior remains unchanged.
|
||||||
|
|
||||||
|
## Tests / Validation
|
||||||
|
|
||||||
|
- Run `cargo test -p prometeu-system`.
|
||||||
|
- Include any focused tests for `WindowManager` after the move.
|
||||||
|
|
||||||
|
## Risks
|
||||||
|
|
||||||
|
- `PrometeuHub` currently owns its `WindowManager`; moving ownership may require
|
||||||
|
method signatures to pass through `SystemOS` cleanly.
|
||||||
|
- The implementation must not accidentally introduce new window/surface
|
||||||
|
semantics while moving ownership.
|
||||||
|
|
||||||
|
## Affected Artifacts
|
||||||
|
|
||||||
|
- `crates/console/prometeu-system/src/programs/prometeu_hub/mod.rs`
|
||||||
|
- `crates/console/prometeu-system/src/programs/prometeu_hub/prometeu_hub.rs`
|
||||||
|
- `crates/console/prometeu-system/src/services/mod.rs`
|
||||||
|
- `crates/console/prometeu-system/src/services/window_manager.rs` or equivalent
|
||||||
|
- `crates/console/prometeu-system/src/os/system_os.rs`
|
||||||
@ -0,0 +1,88 @@
|
|||||||
|
---
|
||||||
|
id: PLN-0053
|
||||||
|
ticket: system-os-service-ownership-and-module-layout
|
||||||
|
title: Move Logging Ownership To SystemOS
|
||||||
|
status: done
|
||||||
|
created: 2026-05-14
|
||||||
|
ref_decisions: [DEC-0024]
|
||||||
|
tags: [runtime, os, services, module-layout, vm, window-manager, logging]
|
||||||
|
---
|
||||||
|
|
||||||
|
## Briefing
|
||||||
|
|
||||||
|
Move `LogService` ownership from `VirtualMachineRuntime` to `SystemOS`, while
|
||||||
|
allowing VM execution code to use OS logging through an explicit reference or
|
||||||
|
handle.
|
||||||
|
|
||||||
|
## Source Decisions
|
||||||
|
|
||||||
|
- `DEC-0024`: `LogService` SHALL move from `VirtualMachineRuntime` to
|
||||||
|
`SystemOS`; VM execution MAY use an explicit OS logging reference or handle
|
||||||
|
but MUST NOT own logging.
|
||||||
|
|
||||||
|
## Target
|
||||||
|
|
||||||
|
Make logging an OS-owned service and reduce `VirtualMachineRuntime` to a user of
|
||||||
|
logging rather than its owner.
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
|
||||||
|
- Source decision: `DEC-0024`.
|
||||||
|
- Depends on `PLN-0051`.
|
||||||
|
- May run after or alongside `PLN-0052`, but code integration is simpler after
|
||||||
|
`SystemOS` service ownership is established.
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
- Add `LogService` ownership to `SystemOS`.
|
||||||
|
- Remove `log_service: LogService` from `VirtualMachineRuntime`.
|
||||||
|
- Update VM runtime methods that read/write logs to receive logging access from
|
||||||
|
`SystemOS` or a narrow explicit handle.
|
||||||
|
- Update construction paths so `LogService::new(4096)` is owned by `SystemOS`.
|
||||||
|
- Update tests that currently inspect runtime-owned logs.
|
||||||
|
|
||||||
|
## Out of Scope
|
||||||
|
|
||||||
|
- Do not change log capacity or log record semantics.
|
||||||
|
- Do not change VM syscall behavior except for the ownership path.
|
||||||
|
- Do not introduce compatibility accessors that imply VM ownership.
|
||||||
|
- Do not move filesystem or memcard ownership in this plan.
|
||||||
|
|
||||||
|
## Execution Plan
|
||||||
|
|
||||||
|
1. Add a `LogService` field to `SystemOS` and initialize it in `SystemOS::new`.
|
||||||
|
2. Remove `LogService` initialization and storage from
|
||||||
|
`VirtualMachineRuntime::new`.
|
||||||
|
3. Update VM logging call paths in `dispatch`, `tick`, lifecycle, and tests to
|
||||||
|
use OS-owned logging.
|
||||||
|
4. If borrow constraints require it, introduce a narrow logging handle/reference
|
||||||
|
parameter at the call boundary instead of giving the VM ownership.
|
||||||
|
5. Update all tests that assert logging behavior.
|
||||||
|
6. Run formatting and tests.
|
||||||
|
|
||||||
|
## Acceptance Criteria
|
||||||
|
|
||||||
|
- `VirtualMachineRuntime` no longer stores `LogService`.
|
||||||
|
- `SystemOS` owns `LogService`.
|
||||||
|
- VM execution can still emit and expose logs through the existing behavior.
|
||||||
|
- No compatibility API suggests that logging remains VM-owned.
|
||||||
|
- Existing logging tests pass or are updated to assert OS ownership.
|
||||||
|
|
||||||
|
## Tests / Validation
|
||||||
|
|
||||||
|
- Run `cargo test -p prometeu-system`.
|
||||||
|
- Include focused tests covering VM log writes and reads after ownership moves
|
||||||
|
to `SystemOS`.
|
||||||
|
|
||||||
|
## Risks
|
||||||
|
|
||||||
|
- Borrowing constraints around `NativeInterface` may require a narrow access
|
||||||
|
pattern rather than simply borrowing all of `SystemOS`.
|
||||||
|
- Tests that currently construct only `VirtualMachineRuntime` may need to move
|
||||||
|
to `SystemOS` construction to reflect the new owner.
|
||||||
|
|
||||||
|
## Affected Artifacts
|
||||||
|
|
||||||
|
- `crates/console/prometeu-system/src/os/system_os.rs`
|
||||||
|
- `crates/console/prometeu-system/src/services/vm_runtime/**`
|
||||||
|
- Tests under the VM runtime module
|
||||||
@ -0,0 +1,95 @@
|
|||||||
|
---
|
||||||
|
id: PLN-0054
|
||||||
|
ticket: system-os-service-ownership-and-module-layout
|
||||||
|
title: Move FS And Memcard Ownership To SystemOS
|
||||||
|
status: done
|
||||||
|
created: 2026-05-14
|
||||||
|
ref_decisions: [DEC-0024]
|
||||||
|
tags: [runtime, os, services, module-layout, vm, window-manager, logging]
|
||||||
|
---
|
||||||
|
|
||||||
|
## Briefing
|
||||||
|
|
||||||
|
Move `VirtualFS` and `MemcardService` ownership from `VirtualMachineRuntime` to
|
||||||
|
`SystemOS`, preserving current VM filesystem and memcard behavior.
|
||||||
|
|
||||||
|
## Source Decisions
|
||||||
|
|
||||||
|
- `DEC-0024`: `VirtualFS` and `MemcardService` SHALL move from
|
||||||
|
`VirtualMachineRuntime` to `SystemOS`; VM execution may use them, but OS owns
|
||||||
|
the service state.
|
||||||
|
|
||||||
|
## Target
|
||||||
|
|
||||||
|
Make filesystem and memcard OS-owned services while keeping VM syscalls and
|
||||||
|
tests behaviorally equivalent.
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
|
||||||
|
- Source decision: `DEC-0024`.
|
||||||
|
- Depends on `PLN-0051`.
|
||||||
|
- Should run after `PLN-0053` so the pattern for VM access to OS-owned services
|
||||||
|
is already established.
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
- Add `VirtualFS`, `FsState`, `MemcardService`, open file handle state, and
|
||||||
|
related handle allocation state to `SystemOS` if they are currently VM-owned
|
||||||
|
service state.
|
||||||
|
- Remove direct ownership of `VirtualFS` and `MemcardService` from
|
||||||
|
`VirtualMachineRuntime`.
|
||||||
|
- Update FS and memcard syscall paths so VM execution uses OS-owned services.
|
||||||
|
- Update tests that currently create a bare `VirtualMachineRuntime` when they
|
||||||
|
need filesystem or memcard services.
|
||||||
|
- Keep service modules under `services/fs` and `services/memcard`.
|
||||||
|
|
||||||
|
## Out of Scope
|
||||||
|
|
||||||
|
- Do not redesign filesystem semantics, path normalization, memcard slot
|
||||||
|
semantics, or persistence policy.
|
||||||
|
- Do not introduce process permissions or mount namespaces.
|
||||||
|
- Do not change cartridge ABI.
|
||||||
|
- Do not move unrelated telemetry, certification, asset, or debug state unless
|
||||||
|
required by compilation and explicitly documented in implementation evidence.
|
||||||
|
|
||||||
|
## Execution Plan
|
||||||
|
|
||||||
|
1. Identify the complete set of FS/memcard-owned fields currently stored in
|
||||||
|
`VirtualMachineRuntime`.
|
||||||
|
2. Add the OS-owned fields to `SystemOS` and initialize them in `SystemOS::new`.
|
||||||
|
3. Remove those service fields from `VirtualMachineRuntime`.
|
||||||
|
4. Update VM dispatch/syscall code to operate on OS-owned FS and memcard state
|
||||||
|
through explicit references or narrow access methods.
|
||||||
|
5. Update tests to construct `SystemOS` when testing FS/memcard behavior through
|
||||||
|
VM execution.
|
||||||
|
6. Run formatting and the affected Rust tests.
|
||||||
|
|
||||||
|
## Acceptance Criteria
|
||||||
|
|
||||||
|
- `VirtualMachineRuntime` no longer owns `VirtualFS` or `MemcardService`.
|
||||||
|
- `SystemOS` owns filesystem and memcard service state needed by VM execution.
|
||||||
|
- Existing FS and memcard behavior is preserved.
|
||||||
|
- No compatibility path implies VM ownership of FS or memcard services.
|
||||||
|
- Any remaining VM-owned fields are execution-specific and justified by the
|
||||||
|
implementation evidence.
|
||||||
|
|
||||||
|
## Tests / Validation
|
||||||
|
|
||||||
|
- Run `cargo test -p prometeu-system`.
|
||||||
|
- Include focused FS and memcard tests after updating constructors/access paths.
|
||||||
|
|
||||||
|
## Risks
|
||||||
|
|
||||||
|
- FS and memcard syscalls are stateful and may be coupled to open handle fields
|
||||||
|
in `VirtualMachineRuntime`; the implementation must move the whole service
|
||||||
|
state coherently.
|
||||||
|
- Bare VM tests may need broader OS fixtures, which can reveal unrelated setup
|
||||||
|
assumptions.
|
||||||
|
|
||||||
|
## Affected Artifacts
|
||||||
|
|
||||||
|
- `crates/console/prometeu-system/src/os/system_os.rs`
|
||||||
|
- `crates/console/prometeu-system/src/services/fs/**`
|
||||||
|
- `crates/console/prometeu-system/src/services/memcard.rs`
|
||||||
|
- `crates/console/prometeu-system/src/services/vm_runtime/**`
|
||||||
|
- FS/memcard runtime tests
|
||||||
@ -1,20 +1,33 @@
|
|||||||
{
|
{
|
||||||
"version": 1,
|
"version": 1,
|
||||||
"domains": {
|
"domains": {
|
||||||
"system/runtime": {
|
"system/os": {
|
||||||
"baseline_percent": 68,
|
"baseline_percent": 78,
|
||||||
"paths": [
|
"paths": [
|
||||||
"crates/console/prometeu-system/src/virtual_machine_runtime.rs",
|
"crates/console/prometeu-system/src/os/",
|
||||||
"crates/console/prometeu-system/src/virtual_machine_runtime/"
|
"crates/console/prometeu-system/src/services/process",
|
||||||
|
"crates/console/prometeu-system/src/services/task"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"fs": {
|
"system/runtime": {
|
||||||
|
"baseline_percent": 70,
|
||||||
|
"paths": [
|
||||||
|
"crates/console/prometeu-system/src/services/vm_runtime"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"system/fs": {
|
||||||
"baseline_percent": 77,
|
"baseline_percent": 77,
|
||||||
"paths": [
|
"paths": [
|
||||||
"crates/console/prometeu-system/src/services/fs/",
|
"crates/console/prometeu-system/src/services/fs/",
|
||||||
"crates/console/prometeu-system/src/services/memcard.rs"
|
"crates/console/prometeu-system/src/services/memcard.rs"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"system/programs": {
|
||||||
|
"baseline_percent": 55,
|
||||||
|
"paths": [
|
||||||
|
"crates/console/prometeu-system/src/programs/"
|
||||||
|
]
|
||||||
|
},
|
||||||
"asset/bank": {
|
"asset/bank": {
|
||||||
"baseline_percent": 86,
|
"baseline_percent": 86,
|
||||||
"paths": [
|
"paths": [
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user