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