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