1use crate::{
2 collections::map::{HashMap, HashSet},
3 runtime, snapshot_state_observer, Applier, ApplierGuard, ApplierHost, CommandQueue, Composer,
4 CompositionPassDebugStats, ConcreteApplierHost, DefaultScheduler, Key, NodeError, NodeId,
5 RecomposeScope, Runtime, RuntimeHandle, ScopeId, SlotTable, SlotTableDebugStats,
6 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 fn clear_pending_invalid_scopes(&mut self) -> HashSet<ScopeId> {
141 let runtime_handle = self.runtime_handle();
142 let mut cleared = HashSet::default();
143 for (id, weak) in runtime_handle.take_invalidated_scopes() {
144 if let Some(inner) = weak.upgrade() {
145 RecomposeScope { inner }.mark_recomposed();
146 } else {
147 runtime_handle.mark_scope_recomposed(id);
148 }
149 cleared.insert(id);
150 }
151 cleared
152 }
153
154 fn render_root_pass(
155 &mut self,
156 key: Key,
157 content: &mut dyn FnMut(),
158 clear_pending_invalid_scopes: bool,
159 ) -> Result<HashSet<ScopeId>, NodeError> {
160 self.root_key = Some(key);
161 self.root_render_requested = false;
162 let cleared_invalid_scopes = if clear_pending_invalid_scopes {
163 self.clear_pending_invalid_scopes()
164 } else {
165 HashSet::default()
166 };
167 let runtime_handle = self.runtime_handle();
168 runtime_handle.drain_ui();
169 let side_effects = {
170 let _teardown = runtime::enter_state_teardown_scope();
171 let composer = Composer::new(
172 Rc::clone(&self.slots),
173 self.applier.clone(),
174 runtime_handle.clone(),
175 self.observer.clone(),
176 self.root,
177 );
178 self.observer.begin_frame();
179 let (root, commands, side_effects) = composer.install(|composer| {
180 let (_, _) = composer.with_slot_host_pass(
181 Rc::clone(&self.slots),
182 crate::slot_table::SlotPassMode::Compose,
183 |composer| composer.with_group(key, |_| content()),
184 );
185 let root = composer.root();
186 let commands = composer.take_commands();
187 let side_effects = composer.take_side_effects();
188 (root, commands, side_effects)
189 });
190 self.record_pass_stats(&commands, &side_effects);
191 {
192 let mut applier = self.applier.borrow_dyn();
193 commands.apply(&mut *applier)?;
194 for update in runtime_handle.take_updates() {
195 update.apply(&mut *applier)?;
196 }
197 }
198
199 self.root = root;
200 side_effects
201 };
202 runtime_handle.drain_ui();
203 for effect in side_effects {
204 effect();
205 }
206 runtime_handle.drain_ui();
207 Ok(cleared_invalid_scopes)
208 }
209
210 fn reconcile_with_content(
211 &mut self,
212 key: Key,
213 content: &mut dyn FnMut(),
214 mut suppressed_invalid_scopes: Option<HashSet<ScopeId>>,
215 ) -> Result<bool, NodeError> {
216 self.root_key = Some(key);
217 let mut did_work = false;
218 let mut root_render_replays = 0usize;
219 loop {
220 did_work |=
221 self.process_invalid_scopes_filtered(suppressed_invalid_scopes.take().as_ref())?;
222 if !self.take_root_render_request() {
223 return Ok(did_work);
224 }
225
226 root_render_replays += 1;
227 if root_render_replays > ROOT_RENDER_REPLAY_LIMIT {
228 debug_assert!(
229 false,
230 "root render replay exceeded {ROOT_RENDER_REPLAY_LIMIT} iterations — reentrant render bug"
231 );
232 log::error!(
233 "root render replay looped past {ROOT_RENDER_REPLAY_LIMIT} iterations; breaking to keep UI responsive"
234 );
235 return Ok(true);
236 }
237
238 suppressed_invalid_scopes = Some(self.render_root_pass(key, content, true)?);
239 did_work = true;
240 }
241 }
242
243 pub fn render(&mut self, key: Key, mut content: impl FnMut()) -> Result<(), NodeError> {
244 self.reset_last_pass_stats();
245 let _ = self.render_root_pass(key, &mut content, false)?;
246 let _ = self.process_invalid_scopes()?;
247 Ok(())
248 }
249
250 pub fn render_stable(&mut self, key: Key, mut content: impl FnMut()) -> Result<(), NodeError> {
253 self.reset_last_pass_stats();
254 let suppressed_invalid_scopes = self.render_root_pass(key, &mut content, true)?;
255 let _ = self.reconcile_with_content(key, &mut content, Some(suppressed_invalid_scopes))?;
256 Ok(())
257 }
258
259 pub fn reconcile(&mut self, key: Key, mut content: impl FnMut()) -> Result<bool, NodeError> {
262 self.reconcile_with_content(key, &mut content, None)
263 }
264
265 pub fn should_render(&self) -> bool {
274 self.root_render_requested || self.runtime.needs_frame() || self.runtime.has_updates()
275 }
276
277 pub fn runtime_handle(&self) -> RuntimeHandle {
278 self.runtime.handle()
279 }
280
281 pub fn applier_mut(&mut self) -> ApplierGuard<'_, A> {
282 ApplierGuard::new(self.applier.borrow_typed())
283 }
284
285 pub fn root(&self) -> Option<NodeId> {
286 self.root
287 }
288
289 pub fn debug_dump_slot_table_groups(&self) -> Vec<(usize, Key, Option<ScopeId>, usize)> {
290 self.slots.borrow().debug_dump_groups()
291 }
292
293 pub fn debug_dump_all_slots(&self) -> Vec<(usize, String)> {
294 self.slots.borrow().debug_dump_all_slots()
295 }
296
297 pub fn slot_table_heap_bytes(&self) -> usize {
298 self.slots.borrow().heap_bytes()
299 }
300
301 pub fn debug_slot_table_stats(&self) -> SlotTableDebugStats {
302 self.slots.debug_stats()
303 }
304
305 pub fn debug_slot_value_type_counts(&self, limit: usize) -> Vec<SlotValueTypeDebugStat> {
306 self.slots.borrow().debug_value_type_counts(limit)
307 }
308
309 pub fn debug_observer_stats(&self) -> snapshot_state_observer::SnapshotStateObserverDebugStats {
310 self.observer.debug_stats()
311 }
312
313 pub fn debug_last_pass_stats(&self) -> CompositionPassDebugStats {
314 self.last_pass_stats
315 }
316
317 fn process_invalid_scopes_filtered(
318 &mut self,
319 suppressed_invalid_scopes: Option<&HashSet<ScopeId>>,
320 ) -> Result<bool, NodeError> {
321 let runtime_handle = self.runtime_handle();
322 let mut did_recompose = false;
323 let mut loop_count = 0;
324 loop {
325 loop_count += 1;
326 if loop_count > ROOT_RENDER_REPLAY_LIMIT {
327 debug_assert!(
328 false,
329 "process_invalid_scopes exceeded {ROOT_RENDER_REPLAY_LIMIT} iterations — reentrant recomposition bug (a scope keeps re-invalidating)"
330 );
331 log::error!(
332 "process_invalid_scopes looped past {ROOT_RENDER_REPLAY_LIMIT} iterations; breaking to keep UI responsive"
333 );
334 break;
335 }
336 runtime_handle.drain_ui();
337 let pending = runtime_handle.take_invalidated_scopes();
338 if pending.is_empty() {
339 break;
340 }
341 let mut scopes = Vec::new();
342 for (id, weak) in pending {
343 if suppressed_invalid_scopes.is_some_and(|suppressed| suppressed.contains(&id)) {
344 if let Some(inner) = weak.upgrade() {
345 RecomposeScope { inner }.mark_recomposed();
346 } else {
347 runtime_handle.mark_scope_recomposed(id);
348 }
349 continue;
350 }
351 if let Some(inner) = weak.upgrade() {
352 scopes.push(RecomposeScope { inner });
353 } else {
354 runtime_handle.mark_scope_recomposed(id);
355 }
356 }
357 if scopes.is_empty() {
358 continue;
359 }
360 did_recompose = true;
361 let runtime_clone = runtime_handle.clone();
362 let root_host = self.slots_host();
363 let mut scope_groups: Vec<(Rc<SlotsHost>, Vec<RecomposeScope>)> = Vec::new();
364 let mut scope_group_index: HashMap<usize, usize> = HashMap::default();
365 for scope in scopes {
366 let host = scope.slots_host().unwrap_or_else(|| Rc::clone(&root_host));
367 let host_key = Rc::as_ptr(&host) as usize;
368 if let Some(index) = scope_group_index.get(&host_key).copied() {
369 scope_groups[index].1.push(scope);
370 } else {
371 scope_group_index.insert(host_key, scope_groups.len());
372 scope_groups.push((host, vec![scope]));
373 }
374 }
375 let side_effects = {
376 let _teardown = runtime::enter_state_teardown_scope();
377 let composer = Composer::new(
378 Rc::clone(&root_host),
379 self.applier_host(),
380 runtime_clone,
381 self.observer.clone(),
382 self.root,
383 );
384 self.observer.begin_frame();
385 let (root, commands, side_effects, requested_root_render, removed_orphaned) =
386 composer.install(|composer| {
387 let mut removed_orphaned = false;
388 for (host, scopes) in scope_groups.into_iter() {
389 let (_, outcome) = composer.with_slot_host_pass(
390 host,
391 crate::slot_table::SlotPassMode::Recompose,
392 |composer| {
393 for scope in &scopes {
394 composer.recranpose_group(scope);
395 }
396 },
397 );
398 removed_orphaned |= outcome.removed_orphaned_nodes;
399 }
400 let root = composer.root();
401 let commands = composer.take_commands();
402 let side_effects = composer.take_side_effects();
403 let requested_root_render = composer.take_root_render_request();
404 (
405 root,
406 commands,
407 side_effects,
408 requested_root_render,
409 removed_orphaned,
410 )
411 });
412 self.record_pass_stats(&commands, &side_effects);
413 {
414 let mut applier = self.applier.borrow_dyn();
415 commands.apply(&mut *applier)?;
416 for update in runtime_handle.take_updates() {
417 update.apply(&mut *applier)?;
418 }
419 }
420 if root.is_some() {
421 self.root = root;
422 }
423 if removed_orphaned {
424 did_recompose = true;
425 self.root_render_requested = true;
426 }
427 if requested_root_render {
428 self.root_render_requested = true;
429 }
430 side_effects
431 };
432 runtime_handle.drain_ui();
433 for effect in side_effects {
434 effect();
435 }
436 runtime_handle.drain_ui();
437 if self.root_render_requested {
438 break;
439 }
440 }
441 self.finalize_runtime_state();
442 Ok(did_recompose)
443 }
444
445 pub fn process_invalid_scopes(&mut self) -> Result<bool, NodeError> {
446 self.process_invalid_scopes_filtered(None)
447 }
448
449 pub fn flush_pending_node_updates(&mut self) -> Result<(), NodeError> {
450 let updates = self.runtime_handle().take_updates();
451 let mut applier = self.applier.borrow_dyn();
452 for update in updates {
453 update.apply(&mut *applier)?;
454 }
455 Ok(())
456 }
457}
458
459impl<A: Applier + 'static> Drop for Composition<A> {
460 fn drop(&mut self) {
461 self.observer.stop();
462 }
463}