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