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