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 let parent_status = {
302 let mut applier = self.borrow_applier();
303 applier
304 .get_mut(id)
305 .map(|node| node.parent())
306 .unwrap_or(None)
307 };
308 match parent_status {
309 Some(existing) if existing == parent_hint => {}
310 None => {
311 self.commands_mut().push(Command::AttachChild {
312 parent_id: parent_hint,
313 child_id: id,
314 bubble: DirtyBubble::LAYOUT_AND_MEASURE,
315 });
316 }
317 Some(_) => {}
318 }
319 return;
320 }
321
322 let has_parent = {
327 let mut applier = self.borrow_applier();
328 applier
329 .get_mut(id)
330 .map(|node| node.parent().is_some())
331 .unwrap_or(false)
332 };
333 if has_parent {
334 return;
336 }
337
338 self.set_root(Some(id));
340 }
341
342 pub fn with_node_mut<N: Node + 'static, R>(
343 &self,
344 id: NodeId,
345 f: impl FnOnce(&mut N) -> R,
346 ) -> Result<R, NodeError> {
347 let mut applier = self.borrow_applier();
348 let node = applier.get_mut(id)?;
349 let typed = node
350 .as_any_mut()
351 .downcast_mut::<N>()
352 .ok_or(NodeError::TypeMismatch {
353 id,
354 expected: std::any::type_name::<N>(),
355 })?;
356 Ok(f(typed))
357 }
358
359 pub fn push_parent(&self, id: NodeId) {
360 let reused = self.core.last_node_reused.take().unwrap_or(true);
361 let in_subcompose = !self.core.subcompose_stack.borrow().is_empty();
362
363 let mut previous = ChildList::new();
367 if reused || in_subcompose {
368 previous.extend(self.get_node_children(id));
369 } else {
370 let existing_children = self.get_node_children(id);
371 if !existing_children.is_empty() {
372 previous.extend(existing_children);
373 }
374 }
375 let attach_mode = if in_subcompose || !previous.is_empty() {
376 ParentAttachMode::DeferredSync
377 } else {
378 ParentAttachMode::ImmediateAppend
379 };
380
381 self.parent_stack().push(ParentFrame {
382 id,
383 previous,
384 new_children: ChildList::new(),
385 new_children_membership: None,
386 attach_mode,
387 });
388 }
389
390 pub fn pop_parent(&self) {
391 let frame_opt = {
392 let mut stack = self.parent_stack();
393 stack.pop()
394 };
395 if let Some(frame) = frame_opt {
396 let ParentFrame {
397 id,
398 previous,
399 new_children,
400 new_children_membership: _new_children_membership,
401 attach_mode,
402 } = frame;
403
404 log::trace!(target: "cranpose::compose::parent", "pop_parent: node #{}", id);
405 log::trace!(
406 target: "cranpose::compose::parent",
407 "previous children: {:?}",
408 previous
409 );
410 log::trace!(
411 target: "cranpose::compose::parent",
412 "new children: {:?}",
413 new_children
414 );
415 if matches!(attach_mode, ParentAttachMode::DeferredSync) {
416 let _ = previous;
417 self.commands_mut().push(Command::SyncChildren {
418 parent_id: id,
419 expected_children: new_children,
420 });
421 }
422 }
423 }
424
425 pub(crate) fn take_commands(&self) -> CommandQueue {
426 std::mem::take(&mut *self.commands_mut())
427 }
428
429 pub fn apply_pending_commands(&self) -> Result<(), NodeError> {
434 let commands = self.take_commands();
435 let runtime_handle = self.runtime_handle();
436 let result = {
437 let mut applier = self.borrow_applier();
438 let mut result = commands.apply(&mut *applier);
439 if result.is_ok() {
440 for update in runtime_handle.take_updates() {
441 if let Err(err) = update.apply(&mut *applier) {
442 result = Err(err);
443 break;
444 }
445 }
446 }
447 result
448 };
449 if result.is_err() {
450 let host = self.active_slots_host();
451 if !host.has_active_pass() {
452 host.abandon_after_apply_failure();
453 }
454 }
455 result?;
456 runtime_handle.drain_ui();
457 Ok(())
458 }
459
460 pub fn register_side_effect(&self, effect: impl FnOnce() + 'static) {
461 self.side_effects_mut().push(Box::new(effect));
462 }
463
464 pub fn take_side_effects(&self) -> Vec<Box<dyn FnOnce()>> {
465 std::mem::take(&mut *self.side_effects_mut())
466 }
467
468 pub(crate) fn root(&self) -> Option<NodeId> {
469 self.core.root.get()
470 }
471
472 pub(crate) fn set_root(&self, node: Option<NodeId>) {
473 self.core.root.set(node);
474 }
475}