1use std::any::TypeId;
2
3use crate::{
4 Applier, ChildList, Command, CommandQueue, Composer, DirtyBubble, EmittedNode, MutableState,
5 Node, NodeError, NodeId, OwnedMutableState, ParentAttachMode, ParentFrame, debug_scope_label,
6 recompose::RecomposeChildCursor, slot::NodeSlotUpdate,
7};
8
9impl Composer {
10 fn planned_node_parent(&self, id: NodeId) -> Option<NodeId> {
11 if let Some(parent) = self.current_parent_hint().filter(|parent| *parent != id) {
12 return Some(parent);
13 }
14 let mut applier = self.borrow_applier();
15 applier.get_mut(id).ok().and_then(|node| node.parent())
16 }
17
18 fn queue_replaced_slot_node_removal(&self, old_id: NodeId, old_generation: u32) {
19 let current_generation = self.borrow_applier().node_generation(old_id);
20 if current_generation != old_generation {
21 log::trace!(
22 target: "cranpose::compose::emit",
23 "skipping stale replacement cleanup for node #{old_id} (slot_generation={old_generation} current_generation={current_generation})",
24 );
25 return;
26 }
27
28 log::trace!(
29 target: "cranpose::compose::emit",
30 "removing replaced node #{old_id} (generation={old_generation})",
31 );
32 self.commands_mut().push(Command::RemoveNode { id: old_id });
33 }
34
35 #[track_caller]
38 pub fn use_state<T: Clone + 'static>(&self, init: impl FnOnce() -> T) -> MutableState<T> {
39 let source = crate::caller_location_key();
40 let runtime = self.runtime_handle();
41 let state = self.with_slot_session_mut(|slots| {
42 slots.remember(source, || {
43 OwnedMutableState::with_runtime(init(), runtime.clone())
44 })
45 });
46 state.with(super::state::OwnedMutableState::handle)
47 }
48
49 fn trace_scope(&self) -> (usize, Option<&'static str>) {
51 self.current_recompose_scope().map_or((0, None), |scope| {
52 (scope.id(), debug_scope_label(scope.id()))
53 })
54 }
55
56 fn emit_node_box<N: Node + 'static>(
57 &self,
58 source: crate::Key,
59 make_node: impl FnOnce(&mut dyn Applier) -> EmittedNode,
60 ) -> NodeId {
61 let adopted = {
62 let mut skip = 0;
63 loop {
64 let Some(found) = self
65 .with_slot_session_mut(|slots| slots.peek_node_record_by_source(source, skip))
66 else {
67 break None;
68 };
69 let (type_ok, gen_ok) = {
70 let mut applier = self.borrow_applier();
71 let gen_ok = applier.node_generation(found.id) == found.generation;
72 let type_ok = match applier.get_mut(found.id) {
73 Ok(node) => node.as_any_mut().downcast_ref::<N>().is_some(),
74 Err(_) => false,
75 };
76 (type_ok, gen_ok)
77 };
78 if type_ok && gen_ok {
79 break Some(found);
80 }
81 skip += 1;
82 }
83 };
84
85 if let Some(found) = adopted {
86 let id = found.id;
87 if log::log_enabled!(target: "cranpose::compose::emit", log::Level::Trace) {
88 let (scope_id, scope_label) = self.trace_scope();
89 log::trace!(
90 target: "cranpose::compose::emit",
91 "reusing node #{id} as {} [scope_id={scope_id} scope_label={scope_label:?}]",
92 std::any::type_name::<N>(),
93 );
94 }
95 self.attach_to_parent(id);
96 let parent_id = self.planned_node_parent(id);
97 let recorded = self
98 .with_slot_session_mut(|slots| slots.adopt_node_record(found, parent_id, source));
99 match recorded {
100 NodeSlotUpdate::Reused {
101 id: recorded_id,
102 generation,
103 } => {
104 debug_assert_eq!(recorded_id, id);
105 debug_assert_eq!(generation, found.generation);
106 }
107 NodeSlotUpdate::Inserted { .. } => {
108 log::warn!(
109 target: "cranpose::compose::emit",
110 "slot writer inserted node #{id} while reusing the same node identity",
111 );
112 }
113 NodeSlotUpdate::Replaced {
114 old_id,
115 old_generation,
116 ..
117 } => {
118 log::warn!(
119 target: "cranpose::compose::emit",
120 "slot writer replaced node #{old_id} while reusing node #{id}",
121 );
122 self.queue_replaced_slot_node_removal(old_id, old_generation);
123 }
124 }
125 return id;
126 }
127
128 let (id, generation) = {
129 let mut applier = self.borrow_applier();
130 let emitted = make_node(&mut *applier);
131 let id = match emitted {
132 EmittedNode::Fresh(node) => applier.create(node),
133 EmittedNode::Recycled(recycled) => {
134 let (stable_id, node, warm_origin) = recycled.into_parts();
135 let insertion = applier.insert_recycled_node_or_create(stable_id, node);
136 if let Some(error) = insertion.fallback_error.as_ref() {
137 log::warn!(
138 target: "cranpose::compose::emit",
139 "discarding stale recycled stable id #{stable_id}: {error}",
140 );
141 }
142 applier.set_recycled_node_origin(insertion.id, warm_origin);
143 insertion.id
144 }
145 };
146 let generation = applier.node_generation(id);
147 (id, generation)
148 };
149 if log::log_enabled!(target: "cranpose::compose::emit", log::Level::Trace) {
150 let (scope_id, scope_label) = self.trace_scope();
151 log::trace!(
152 target: "cranpose::compose::emit",
153 "creating node #{} (gen={}) as {} [scope_id={} scope_label={:?}]",
154 id,
155 generation,
156 std::any::type_name::<N>(),
157 scope_id,
158 scope_label,
159 );
160 }
161 self.commands_mut().push(Command::MountNode { id });
162 self.attach_to_parent(id);
163 let parent_id = self.planned_node_parent(id);
164 let recorded = self.with_slot_session_mut(|slots| {
165 slots.record_node_with_parent(id, generation, parent_id, source)
166 });
167 match recorded {
168 NodeSlotUpdate::Inserted {
169 id: recorded_id,
170 generation: recorded_generation,
171 } => {
172 debug_assert_eq!(recorded_id, id);
173 debug_assert_eq!(recorded_generation, generation);
174 }
175 NodeSlotUpdate::Replaced {
176 old_id,
177 old_generation,
178 new_id,
179 new_generation,
180 } => {
181 debug_assert_eq!(new_id, id);
182 debug_assert_eq!(new_generation, generation);
183 self.queue_replaced_slot_node_removal(old_id, old_generation);
184 }
185 NodeSlotUpdate::Reused { .. } => {
186 log::warn!(
187 target: "cranpose::compose::emit",
188 "slot writer reported reuse for newly emitted node #{id}",
189 );
190 }
191 }
192 id
193 }
194
195 #[track_caller]
199 pub fn emit_node<N: Node + 'static>(&self, init: impl FnOnce() -> N) -> NodeId {
200 let source = crate::caller_location_key();
201 self.emit_node_box::<N>(source, |_| EmittedNode::Fresh(Box::new(init())))
202 }
203
204 #[track_caller]
207 pub fn emit_recyclable_node<N: Node + 'static>(
208 &self,
209 init: impl FnOnce() -> N,
210 reset: impl FnOnce(&mut N),
211 ) -> NodeId {
212 let source = crate::caller_location_key();
213 self.emit_node_box::<N>(source, |applier| {
214 let key = TypeId::of::<N>();
215 if let Some(mut recycled) = applier.take_recycled_node(key) {
216 if let Some(typed) = recycled.node_mut().as_any_mut().downcast_mut::<N>() {
217 reset(typed);
218 return EmittedNode::Recycled(recycled);
219 }
220 log::warn!(
221 target: "cranpose::compose::emit",
222 "discarding recycled node shell with mismatched type for {}",
223 std::any::type_name::<N>(),
224 );
225 }
226
227 let node = Box::new(init());
228 applier.record_fresh_recyclable_creation(key);
229 if let Some(shell) = node.rehouse_for_recycle() {
230 applier.seed_recycled_node_shell(key, node.recycle_pool_limit(), shell);
231 }
232 EmittedNode::Fresh(node)
233 })
234 }
235
236 fn skip_recompose_child(&self) {
237 let cursor = match self.core.recompose_child_cursor.get() {
238 RecomposeChildCursor::After { first, placed } => RecomposeChildCursor::After {
239 first,
240 placed: placed + 1,
241 },
242 RecomposeChildCursor::At(index) => RecomposeChildCursor::At(index + 1),
243 RecomposeChildCursor::Unknown => return,
244 };
245 self.core.recompose_child_cursor.set(cursor);
246 }
247
248 fn next_recompose_insert_index(&self) -> Option<usize> {
249 let RecomposeChildCursor::At(index) = self.resolve_recompose_child_cursor() else {
250 return None;
251 };
252 self.core
253 .recompose_child_cursor
254 .set(RecomposeChildCursor::At(index + 1));
255 Some(index)
256 }
257
258 pub(crate) fn attach_to_parent(&self, id: NodeId) {
259 if self.attach_to_current_parent(id) {
260 return;
261 }
262 self.attach_without_parent_frame(id);
263 }
264
265 fn attach_to_current_parent(&self, id: NodeId) -> bool {
266 let mut parent_stack = self.parent_stack();
267 if let Some(parent_id) = parent_stack.last().map(|frame| frame.id) {
268 let stale_root_parent = self.core.root.get() == Some(parent_id) && {
269 let mut applier = self.borrow_applier();
270 applier.get_mut(parent_id).is_err()
271 };
272 if stale_root_parent {
273 parent_stack.pop();
274 self.set_root(None);
275 } else {
276 let Some(frame) = parent_stack.last_mut() else {
277 return false;
278 };
279 let attach_mode = frame.attach_mode;
280 if parent_id == id {
281 return true;
282 }
283 let attached_before = frame.previous.get(frame.new_children.len()) == Some(&id);
286 if matches!(attach_mode, ParentAttachMode::DeferredSync) {
287 frame.new_children.push(id);
288 }
289 drop(parent_stack);
290
291 if attached_before {
292 debug_assert!(
293 self.borrow_applier()
294 .get_mut(id)
295 .map_or(true, |node| node.parent().is_none_or(|p| p == parent_id)),
296 "a parent's previous child must name no other parent"
297 );
298 } else {
299 let mut applier = self.borrow_applier();
300 if let Ok(child_node) = applier.get_mut(id) {
301 child_node.set_parent_for_bubbling(parent_id);
302 }
303 }
304 if matches!(attach_mode, ParentAttachMode::ImmediateAppend) {
305 self.commands_mut().push(Command::AttachChild {
306 parent_id,
307 child_id: id,
308 insert_index: None,
309 bubble: DirtyBubble::LAYOUT_AND_MEASURE,
310 });
311 }
312 return true;
313 }
314 }
315 false
316 }
317
318 fn attach_without_parent_frame(&self, id: NodeId) {
319 let in_subcompose = !self.subcompose_stack().is_empty();
320 if in_subcompose {
321 let has_parent = {
322 let mut applier = self.borrow_applier();
323 applier
324 .get_mut(id)
325 .is_ok_and(|node| node.parent().is_some())
326 };
327
328 if !has_parent {
329 let mut subcompose_stack = self.subcompose_stack();
330 if let Some(frame) = subcompose_stack.last_mut() {
331 frame.nodes.push(id);
332 }
333 }
334 return;
335 }
336
337 if let Some(parent_hint) = self.core.recompose_parent_hint.get() {
338 if parent_hint == id {
339 debug_assert_ne!(
340 parent_hint, id,
341 "a node cannot be attached as its own parent"
342 );
343 return;
344 }
345 let parent_status = {
346 let mut applier = self.borrow_applier();
347 applier.get_mut(id).map_or(None, |node| node.parent())
348 };
349 match parent_status {
350 Some(existing) if existing == parent_hint => {
351 self.skip_recompose_child();
352 }
353 None => {
354 let insert_index = self.next_recompose_insert_index();
355 self.commands_mut().push(Command::AttachChild {
356 parent_id: parent_hint,
357 child_id: id,
358 insert_index,
359 bubble: DirtyBubble::LAYOUT_AND_MEASURE,
360 });
361 }
362 Some(_) => {}
363 }
364 return;
365 }
366
367 let has_parent = {
368 let mut applier = self.borrow_applier();
369 applier
370 .get_mut(id)
371 .is_ok_and(|node| node.parent().is_some())
372 };
373 if has_parent {
374 return;
375 }
376
377 self.set_root(Some(id));
378 }
379
380 pub fn with_node_mut<N: Node + 'static, R>(
383 &self,
384 id: NodeId,
385 f: impl FnOnce(&mut N) -> R,
386 ) -> Result<R, NodeError> {
387 let mut applier = self.borrow_applier();
388 let node = applier.get_mut(id)?;
389 let typed =
390 node.as_any_mut()
391 .downcast_mut::<N>()
392 .ok_or_else(|| NodeError::TypeMismatch {
393 id,
394 expected: std::any::type_name::<N>(),
395 })?;
396 Ok(f(typed))
397 }
398
399 pub fn push_parent(&self, id: NodeId) {
402 let in_subcompose = !self.core.subcompose_stack.borrow().is_empty();
403
404 let children = self.get_node_children(id);
405 let previous = if children.is_empty() {
406 ChildList::new()
407 } else {
408 ChildList::from_slice(&children)
409 };
410 let attach_mode = if in_subcompose || !previous.is_empty() {
411 ParentAttachMode::DeferredSync
412 } else {
413 ParentAttachMode::ImmediateAppend
414 };
415
416 self.parent_stack().push(ParentFrame {
417 id,
418 previous,
419 new_children: ChildList::new(),
420 new_children_membership: None,
421 attach_mode,
422 synthetic_root: false,
423 });
424 }
425
426 pub fn compose_no_children(&self, id: NodeId, holds_children: bool) {
432 if holds_children || !self.core.subcompose_stack.borrow().is_empty() {
433 self.push_parent(id);
434 self.pop_parent();
435 }
436 }
437
438 pub fn pop_parent(&self) {
441 let frame_opt = {
442 let mut stack = self.parent_stack();
443 stack.pop()
444 };
445 if let Some(frame) = frame_opt {
446 let ParentFrame {
447 id,
448 previous,
449 new_children,
450 new_children_membership: _new_children_membership,
451 attach_mode,
452 synthetic_root: _synthetic_root,
453 } = frame;
454
455 log::trace!(target: "cranpose::compose::parent", "pop_parent: node #{id}");
456 log::trace!(
457 target: "cranpose::compose::parent",
458 "previous children: {previous:?}"
459 );
460 log::trace!(
461 target: "cranpose::compose::parent",
462 "new children: {new_children:?}"
463 );
464 if matches!(attach_mode, ParentAttachMode::DeferredSync) {
465 let _ = previous;
466 self.commands_mut().push(Command::SyncChildren {
467 parent_id: id,
468 expected_children: new_children,
469 });
470 }
471 }
472 }
473
474 pub(crate) fn take_commands(&self) -> CommandQueue {
475 std::mem::take(&mut *self.commands_mut())
476 }
477
478 pub fn apply_pending_commands(&self) -> Result<(), NodeError> {
483 self.resolve_recompose_child_cursor();
484 let mut commands = self.take_commands();
485 let runtime_handle = self.runtime_handle();
486 let result = {
487 let mut applier = self.borrow_applier();
488 let mut result = commands.apply(&mut *applier);
489 if result.is_ok() {
490 for update in runtime_handle.take_updates() {
491 if let Err(err) = update.apply(&mut *applier) {
492 result = Err(err);
493 break;
494 }
495 }
496 }
497 result
498 };
499 let mut queued = self.commands_mut();
501 if queued.len() == 0 {
502 std::mem::swap(&mut *queued, &mut commands);
503 }
504 drop(queued);
505 if result.is_err() {
506 let host = self.active_slots_host();
507 if !host.has_active_pass() {
508 host.abandon_after_apply_failure();
509 }
510 }
511 result
512 }
513
514 pub fn register_side_effect(&self, effect: impl FnOnce() + 'static) {
516 self.side_effects_mut().push(Box::new(effect));
517 }
518
519 pub fn take_side_effects(&self) -> Vec<Box<dyn FnOnce()>> {
521 std::mem::take(&mut *self.side_effects_mut())
522 }
523
524 pub(crate) fn root(&self) -> Option<NodeId> {
525 self.core.root.get()
526 }
527
528 pub(crate) fn set_root(&self, node: Option<NodeId>) {
529 self.core.root.set(node);
530 }
531}