use crate::CrashReport; use crate::os::SystemOS; use crate::os::{ DEFAULT_GAME_PAUSE_BUDGET_TICKS, GameLifecycleEvent, GameLifecycleEventKind, LifecycleError, LifecycleOperation, }; use crate::process::{Process, ProcessId, ProcessKind, ProcessState}; use crate::services::foreground::{ ForegroundOwner, ForegroundStackError, ResidentGame, ResidentGameState, }; use crate::task::{TaskId, TaskKind, TaskState}; pub struct LifecycleFacade<'a> { pub(in crate::os) os: &'a mut SystemOS, } impl<'a> LifecycleFacade<'a> { pub fn task_state(&self, task_id: TaskId) -> Option { self.os.task_manager.get(task_id).map(|task| task.state) } pub fn process_state_for_task(&self, task_id: TaskId) -> Result { process_for_task(self.os, task_id).map(|process| process.state) } pub fn process_kind_for_task(&self, task_id: TaskId) -> Result { process_for_task(self.os, task_id).map(|process| process.kind) } pub fn set_foreground_task(&mut self, task_id: TaskId) -> Result<(), LifecycleError> { match task_kind_for_task(self.os, task_id)? { TaskKind::Game => self.set_game_foreground_task(task_id), TaskKind::Shell => self.set_shell_foreground_task(task_id), } } pub fn set_game_foreground_task(&mut self, task_id: TaskId) -> Result<(), LifecycleError> { let process_id = process_id_for_task(self.os, task_id)?; if task_kind_for_task(self.os, task_id)? != TaskKind::Game { return Err(LifecycleError::InvalidTransition { task_id, from: self.task_state(task_id).unwrap_or(TaskState::Closed), operation: LifecycleOperation::SetForeground, }); } self.os.foreground_stack.set_game_foreground(task_id).map_err(map_foreground_error)?; self.os.task_manager.set_foreground(task_id); self.os.process_manager.mark_running(process_id); Ok(()) } pub fn set_shell_foreground_task(&mut self, task_id: TaskId) -> Result<(), LifecycleError> { let process_id = process_id_for_task(self.os, task_id)?; if task_kind_for_task(self.os, task_id)? != TaskKind::Shell { return Err(LifecycleError::InvalidTransition { task_id, from: self.task_state(task_id).unwrap_or(TaskState::Closed), operation: LifecycleOperation::SetForeground, }); } if let Some(game_task_id) = self.os.foreground_stack.resident_game_task() && game_task_id != task_id && self.task_state(game_task_id) != Some(TaskState::Suspended) { let game_process_id = process_id_for_task(self.os, game_task_id)?; self.os.task_manager.mark_suspended(game_task_id); self.os.process_manager.mark_suspended(game_process_id); } self.os.foreground_stack.set_shell_foreground(task_id).map_err(map_foreground_error)?; self.os.task_manager.set_foreground(task_id); self.os.process_manager.mark_running(process_id); Ok(()) } pub fn foreground_owner(&self) -> ForegroundOwner { self.os.foreground_stack.owner() } pub fn resident_game(&self) -> Option { self.os.foreground_stack.resident_game() } pub fn resident_game_task(&self) -> Option { self.os.foreground_stack.resident_game_task() } pub fn request_home_from_game(&mut self, task_id: TaskId) -> Result<(), LifecycleError> { let _process_id = process_id_for_task(self.os, task_id)?; let previous = self.os.foreground_stack.resident_game(); self.os .foreground_stack .request_home_from_game(task_id, DEFAULT_GAME_PAUSE_BUDGET_TICKS) .map_err(map_foreground_error)?; if !matches!( previous.map(|game| game.state), Some(ResidentGameState::PauseRequested { .. } | ResidentGameState::PausedSuspended) ) { self.os .game_lifecycle_events .push(GameLifecycleEvent { task_id, kind: GameLifecycleEventKind::Pause }); } Ok(()) } pub fn advance_game_pause_budget(&mut self, task_id: TaskId) -> Result { let expired = self.os.foreground_stack.advance_pause_budget(task_id).map_err(map_foreground_error)?; if expired { let process_id = process_id_for_task(self.os, task_id)?; self.os.task_manager.mark_suspended(task_id); self.os.process_manager.mark_suspended(process_id); } Ok(expired) } pub fn pending_game_lifecycle_events(&self) -> &[GameLifecycleEvent] { &self.os.game_lifecycle_events } pub fn take_game_lifecycle_events(&mut self) -> Vec { std::mem::take(&mut self.os.game_lifecycle_events) } pub fn return_to_hub(&mut self) { self.os.foreground_stack.return_to_hub(); self.os.task_manager.clear_foreground(); } pub fn suspend_task(&mut self, task_id: TaskId) -> Result<(), LifecycleError> { let process_id = process_id_for_task(self.os, task_id)?; self.os.task_manager.mark_suspended(task_id); self.os.process_manager.mark_suspended(process_id); match self.os.foreground_stack.owner() { ForegroundOwner::Game(owner) if owner == task_id => { self.os .foreground_stack .request_home_from_game(task_id, DEFAULT_GAME_PAUSE_BUDGET_TICKS) .map_err(map_foreground_error)?; self.os .foreground_stack .advance_pause_budget(task_id) .map_err(map_foreground_error)?; } ForegroundOwner::Shell(owner) if owner == task_id => { self.os.foreground_stack.return_to_hub(); } _ => {} } Ok(()) } pub fn resume_task(&mut self, task_id: TaskId) -> Result<(), LifecycleError> { let task = self.os.task_manager.get(task_id).ok_or(LifecycleError::TaskNotFound(task_id))?; if task.state != TaskState::Suspended { return Err(LifecycleError::InvalidTransition { task_id, from: task.state, operation: LifecycleOperation::Resume, }); } let process_id = process_id_for_task(self.os, task_id)?; match task_kind_for_task(self.os, task_id)? { TaskKind::Game => { self.os .foreground_stack .request_resume_game(task_id) .map_err(map_foreground_error)?; self.os.task_manager.set_foreground(task_id); self.os.process_manager.mark_running(process_id); self.os.game_lifecycle_events.push(GameLifecycleEvent { task_id, kind: GameLifecycleEventKind::ResumeForeground, }); self.os .foreground_stack .complete_resume_game(task_id) .map_err(map_foreground_error)?; } TaskKind::Shell => { self.os .foreground_stack .set_shell_foreground(task_id) .map_err(map_foreground_error)?; self.os.task_manager.set_foreground(task_id); self.os.process_manager.mark_running(process_id); } } Ok(()) } pub fn close_task(&mut self, task_id: TaskId) -> Result<(), LifecycleError> { let process_id = process_id_for_task(self.os, task_id)?; self.os.task_manager.close_task(task_id); self.os.process_manager.mark_stopped(process_id); if self.os.foreground_stack.resident_game_task() == Some(task_id) { self.os.foreground_stack.clear_resident_game(task_id); } else if matches!(self.os.foreground_stack.owner(), ForegroundOwner::Shell(owner) if owner == task_id) { self.os.foreground_stack.return_to_hub(); } Ok(()) } pub fn crash_task( &mut self, task_id: TaskId, _report: Option<&CrashReport>, ) -> Result<(), LifecycleError> { let process_id = process_id_for_task(self.os, task_id)?; self.os.task_manager.mark_crashed(task_id); self.os.process_manager.mark_crashed(process_id); if self.os.foreground_stack.resident_game_task() == Some(task_id) { self.os.foreground_stack.clear_resident_game(task_id); } else if matches!(self.os.foreground_stack.owner(), ForegroundOwner::Shell(owner) if owner == task_id) { self.os.foreground_stack.return_to_hub(); } Ok(()) } } fn task_kind_for_task(os: &SystemOS, task_id: TaskId) -> Result { let task = os.task_manager.get(task_id).ok_or(LifecycleError::TaskNotFound(task_id))?; Ok(task.kind) } fn process_id_for_task(os: &SystemOS, task_id: TaskId) -> Result { let task = os.task_manager.get(task_id).ok_or(LifecycleError::TaskNotFound(task_id))?; if !os.process_manager.contains(task.process_id) { return Err(LifecycleError::ProcessNotFound(task.process_id)); } Ok(task.process_id) } fn process_for_task(os: &SystemOS, task_id: TaskId) -> Result<&Process, LifecycleError> { let process_id = process_id_for_task(os, task_id)?; os.process_manager.get(process_id).ok_or(LifecycleError::ProcessNotFound(process_id)) } fn map_foreground_error(error: ForegroundStackError) -> LifecycleError { match error { ForegroundStackError::ResidentGameAlreadyExists { existing, requested } => { LifecycleError::ResidentGameAlreadyExists { existing, requested } } ForegroundStackError::ForegroundShellAlreadyExists { existing, requested } => { LifecycleError::ForegroundShellAlreadyExists { existing, requested } } ForegroundStackError::TaskIsNotResidentGame(task_id) => { LifecycleError::TaskIsNotResidentGame(task_id) } } }