1use crate::{
2 debug_scope_label, Applier, ChildList, Command, CommandQueue, Composer, DirtyBubble,
3 EmittedNode, MutableState, Node, NodeError, NodeId, OwnedMutableState, ParentAttachMode,
4 ParentFrame,
5};
6use std::any::TypeId;
7
8impl Composer {
9 pub fn use_state<T: Clone + 'static>(&self, init: impl FnOnce() -> T) -> MutableState<T> {
10 let runtime = self.runtime_handle();
11 let state = self.with_slot_session_mut(|slots| {
12 slots.remember(|| OwnedMutableState::with_runtime(init(), runtime.clone()))
13 });
14 state.with(|state| state.handle())
15 }
16
17 fn emit_node_box<N: Node + 'static>(
18 &self,
19 make_node: impl FnOnce(&mut dyn Applier) -> EmittedNode,
20 ) -> NodeId {
21 let (existing_id, type_matches, gen_matches) = {
23 if let Some((id, slot_gen)) = self.with_slot_session_mut(|slots| slots.peek_node()) {
24 let mut applier = self.borrow_applier();
25 let gen_ok = applier.node_generation(id) == slot_gen;
26 let type_ok = match applier.get_mut(id) {
27 Ok(node) => node.as_any_mut().downcast_ref::<N>().is_some(),
28 Err(_) => false,
29 };
30 (Some(id), type_ok, gen_ok)
31 } else {
32 (None, false, false)
33 }
34 };
35
36 if let Some(id) = existing_id {
38 if type_matches && gen_matches {
39 self.core.last_node_reused.set(Some(true));
40 let scope_debug = self
41 .current_recranpose_scope()
42 .map(|scope| (scope.id(), debug_scope_label(scope.id())))
43 .unwrap_or((0, None));
44 log::trace!(
45 target: "cranpose::compose::emit",
46 "reusing node #{id} as {} [scope_id={} scope_label={:?}]",
47 std::any::type_name::<N>(),
48 scope_debug.0,
49 scope_debug.1,
50 );
51 self.with_slot_session_mut(|slots| slots.advance_after_node_read());
52
53 self.commands_mut().push(Command::update_node::<N>(id));
54 self.attach_to_parent(id);
55 return id;
56 }
57 }
58
59 if let Some(old_id) = existing_id {
62 if !gen_matches {
63 log::trace!(
66 target: "cranpose::compose::emit",
67 "stale generation for node #{old_id} (current={})",
68 self.borrow_applier().node_generation(old_id)
69 );
70 } else if !type_matches {
71 log::trace!(
73 target: "cranpose::compose::emit",
74 "replacing node #{old_id} with new {}",
75 std::any::type_name::<N>()
76 );
77 self.commands_mut().push(Command::RemoveNode { id: old_id });
78 }
79 }
80
81 let (id, gen) = {
83 let mut applier = self.borrow_applier();
84 let emitted = make_node(&mut *applier);
85 let id = match emitted {
86 EmittedNode::Fresh(node) => applier.create(node),
87 EmittedNode::Recycled(recycled) => {
88 let (stable_id, node, warm_origin) = recycled.into_parts();
89 applier
90 .insert_with_id(stable_id, node)
91 .expect("recycled stable id should be available");
92 applier.set_recycled_node_origin(stable_id, warm_origin);
93 stable_id
94 }
95 };
96 let gen = applier.node_generation(id);
97 (id, gen)
98 };
99 self.core.last_node_reused.set(Some(false));
100 let scope_debug = self
101 .current_recranpose_scope()
102 .map(|scope| (scope.id(), debug_scope_label(scope.id())))
103 .unwrap_or((0, None));
104 log::trace!(
105 target: "cranpose::compose::emit",
106 "creating node #{} (gen={}) as {} [scope_id={} scope_label={:?}]",
107 id,
108 gen,
109 std::any::type_name::<N>(),
110 scope_debug.0,
111 scope_debug.1,
112 );
113 {
114 self.with_slot_session_mut(|slots| slots.record_node(id, gen));
115 }
116 self.commands_mut().push(Command::MountNode { id });
117 self.attach_to_parent(id);
118 id
119 }
120
121 pub fn emit_node<N: Node + 'static>(&self, init: impl FnOnce() -> N) -> NodeId {
122 self.emit_node_box::<N>(|_| EmittedNode::Fresh(Box::new(init())))
123 }
124
125 pub fn emit_recyclable_node<N: Node + 'static>(
126 &self,
127 init: impl FnOnce() -> N,
128 reset: impl FnOnce(&mut N),
129 ) -> NodeId {
130 self.emit_node_box::<N>(|applier| {
131 let key = TypeId::of::<N>();
132 if let Some(mut recycled) = applier.take_recycled_node(key) {
133 let typed = recycled
134 .node_mut()
135 .as_any_mut()
136 .downcast_mut::<N>()
137 .expect("recycled node type mismatch");
138 reset(typed);
139 EmittedNode::Recycled(recycled)
140 } else {
141 let node = Box::new(init());
142 applier.record_fresh_recyclable_creation(key);
143 if let Some(shell) = node.rehouse_for_recycle() {
144 applier.seed_recycled_node_shell(key, node.recycle_pool_limit(), shell);
145 }
146 EmittedNode::Fresh(node)
147 }
148 })
149 }
150
151 fn attach_to_parent(&self, id: NodeId) {
152 self.attach_to_parent_with_mode(id, false);
153 }
154
155 pub(crate) fn attach_to_parent_with_mode(
156 &self,
157 id: NodeId,
158 force_reparent_current_parent: bool,
159 ) {
160 let mut parent_stack = self.parent_stack();
166 if let Some(parent_id) = parent_stack.last().map(|frame| frame.id) {
167 let stale_root_parent = self.core.root.get() == Some(parent_id) && {
168 let mut applier = self.borrow_applier();
169 applier.get_mut(parent_id).is_err()
170 };
171 if stale_root_parent {
172 parent_stack.pop();
173 self.set_root(None);
174 } else {
175 let frame = parent_stack
176 .last_mut()
177 .expect("active parent frame should remain available");
178 let attach_mode = frame.attach_mode;
179 if parent_id == id {
180 return;
181 }
182 if matches!(attach_mode, ParentAttachMode::DeferredSync) {
183 frame.new_children.push(id);
184 }
185 drop(parent_stack);
186
187 {
198 let mut applier = self.borrow_applier();
199 if let Ok(child_node) = applier.get_mut(id) {
200 let existing_parent = child_node.parent();
201 let should_set = if force_reparent_current_parent {
206 existing_parent != Some(parent_id)
207 } else {
208 match existing_parent {
209 None => true,
210 Some(existing) => {
211 let root_id = self.core.root.get();
213 parent_id != root_id.unwrap_or(0)
214 || existing == root_id.unwrap_or(0)
215 }
216 }
217 };
218 if should_set {
219 child_node.set_parent_for_bubbling(parent_id);
220 }
221 }
222 }
223 if matches!(attach_mode, ParentAttachMode::ImmediateAppend) {
224 self.commands_mut().push(Command::AttachChild {
225 parent_id,
226 child_id: id,
227 bubble: DirtyBubble::LAYOUT_AND_MEASURE,
228 });
229 }
230 return;
231 }
232 }
233 drop(parent_stack);
234
235 let in_subcompose = !self.subcompose_stack().is_empty();
237 if in_subcompose {
238 let has_parent = {
242 let mut applier = self.borrow_applier();
243 applier
244 .get_mut(id)
245 .map(|node| node.parent().is_some())
246 .unwrap_or(false)
247 };
248
249 if !has_parent {
250 let mut subcompose_stack = self.subcompose_stack();
251 if let Some(frame) = subcompose_stack.last_mut() {
252 frame.nodes.push(id);
253 }
254 }
255 return;
256 }
257
258 if let Some(parent_hint) = self.core.recranpose_parent_hint.get() {
260 let parent_status = {
261 let mut applier = self.borrow_applier();
262 applier
263 .get_mut(id)
264 .map(|node| node.parent())
265 .unwrap_or(None)
266 };
267 match parent_status {
268 Some(existing) if existing == parent_hint => {}
269 None => {
270 self.commands_mut().push(Command::AttachChild {
271 parent_id: parent_hint,
272 child_id: id,
273 bubble: DirtyBubble::LAYOUT_AND_MEASURE,
274 });
275 }
276 Some(_) => {}
277 }
278 return;
279 }
280
281 let has_parent = {
286 let mut applier = self.borrow_applier();
287 applier
288 .get_mut(id)
289 .map(|node| node.parent().is_some())
290 .unwrap_or(false)
291 };
292 if has_parent {
293 return;
295 }
296
297 self.set_root(Some(id));
299 }
300
301 pub fn with_node_mut<N: Node + 'static, R>(
302 &self,
303 id: NodeId,
304 f: impl FnOnce(&mut N) -> R,
305 ) -> Result<R, NodeError> {
306 let mut applier = self.borrow_applier();
307 let node = applier.get_mut(id)?;
308 let typed = node
309 .as_any_mut()
310 .downcast_mut::<N>()
311 .ok_or(NodeError::TypeMismatch {
312 id,
313 expected: std::any::type_name::<N>(),
314 })?;
315 Ok(f(typed))
316 }
317
318 pub fn push_parent(&self, id: NodeId) {
319 let reused = self.core.last_node_reused.take().unwrap_or(true);
320 let in_subcompose = !self.core.subcompose_stack.borrow().is_empty();
321
322 let mut previous = ChildList::new();
326 if reused || in_subcompose {
327 previous.extend(self.get_node_children(id));
328 } else {
329 let existing_children = self.get_node_children(id);
330 if !existing_children.is_empty() {
331 previous.extend(existing_children);
332 }
333 }
334 let attach_mode = if in_subcompose || !previous.is_empty() {
335 ParentAttachMode::DeferredSync
336 } else {
337 ParentAttachMode::ImmediateAppend
338 };
339
340 self.parent_stack().push(ParentFrame {
341 id,
342 previous,
343 new_children: ChildList::new(),
344 attach_mode,
345 });
346 }
347
348 pub fn pop_parent(&self) {
349 let frame_opt = {
350 let mut stack = self.parent_stack();
351 stack.pop()
352 };
353 if let Some(frame) = frame_opt {
354 let ParentFrame {
355 id,
356 previous,
357 new_children,
358 attach_mode,
359 } = frame;
360
361 log::trace!(target: "cranpose::compose::parent", "pop_parent: node #{}", id);
362 log::trace!(
363 target: "cranpose::compose::parent",
364 "previous children: {:?}",
365 previous
366 );
367 log::trace!(
368 target: "cranpose::compose::parent",
369 "new children: {:?}",
370 new_children
371 );
372 if matches!(attach_mode, ParentAttachMode::DeferredSync) {
373 let _ = previous;
374 self.commands_mut().push(Command::SyncChildren {
375 parent_id: id,
376 expected_children: new_children,
377 });
378 }
379 }
380 }
381
382 pub(crate) fn take_commands(&self) -> CommandQueue {
383 std::mem::take(&mut *self.commands_mut())
384 }
385
386 pub fn apply_pending_commands(&self) -> Result<(), NodeError> {
391 let commands = self.take_commands();
392 let runtime_handle = self.runtime_handle();
393 {
394 let mut applier = self.borrow_applier();
395 commands.apply(&mut *applier)?;
396 for update in runtime_handle.take_updates() {
397 update.apply(&mut *applier)?;
398 }
399 }
400 runtime_handle.drain_ui();
401 Ok(())
402 }
403
404 pub fn register_side_effect(&self, effect: impl FnOnce() + 'static) {
405 self.side_effects_mut().push(Box::new(effect));
406 }
407
408 pub fn take_side_effects(&self) -> Vec<Box<dyn FnOnce()>> {
409 std::mem::take(&mut *self.side_effects_mut())
410 }
411
412 pub(crate) fn root(&self) -> Option<NodeId> {
413 self.core.root.get()
414 }
415
416 pub(crate) fn set_root(&self, node: Option<NodeId>) {
417 self.core.root.set(node);
418 }
419}