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) = existing_id {
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 slot_gen =
78 existing_generation.expect("reused node must keep its slot generation");
79 let parent_id = self.recorded_node_parent(id);
80 let recorded = self.with_slot_session_mut(|slots| {
81 slots.record_node_with_parent(id, slot_gen, parent_id)
82 });
83 match recorded {
84 NodeSlotUpdate::Reused {
85 id: recorded_id,
86 generation,
87 } => {
88 debug_assert_eq!(recorded_id, id);
89 debug_assert_eq!(generation, slot_gen);
90 }
91 NodeSlotUpdate::Inserted { .. } | NodeSlotUpdate::Replaced { .. } => {
92 panic!("reused node recording must keep the same node identity");
93 }
94 }
95 self.core.last_node_reused.set(Some(true));
96 return id;
97 }
98 }
99
100 let (id, gen) = {
102 let mut applier = self.borrow_applier();
103 let emitted = make_node(&mut *applier);
104 let id = match emitted {
105 EmittedNode::Fresh(node) => applier.create(node),
106 EmittedNode::Recycled(recycled) => {
107 let (stable_id, node, warm_origin) = recycled.into_parts();
108 applier
109 .insert_with_id(stable_id, node)
110 .expect("recycled stable id should be available");
111 applier.set_recycled_node_origin(stable_id, warm_origin);
112 stable_id
113 }
114 };
115 let gen = applier.node_generation(id);
116 (id, gen)
117 };
118 let scope_debug = self
119 .current_recranpose_scope()
120 .map(|scope| (scope.id(), debug_scope_label(scope.id())))
121 .unwrap_or((0, None));
122 log::trace!(
123 target: "cranpose::compose::emit",
124 "creating node #{} (gen={}) as {} [scope_id={} scope_label={:?}]",
125 id,
126 gen,
127 std::any::type_name::<N>(),
128 scope_debug.0,
129 scope_debug.1,
130 );
131 self.commands_mut().push(Command::MountNode { id });
132 self.attach_to_parent(id);
133 let parent_id = self.recorded_node_parent(id);
134 let recorded =
135 self.with_slot_session_mut(|slots| slots.record_node_with_parent(id, gen, parent_id));
136 match recorded {
137 NodeSlotUpdate::Inserted {
138 id: recorded_id,
139 generation,
140 } => {
141 debug_assert_eq!(recorded_id, id);
142 debug_assert_eq!(generation, gen);
143 }
144 NodeSlotUpdate::Replaced {
145 old_id,
146 old_generation,
147 new_id,
148 new_generation,
149 } => {
150 debug_assert_eq!(new_id, id);
151 debug_assert_eq!(new_generation, gen);
152 self.queue_replaced_slot_node_removal(old_id, old_generation);
153 }
154 NodeSlotUpdate::Reused { .. } => {
155 panic!("fresh or replacement node recording must not report normal reuse");
156 }
157 }
158 self.core.last_node_reused.set(Some(false));
159 id
160 }
161
162 pub fn emit_node<N: Node + 'static>(&self, init: impl FnOnce() -> N) -> NodeId {
163 self.emit_node_box::<N>(|_| EmittedNode::Fresh(Box::new(init())))
164 }
165
166 pub fn emit_recyclable_node<N: Node + 'static>(
167 &self,
168 init: impl FnOnce() -> N,
169 reset: impl FnOnce(&mut N),
170 ) -> NodeId {
171 self.emit_node_box::<N>(|applier| {
172 let key = TypeId::of::<N>();
173 if let Some(mut recycled) = applier.take_recycled_node(key) {
174 let typed = recycled
175 .node_mut()
176 .as_any_mut()
177 .downcast_mut::<N>()
178 .expect("recycled node type mismatch");
179 reset(typed);
180 EmittedNode::Recycled(recycled)
181 } else {
182 let node = Box::new(init());
183 applier.record_fresh_recyclable_creation(key);
184 if let Some(shell) = node.rehouse_for_recycle() {
185 applier.seed_recycled_node_shell(key, node.recycle_pool_limit(), shell);
186 }
187 EmittedNode::Fresh(node)
188 }
189 })
190 }
191
192 fn attach_to_parent(&self, id: NodeId) {
193 self.attach_to_parent_with_mode(id, false);
194 }
195
196 pub(crate) fn attach_to_parent_with_mode(
197 &self,
198 id: NodeId,
199 force_reparent_current_parent: bool,
200 ) {
201 let mut parent_stack = self.parent_stack();
207 if let Some(parent_id) = parent_stack.last().map(|frame| frame.id) {
208 let stale_root_parent = self.core.root.get() == Some(parent_id) && {
209 let mut applier = self.borrow_applier();
210 applier.get_mut(parent_id).is_err()
211 };
212 if stale_root_parent {
213 parent_stack.pop();
214 self.set_root(None);
215 } else {
216 let frame = parent_stack
217 .last_mut()
218 .expect("active parent frame should remain available");
219 let attach_mode = frame.attach_mode;
220 if parent_id == id {
221 return;
222 }
223 if matches!(attach_mode, ParentAttachMode::DeferredSync) {
224 frame.new_children.push(id);
225 }
226 drop(parent_stack);
227
228 {
239 let mut applier = self.borrow_applier();
240 if let Ok(child_node) = applier.get_mut(id) {
241 let existing_parent = child_node.parent();
242 let should_set = if force_reparent_current_parent {
247 existing_parent != Some(parent_id)
248 } else {
249 match existing_parent {
250 None => true,
251 Some(existing) => {
252 let root_id = self.core.root.get();
254 parent_id != root_id.unwrap_or(0)
255 || existing == root_id.unwrap_or(0)
256 }
257 }
258 };
259 if should_set {
260 child_node.set_parent_for_bubbling(parent_id);
261 }
262 }
263 }
264 if matches!(attach_mode, ParentAttachMode::ImmediateAppend) {
265 self.commands_mut().push(Command::AttachChild {
266 parent_id,
267 child_id: id,
268 bubble: DirtyBubble::LAYOUT_AND_MEASURE,
269 });
270 }
271 return;
272 }
273 }
274 drop(parent_stack);
275
276 let in_subcompose = !self.subcompose_stack().is_empty();
278 if in_subcompose {
279 let has_parent = {
283 let mut applier = self.borrow_applier();
284 applier
285 .get_mut(id)
286 .map(|node| node.parent().is_some())
287 .unwrap_or(false)
288 };
289
290 if !has_parent {
291 let mut subcompose_stack = self.subcompose_stack();
292 if let Some(frame) = subcompose_stack.last_mut() {
293 frame.nodes.push(id);
294 }
295 }
296 return;
297 }
298
299 if let Some(parent_hint) = self.core.recranpose_parent_hint.get() {
301 if parent_hint == id {
302 debug_assert_ne!(
303 parent_hint, id,
304 "a node cannot be attached as its own parent"
305 );
306 return;
307 }
308 let parent_status = {
309 let mut applier = self.borrow_applier();
310 applier
311 .get_mut(id)
312 .map(|node| node.parent())
313 .unwrap_or(None)
314 };
315 match parent_status {
316 Some(existing) if existing == parent_hint => {}
317 None => {
318 self.commands_mut().push(Command::AttachChild {
319 parent_id: parent_hint,
320 child_id: id,
321 bubble: DirtyBubble::LAYOUT_AND_MEASURE,
322 });
323 }
324 Some(_) => {}
325 }
326 return;
327 }
328
329 let has_parent = {
334 let mut applier = self.borrow_applier();
335 applier
336 .get_mut(id)
337 .map(|node| node.parent().is_some())
338 .unwrap_or(false)
339 };
340 if has_parent {
341 return;
343 }
344
345 self.set_root(Some(id));
347 }
348
349 pub fn with_node_mut<N: Node + 'static, R>(
350 &self,
351 id: NodeId,
352 f: impl FnOnce(&mut N) -> R,
353 ) -> Result<R, NodeError> {
354 let mut applier = self.borrow_applier();
355 let node = applier.get_mut(id)?;
356 let typed = node
357 .as_any_mut()
358 .downcast_mut::<N>()
359 .ok_or(NodeError::TypeMismatch {
360 id,
361 expected: std::any::type_name::<N>(),
362 })?;
363 Ok(f(typed))
364 }
365
366 pub fn push_parent(&self, id: NodeId) {
367 let reused = self.core.last_node_reused.take().unwrap_or(true);
368 let in_subcompose = !self.core.subcompose_stack.borrow().is_empty();
369
370 let mut previous = ChildList::new();
374 if reused || in_subcompose {
375 previous.extend(self.get_node_children(id));
376 } else {
377 let existing_children = self.get_node_children(id);
378 if !existing_children.is_empty() {
379 previous.extend(existing_children);
380 }
381 }
382 let attach_mode = if in_subcompose || !previous.is_empty() {
383 ParentAttachMode::DeferredSync
384 } else {
385 ParentAttachMode::ImmediateAppend
386 };
387
388 self.parent_stack().push(ParentFrame {
389 id,
390 previous,
391 new_children: ChildList::new(),
392 new_children_membership: None,
393 attach_mode,
394 synthetic_root: false,
395 });
396 }
397
398 pub fn pop_parent(&self) {
399 let frame_opt = {
400 let mut stack = self.parent_stack();
401 stack.pop()
402 };
403 if let Some(frame) = frame_opt {
404 let ParentFrame {
405 id,
406 previous,
407 new_children,
408 new_children_membership: _new_children_membership,
409 attach_mode,
410 synthetic_root: _synthetic_root,
411 } = frame;
412
413 log::trace!(target: "cranpose::compose::parent", "pop_parent: node #{}", id);
414 log::trace!(
415 target: "cranpose::compose::parent",
416 "previous children: {:?}",
417 previous
418 );
419 log::trace!(
420 target: "cranpose::compose::parent",
421 "new children: {:?}",
422 new_children
423 );
424 if matches!(attach_mode, ParentAttachMode::DeferredSync) {
425 let _ = previous;
426 self.commands_mut().push(Command::SyncChildren {
427 parent_id: id,
428 expected_children: new_children,
429 });
430 }
431 }
432 }
433
434 pub(crate) fn take_commands(&self) -> CommandQueue {
435 std::mem::take(&mut *self.commands_mut())
436 }
437
438 pub fn apply_pending_commands(&self) -> Result<(), NodeError> {
443 let commands = self.take_commands();
444 let runtime_handle = self.runtime_handle();
445 let result = {
446 let mut applier = self.borrow_applier();
447 let mut result = commands.apply(&mut *applier);
448 if result.is_ok() {
449 for update in runtime_handle.take_updates() {
450 if let Err(err) = update.apply(&mut *applier) {
451 result = Err(err);
452 break;
453 }
454 }
455 }
456 result
457 };
458 if result.is_err() {
459 let host = self.active_slots_host();
460 if !host.has_active_pass() {
461 host.abandon_after_apply_failure();
462 }
463 }
464 result?;
465 runtime_handle.drain_ui();
466 Ok(())
467 }
468
469 pub fn register_side_effect(&self, effect: impl FnOnce() + 'static) {
470 self.side_effects_mut().push(Box::new(effect));
471 }
472
473 pub fn take_side_effects(&self) -> Vec<Box<dyn FnOnce()>> {
474 std::mem::take(&mut *self.side_effects_mut())
475 }
476
477 pub(crate) fn root(&self) -> Option<NodeId> {
478 self.core.root.get()
479 }
480
481 pub(crate) fn set_root(&self, node: Option<NodeId>) {
482 self.core.root.set(node);
483 }
484}