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) = {
126 let mut applier = self.borrow_applier();
127 let emitted = make_node(&mut *applier);
128 let id = match emitted {
129 EmittedNode::Fresh(node) => applier.create(node),
130 EmittedNode::Recycled(recycled) => {
131 let (stable_id, node, warm_origin) = recycled.into_parts();
132 let insertion = applier.insert_recycled_node_or_create(stable_id, node);
133 if let Some(error) = insertion.fallback_error.as_ref() {
134 log::warn!(
135 target: "cranpose::compose::emit",
136 "discarding stale recycled stable id #{stable_id}: {error}",
137 );
138 }
139 applier.set_recycled_node_origin(insertion.id, warm_origin);
140 insertion.id
141 }
142 };
143 let generation = applier.node_generation(id);
144 (id, generation)
145 };
146 let scope_debug = self
147 .current_recompose_scope()
148 .map(|scope| (scope.id(), debug_scope_label(scope.id())))
149 .unwrap_or((0, None));
150 log::trace!(
151 target: "cranpose::compose::emit",
152 "creating node #{} (gen={}) as {} [scope_id={} scope_label={:?}]",
153 id,
154 generation,
155 std::any::type_name::<N>(),
156 scope_debug.0,
157 scope_debug.1,
158 );
159 self.commands_mut().push(Command::MountNode { id });
160 self.attach_to_parent(id);
161 let parent_id = self.recorded_node_parent(id);
162 let recorded = self.with_slot_session_mut(|slots| {
163 slots.record_node_with_parent(id, generation, parent_id, source)
164 });
165 match recorded {
166 NodeSlotUpdate::Inserted {
167 id: recorded_id,
168 generation: recorded_generation,
169 } => {
170 debug_assert_eq!(recorded_id, id);
171 debug_assert_eq!(recorded_generation, generation);
172 }
173 NodeSlotUpdate::Replaced {
174 old_id,
175 old_generation,
176 new_id,
177 new_generation,
178 } => {
179 debug_assert_eq!(new_id, id);
180 debug_assert_eq!(new_generation, generation);
181 self.queue_replaced_slot_node_removal(old_id, old_generation);
182 }
183 NodeSlotUpdate::Reused { .. } => {
184 log::warn!(
185 target: "cranpose::compose::emit",
186 "slot writer reported reuse for newly emitted node #{id}",
187 );
188 }
189 }
190 self.core.last_node_reused.set(Some(false));
191 id
192 }
193
194 #[track_caller]
195 pub fn emit_node<N: Node + 'static>(&self, init: impl FnOnce() -> N) -> NodeId {
196 let source = crate::caller_location_key();
197 self.emit_node_box::<N>(source, |_| EmittedNode::Fresh(Box::new(init())))
198 }
199
200 #[track_caller]
201 pub fn emit_recyclable_node<N: Node + 'static>(
202 &self,
203 init: impl FnOnce() -> N,
204 reset: impl FnOnce(&mut N),
205 ) -> NodeId {
206 let source = crate::caller_location_key();
207 self.emit_node_box::<N>(source, |applier| {
208 let key = TypeId::of::<N>();
209 if let Some(mut recycled) = applier.take_recycled_node(key) {
210 if let Some(typed) = recycled.node_mut().as_any_mut().downcast_mut::<N>() {
211 reset(typed);
212 return EmittedNode::Recycled(recycled);
213 }
214 log::warn!(
215 target: "cranpose::compose::emit",
216 "discarding recycled node shell with mismatched type for {}",
217 std::any::type_name::<N>(),
218 );
219 }
220
221 let node = Box::new(init());
222 applier.record_fresh_recyclable_creation(key);
223 if let Some(shell) = node.rehouse_for_recycle() {
224 applier.seed_recycled_node_shell(key, node.recycle_pool_limit(), shell);
225 }
226 EmittedNode::Fresh(node)
227 })
228 }
229
230 fn attach_to_parent(&self, id: NodeId) {
231 self.attach_to_parent_with_mode(id, false);
232 }
233
234 pub(crate) fn attach_to_parent_with_mode(
235 &self,
236 id: NodeId,
237 force_reparent_current_parent: bool,
238 ) {
239 let mut parent_stack = self.parent_stack();
240 if let Some(parent_id) = parent_stack.last().map(|frame| frame.id) {
241 let stale_root_parent = self.core.root.get() == Some(parent_id) && {
242 let mut applier = self.borrow_applier();
243 applier.get_mut(parent_id).is_err()
244 };
245 if stale_root_parent {
246 parent_stack.pop();
247 self.set_root(None);
248 } else {
249 let Some(frame) = parent_stack.last_mut() else {
250 return;
251 };
252 let attach_mode = frame.attach_mode;
253 if parent_id == id {
254 return;
255 }
256 if matches!(attach_mode, ParentAttachMode::DeferredSync) {
257 frame.new_children.push(id);
258 }
259 drop(parent_stack);
260
261 {
262 let mut applier = self.borrow_applier();
263 if let Ok(child_node) = applier.get_mut(id) {
264 let existing_parent = child_node.parent();
265 let should_set = if force_reparent_current_parent {
266 existing_parent != Some(parent_id)
267 } else {
268 match existing_parent {
269 None => true,
270 Some(existing) => {
271 let root_id = self.core.root.get();
272 parent_id != root_id.unwrap_or(0)
273 || existing == root_id.unwrap_or(0)
274 }
275 }
276 };
277 if should_set {
278 child_node.set_parent_for_bubbling(parent_id);
279 }
280 }
281 }
282 if matches!(attach_mode, ParentAttachMode::ImmediateAppend) {
283 self.commands_mut().push(Command::AttachChild {
284 parent_id,
285 child_id: id,
286 bubble: DirtyBubble::LAYOUT_AND_MEASURE,
287 });
288 }
289 return;
290 }
291 }
292 drop(parent_stack);
293
294 let in_subcompose = !self.subcompose_stack().is_empty();
295 if in_subcompose {
296 let has_parent = {
297 let mut applier = self.borrow_applier();
298 applier
299 .get_mut(id)
300 .map(|node| node.parent().is_some())
301 .unwrap_or(false)
302 };
303
304 if !has_parent {
305 let mut subcompose_stack = self.subcompose_stack();
306 if let Some(frame) = subcompose_stack.last_mut() {
307 frame.nodes.push(id);
308 }
309 }
310 return;
311 }
312
313 if let Some(parent_hint) = self.core.recompose_parent_hint.get() {
314 if parent_hint == id {
315 debug_assert_ne!(
316 parent_hint, id,
317 "a node cannot be attached as its own parent"
318 );
319 return;
320 }
321 let parent_status = {
322 let mut applier = self.borrow_applier();
323 applier
324 .get_mut(id)
325 .map(|node| node.parent())
326 .unwrap_or(None)
327 };
328 match parent_status {
329 Some(existing) if existing == parent_hint => {}
330 None => {
331 self.commands_mut().push(Command::AttachChild {
332 parent_id: parent_hint,
333 child_id: id,
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}