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#[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 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;