implements PLN-0061 shell running lifecycle integration
This commit is contained in:
parent
d929f8e2f1
commit
c2698e6a22
@ -175,10 +175,13 @@ mod tests {
|
|||||||
use prometeu_bytecode::model::{BytecodeModule, FunctionMeta, SyscallDecl};
|
use prometeu_bytecode::model::{BytecodeModule, FunctionMeta, SyscallDecl};
|
||||||
use prometeu_drivers::hardware::Hardware;
|
use prometeu_drivers::hardware::Hardware;
|
||||||
use prometeu_hal::cartridge::{AppMode, AssetsPayloadSource};
|
use prometeu_hal::cartridge::{AppMode, AssetsPayloadSource};
|
||||||
|
use prometeu_hal::color::Color;
|
||||||
|
use prometeu_hal::primitives::Rect;
|
||||||
use prometeu_hal::syscalls::caps;
|
use prometeu_hal::syscalls::caps;
|
||||||
use prometeu_system::CrashReport;
|
use prometeu_system::CrashReport;
|
||||||
use prometeu_system::process::ProcessState;
|
use prometeu_system::process::ProcessState;
|
||||||
use prometeu_system::task::TaskState;
|
use prometeu_system::task::{TaskId, TaskState};
|
||||||
|
use prometeu_system::windows::WindowOwner;
|
||||||
|
|
||||||
fn halting_program() -> Vec<u8> {
|
fn halting_program() -> Vec<u8> {
|
||||||
let code = assemble("HALT").expect("assemble");
|
let code = assemble("HALT").expect("assemble");
|
||||||
@ -262,6 +265,34 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn load_shell_running_firmware() -> (Firmware, Hardware, InputSignals, TaskId) {
|
||||||
|
let mut firmware = Firmware::new(None);
|
||||||
|
let mut hardware = Hardware::new();
|
||||||
|
let signals = InputSignals::default();
|
||||||
|
|
||||||
|
firmware.load_cartridge(valid_cartridge(AppMode::Shell));
|
||||||
|
firmware.tick(&signals, &mut hardware);
|
||||||
|
|
||||||
|
let task_id = match &firmware.state {
|
||||||
|
FirmwareState::ShellRunning(step) => step.task_id,
|
||||||
|
other => panic!("expected ShellRunning state, got {:?}", other),
|
||||||
|
};
|
||||||
|
|
||||||
|
(firmware, hardware, signals, task_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn focus_window(firmware: &mut Firmware, owner: WindowOwner) {
|
||||||
|
let mut windows = firmware.os.windows();
|
||||||
|
windows.remove_all_windows();
|
||||||
|
let id = windows.add_window(
|
||||||
|
"Test Window".to_string(),
|
||||||
|
owner,
|
||||||
|
Rect { x: 0, y: 0, w: 10, h: 10 },
|
||||||
|
Color::WHITE,
|
||||||
|
);
|
||||||
|
windows.set_focus(id);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn load_cartridge_transitions_to_app_crashes_when_vm_init_fails() {
|
fn load_cartridge_transitions_to_app_crashes_when_vm_init_fails() {
|
||||||
let mut firmware = Firmware::new(None);
|
let mut firmware = Firmware::new(None);
|
||||||
@ -362,14 +393,79 @@ mod tests {
|
|||||||
|
|
||||||
firmware.load_cartridge(valid_cartridge(AppMode::Shell));
|
firmware.load_cartridge(valid_cartridge(AppMode::Shell));
|
||||||
firmware.tick(&signals, &mut hardware);
|
firmware.tick(&signals, &mut hardware);
|
||||||
|
let task_id = match &firmware.state {
|
||||||
|
FirmwareState::ShellRunning(step) => step.task_id,
|
||||||
|
other => panic!("expected ShellRunning state, got {:?}", other),
|
||||||
|
};
|
||||||
|
|
||||||
let tick_index_before_system_update = firmware.os.vm().tick_index();
|
let tick_index_before_system_update = firmware.os.vm().tick_index();
|
||||||
firmware.tick(&signals, &mut hardware);
|
firmware.tick(&signals, &mut hardware);
|
||||||
|
|
||||||
assert!(matches!(firmware.state, FirmwareState::ShellRunning(_)));
|
assert!(matches!(firmware.state, FirmwareState::ShellRunning(_)));
|
||||||
|
assert!(firmware.os.windows().focused_window_belongs_to_task(task_id));
|
||||||
assert_eq!(firmware.os.vm().tick_index(), tick_index_before_system_update + 1);
|
assert_eq!(firmware.os.vm().tick_index(), tick_index_before_system_update + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn shell_running_closes_task_and_returns_hub_home_without_focused_window() {
|
||||||
|
let (mut firmware, mut hardware, signals, task_id) = load_shell_running_firmware();
|
||||||
|
|
||||||
|
firmware.os.windows().remove_all_windows();
|
||||||
|
firmware.tick(&signals, &mut hardware);
|
||||||
|
|
||||||
|
assert!(matches!(firmware.state, FirmwareState::HubHome(_)));
|
||||||
|
assert_eq!(firmware.os.lifecycle().task_state(task_id), Some(TaskState::Closed));
|
||||||
|
assert_eq!(
|
||||||
|
firmware.os.lifecycle().process_state_for_task(task_id),
|
||||||
|
Ok(ProcessState::Stopped)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn shell_running_closes_task_and_returns_hub_home_for_focused_hub_window() {
|
||||||
|
let (mut firmware, mut hardware, signals, task_id) = load_shell_running_firmware();
|
||||||
|
|
||||||
|
focus_window(&mut firmware, WindowOwner::Hub);
|
||||||
|
firmware.tick(&signals, &mut hardware);
|
||||||
|
|
||||||
|
assert!(matches!(firmware.state, FirmwareState::HubHome(_)));
|
||||||
|
assert_eq!(firmware.os.lifecycle().task_state(task_id), Some(TaskState::Closed));
|
||||||
|
assert_eq!(
|
||||||
|
firmware.os.lifecycle().process_state_for_task(task_id),
|
||||||
|
Ok(ProcessState::Stopped)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn shell_running_closes_task_and_returns_hub_home_for_focused_overlay_window() {
|
||||||
|
let (mut firmware, mut hardware, signals, task_id) = load_shell_running_firmware();
|
||||||
|
|
||||||
|
focus_window(&mut firmware, WindowOwner::Overlay);
|
||||||
|
firmware.tick(&signals, &mut hardware);
|
||||||
|
|
||||||
|
assert!(matches!(firmware.state, FirmwareState::HubHome(_)));
|
||||||
|
assert_eq!(firmware.os.lifecycle().task_state(task_id), Some(TaskState::Closed));
|
||||||
|
assert_eq!(
|
||||||
|
firmware.os.lifecycle().process_state_for_task(task_id),
|
||||||
|
Ok(ProcessState::Stopped)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn shell_running_closes_task_and_returns_hub_home_for_other_task_window() {
|
||||||
|
let (mut firmware, mut hardware, signals, task_id) = load_shell_running_firmware();
|
||||||
|
|
||||||
|
focus_window(&mut firmware, WindowOwner::Task(TaskId(task_id.0 + 1)));
|
||||||
|
firmware.tick(&signals, &mut hardware);
|
||||||
|
|
||||||
|
assert!(matches!(firmware.state, FirmwareState::HubHome(_)));
|
||||||
|
assert_eq!(firmware.os.lifecycle().task_state(task_id), Some(TaskState::Closed));
|
||||||
|
assert_eq!(
|
||||||
|
firmware.os.lifecycle().process_state_for_task(task_id),
|
||||||
|
Ok(ProcessState::Stopped)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn load_cartridge_routes_game_apps_to_game_running() {
|
fn load_cartridge_routes_game_apps_to_game_running() {
|
||||||
let mut firmware = Firmware::new(None);
|
let mut firmware = Firmware::new(None);
|
||||||
@ -380,5 +476,6 @@ mod tests {
|
|||||||
firmware.tick(&signals, &mut hardware);
|
firmware.tick(&signals, &mut hardware);
|
||||||
|
|
||||||
assert!(matches!(firmware.state, FirmwareState::GameRunning(_)));
|
assert!(matches!(firmware.state, FirmwareState::GameRunning(_)));
|
||||||
|
assert_eq!(firmware.os.windows().window_count(), 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -66,10 +66,13 @@ impl ShellRunningStep {
|
|||||||
return Some(FirmwareState::AppCrashes(AppCrashesStep { report }));
|
return Some(FirmwareState::AppCrashes(AppCrashesStep { report }));
|
||||||
}
|
}
|
||||||
|
|
||||||
if !outcome.focused_window_active {
|
if !ctx.os.windows().focused_window_belongs_to_task(self.task_id) {
|
||||||
ctx.os.info(
|
ctx.os.info(
|
||||||
LogSource::Hub,
|
LogSource::Hub,
|
||||||
format!("Closing shell task {:?}: no focused window active", self.task_id),
|
format!(
|
||||||
|
"Closing shell task {:?}: focused window does not belong to task",
|
||||||
|
self.task_id,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
if let Err(error) = ctx.os.lifecycle().close_task(self.task_id) {
|
if let Err(error) = ctx.os.lifecycle().close_task(self.task_id) {
|
||||||
ctx.os.error(
|
ctx.os.error(
|
||||||
|
|||||||
@ -11,7 +11,6 @@ pub struct PrometeuHub;
|
|||||||
|
|
||||||
pub struct SystemProfileUpdate {
|
pub struct SystemProfileUpdate {
|
||||||
pub crash: Option<CrashReport>,
|
pub crash: Option<CrashReport>,
|
||||||
pub focused_window_active: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for PrometeuHub {
|
impl Default for PrometeuHub {
|
||||||
@ -103,9 +102,6 @@ impl PrometeuHub {
|
|||||||
self.render(os, hw);
|
self.render(os, hw);
|
||||||
hw.gfx_mut().present();
|
hw.gfx_mut().present();
|
||||||
|
|
||||||
SystemProfileUpdate {
|
SystemProfileUpdate { crash }
|
||||||
crash,
|
|
||||||
focused_window_active: os.windows().focused_window().is_some(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
{"type":"meta","next_id":{"DSC":36,"AGD":36,"DEC":28,"PLN":62,"LSN":44,"CLSN":1}}
|
{"type":"meta","next_id":{"DSC":36,"AGD":36,"DEC":28,"PLN":62,"LSN":44,"CLSN":1}}
|
||||||
{"type":"discussion","id":"DSC-0035","status":"in_progress","ticket":"task-owned-shell-windows","title":"Agenda - Task-Owned Shell Windows","created_at":"2026-05-15","updated_at":"2026-05-15","tags":["runtime","os","task","window-manager","shell","lifecycle"],"agendas":[{"id":"AGD-0035","file":"AGD-0035-agenda-task-owned-shell-windows.md","status":"accepted","created_at":"2026-05-15","updated_at":"2026-05-15"}],"decisions":[{"id":"DEC-0027","file":"DEC-0027-decision-task-owned-shell-windows.md","status":"accepted","created_at":"2026-05-15","updated_at":"2026-05-15","ref_agenda":"AGD-0035"}],"plans":[{"id":"PLN-0059","file":"PLN-0059-plan-task-window-contract-specs.md","status":"done","created_at":"2026-05-15","updated_at":"2026-05-15","ref_decisions":["DEC-0027"]},{"id":"PLN-0060","file":"PLN-0060-plan-focused-window-ownership-api.md","status":"done","created_at":"2026-05-15","updated_at":"2026-05-15","ref_decisions":["DEC-0027"]},{"id":"PLN-0061","file":"PLN-0061-plan-shell-running-lifecycle-integration.md","status":"open","created_at":"2026-05-15","updated_at":"2026-05-15","ref_decisions":["DEC-0027"]}],"lessons":[]}
|
{"type":"discussion","id":"DSC-0035","status":"in_progress","ticket":"task-owned-shell-windows","title":"Agenda - Task-Owned Shell Windows","created_at":"2026-05-15","updated_at":"2026-05-15","tags":["runtime","os","task","window-manager","shell","lifecycle"],"agendas":[{"id":"AGD-0035","file":"AGD-0035-agenda-task-owned-shell-windows.md","status":"accepted","created_at":"2026-05-15","updated_at":"2026-05-15"}],"decisions":[{"id":"DEC-0027","file":"DEC-0027-decision-task-owned-shell-windows.md","status":"accepted","created_at":"2026-05-15","updated_at":"2026-05-15","ref_agenda":"AGD-0035"}],"plans":[{"id":"PLN-0059","file":"PLN-0059-plan-task-window-contract-specs.md","status":"done","created_at":"2026-05-15","updated_at":"2026-05-15","ref_decisions":["DEC-0027"]},{"id":"PLN-0060","file":"PLN-0060-plan-focused-window-ownership-api.md","status":"done","created_at":"2026-05-15","updated_at":"2026-05-15","ref_decisions":["DEC-0027"]},{"id":"PLN-0061","file":"PLN-0061-plan-shell-running-lifecycle-integration.md","status":"done","created_at":"2026-05-15","updated_at":"2026-05-15","ref_decisions":["DEC-0027"]}],"lessons":[]}
|
||||||
{"type":"discussion","id":"DSC-0034","status":"done","ticket":"system-os-domain-facades","title":"Agenda - SystemOS Domain Facades","created_at":"2026-05-15","updated_at":"2026-05-15","tags":["runtime","os","services","api-surface","lifecycle","fs"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0043","file":"discussion/lessons/DSC-0034-system-os-domain-facades/LSN-0043-systemos-domain-facades.md","status":"done","created_at":"2026-05-15","updated_at":"2026-05-15"}]}
|
{"type":"discussion","id":"DSC-0034","status":"done","ticket":"system-os-domain-facades","title":"Agenda - SystemOS Domain Facades","created_at":"2026-05-15","updated_at":"2026-05-15","tags":["runtime","os","services","api-surface","lifecycle","fs"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0043","file":"discussion/lessons/DSC-0034-system-os-domain-facades/LSN-0043-systemos-domain-facades.md","status":"done","created_at":"2026-05-15","updated_at":"2026-05-15"}]}
|
||||||
{"type":"discussion","id":"DSC-0023","status":"done","ticket":"perf-full-migration-to-atomic-telemetry","title":"Agenda - [PERF] Full Migration to Atomic Telemetry","created_at":"2026-04-10","updated_at":"2026-04-10","tags":["perf","runtime","telemetry"],"agendas":[{"id":"AGD-0021","file":"AGD-0021-full-migration-to-atomic-telemetry.md","status":"done","created_at":"2026-04-10","updated_at":"2026-04-10"}],"decisions":[{"id":"DEC-0008","file":"DEC-0008-full-migration-to-atomic-telemetry.md","status":"accepted","created_at":"2026-04-10","updated_at":"2026-04-10"}],"plans":[{"id":"PLN-0007","file":"PLN-0007-full-migration-to-atomic-telemetry.md","status":"done","created_at":"2026-04-10","updated_at":"2026-04-10"}],"lessons":[{"id":"LSN-0028","file":"discussion/lessons/DSC-0023-perf-full-migration-to-atomic-telemetry/LSN-0028-converging-to-single-atomic-telemetry-source.md","status":"done","created_at":"2026-04-10","updated_at":"2026-04-10"}]}
|
{"type":"discussion","id":"DSC-0023","status":"done","ticket":"perf-full-migration-to-atomic-telemetry","title":"Agenda - [PERF] Full Migration to Atomic Telemetry","created_at":"2026-04-10","updated_at":"2026-04-10","tags":["perf","runtime","telemetry"],"agendas":[{"id":"AGD-0021","file":"AGD-0021-full-migration-to-atomic-telemetry.md","status":"done","created_at":"2026-04-10","updated_at":"2026-04-10"}],"decisions":[{"id":"DEC-0008","file":"DEC-0008-full-migration-to-atomic-telemetry.md","status":"accepted","created_at":"2026-04-10","updated_at":"2026-04-10"}],"plans":[{"id":"PLN-0007","file":"PLN-0007-full-migration-to-atomic-telemetry.md","status":"done","created_at":"2026-04-10","updated_at":"2026-04-10"}],"lessons":[{"id":"LSN-0028","file":"discussion/lessons/DSC-0023-perf-full-migration-to-atomic-telemetry/LSN-0028-converging-to-single-atomic-telemetry-source.md","status":"done","created_at":"2026-04-10","updated_at":"2026-04-10"}]}
|
||||||
{"type":"discussion","id":"DSC-0020","status":"done","ticket":"jenkins-gitea-integration","title":"Jenkins Gitea Integration and Relocation","created_at":"2026-04-07","updated_at":"2026-04-07","tags":["ci","jenkins","gitea"],"agendas":[{"id":"AGD-0018","file":"AGD-0018-jenkins-gitea-integration-and-relocation.md","status":"done","created_at":"2026-04-07","updated_at":"2026-04-07"}],"decisions":[{"id":"DEC-0003","file":"DEC-0003-jenkins-gitea-strategy.md","status":"accepted","created_at":"2026-04-07","updated_at":"2026-04-07"}],"plans":[{"id":"PLN-0003","file":"PLN-0003-jenkins-gitea-execution.md","status":"done","created_at":"2026-04-07","updated_at":"2026-04-07"}],"lessons":[{"id":"LSN-0021","file":"discussion/lessons/DSC-0020-jenkins-gitea-integration/LSN-0021-jenkins-gitea-integration.md","status":"done","created_at":"2026-04-07","updated_at":"2026-04-07"}]}
|
{"type":"discussion","id":"DSC-0020","status":"done","ticket":"jenkins-gitea-integration","title":"Jenkins Gitea Integration and Relocation","created_at":"2026-04-07","updated_at":"2026-04-07","tags":["ci","jenkins","gitea"],"agendas":[{"id":"AGD-0018","file":"AGD-0018-jenkins-gitea-integration-and-relocation.md","status":"done","created_at":"2026-04-07","updated_at":"2026-04-07"}],"decisions":[{"id":"DEC-0003","file":"DEC-0003-jenkins-gitea-strategy.md","status":"accepted","created_at":"2026-04-07","updated_at":"2026-04-07"}],"plans":[{"id":"PLN-0003","file":"PLN-0003-jenkins-gitea-execution.md","status":"done","created_at":"2026-04-07","updated_at":"2026-04-07"}],"lessons":[{"id":"LSN-0021","file":"discussion/lessons/DSC-0020-jenkins-gitea-integration/LSN-0021-jenkins-gitea-integration.md","status":"done","created_at":"2026-04-07","updated_at":"2026-04-07"}]}
|
||||||
|
|||||||
@ -2,9 +2,9 @@
|
|||||||
id: PLN-0061
|
id: PLN-0061
|
||||||
ticket: task-owned-shell-windows
|
ticket: task-owned-shell-windows
|
||||||
title: Plan - Shell Running Lifecycle Integration
|
title: Plan - Shell Running Lifecycle Integration
|
||||||
status: open
|
status: done
|
||||||
created: 2026-05-15
|
created: 2026-05-15
|
||||||
completed:
|
completed: 2026-05-15
|
||||||
ref_decisions: [DEC-0027]
|
ref_decisions: [DEC-0027]
|
||||||
tags: [runtime, os, task, window-manager, shell, lifecycle, firmware, tests]
|
tags: [runtime, os, task, window-manager, shell, lifecycle, firmware, tests]
|
||||||
---
|
---
|
||||||
@ -100,15 +100,15 @@ Included:
|
|||||||
|
|
||||||
## Criterios de Aceite
|
## Criterios de Aceite
|
||||||
|
|
||||||
- [ ] `ShellRunningStep` no longer uses `focused_window_active` as its
|
- [x] `ShellRunningStep` no longer uses `focused_window_active` as its
|
||||||
continuation contract.
|
continuation contract.
|
||||||
- [ ] `ShellRunningStep` uses
|
- [x] `ShellRunningStep` uses
|
||||||
`os.windows().focused_window_belongs_to_task(self.task_id)`.
|
`os.windows().focused_window_belongs_to_task(self.task_id)`.
|
||||||
- [ ] A false predicate closes the task through lifecycle before returning
|
- [x] A false predicate closes the task through lifecycle before returning
|
||||||
`HubHome`.
|
`HubHome`.
|
||||||
- [ ] `Hub`, `Overlay`, absent focus, and another task's focused window all
|
- [x] `Hub`, `Overlay`, absent focus, and another task's focused window all
|
||||||
fail the predicate for the current shell task.
|
fail the predicate for the current shell task.
|
||||||
- [ ] Game-running behavior is unchanged and remains outside `WindowManager`.
|
- [x] Game-running behavior is unchanged and remains outside `WindowManager`.
|
||||||
|
|
||||||
## Tests / Validacao
|
## Tests / Validacao
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user