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 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(|state| state.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((id, slot_gen)) = 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(id) == slot_gen;
63 let type_ok = match applier.get_mut(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 let committed = self.with_slot_session_mut(|slots| {
71 slots.adopt_node_record_by_source(source, skip)
72 });
73 debug_assert_eq!(committed, Some((id, slot_gen)));
74 break Some((id, slot_gen));
75 }
76 skip += 1;
77 }
78 };
79
80 if let Some((id, slot_gen)) = adopted {
81 let scope_debug = self
82 .current_recompose_scope()
83 .map(|scope| (scope.id(), debug_scope_label(scope.id())))
84 .unwrap_or((0, None));
85 log::trace!(
86 target: "cranpose::compose::emit",
87 "reusing node #{id} as {} [scope_id={} scope_label={:?}]",
88 std::any::type_name::<N>(),
89 scope_debug.0,
90 scope_debug.1,
91 );
92 self.commands_mut().push(Command::update_node::<N>(id));
93 self.attach_to_parent(id);
94 let parent_id = self.planned_node_parent(id);
95 let recorded = self.with_slot_session_mut(|slots| {
96 slots.record_node_with_parent(id, slot_gen, parent_id, source)
97 });
98 match recorded {
99 NodeSlotUpdate::Reused {
100 id: recorded_id,
101 generation,
102 } => {
103 debug_assert_eq!(recorded_id, id);
104 debug_assert_eq!(generation, slot_gen);
105 }
106 NodeSlotUpdate::Inserted { .. } => {
107 log::warn!(
108 target: "cranpose::compose::emit",
109 "slot writer inserted node #{id} while reusing the same node identity",
110 );
111 }
112 NodeSlotUpdate::Replaced {
113 old_id,
114 old_generation,
115 ..
116 } => {
117 log::warn!(
118 target: "cranpose::compose::emit",
119 "slot writer replaced node #{old_id} while reusing node #{id}",
120 );
121 self.queue_replaced_slot_node_removal(old_id, old_generation);
122 }
123 }
124 self.core.last_node_reused.set(Some(true));
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 let scope_debug = self
150 .current_recompose_scope()
151 .map(|scope| (scope.id(), debug_scope_label(scope.id())))
152 .unwrap_or((0, None));
153 log::trace!(
154 target: "cranpose::compose::emit",
155 "creating node #{} (gen={}) as {} [scope_id={} scope_label={:?}]",
156 id,
157 generation,
158 std::any::type_name::<N>(),
159 scope_debug.0,
160 scope_debug.1,
161 );
162 self.commands_mut().push(Command::MountNode { id });
163 self.attach_to_parent(id);
164 let parent_id = self.planned_node_parent(id);
165 let recorded = self.with_slot_session_mut(|slots| {
166 slots.record_node_with_parent(id, generation, parent_id, source)
167 });
168 match recorded {
169 NodeSlotUpdate::Inserted {
170 id: recorded_id,
171 generation: recorded_generation,
172 } => {
173 debug_assert_eq!(recorded_id, id);
174 debug_assert_eq!(recorded_generation, generation);
175 }
176 NodeSlotUpdate::Replaced {
177 old_id,
178 old_generation,
179 new_id,
180 new_generation,
181 } => {
182 debug_assert_eq!(new_id, id);
183 debug_assert_eq!(new_generation, generation);
184 self.queue_replaced_slot_node_removal(old_id, old_generation);
185 }
186 NodeSlotUpdate::Reused { .. } => {
187 log::warn!(
188 target: "cranpose::compose::emit",
189 "slot writer reported reuse for newly emitted node #{id}",
190 );
191 }
192 }
193 self.core.last_node_reused.set(Some(false));
194 id
195 }
196
197 #[track_caller]
198 pub fn emit_node<N: Node + 'static>(&self, init: impl FnOnce() -> N) -> NodeId {
199 let source = crate::caller_location_key();
200 self.emit_node_box::<N>(source, |_| EmittedNode::Fresh(Box::new(init())))
201 }
202
203 #[track_caller]
204 pub fn emit_recyclable_node<N: Node + 'static>(
205 &self,
206 init: impl FnOnce() -> N,
207 reset: impl FnOnce(&mut N),
208 ) -> NodeId {
209 let source = crate::caller_location_key();
210 self.emit_node_box::<N>(source, |applier| {
211 let key = TypeId::of::<N>();
212 if let Some(mut recycled) = applier.take_recycled_node(key) {
213 if let Some(typed) = recycled.node_mut().as_any_mut().downcast_mut::<N>() {
214 reset(typed);
215 return EmittedNode::Recycled(recycled);
216 }
217 log::warn!(
218 target: "cranpose::compose::emit",
219 "discarding recycled node shell with mismatched type for {}",
220 std::any::type_name::<N>(),
221 );
222 }
223
224 let node = Box::new(init());
225 applier.record_fresh_recyclable_creation(key);
226 if let Some(shell) = node.rehouse_for_recycle() {
227 applier.seed_recycled_node_shell(key, node.recycle_pool_limit(), shell);
228 }
229 EmittedNode::Fresh(node)
230 })
231 }
232
233 fn advance_recompose_child_cursor(&self) -> Option<usize> {
234 let cursor = self.core.recompose_child_cursor.get()?;
235 self.core.recompose_child_cursor.set(Some(cursor + 1));
236 Some(cursor)
237 }
238
239 pub(crate) fn attach_to_parent(&self, id: NodeId) {
240 if self.attach_to_current_parent(id) {
241 return;
242 }
243 self.attach_without_parent_frame(id);
244 }
245
246 fn attach_to_current_parent(&self, id: NodeId) -> bool {
247 let mut parent_stack = self.parent_stack();
248 if let Some(parent_id) = parent_stack.last().map(|frame| frame.id) {
249 let stale_root_parent = self.core.root.get() == Some(parent_id) && {
250 let mut applier = self.borrow_applier();
251 applier.get_mut(parent_id).is_err()
252 };
253 if stale_root_parent {
254 parent_stack.pop();
255 self.set_root(None);
256 } else {
257 let Some(frame) = parent_stack.last_mut() else {
258 return false;
259 };
260 let attach_mode = frame.attach_mode;
261 if parent_id == id {
262 return true;
263 }
264 if matches!(attach_mode, ParentAttachMode::DeferredSync) {
265 frame.new_children.push(id);
266 }
267 drop(parent_stack);
268
269 {
270 let mut applier = self.borrow_applier();
271 if let Ok(child_node) = applier.get_mut(id) {
272 child_node.set_parent_for_bubbling(parent_id);
273 }
274 }
275 if matches!(attach_mode, ParentAttachMode::ImmediateAppend) {
276 self.commands_mut().push(Command::AttachChild {
277 parent_id,
278 child_id: id,
279 insert_index: None,
280 bubble: DirtyBubble::LAYOUT_AND_MEASURE,
281 });
282 }
283 return true;
284 }
285 }
286 false
287 }
288
289 fn attach_without_parent_frame(&self, id: NodeId) {
290 let in_subcompose = !self.subcompose_stack().is_empty();
291 if in_subcompose {
292 let has_parent = {
293 let mut applier = self.borrow_applier();
294 applier
295 .get_mut(id)
296 .map(|node| node.parent().is_some())
297 .unwrap_or(false)
298 };
299
300 if !has_parent {
301 let mut subcompose_stack = self.subcompose_stack();
302 if let Some(frame) = subcompose_stack.last_mut() {
303 frame.nodes.push(id);
304 }
305 }
306 return;
307 }
308
309 if let Some(parent_hint) = self.core.recompose_parent_hint.get() {
310 if parent_hint == id {
311 debug_assert_ne!(
312 parent_hint, id,
313 "a node cannot be attached as its own parent"
314 );
315 return;
316 }
317 let parent_status = {
318 let mut applier = self.borrow_applier();
319 applier
320 .get_mut(id)
321 .map(|node| node.parent())
322 .unwrap_or(None)
323 };
324 match parent_status {
325 Some(existing) if existing == parent_hint => {
326 self.advance_recompose_child_cursor();
327 }
328 None => {
329 let insert_index = self.advance_recompose_child_cursor();
330 self.commands_mut().push(Command::AttachChild {
331 parent_id: parent_hint,
332 child_id: id,
333 insert_index,
334 bubble: DirtyBubble::LAYOUT_AND_MEASURE,
335 });
336 }
337 Some(_) => {}
338 }
339 return;
340 }
341
342 let has_parent = {
343 let mut applier = self.borrow_applier();
344 applier
345 .get_mut(id)
346 .map(|node| node.parent().is_some())
347 .unwrap_or(false)
348 };
349 if has_parent {
350 return;
351 }
352
353 self.set_root(Some(id));
354 }
355
356 pub fn with_node_mut<N: Node + 'static, R>(
357 &self,
358 id: NodeId,
359 f: impl FnOnce(&mut N) -> R,
360 ) -> Result<R, NodeError> {
361 let mut applier = self.borrow_applier();
362 let node = applier.get_mut(id)?;
363 let typed = node
364 .as_any_mut()
365 .downcast_mut::<N>()
366 .ok_or(NodeError::TypeMismatch {
367 id,
368 expected: std::any::type_name::<N>(),
369 })?;
370 Ok(f(typed))
371 }
372
373 pub fn push_parent(&self, id: NodeId) {
374 let reused = self.core.last_node_reused.take().unwrap_or(true);
375 let in_subcompose = !self.core.subcompose_stack.borrow().is_empty();
376
377 let mut previous = ChildList::new();
378 if reused || in_subcompose {
379 previous.extend(self.get_node_children(id));
380 } else {
381 let existing_children = self.get_node_children(id);
382 if !existing_children.is_empty() {
383 previous.extend(existing_children);
384 }
385 }
386 let attach_mode = if in_subcompose || !previous.is_empty() {
387 ParentAttachMode::DeferredSync
388 } else {
389 ParentAttachMode::ImmediateAppend
390 };
391
392 self.parent_stack().push(ParentFrame {
393 id,
394 previous,
395 new_children: ChildList::new(),
396 new_children_membership: None,
397 attach_mode,
398 synthetic_root: false,
399 });
400 }
401
402 pub fn pop_parent(&self) {
403 let frame_opt = {
404 let mut stack = self.parent_stack();
405 stack.pop()
406 };
407 if let Some(frame) = frame_opt {
408 let ParentFrame {
409 id,
410 previous,
411 new_children,
412 new_children_membership: _new_children_membership,
413 attach_mode,
414 synthetic_root: _synthetic_root,
415 } = frame;
416
417 log::trace!(target: "cranpose::compose::parent", "pop_parent: node #{}", id);
418 log::trace!(
419 target: "cranpose::compose::parent",
420 "previous children: {:?}",
421 previous
422 );
423 log::trace!(
424 target: "cranpose::compose::parent",
425 "new children: {:?}",
426 new_children
427 );
428 if matches!(attach_mode, ParentAttachMode::DeferredSync) {
429 let _ = previous;
430 self.commands_mut().push(Command::SyncChildren {
431 parent_id: id,
432 expected_children: new_children,
433 });
434 }
435 }
436 }
437
438 pub(crate) fn take_commands(&self) -> CommandQueue {
439 std::mem::take(&mut *self.commands_mut())
440 }
441
442 pub fn apply_pending_commands(&self) -> Result<(), NodeError> {
447 let commands = self.take_commands();
448 let runtime_handle = self.runtime_handle();
449 let result = {
450 let mut applier = self.borrow_applier();
451 let mut result = commands.apply(&mut *applier);
452 if result.is_ok() {
453 for update in runtime_handle.take_updates() {
454 if let Err(err) = update.apply(&mut *applier) {
455 result = Err(err);
456 break;
457 }
458 }
459 }
460 result
461 };
462 if result.is_err() {
463 let host = self.active_slots_host();
464 if !host.has_active_pass() {
465 host.abandon_after_apply_failure();
466 }
467 }
468 result?;
469 runtime_handle.drain_ui();
470 Ok(())
471 }
472
473 pub fn register_side_effect(&self, effect: impl FnOnce() + 'static) {
474 self.side_effects_mut().push(Box::new(effect));
475 }
476
477 pub fn take_side_effects(&self) -> Vec<Box<dyn FnOnce()>> {
478 std::mem::take(&mut *self.side_effects_mut())
479 }
480
481 pub(crate) fn root(&self) -> Option<NodeId> {
482 self.core.root.get()
483 }
484
485 pub(crate) fn set_root(&self, node: Option<NodeId>) {
486 self.core.root.set(node);
487 }
488}