bQUARKz a0601fe816
All checks were successful
Intrepid/Prometeu/Runtime/pipeline/head This commit looks good
shell running step should run only when task in foreground
2026-05-15 09:28:13 +01:00

257 lines
8.4 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::memcard::MemcardService;
use crate::services::window_manager::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) 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(),
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 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 window(&mut self) -> WindowFacade<'_> {
WindowFacade { os: self }
}
pub(super) fn clear_cartridge_service_state(&mut self) {
self.open_files.clear();
self.next_handle = 1;
self.memcard.clear_all_staging();
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::os::{LifecycleError, LifecycleOperation};
use crate::process::ProcessState;
use crate::task::{TaskId, TaskState};
fn process_state_for_task(os: &SystemOS, task_id: TaskId) -> ProcessState {
let task = os.task_manager.get(task_id).expect("task should exist");
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 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,
})
);
}
}