Skip to main content

lgui_core/runtime/
session.rs

1use std::sync::Arc;
2#[cfg(feature = "diagnostics-timing")]
3use std::time::Instant;
4
5use super::{
6    frame::InvalidationSet,
7    host::{HostCommit, HostRuntime},
8};
9use crate::core::UiRect;
10use crate::{
11    application::AppView,
12    core::{
13        ComponentRuntimeMetrics, HostProjectionMetrics, HostTree, HostTreeBuilder,
14        LayoutCommitMetrics, LayoutRuntime, UiRuntime, UiScale, UiTaskSpawner, UiWake,
15    },
16};
17
18/// Owns the complete retained UI state for one native window.
19///
20/// A backend may be recreated or switched without replacing this session, so component,
21/// input, layout and scene identities survive renderer lifecycle changes.
22#[derive(Default)]
23pub struct UiSession {
24    runtime: UiRuntime,
25    host: HostRuntime,
26    layout: LayoutRuntime,
27    tree: HostTree,
28    invalidations: InvalidationSet,
29    render_context: Option<(UiRect, UiScale)>,
30    component_metrics: ComponentRuntimeMetrics,
31    projection_metrics: HostProjectionMetrics,
32    #[cfg(feature = "diagnostics-timing")]
33    render_timings: SessionRenderTimings,
34}
35
36#[cfg(feature = "diagnostics-timing")]
37#[derive(Clone, Copy, Debug, Default)]
38#[doc(hidden)]
39pub struct SessionRenderTimings {
40    pub pending_updates_ms: f32,
41    pub prepare_render_ms: f32,
42    pub retained_snapshot_ms: f32,
43    pub declarative_mount_ms: f32,
44    pub focus_animation_sync_ms: f32,
45    pub focus_sync_ms: f32,
46    pub focus_rebuild_ms: f32,
47    pub animation_target_sync_ms: f32,
48    pub animation_rebuild_ms: f32,
49    pub animation_sync_nodes: usize,
50    pub focus_sync_needed: bool,
51    pub runtime_reconcile_ms: f32,
52    pub layout_ms: f32,
53    pub host_commit_ms: f32,
54    pub total_ms: f32,
55}
56
57impl UiSession {
58    pub fn new() -> Self {
59        Self::default()
60    }
61
62    pub fn runtime(&self) -> &UiRuntime {
63        &self.runtime
64    }
65
66    pub fn runtime_mut(&mut self) -> &mut UiRuntime {
67        &mut self.runtime
68    }
69
70    pub fn invalidations_mut(&mut self) -> &mut InvalidationSet {
71        &mut self.invalidations
72    }
73
74    pub fn render_tree(&self) -> HostTree {
75        self.tree.clone()
76    }
77
78    pub fn prepare_render(&mut self, viewport: UiRect, scale: UiScale) {
79        let context = (viewport, scale);
80        if self.render_context != Some(context) {
81            self.runtime.invalidate_all_components();
82            self.render_context = Some(context);
83            self.invalidate_all();
84        }
85    }
86
87    pub fn replace_tree(&mut self, tree: HostTree) {
88        self.tree = tree;
89    }
90
91    pub fn tree(&self) -> &HostTree {
92        &self.tree
93    }
94
95    pub fn has_tree(&self) -> bool {
96        !self.tree.nodes().is_empty()
97    }
98
99    pub fn handle_input(&mut self, input: crate::core::InputEvent) -> crate::core::RuntimeOutput {
100        self.runtime.handle_input(&self.tree, input)
101    }
102
103    pub fn advance(&mut self, elapsed_ms: f32) -> crate::core::RuntimeOutput {
104        self.runtime.advance(&mut self.tree, elapsed_ms)
105    }
106
107    pub fn handle_default_action(
108        &mut self,
109        action: crate::core::UiDefaultAction,
110    ) -> crate::core::RuntimeOutput {
111        self.runtime.handle_default_action(&self.tree, action)
112    }
113
114    pub fn commit(&mut self, viewport: UiRect) -> HostCommit {
115        let changes = self.tree.take_projection_changes();
116        let (_, changes) = self.layout.update_projection(&mut self.tree, changes);
117        self.host.commit_projection(
118            &self.tree,
119            &self.runtime.interaction_state(),
120            viewport,
121            &mut self.invalidations,
122            changes,
123        )
124    }
125
126    pub fn render_view(&mut self, view: &AppView, viewport: UiRect, scale: UiScale) -> HostCommit {
127        #[cfg(feature = "diagnostics-timing")]
128        let total_started = Instant::now();
129        #[cfg(feature = "diagnostics-timing")]
130        let pending_updates_started = Instant::now();
131        self.apply_pending_updates();
132        #[cfg(feature = "diagnostics-timing")]
133        let pending_updates_ms = elapsed_ms(pending_updates_started);
134
135        #[cfg(feature = "diagnostics-timing")]
136        let prepare_render_started = Instant::now();
137        self.prepare_render(viewport, scale);
138        #[cfg(feature = "diagnostics-timing")]
139        let prepare_render_ms = elapsed_ms(prepare_render_started);
140
141        #[cfg(feature = "diagnostics-timing")]
142        let retained_snapshot_started = Instant::now();
143        let retained = self.render_tree();
144        #[cfg(feature = "diagnostics-timing")]
145        let retained_snapshot_ms = elapsed_ms(retained_snapshot_started);
146
147        #[cfg(feature = "diagnostics-timing")]
148        let declarative_mount_started = Instant::now();
149        let (mut tree, mut projection_metrics) =
150            self.build_view_tree_from(retained, Arc::clone(view), viewport, scale);
151        #[cfg(feature = "diagnostics-timing")]
152        let declarative_mount_ms = elapsed_ms(declarative_mount_started);
153
154        #[cfg(feature = "diagnostics-timing")]
155        let focus_sync_started = Instant::now();
156        #[cfg(feature = "diagnostics-timing")]
157        let focus_sync_needed = tree.needs_focus_sync();
158        let focus_changed = self.runtime.sync_tree_focus(&tree);
159        #[cfg(feature = "diagnostics-timing")]
160        let focus_sync_ms = elapsed_ms(focus_sync_started);
161        #[cfg(feature = "diagnostics-timing")]
162        let focus_rebuild_started = Instant::now();
163        if focus_changed {
164            (tree, projection_metrics) =
165                self.build_view_tree_from(tree, Arc::clone(view), viewport, scale);
166        }
167        #[cfg(feature = "diagnostics-timing")]
168        let focus_rebuild_ms = elapsed_ms(focus_rebuild_started);
169        #[cfg(feature = "diagnostics-timing")]
170        let animation_target_sync_started = Instant::now();
171        #[cfg(feature = "diagnostics-timing")]
172        let animation_sync_nodes = tree.animation_sync_ids().count();
173        let animation_targets_changed = self.runtime.sync_tree_animation_targets(&tree);
174        #[cfg(feature = "diagnostics-timing")]
175        let animation_target_sync_ms = elapsed_ms(animation_target_sync_started);
176        #[cfg(feature = "diagnostics-timing")]
177        let animation_rebuild_started = Instant::now();
178        if animation_targets_changed {
179            (tree, projection_metrics) =
180                self.build_view_tree_from(tree, Arc::clone(view), viewport, scale);
181        }
182        #[cfg(feature = "diagnostics-timing")]
183        let animation_rebuild_ms = elapsed_ms(animation_rebuild_started);
184        self.component_metrics = self.runtime.component_tree().metrics();
185        self.projection_metrics = projection_metrics;
186        self.replace_tree(tree);
187        #[cfg(feature = "diagnostics-timing")]
188        let focus_animation_sync_ms =
189            focus_sync_ms + focus_rebuild_ms + animation_target_sync_ms + animation_rebuild_ms;
190
191        #[cfg(feature = "diagnostics-timing")]
192        let layout_started = Instant::now();
193        let changes = self.tree.take_projection_changes();
194        let (_, changes) = self.layout.update_projection(&mut self.tree, changes);
195        #[cfg(feature = "diagnostics-timing")]
196        let layout_ms = elapsed_ms(layout_started);
197
198        #[cfg(feature = "diagnostics-timing")]
199        let host_commit_started = Instant::now();
200        let commit = self.host.commit_projection(
201            &self.tree,
202            &self.runtime.interaction_state(),
203            viewport,
204            &mut self.invalidations,
205            changes,
206        );
207        #[cfg(feature = "diagnostics-timing")]
208        {
209            self.render_timings = SessionRenderTimings {
210                pending_updates_ms,
211                prepare_render_ms,
212                retained_snapshot_ms,
213                declarative_mount_ms,
214                focus_animation_sync_ms,
215                focus_sync_ms,
216                focus_rebuild_ms,
217                animation_target_sync_ms,
218                animation_rebuild_ms,
219                animation_sync_nodes,
220                focus_sync_needed,
221                runtime_reconcile_ms: 0.0,
222                layout_ms,
223                host_commit_ms: elapsed_ms(host_commit_started),
224                total_ms: elapsed_ms(total_started),
225            };
226        }
227        commit
228    }
229
230    fn build_view_tree_from(
231        &self,
232        retained: HostTree,
233        view: AppView,
234        viewport: UiRect,
235        scale: UiScale,
236    ) -> (HostTree, HostProjectionMetrics) {
237        let interaction = self.runtime.interaction_state();
238        let mut builder = HostTreeBuilder::from_retained(retained);
239        builder.mount(
240            view,
241            viewport,
242            &interaction,
243            self.runtime.animations(),
244            self.runtime.component_states(),
245            self.runtime.component_tree(),
246            self.runtime.contexts(),
247            self.runtime.hook_states(),
248            self.runtime.hook_updates(),
249            self.runtime.task_spawner(),
250            self.runtime.effects(),
251            scale,
252        );
253        let metrics = builder.projection_metrics();
254        (builder.finish(), metrics)
255    }
256
257    pub fn apply_pending_updates(&mut self) -> crate::core::PendingUpdateOutput {
258        let updates = self.runtime.apply_pending_updates(&self.tree);
259        if updates.focus_changed {
260            self.invalidate_all();
261        }
262        updates
263    }
264
265    pub fn layout_metrics(&self) -> LayoutCommitMetrics {
266        self.layout.metrics()
267    }
268
269    pub fn component_metrics(&self) -> ComponentRuntimeMetrics {
270        self.component_metrics
271    }
272
273    pub fn projection_metrics(&self) -> HostProjectionMetrics {
274        self.projection_metrics
275    }
276
277    pub(crate) fn memory_usage(&self) -> (crate::memory::CacheUsage, crate::memory::CacheUsage) {
278        let component = self.runtime.component_tree().output_memory_usage();
279        let host_scene_bytes = self
280            .tree
281            .estimated_bytes()
282            .saturating_add(self.host.estimated_bytes());
283        let host_scene = crate::memory::CacheUsage {
284            rebuildable_bytes: host_scene_bytes,
285            cpu_bytes: host_scene_bytes,
286            entries: self
287                .tree
288                .nodes()
289                .len()
290                .saturating_add(usize::from(self.has_tree())),
291            largest_entry_bytes: self.host.estimated_bytes(),
292            ..Default::default()
293        };
294        (component, host_scene)
295    }
296
297    pub(crate) fn trim_component_outputs(&mut self, target_bytes: usize) -> usize {
298        let released = self.runtime.component_tree().trim_outputs(target_bytes);
299        if released > 0 {
300            self.invalidations.invalidate_all();
301        }
302        released
303    }
304
305    pub(crate) fn trim_host_scene(&mut self) -> usize {
306        let before = self.memory_usage().1.rebuildable_bytes;
307        self.clear_host();
308        self.invalidate_all();
309        before
310    }
311
312    #[cfg(feature = "diagnostics-timing")]
313    #[doc(hidden)]
314    pub fn render_timings(&self) -> SessionRenderTimings {
315        self.render_timings
316    }
317
318    pub fn invalidate_all(&mut self) {
319        self.runtime.invalidate_all_components();
320        self.invalidations.invalidate_all();
321    }
322
323    pub fn clear_host(&mut self) {
324        self.host.clear();
325        self.layout.clear();
326        self.tree = HostTree::new();
327        self.render_context = None;
328        self.invalidations = InvalidationSet::new();
329    }
330
331    /// Releases retained drawing data while preserving state, hooks, effects and tasks.
332    pub(crate) fn suspend_rendering(&mut self) {
333        self.runtime.suspend_rendering();
334        self.trim_component_outputs(0);
335        self.trim_host_scene();
336    }
337
338    pub fn reset(&mut self) {
339        self.runtime.reset();
340        self.clear_host();
341    }
342
343    pub fn set_wake(&self, wake: UiWake) {
344        self.runtime.set_wake(wake);
345    }
346
347    pub fn set_task_spawner(&mut self, spawner: UiTaskSpawner) {
348        self.runtime.set_task_spawner(spawner);
349    }
350}
351
352#[cfg(feature = "diagnostics-timing")]
353fn elapsed_ms(started: Instant) -> f32 {
354    started.elapsed().as_secs_f32() * 1_000.0
355}
356
357#[cfg(test)]
358#[path = "session_test.rs"]
359mod tests;