2026-07-03 23:04:23 +01:00

361 lines
12 KiB
Rust

use crate::VirtualMachineRuntime;
use crate::fs::{FsState, VirtualFS};
use crate::os::{FsFacade, LifecycleFacade, SessionsFacade, VmFacade, WindowFacade};
use crate::process::ProcessManager;
use crate::services::foreground::ForegroundStack;
use crate::services::memcard::MemcardService;
use crate::services::windows::WindowManager;
use crate::task::TaskManager;
use prometeu_hal::log::{LogEvent, LogLevel, LogService, LogSource};
use prometeu_hal::telemetry::CertificationConfig;
use std::collections::HashMap;
use std::sync::Arc;
pub struct SystemOS {
pub(super) vm_runtime: VirtualMachineRuntime,
pub(super) process_manager: ProcessManager,
pub(super) task_manager: TaskManager,
pub(super) foreground_stack: ForegroundStack,
pub(super) window_manager: WindowManager,
pub(super) log_service: LogService,
pub(super) fs: VirtualFS,
pub(super) fs_state: FsState,
pub(super) memcard: MemcardService,
pub(super) open_files: HashMap<u32, String>,
pub(super) next_handle: u32,
}
impl SystemOS {
pub fn new(cap_config: Option<CertificationConfig>) -> Self {
let log_service = LogService::new(4096);
let mut os = Self {
vm_runtime: VirtualMachineRuntime::new_with_log_counter(
cap_config,
Arc::clone(&log_service.logs_count),
),
process_manager: ProcessManager::new(),
task_manager: TaskManager::new(),
foreground_stack: ForegroundStack::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 trace(&mut self, source: LogSource, msg: String) {
self.log(LogLevel::Trace, source, 0, msg);
}
pub fn trace_tag(&mut self, source: LogSource, tag: u16, msg: String) {
self.log(LogLevel::Trace, source, tag, msg);
}
pub fn debug(&mut self, source: LogSource, msg: String) {
self.log(LogLevel::Debug, source, 0, msg);
}
pub fn debug_tag(&mut self, source: LogSource, tag: u16, msg: String) {
self.log(LogLevel::Debug, source, tag, msg);
}
pub fn info(&mut self, source: LogSource, msg: String) {
self.log(LogLevel::Info, source, 0, msg);
}
pub fn info_tag(&mut self, source: LogSource, tag: u16, msg: String) {
self.log(LogLevel::Info, source, tag, msg);
}
pub fn warn(&mut self, source: LogSource, msg: String) {
self.log(LogLevel::Warn, source, 0, msg);
}
pub fn warn_tag(&mut self, source: LogSource, tag: u16, msg: String) {
self.log(LogLevel::Warn, source, tag, msg);
}
pub fn error(&mut self, source: LogSource, msg: String) {
self.log(LogLevel::Error, source, 0, msg);
}
pub fn error_tag(&mut self, source: LogSource, tag: u16, msg: String) {
self.log(LogLevel::Error, source, tag, msg);
}
pub fn recent_logs(&self, n: usize) -> Vec<LogEvent> {
self.log_service.get_recent(n)
}
pub fn logs_after(&self, seq: u64) -> Vec<LogEvent> {
self.log_service.get_after(seq)
}
pub fn frame_index(&mut self) -> u64 {
self.vm().logical_frame_index()
}
pub fn lifecycle(&mut self) -> LifecycleFacade<'_> {
LifecycleFacade { os: self }
}
pub fn sessions(&mut self) -> SessionsFacade<'_> {
SessionsFacade { os: self }
}
pub fn vm(&mut self) -> VmFacade<'_> {
VmFacade { os: self }
}
pub fn fs(&mut self) -> FsFacade<'_> {
FsFacade { os: self }
}
pub fn windows(&mut self) -> WindowFacade<'_> {
WindowFacade { os: self }
}
pub(super) fn clear_cartridge_service_state(&mut self) {
self.open_files.clear();
self.next_handle = 1;
self.memcard.clear_all_staging();
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::os::{LifecycleError, LifecycleOperation};
use crate::process::ProcessState;
use crate::services::foreground::{ForegroundOwner, ResidentGame, ResidentGameState};
use crate::task::{TaskId, TaskState};
fn process_state_for_task(os: &SystemOS, task_id: TaskId) -> ProcessState {
let task = os.task_manager.get(task_id).expect("task should exist");
os.process_manager.get(task.process_id).expect("process should exist").state
}
#[test]
fn set_foreground_task_marks_task_and_process_running() {
let mut os = SystemOS::new(None);
let task_id = os.sessions().create_vm_game_task(42, "Sector Crawl");
os.lifecycle().suspend_task(task_id).expect("suspend should succeed");
os.lifecycle().set_foreground_task(task_id).expect("foreground should succeed");
assert_eq!(
os.task_manager.get(task_id).expect("task should exist").state,
TaskState::Foreground
);
assert_eq!(process_state_for_task(&os, task_id), ProcessState::Running);
}
#[test]
fn foreground_stack_starts_at_hub() {
let os = SystemOS::new(None);
assert_eq!(os.foreground_stack.owner(), ForegroundOwner::Hub);
assert_eq!(os.foreground_stack.resident_game(), None);
}
#[test]
fn game_session_registers_single_resident_game() {
let mut os = SystemOS::new(None);
let task_id = os.sessions().create_vm_game_task(42, "Sector Crawl");
assert_eq!(os.lifecycle().foreground_owner(), ForegroundOwner::Game(task_id));
assert_eq!(
os.lifecycle().resident_game(),
Some(ResidentGame { task_id, state: ResidentGameState::Foreground })
);
}
#[test]
fn second_resident_game_is_rejected() {
let mut os = SystemOS::new(None);
let first = os.sessions().create_vm_game_task(42, "Sector Crawl");
let process_id = os.process_manager.spawn_vm_game(43, "Second");
let second = os.task_manager.create_game_task(process_id, 43, "Second");
assert_eq!(
os.lifecycle().set_game_foreground_task(second),
Err(LifecycleError::ResidentGameAlreadyExists { existing: first, requested: second })
);
}
#[test]
fn home_request_moves_game_to_resident_suspended_and_hub_foreground() {
let mut os = SystemOS::new(None);
let task_id = os.sessions().create_vm_game_task(42, "Sector Crawl");
os.lifecycle().request_home_from_game(task_id).expect("home request should succeed");
assert_eq!(os.lifecycle().foreground_owner(), ForegroundOwner::Hub);
assert_eq!(
os.lifecycle().resident_game(),
Some(ResidentGame { task_id, state: ResidentGameState::PausedSuspended })
);
assert_eq!(
os.task_manager.get(task_id).expect("task should exist").state,
TaskState::Suspended
);
assert_eq!(process_state_for_task(&os, task_id), ProcessState::Suspended);
}
#[test]
fn shell_foreground_keeps_game_resident_and_marks_it_suspended() {
let mut os = SystemOS::new(None);
let game = os.sessions().create_vm_game_task(42, "Sector Crawl");
os.lifecycle().request_home_from_game(game).expect("home request should succeed");
let shell = os.sessions().create_vm_shell_task(7, "Settings");
assert_eq!(os.lifecycle().foreground_owner(), ForegroundOwner::Shell(shell));
assert_eq!(
os.lifecycle().resident_game(),
Some(ResidentGame { task_id: game, state: ResidentGameState::PausedSuspended })
);
}
#[test]
fn second_foreground_shell_is_rejected() {
let mut os = SystemOS::new(None);
let first = os.sessions().create_vm_shell_task(7, "Settings");
let process_id = os.process_manager.spawn_vm_shell(8, "Files");
let second = os.task_manager.create_shell_task(process_id, 8, "Files");
assert_eq!(
os.lifecycle().set_shell_foreground_task(second),
Err(LifecycleError::ForegroundShellAlreadyExists {
existing: first,
requested: second,
})
);
}
#[test]
fn closing_foreground_shell_returns_to_hub() {
let mut os = SystemOS::new(None);
let shell = os.sessions().create_vm_shell_task(7, "Settings");
os.lifecycle().close_task(shell).expect("close should succeed");
assert_eq!(os.lifecycle().foreground_owner(), ForegroundOwner::Hub);
assert_eq!(
os.task_manager.get(shell).expect("shell should remain").state,
TaskState::Closed
);
}
#[test]
fn suspend_task_marks_task_and_process_suspended() {
let mut os = SystemOS::new(None);
let task_id = os.sessions().create_vm_game_task(42, "Sector Crawl");
os.lifecycle().suspend_task(task_id).expect("suspend should succeed");
assert_eq!(
os.task_manager.get(task_id).expect("task should exist").state,
TaskState::Suspended
);
assert_eq!(process_state_for_task(&os, task_id), ProcessState::Suspended);
}
#[test]
fn resume_task_marks_suspended_task_foreground_and_process_running() {
let mut os = SystemOS::new(None);
let task_id = os.sessions().create_vm_game_task(42, "Sector Crawl");
os.lifecycle().suspend_task(task_id).expect("suspend should succeed");
os.lifecycle().resume_task(task_id).expect("resume should succeed");
assert_eq!(
os.task_manager.get(task_id).expect("task should exist").state,
TaskState::Foreground
);
assert_eq!(process_state_for_task(&os, task_id), ProcessState::Running);
}
#[test]
fn close_task_marks_task_closed_and_process_stopped_without_removing_entities() {
let mut os = SystemOS::new(None);
let task_id = os.sessions().create_vm_game_task(42, "Sector Crawl");
let process_id = os.task_manager.get(task_id).expect("task should exist").process_id;
os.lifecycle().close_task(task_id).expect("close should succeed");
assert_eq!(
os.task_manager.get(task_id).expect("task should remain").state,
TaskState::Closed
);
assert_eq!(
os.process_manager.get(process_id).expect("process should remain").state,
ProcessState::Stopped
);
}
#[test]
fn crash_task_marks_task_and_process_crashed() {
let mut os = SystemOS::new(None);
let task_id = os.sessions().create_vm_game_task(42, "Sector Crawl");
os.lifecycle().crash_task(task_id, None).expect("crash should succeed");
assert_eq!(
os.task_manager.get(task_id).expect("task should exist").state,
TaskState::Crashed
);
assert_eq!(process_state_for_task(&os, task_id), ProcessState::Crashed);
}
#[test]
fn lifecycle_operation_missing_task_returns_typed_error() {
let mut os = SystemOS::new(None);
assert_eq!(
os.lifecycle().suspend_task(TaskId(999)),
Err(LifecycleError::TaskNotFound(TaskId(999)))
);
}
#[test]
fn lifecycle_operation_missing_process_returns_typed_error() {
let mut os = SystemOS::new(None);
let task_id = os.sessions().create_vm_game_task(42, "Sector Crawl");
let process_id = os.task_manager.get(task_id).expect("task should exist").process_id;
os.process_manager.mark_stopped(process_id);
os.process_manager.remove_stopped();
assert_eq!(
os.lifecycle().suspend_task(task_id),
Err(LifecycleError::ProcessNotFound(process_id))
);
}
#[test]
fn resume_task_from_non_suspended_state_returns_invalid_transition() {
let mut os = SystemOS::new(None);
let task_id = os.sessions().create_vm_game_task(42, "Sector Crawl");
assert_eq!(
os.lifecycle().resume_task(task_id),
Err(LifecycleError::InvalidTransition {
task_id,
from: TaskState::Foreground,
operation: LifecycleOperation::Resume,
})
);
}
}