1use crate::{
2 collections::map::{HashMap, HashSet},
3 remove_child_and_cleanup_now, runtime, snapshot_state_observer, Applier, ApplierGuard,
4 ApplierHost, CommandQueue, Composer, CompositionPassDebugStats, ConcreteApplierHost,
5 DefaultScheduler, Key, NodeError, NodeId, RecomposeScope, Runtime, RuntimeHandle, ScopeId,
6 SlotTable, SlotTableDebugStats, SlotValueTypeDebugStat, SlotsHost, SnapshotStateObserver,
7};
8use std::rc::Rc;
9use std::sync::Arc;
10
11pub struct Composition<A: Applier + 'static> {
12 pub(crate) slots: Rc<SlotsHost>,
13 pub(crate) applier: Rc<ConcreteApplierHost<A>>,
14 pub(crate) runtime: Runtime,
15 pub(crate) observer: SnapshotStateObserver,
16 pub(crate) root: Option<NodeId>,
17 pub(crate) root_key: Option<Key>,
18 pub(crate) root_render_requested: bool,
19 pub(crate) last_pass_stats: CompositionPassDebugStats,
20}
21
22pub const ROOT_RENDER_REPLAY_LIMIT: usize = 100;
38
39impl<A: Applier + 'static> Composition<A> {
40 pub fn new(applier: A) -> Self {
41 Self::with_runtime(applier, Runtime::new(Arc::new(DefaultScheduler)))
42 }
43
44 pub fn with_runtime(applier: A, runtime: Runtime) -> Self {
45 let slots = Rc::new(SlotsHost::new(SlotTable::new()));
46 let applier = Rc::new(ConcreteApplierHost::new(applier));
47 let observer_handle = runtime.handle();
48 let observer = SnapshotStateObserver::new(move |callback| {
49 observer_handle.enqueue_ui_task(callback);
50 });
51 observer.start();
52 Self {
53 slots,
54 applier,
55 runtime,
56 observer,
57 root: None,
58 root_key: None,
59 root_render_requested: false,
60 last_pass_stats: CompositionPassDebugStats::default(),
61 }
62 }
63
64 pub fn root_key(&self) -> Option<Key> {
67 self.root_key
68 }
69
70 fn slots_host(&self) -> Rc<SlotsHost> {
71 Rc::clone(&self.slots)
72 }
73
74 fn applier_host(&self) -> Rc<dyn ApplierHost> {
75 self.applier.clone()
76 }
77
78 fn reset_last_pass_stats(&mut self) {
79 self.last_pass_stats = CompositionPassDebugStats::default();
80 }
81
82 pub fn take_root_render_request(&mut self) -> bool {
83 std::mem::take(&mut self.root_render_requested)
84 }
85
86 fn record_pass_stats(
87 &mut self,
88 commands: &CommandQueue,
89 side_effects: &Vec<Box<dyn FnOnce()>>,
90 ) {
91 self.last_pass_stats.commands_len = self.last_pass_stats.commands_len.max(commands.len());
92 self.last_pass_stats.commands_cap =
93 self.last_pass_stats.commands_cap.max(commands.capacity());
94 self.last_pass_stats.command_payload_len_bytes = self
95 .last_pass_stats
96 .command_payload_len_bytes
97 .max(commands.payload_len_bytes());
98 self.last_pass_stats.command_payload_cap_bytes = self
99 .last_pass_stats
100 .command_payload_cap_bytes
101 .max(commands.payload_capacity_bytes());
102 self.last_pass_stats.sync_children_len = self
103 .last_pass_stats
104 .sync_children_len
105 .max(commands.sync_children.len());
106 self.last_pass_stats.sync_children_cap = self
107 .last_pass_stats
108 .sync_children_cap
109 .max(commands.sync_children.capacity());
110 self.last_pass_stats.sync_child_ids_len = self
111 .last_pass_stats
112 .sync_child_ids_len
113 .max(commands.sync_child_ids.len());
114 self.last_pass_stats.sync_child_ids_cap = self
115 .last_pass_stats
116 .sync_child_ids_cap
117 .max(commands.sync_child_ids.capacity());
118 self.last_pass_stats.side_effects_len = self
119 .last_pass_stats
120 .side_effects_len
121 .max(side_effects.len());
122 self.last_pass_stats.side_effects_cap = self
123 .last_pass_stats
124 .side_effects_cap
125 .max(side_effects.capacity());
126 }
127
128 fn finalize_runtime_state(&mut self) {
129 let runtime_handle = self.runtime_handle();
130 self.observer.prune_dead_scopes();
131 if !self.runtime.has_updates()
132 && !runtime_handle.has_invalid_scopes()
133 && !runtime_handle.has_frame_callbacks()
134 && !runtime_handle.has_pending_ui()
135 {
136 self.runtime.set_needs_frame(false);
137 }
138 }
139
140 pub(crate) fn finalize_compaction(&mut self) -> Result<bool, NodeError> {
141 let mut removed_orphaned = false;
142 let mut orphaned_node_count = 0usize;
143 self.slots.borrow_mut().compact();
144 let orphaned = self.slots.borrow_mut().drain_orphaned_node_ids();
145 {
146 let mut applier = self.applier.borrow_dyn();
147 for orphaned in orphaned {
148 if !matches!(
149 self.slots.borrow().orphaned_node_state(orphaned),
150 crate::slot_table::NodeSlotState::Missing
151 ) {
152 continue;
153 }
154 if applier.node_generation(orphaned.id) != orphaned.generation {
155 continue;
156 }
157 removed_orphaned = true;
158 orphaned_node_count += 1;
159 let parent_id = applier
160 .get_mut(orphaned.id)
161 .ok()
162 .and_then(|node| node.parent());
163 if let Some(parent_id) = parent_id {
164 let _ = remove_child_and_cleanup_now(&mut *applier, parent_id, orphaned.id);
165 continue;
166 }
167 if let Ok(node) = applier.get_mut(orphaned.id) {
168 node.on_removed_from_parent();
169 node.unmount();
170 }
171 let _ = applier.remove(orphaned.id);
172 }
173 }
174 self.applier_host().compact();
175 self.applier.borrow_dyn().clear_recycled_nodes();
176 if removed_orphaned {
177 log::debug!(
178 "finalize_compaction: removing {} orphaned nodes",
179 orphaned_node_count
180 );
181 }
182 Ok(removed_orphaned)
183 }
184
185 fn clear_pending_invalid_scopes(&mut self) -> HashSet<ScopeId> {
186 let runtime_handle = self.runtime_handle();
187 let mut cleared = HashSet::default();
188 for (id, _) in runtime_handle.take_invalidated_scopes() {
189 runtime_handle.mark_scope_recomposed(id);
190 cleared.insert(id);
191 }
192 cleared
193 }
194
195 fn render_root_pass(
196 &mut self,
197 key: Key,
198 content: &mut dyn FnMut(),
199 clear_pending_invalid_scopes: bool,
200 ) -> Result<HashSet<ScopeId>, NodeError> {
201 self.root_key = Some(key);
202 self.root_render_requested = false;
203 let cleared_invalid_scopes = if clear_pending_invalid_scopes {
204 self.clear_pending_invalid_scopes()
205 } else {
206 HashSet::default()
207 };
208 self.slots.borrow_mut().reset();
209 let runtime_handle = self.runtime_handle();
210 runtime_handle.drain_ui();
211 let side_effects = {
212 let _teardown = runtime::enter_state_teardown_scope();
213 let composer = Composer::new(
214 Rc::clone(&self.slots),
215 self.applier.clone(),
216 runtime_handle.clone(),
217 self.observer.clone(),
218 self.root,
219 );
220 self.observer.begin_frame();
221 let (root, commands, side_effects) = composer.install(|composer| {
222 composer.with_group(key, |_| content());
223 let root = composer.root();
224 let commands = composer.take_commands();
225 let side_effects = composer.take_side_effects();
226 (root, commands, side_effects)
227 });
228 self.record_pass_stats(&commands, &side_effects);
229 {
230 let mut applier = self.applier.borrow_dyn();
231 commands.apply(&mut *applier)?;
232 for update in runtime_handle.take_updates() {
233 update.apply(&mut *applier)?;
234 }
235 }
236
237 self.root = root;
238 {
239 let mut slots = self.slots.borrow_mut();
240 let _ = slots.finalize_current_group();
241 slots.flush();
242 }
243 let _ = self.finalize_compaction()?;
244 side_effects
245 };
246 runtime_handle.drain_ui();
247 for effect in side_effects {
248 effect();
249 }
250 runtime_handle.drain_ui();
251 Ok(cleared_invalid_scopes)
252 }
253
254 fn reconcile_with_content(
255 &mut self,
256 key: Key,
257 content: &mut dyn FnMut(),
258 mut suppressed_invalid_scopes: Option<HashSet<ScopeId>>,
259 ) -> Result<bool, NodeError> {
260 self.root_key = Some(key);
261 let mut did_work = false;
262 let mut root_render_replays = 0usize;
263 loop {
264 did_work |=
265 self.process_invalid_scopes_filtered(suppressed_invalid_scopes.take().as_ref())?;
266 if !self.take_root_render_request() {
267 return Ok(did_work);
268 }
269
270 root_render_replays += 1;
271 if root_render_replays > ROOT_RENDER_REPLAY_LIMIT {
272 debug_assert!(
273 false,
274 "root render replay exceeded {ROOT_RENDER_REPLAY_LIMIT} iterations — reentrant render bug"
275 );
276 log::error!(
277 "root render replay looped past {ROOT_RENDER_REPLAY_LIMIT} iterations; breaking to keep UI responsive"
278 );
279 return Ok(true);
280 }
281
282 suppressed_invalid_scopes = Some(self.render_root_pass(key, content, true)?);
283 did_work = true;
284 }
285 }
286
287 pub fn render(&mut self, key: Key, mut content: impl FnMut()) -> Result<(), NodeError> {
288 self.reset_last_pass_stats();
289 let _ = self.render_root_pass(key, &mut content, false)?;
290 let _ = self.process_invalid_scopes()?;
291 Ok(())
292 }
293
294 pub fn render_stable(&mut self, key: Key, mut content: impl FnMut()) -> Result<(), NodeError> {
297 self.reset_last_pass_stats();
298 let suppressed_invalid_scopes = self.render_root_pass(key, &mut content, true)?;
299 let _ = self.reconcile_with_content(key, &mut content, Some(suppressed_invalid_scopes))?;
300 Ok(())
301 }
302
303 pub fn reconcile(&mut self, key: Key, mut content: impl FnMut()) -> Result<bool, NodeError> {
306 self.reconcile_with_content(key, &mut content, None)
307 }
308
309 pub fn should_render(&self) -> bool {
318 self.root_render_requested || self.runtime.needs_frame() || self.runtime.has_updates()
319 }
320
321 pub fn runtime_handle(&self) -> RuntimeHandle {
322 self.runtime.handle()
323 }
324
325 pub fn applier_mut(&mut self) -> ApplierGuard<'_, A> {
326 ApplierGuard::new(self.applier.borrow_typed())
327 }
328
329 pub fn root(&self) -> Option<NodeId> {
330 self.root
331 }
332
333 pub fn debug_dump_slot_table_groups(&self) -> Vec<(usize, Key, Option<ScopeId>, usize)> {
334 self.slots.borrow().debug_dump_groups()
335 }
336
337 pub fn debug_dump_all_slots(&self) -> Vec<(usize, String)> {
338 self.slots.borrow().debug_dump_all_slots()
339 }
340
341 pub fn slot_table_heap_bytes(&self) -> usize {
342 self.slots.borrow().heap_bytes()
343 }
344
345 pub fn debug_slot_table_stats(&self) -> SlotTableDebugStats {
346 self.slots.borrow().debug_stats()
347 }
348
349 pub fn debug_slot_value_type_counts(&self, limit: usize) -> Vec<SlotValueTypeDebugStat> {
350 self.slots.borrow().debug_value_type_counts(limit)
351 }
352
353 pub fn debug_observer_stats(&self) -> snapshot_state_observer::SnapshotStateObserverDebugStats {
354 self.observer.debug_stats()
355 }
356
357 pub fn debug_last_pass_stats(&self) -> CompositionPassDebugStats {
358 self.last_pass_stats
359 }
360
361 fn process_invalid_scopes_filtered(
362 &mut self,
363 suppressed_invalid_scopes: Option<&HashSet<ScopeId>>,
364 ) -> Result<bool, NodeError> {
365 let runtime_handle = self.runtime_handle();
366 let mut did_recompose = false;
367 let mut loop_count = 0;
368 loop {
369 loop_count += 1;
370 if loop_count > ROOT_RENDER_REPLAY_LIMIT {
371 debug_assert!(
372 false,
373 "process_invalid_scopes exceeded {ROOT_RENDER_REPLAY_LIMIT} iterations — reentrant recomposition bug (a scope keeps re-invalidating)"
374 );
375 log::error!(
376 "process_invalid_scopes looped past {ROOT_RENDER_REPLAY_LIMIT} iterations; breaking to keep UI responsive"
377 );
378 break;
379 }
380 runtime_handle.drain_ui();
381 let pending = runtime_handle.take_invalidated_scopes();
382 if pending.is_empty() {
383 break;
384 }
385 let mut scopes = Vec::new();
386 for (id, weak) in pending {
387 if suppressed_invalid_scopes.is_some_and(|suppressed| suppressed.contains(&id)) {
388 runtime_handle.mark_scope_recomposed(id);
389 continue;
390 }
391 if let Some(inner) = weak.upgrade() {
392 scopes.push(RecomposeScope { inner });
393 } else {
394 runtime_handle.mark_scope_recomposed(id);
395 }
396 }
397 if scopes.is_empty() {
398 continue;
399 }
400 did_recompose = true;
401 let runtime_clone = runtime_handle.clone();
402 let root_host = self.slots_host();
403 let mut scope_groups: Vec<(Rc<SlotsHost>, Vec<RecomposeScope>)> = Vec::new();
404 let mut scope_group_index: HashMap<usize, usize> = HashMap::default();
405 for scope in scopes {
406 let host = scope.slots_host().unwrap_or_else(|| Rc::clone(&root_host));
407 let host_key = Rc::as_ptr(&host) as usize;
408 if let Some(index) = scope_group_index.get(&host_key).copied() {
409 scope_groups[index].1.push(scope);
410 } else {
411 scope_group_index.insert(host_key, scope_groups.len());
412 scope_groups.push((host, vec![scope]));
413 }
414 }
415 let side_effects = {
416 let _teardown = runtime::enter_state_teardown_scope();
417 let composer = Composer::new(
418 Rc::clone(&root_host),
419 self.applier_host(),
420 runtime_clone,
421 self.observer.clone(),
422 self.root,
423 );
424 self.observer.begin_frame();
425 let (root, commands, side_effects, requested_root_render) =
426 composer.install(|composer| {
427 for (host, scopes) in scope_groups.into_iter() {
428 if Rc::ptr_eq(&host, &root_host) {
429 for scope in &scopes {
430 composer.recranpose_group(scope);
431 }
432 } else {
433 composer.with_slot_override(host, |composer| {
434 for scope in &scopes {
435 composer.recranpose_group(scope);
436 }
437 });
438 }
439 }
440 let root = composer.root();
441 let commands = composer.take_commands();
442 let side_effects = composer.take_side_effects();
443 let requested_root_render = composer.take_root_render_request();
444 (root, commands, side_effects, requested_root_render)
445 });
446 self.record_pass_stats(&commands, &side_effects);
447 {
448 let mut applier = self.applier.borrow_dyn();
449 commands.apply(&mut *applier)?;
450 for update in runtime_handle.take_updates() {
451 update.apply(&mut *applier)?;
452 }
453 }
454 if root.is_some() {
455 self.root = root;
456 }
457 {
458 let mut slots = self.slots.borrow_mut();
459 slots.flush();
460 }
461 let removed_orphaned = self.finalize_compaction()?;
462 if removed_orphaned {
463 did_recompose = true;
464 self.root_render_requested = true;
465 }
466 if requested_root_render {
467 self.root_render_requested = true;
468 }
469 side_effects
470 };
471 runtime_handle.drain_ui();
472 for effect in side_effects {
473 effect();
474 }
475 runtime_handle.drain_ui();
476 if self.root_render_requested {
477 break;
478 }
479 }
480 self.finalize_runtime_state();
481 Ok(did_recompose)
482 }
483
484 pub fn process_invalid_scopes(&mut self) -> Result<bool, NodeError> {
485 self.process_invalid_scopes_filtered(None)
486 }
487
488 pub fn flush_pending_node_updates(&mut self) -> Result<(), NodeError> {
489 let updates = self.runtime_handle().take_updates();
490 let mut applier = self.applier.borrow_dyn();
491 for update in updates {
492 update.apply(&mut *applier)?;
493 }
494 Ok(())
495 }
496}
497
498impl<A: Applier + 'static> Drop for Composition<A> {
499 fn drop(&mut self) {
500 self.observer.stop();
501 }
502}