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 slot::NodeSlotUpdate,
7};
8
9impl Composer {
10 fn recorded_node_parent(&self, id: NodeId) -> Option<NodeId> {
11 let mut applier = self.borrow_applier();
12 applier.get_mut(id).ok().and_then(|node| node.parent())
13 }
14
15 fn queue_replaced_slot_node_removal(&self, old_id: NodeId, old_generation: u32) {
16 let current_generation = self.borrow_applier().node_generation(old_id);
17 if current_generation != old_generation {
18 log::trace!(
19 target: "cranpose::compose::emit",
20 "skipping stale replacement cleanup for node #{old_id} (slot_generation={old_generation} current_generation={current_generation})",
21 );
22 return;
23 }
24
25 log::trace!(
26 target: "cranpose::compose::emit",
27 "removing replaced node #{old_id} (generation={old_generation})",
28 );
29 self.commands_mut().push(Command::RemoveNode { id: old_id });
30 }
31
32 #[track_caller]
33 pub fn use_state<T: Clone + 'static>(&self, init: impl FnOnce() -> T) -> MutableState<T> {
34 let source = crate::caller_location_key();
35 let runtime = self.runtime_handle();
36 let state = self.with_slot_session_mut(|slots| {
37 slots.remember(source, || {
38 OwnedMutableState::with_runtime(init(), runtime.clone())
39 })
40 });
41 state.with(|state| state.handle())
42 }
43
44 fn emit_node_box<N: Node + 'static>(
45 &self,
46 source: crate::Key,
47 make_node: impl FnOnce(&mut dyn Applier) -> EmittedNode,
48 ) -> NodeId {
49 let adopted = {
50 let mut skip = 0;
51 loop {
52 let Some((id, slot_gen)) = self
53 .with_slot_session_mut(|slots| slots.peek_node_record_by_source(source, skip))
54 else {
55 break None;
56 };
57 let (type_ok, gen_ok) = {
58 let mut applier = self.borrow_applier();
59 let gen_ok = applier.node_generation(id) == slot_gen;
60 let type_ok = match applier.get_mut(id) {
61 Ok(node) => node.as_any_mut().downcast_ref::<N>().is_some(),
62 Err(_) => false,
63 };
64 (type_ok, gen_ok)
65 };
66 if type_ok && gen_ok {
67 let committed = self.with_slot_session_mut(|slots| {
68 slots.adopt_node_record_by_source(source, skip)
69 });
70 debug_assert_eq!(committed, Some((id, slot_gen)));
71 break Some((id, slot_gen));
72 }
73 skip += 1;
74 }
75 };
76
77 if let Some((id, slot_gen)) = adopted {
78 let scope_debug = self
79 .current_recompose_scope()
80 .map(|scope| (scope.id(), debug_scope_label(scope.id())))
81 .unwrap_or((0, None));
82 log::trace!(
83 target: "cranpose::compose::emit",
84 "reusing node #{id} as {} [scope_id={} scope_label={:?}]",
85 std::any::type_name::<N>(),
86 scope_debug.0,
87 scope_debug.1,
88 );
89 self.commands_mut().push(Command::update_node::<N>(id));
90 self.attach_to_parent(id);
91 let parent_id = self.recorded_node_parent(id);
92 let recorded = self.with_slot_session_mut(|slots| {
93 slots.record_node_with_parent(id, slot_gen, parent_id, source)
94 });
95 match recorded {
96 NodeSlotUpdate::Reused {
97 id: recorded_id,
98 generation,
99 } => {
100 debug_assert_eq!(recorded_id, id);
101 debug_assert_eq!(generation, slot_gen);
102 }
103 NodeSlotUpdate::Inserted { .. } => {
104 log::warn!(
105 target: "cranpose::compose::emit",
106 "slot writer inserted node #{id} while reusing the same node identity",
107 );
108 }
109 NodeSlotUpdate::Replaced {
110 old_id,
111 old_generation,
112 ..
113 } => {
114 log::warn!(
115 target: "cranpose::compose::emit",
116 "slot writer replaced node #{old_id} while reusing node #{id}",
117 );
118 self.queue_replaced_slot_node_removal(old_id, old_generation);
119 }
120 }
121 self.core.last_node_reused.set(Some(true));
122 return id;
123 }
124
125 let (id, generation) = {
127 let mut applier = self.borrow_applier();
128 let emitted = make_node(&mut *applier);
129 let id = match emitted {
130 EmittedNode::Fresh(node) => applier.create(node),
131 EmittedNode::Recycled(recycled) => {
132 let (stable_id, node, warm_origin) = recycled.into_parts();
133 let insertion = applier.insert_recycled_node_or_create(stable_id, node);
134 if let Some(error) = insertion.fallback_error.as_ref() {
135 log::warn!(
136 target: "cranpose::compose::emit",
137 "discarding stale recycled stable id #{stable_id}: {error}",
138 );
139 }
140 applier.set_recycled_node_origin(insertion.id, warm_origin);
141 insertion.id
142 }
143 };
144 let generation = applier.node_generation(id);
145 (id, generation)
146 };
147 let scope_debug = self
148 .current_recompose_scope()
149 .map(|scope| (scope.id(), debug_scope_label(scope.id())))
150 .unwrap_or((0, None));
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_debug.0,
158 scope_debug.1,
159 );
160 self.commands_mut().push(Command::MountNode { id });
161 self.attach_to_parent(id);
162 let parent_id = self.recorded_node_parent(id);
163 let recorded = self.with_slot_session_mut(|slots| {
164 slots.record_node_with_parent(id, generation, parent_id, source)
165 });
166 match recorded {
167 NodeSlotUpdate::Inserted {
168 id: recorded_id,
169 generation: recorded_generation,
170 } => {
171 debug_assert_eq!(recorded_id, id);
172 debug_assert_eq!(recorded_generation, generation);
173 }
174 NodeSlotUpdate::Replaced {
175 old_id,
176 old_generation,
177 new_id,
178 new_generation,
179 } => {
180 debug_assert_eq!(new_id, id);
181 debug_assert_eq!(new_generation, generation);
182 self.queue_replaced_slot_node_removal(old_id, old_generation);
183 }
184 NodeSlotUpdate::Reused { .. } => {
185 log::warn!(
186 target: "cranpose::compose::emit",
187 "slot writer reported reuse for newly emitted node #{id}",
188 );
189 }
190 }
191 self.core.last_node_reused.set(Some(false));
192 id
193 }
194
195 #[track_caller]
196 pub fn emit_node<N: Node + 'static>(&self, init: impl FnOnce() -> N) -> NodeId {
197 let source = crate::caller_location_key();
198 self.emit_node_box::<N>(source, |_| EmittedNode::Fresh(Box::new(init())))
199 }
200
201 #[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 attach_to_parent(&self, id: NodeId) {
232 self.attach_to_parent_with_mode(id, false);
233 }
234
235 pub(crate) fn attach_to_parent_with_mode(
236 &self,
237 id: NodeId,
238 force_reparent_current_parent: bool,
239 ) {
240 let mut parent_stack = self.parent_stack();
246 if let Some(parent_id) = parent_stack.last().map(|frame| frame.id) {
247 let stale_root_parent = self.core.root.get() == Some(parent_id) && {
248 let mut applier = self.borrow_applier();
249 applier.get_mut(parent_id).is_err()
250 };
251 if stale_root_parent {
252 parent_stack.pop();
253 self.set_root(None);
254 } else {
255 let Some(frame) = parent_stack.last_mut() else {
256 return;
257 };
258 let attach_mode = frame.attach_mode;
259 if parent_id == id {
260 return;
261 }
262 if matches!(attach_mode, ParentAttachMode::DeferredSync) {
263 frame.new_children.push(id);
264 }
265 drop(parent_stack);
266
267 {
278 let mut applier = self.borrow_applier();
279 if let Ok(child_node) = applier.get_mut(id) {
280 let existing_parent = child_node.parent();
281 let should_set = if force_reparent_current_parent {
286 existing_parent != Some(parent_id)
287 } else {
288 match existing_parent {
289 None => true,
290 Some(existing) => {
291 let root_id = self.core.root.get();
293 parent_id != root_id.unwrap_or(0)
294 || existing == root_id.unwrap_or(0)
295 }
296 }
297 };
298 if should_set {
299 child_node.set_parent_for_bubbling(parent_id);
300 }
301 }
302 }
303 if matches!(attach_mode, ParentAttachMode::ImmediateAppend) {
304 self.commands_mut().push(Command::AttachChild {
305 parent_id,
306 child_id: id,
307 bubble: DirtyBubble::LAYOUT_AND_MEASURE,
308 });
309 }
310 return;
311 }
312 }
313 drop(parent_stack);
314
315 let in_subcompose = !self.subcompose_stack().is_empty();
317 if in_subcompose {
318 let has_parent = {
322 let mut applier = self.borrow_applier();
323 applier
324 .get_mut(id)
325 .map(|node| node.parent().is_some())
326 .unwrap_or(false)
327 };
328
329 if !has_parent {
330 let mut subcompose_stack = self.subcompose_stack();
331 if let Some(frame) = subcompose_stack.last_mut() {
332 frame.nodes.push(id);
333 }
334 }
335 return;
336 }
337
338 if let Some(parent_hint) = self.core.recompose_parent_hint.get() {
340 if parent_hint == id {
341 debug_assert_ne!(
342 parent_hint, id,
343 "a node cannot be attached as its own parent"
344 );
345 return;
346 }
347 let parent_status = {
348 let mut applier = self.borrow_applier();
349 applier
350 .get_mut(id)
351 .map(|node| node.parent())
352 .unwrap_or(None)
353 };
354 match parent_status {
355 Some(existing) if existing == parent_hint => {}
356 None => {
357 self.commands_mut().push(Command::AttachChild {
358 parent_id: parent_hint,
359 child_id: id,
360 bubble: DirtyBubble::LAYOUT_AND_MEASURE,
361 });
362 }
363 Some(_) => {}
364 }
365 return;
366 }
367
368 let has_parent = {
373 let mut applier = self.borrow_applier();
374 applier
375 .get_mut(id)
376 .map(|node| node.parent().is_some())
377 .unwrap_or(false)
378 };
379 if has_parent {
380 return;
382 }
383
384 self.set_root(Some(id));
386 }
387
388 pub fn with_node_mut<N: Node + 'static, R>(
389 &self,
390 id: NodeId,
391 f: impl FnOnce(&mut N) -> R,
392 ) -> Result<R, NodeError> {
393 let mut applier = self.borrow_applier();
394 let node = applier.get_mut(id)?;
395 let typed = node
396 .as_any_mut()
397 .downcast_mut::<N>()
398 .ok_or(NodeError::TypeMismatch {
399 id,
400 expected: std::any::type_name::<N>(),
401 })?;
402 Ok(f(typed))
403 }
404
405 pub fn push_parent(&self, id: NodeId) {
406 let reused = self.core.last_node_reused.take().unwrap_or(true);
407 let in_subcompose = !self.core.subcompose_stack.borrow().is_empty();
408
409 let mut previous = ChildList::new();
413 if reused || in_subcompose {
414 previous.extend(self.get_node_children(id));
415 } else {
416 let existing_children = self.get_node_children(id);
417 if !existing_children.is_empty() {
418 previous.extend(existing_children);
419 }
420 }
421 let attach_mode = if in_subcompose || !previous.is_empty() {
422 ParentAttachMode::DeferredSync
423 } else {
424 ParentAttachMode::ImmediateAppend
425 };
426
427 self.parent_stack().push(ParentFrame {
428 id,
429 previous,
430 new_children: ChildList::new(),
431 new_children_membership: None,
432 attach_mode,
433 synthetic_root: false,
434 });
435 }
436
437 pub fn pop_parent(&self) {
438 let frame_opt = {
439 let mut stack = self.parent_stack();
440 stack.pop()
441 };
442 if let Some(frame) = frame_opt {
443 let ParentFrame {
444 id,
445 previous,
446 new_children,
447 new_children_membership: _new_children_membership,
448 attach_mode,
449 synthetic_root: _synthetic_root,
450 } = frame;
451
452 log::trace!(target: "cranpose::compose::parent", "pop_parent: node #{}", id);
453 log::trace!(
454 target: "cranpose::compose::parent",
455 "previous children: {:?}",
456 previous
457 );
458 log::trace!(
459 target: "cranpose::compose::parent",
460 "new children: {:?}",
461 new_children
462 );
463 if matches!(attach_mode, ParentAttachMode::DeferredSync) {
464 let _ = previous;
465 self.commands_mut().push(Command::SyncChildren {
466 parent_id: id,
467 expected_children: new_children,
468 });
469 }
470 }
471 }
472
473 pub(crate) fn take_commands(&self) -> CommandQueue {
474 std::mem::take(&mut *self.commands_mut())
475 }
476
477 pub fn apply_pending_commands(&self) -> Result<(), NodeError> {
482 let commands = self.take_commands();
483 let runtime_handle = self.runtime_handle();
484 let result = {
485 let mut applier = self.borrow_applier();
486 let mut result = commands.apply(&mut *applier);
487 if result.is_ok() {
488 for update in runtime_handle.take_updates() {
489 if let Err(err) = update.apply(&mut *applier) {
490 result = Err(err);
491 break;
492 }
493 }
494 }
495 result
496 };
497 if result.is_err() {
498 let host = self.active_slots_host();
499 if !host.has_active_pass() {
500 host.abandon_after_apply_failure();
501 }
502 }
503 result?;
504 runtime_handle.drain_ui();
505 Ok(())
506 }
507
508 pub fn register_side_effect(&self, effect: impl FnOnce() + 'static) {
509 self.side_effects_mut().push(Box::new(effect));
510 }
511
512 pub fn take_side_effects(&self) -> Vec<Box<dyn FnOnce()>> {
513 std::mem::take(&mut *self.side_effects_mut())
514 }
515
516 pub(crate) fn root(&self) -> Option<NodeId> {
517 self.core.root.get()
518 }
519
520 pub(crate) fn set_root(&self, node: Option<NodeId>) {
521 self.core.root.set(node);
522 }
523}