2026-07-04 18:52:49 +01:00

278 lines
10 KiB
Rust

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<TaskState> {
self.os.task_manager.get(task_id).map(|task| task.state)
}
pub fn process_state_for_task(&self, task_id: TaskId) -> Result<ProcessState, LifecycleError> {
process_for_task(self.os, task_id).map(|process| process.state)
}
pub fn process_kind_for_task(&self, task_id: TaskId) -> Result<ProcessKind, LifecycleError> {
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<ResidentGame> {
self.os.foreground_stack.resident_game()
}
pub fn resident_game_task(&self) -> Option<TaskId> {
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<bool, LifecycleError> {
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<GameLifecycleEvent> {
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<TaskKind, LifecycleError> {
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<ProcessId, LifecycleError> {
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)
}
}
}