diff --git a/apps/lumbridge/src/main.rs b/apps/lumbridge/src/main.rs index 5d49d55..7bcce3f 100644 --- a/apps/lumbridge/src/main.rs +++ b/apps/lumbridge/src/main.rs @@ -368,10 +368,14 @@ fn dim_color(color: Rgba) -> Rgba { } impl LumbridgeShell { - fn new(window: &mut Window, cx: &mut Context) -> Self { - let root_focus = cx.focus_handle(); - window.focus(&root_focus); - + /// Advances the deterministic comparison surfaces and the usage feed. + /// + /// These five surfaces are not decoration: they are the workload the UI + /// framework comparison is measured against, so they keep running whether + /// or not a terminal is selected. The loop ends when `update` reports the + /// entity is gone, which is the only signal a detached task gets that the + /// window it was feeding has closed. + fn drive_synthetic_surfaces(cx: &mut Context) { cx.spawn(async move |this, cx| { loop { cx.background_executor() @@ -391,7 +395,16 @@ impl LumbridgeShell { } }) .detach(); + } + /// Drains whatever the runtime actors have produced since the last poll. + /// + /// Separate from the synthetic tick above and on its own interval, because + /// one is a fixture being animated and the other is real PTY output whose + /// latency a person can feel. Repainting only when `drain_runtime_events` + /// reports something arrived keeps an idle terminal from waking the + /// renderer on every poll. + fn poll_runtime_events(cx: &mut Context) { cx.spawn(async move |this, cx| { loop { cx.background_executor().timer(RUNTIME_POLL_INTERVAL).await; @@ -408,6 +421,47 @@ impl LumbridgeShell { } }) .detach(); + } + + /// Gives every restored Terminal panel a runtime actor of its own. + /// + /// A panel whose session cannot be launched is kept, not dropped, and + /// carries the failure as its status. Decision 0011 makes a panel identity + /// durable, so a panel that came back from `SQLite` and then failed to spawn + /// still has to appear -- silently omitting it would leave a workspace + /// missing a pane the user saved, with nothing on screen to say why. + fn launch_restored_terminals( + panels: &PanelRegistry, + dimensions: TerminalDimensions, + ) -> ( + RuntimeRegistry, + BTreeMap, + ) { + let mut runtimes = RuntimeRegistry::new(); + let mut live_terminals = BTreeMap::new(); + for panel in panels + .panels() + .iter() + .filter(|panel| panel.kind == PanelKind::Terminal) + { + let mut terminal = LiveTerminalState::new(dimensions); + if let Err(error) = spawn_live_runtime(&mut runtimes, panel.id, panel.seed, dimensions) + { + terminal.status = LiveRuntimeStatus::Fault(error.to_string()); + } + live_terminals.insert(panel.id, terminal); + } + (runtimes, live_terminals) + } +} + +impl LumbridgeShell { + fn new(window: &mut Window, cx: &mut Context) -> Self { + let root_focus = cx.focus_handle(); + window.focus(&root_focus); + + Self::drive_synthetic_surfaces(cx); + Self::poll_runtime_events(cx); let (store, panels, persistence_status) = persistence::load_panel_registry(&ProcessEnv); let sidebar = SidebarState::default(); @@ -418,21 +472,8 @@ impl LumbridgeShell { sidebar.width(), cell, ); - let mut runtimes = RuntimeRegistry::new(); - let mut live_terminals = BTreeMap::new(); - for panel in panels - .panels() - .iter() - .filter(|panel| panel.kind == PanelKind::Terminal) - { - let mut terminal = LiveTerminalState::new(terminal_dimensions); - if let Err(error) = - spawn_live_runtime(&mut runtimes, panel.id, panel.seed, terminal_dimensions) - { - terminal.status = LiveRuntimeStatus::Fault(error.to_string()); - } - live_terminals.insert(panel.id, terminal); - } + let (runtimes, live_terminals) = + Self::launch_restored_terminals(&panels, terminal_dimensions); cx.observe_window_bounds(window, |shell, window, cx| { let dimensions = terminal_dimensions_for_window(