278 lines
10 KiB
Rust
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)
|
|
}
|
|
}
|
|
}
|