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