use crate::slot::NodeSlotUpdate;
use crate::{
debug_scope_label, Applier, ChildList, Command, CommandQueue, Composer, DirtyBubble,
EmittedNode, MutableState, Node, NodeError, NodeId, OwnedMutableState, ParentAttachMode,
ParentFrame,
};
use std::any::TypeId;
impl Composer {
fn recorded_node_parent(&self, id: NodeId) -> Option<NodeId> {
let mut applier = self.borrow_applier();
applier.get_mut(id).ok().and_then(|node| node.parent())
}
fn queue_replaced_slot_node_removal(&self, old_id: NodeId, old_generation: u32) {
let current_generation = self.borrow_applier().node_generation(old_id);
if current_generation != old_generation {
log::trace!(
target: "cranpose::compose::emit",
"skipping stale replacement cleanup for node #{old_id} (slot_generation={old_generation} current_generation={current_generation})",
);
return;
}
log::trace!(
target: "cranpose::compose::emit",
"removing replaced node #{old_id} (generation={old_generation})",
);
self.commands_mut().push(Command::RemoveNode { id: old_id });
}
pub fn use_state<T: Clone + 'static>(&self, init: impl FnOnce() -> T) -> MutableState<T> {
let runtime = self.runtime_handle();
let state = self.with_slot_session_mut(|slots| {
slots.remember(|| OwnedMutableState::with_runtime(init(), runtime.clone()))
});
state.with(|state| state.handle())
}
fn emit_node_box<N: Node + 'static>(
&self,
make_node: impl FnOnce(&mut dyn Applier) -> EmittedNode,
) -> NodeId {
let (existing_id, existing_generation, type_matches, gen_matches) = {
if let Some((id, slot_gen)) =
self.with_slot_session_mut(|slots| slots.current_node_record())
{
let mut applier = self.borrow_applier();
let gen_ok = applier.node_generation(id) == slot_gen;
let type_ok = match applier.get_mut(id) {
Ok(node) => node.as_any_mut().downcast_ref::<N>().is_some(),
Err(_) => false,
};
(Some(id), Some(slot_gen), type_ok, gen_ok)
} else {
(None, None, false, false)
}
};
if let Some(id) = existing_id {
if type_matches && gen_matches {
let scope_debug = self
.current_recranpose_scope()
.map(|scope| (scope.id(), debug_scope_label(scope.id())))
.unwrap_or((0, None));
log::trace!(
target: "cranpose::compose::emit",
"reusing node #{id} as {} [scope_id={} scope_label={:?}]",
std::any::type_name::<N>(),
scope_debug.0,
scope_debug.1,
);
self.commands_mut().push(Command::update_node::<N>(id));
self.attach_to_parent(id);
let slot_gen =
existing_generation.expect("reused node must keep its slot generation");
let parent_id = self.recorded_node_parent(id);
let recorded = self.with_slot_session_mut(|slots| {
slots.record_node_with_parent(id, slot_gen, parent_id)
});
match recorded {
NodeSlotUpdate::Reused {
id: recorded_id,
generation,
} => {
debug_assert_eq!(recorded_id, id);
debug_assert_eq!(generation, slot_gen);
}
NodeSlotUpdate::Inserted { .. } | NodeSlotUpdate::Replaced { .. } => {
panic!("reused node recording must keep the same node identity");
}
}
self.core.last_node_reused.set(Some(true));
return id;
}
}
let (id, gen) = {
let mut applier = self.borrow_applier();
let emitted = make_node(&mut *applier);
let id = match emitted {
EmittedNode::Fresh(node) => applier.create(node),
EmittedNode::Recycled(recycled) => {
let (stable_id, node, warm_origin) = recycled.into_parts();
applier
.insert_with_id(stable_id, node)
.expect("recycled stable id should be available");
applier.set_recycled_node_origin(stable_id, warm_origin);
stable_id
}
};
let gen = applier.node_generation(id);
(id, gen)
};
let scope_debug = self
.current_recranpose_scope()
.map(|scope| (scope.id(), debug_scope_label(scope.id())))
.unwrap_or((0, None));
log::trace!(
target: "cranpose::compose::emit",
"creating node #{} (gen={}) as {} [scope_id={} scope_label={:?}]",
id,
gen,
std::any::type_name::<N>(),
scope_debug.0,
scope_debug.1,
);
self.commands_mut().push(Command::MountNode { id });
self.attach_to_parent(id);
let parent_id = self.recorded_node_parent(id);
let recorded =
self.with_slot_session_mut(|slots| slots.record_node_with_parent(id, gen, parent_id));
match recorded {
NodeSlotUpdate::Inserted {
id: recorded_id,
generation,
} => {
debug_assert_eq!(recorded_id, id);
debug_assert_eq!(generation, gen);
}
NodeSlotUpdate::Replaced {
old_id,
old_generation,
new_id,
new_generation,
} => {
debug_assert_eq!(new_id, id);
debug_assert_eq!(new_generation, gen);
self.queue_replaced_slot_node_removal(old_id, old_generation);
}
NodeSlotUpdate::Reused { .. } => {
panic!("fresh or replacement node recording must not report normal reuse");
}
}
self.core.last_node_reused.set(Some(false));
id
}
pub fn emit_node<N: Node + 'static>(&self, init: impl FnOnce() -> N) -> NodeId {
self.emit_node_box::<N>(|_| EmittedNode::Fresh(Box::new(init())))
}
pub fn emit_recyclable_node<N: Node + 'static>(
&self,
init: impl FnOnce() -> N,
reset: impl FnOnce(&mut N),
) -> NodeId {
self.emit_node_box::<N>(|applier| {
let key = TypeId::of::<N>();
if let Some(mut recycled) = applier.take_recycled_node(key) {
let typed = recycled
.node_mut()
.as_any_mut()
.downcast_mut::<N>()
.expect("recycled node type mismatch");
reset(typed);
EmittedNode::Recycled(recycled)
} else {
let node = Box::new(init());
applier.record_fresh_recyclable_creation(key);
if let Some(shell) = node.rehouse_for_recycle() {
applier.seed_recycled_node_shell(key, node.recycle_pool_limit(), shell);
}
EmittedNode::Fresh(node)
}
})
}
fn attach_to_parent(&self, id: NodeId) {
self.attach_to_parent_with_mode(id, false);
}
pub(crate) fn attach_to_parent_with_mode(
&self,
id: NodeId,
force_reparent_current_parent: bool,
) {
let mut parent_stack = self.parent_stack();
if let Some(parent_id) = parent_stack.last().map(|frame| frame.id) {
let stale_root_parent = self.core.root.get() == Some(parent_id) && {
let mut applier = self.borrow_applier();
applier.get_mut(parent_id).is_err()
};
if stale_root_parent {
parent_stack.pop();
self.set_root(None);
} else {
let frame = parent_stack
.last_mut()
.expect("active parent frame should remain available");
let attach_mode = frame.attach_mode;
if parent_id == id {
return;
}
if matches!(attach_mode, ParentAttachMode::DeferredSync) {
frame.new_children.push(id);
}
drop(parent_stack);
{
let mut applier = self.borrow_applier();
if let Ok(child_node) = applier.get_mut(id) {
let existing_parent = child_node.parent();
let should_set = if force_reparent_current_parent {
existing_parent != Some(parent_id)
} else {
match existing_parent {
None => true,
Some(existing) => {
let root_id = self.core.root.get();
parent_id != root_id.unwrap_or(0)
|| existing == root_id.unwrap_or(0)
}
}
};
if should_set {
child_node.set_parent_for_bubbling(parent_id);
}
}
}
if matches!(attach_mode, ParentAttachMode::ImmediateAppend) {
self.commands_mut().push(Command::AttachChild {
parent_id,
child_id: id,
bubble: DirtyBubble::LAYOUT_AND_MEASURE,
});
}
return;
}
}
drop(parent_stack);
let in_subcompose = !self.subcompose_stack().is_empty();
if in_subcompose {
let has_parent = {
let mut applier = self.borrow_applier();
applier
.get_mut(id)
.map(|node| node.parent().is_some())
.unwrap_or(false)
};
if !has_parent {
let mut subcompose_stack = self.subcompose_stack();
if let Some(frame) = subcompose_stack.last_mut() {
frame.nodes.push(id);
}
}
return;
}
if let Some(parent_hint) = self.core.recranpose_parent_hint.get() {
let parent_status = {
let mut applier = self.borrow_applier();
applier
.get_mut(id)
.map(|node| node.parent())
.unwrap_or(None)
};
match parent_status {
Some(existing) if existing == parent_hint => {}
None => {
self.commands_mut().push(Command::AttachChild {
parent_id: parent_hint,
child_id: id,
bubble: DirtyBubble::LAYOUT_AND_MEASURE,
});
}
Some(_) => {}
}
return;
}
let has_parent = {
let mut applier = self.borrow_applier();
applier
.get_mut(id)
.map(|node| node.parent().is_some())
.unwrap_or(false)
};
if has_parent {
return;
}
self.set_root(Some(id));
}
pub fn with_node_mut<N: Node + 'static, R>(
&self,
id: NodeId,
f: impl FnOnce(&mut N) -> R,
) -> Result<R, NodeError> {
let mut applier = self.borrow_applier();
let node = applier.get_mut(id)?;
let typed = node
.as_any_mut()
.downcast_mut::<N>()
.ok_or(NodeError::TypeMismatch {
id,
expected: std::any::type_name::<N>(),
})?;
Ok(f(typed))
}
pub fn push_parent(&self, id: NodeId) {
let reused = self.core.last_node_reused.take().unwrap_or(true);
let in_subcompose = !self.core.subcompose_stack.borrow().is_empty();
let mut previous = ChildList::new();
if reused || in_subcompose {
previous.extend(self.get_node_children(id));
} else {
let existing_children = self.get_node_children(id);
if !existing_children.is_empty() {
previous.extend(existing_children);
}
}
let attach_mode = if in_subcompose || !previous.is_empty() {
ParentAttachMode::DeferredSync
} else {
ParentAttachMode::ImmediateAppend
};
self.parent_stack().push(ParentFrame {
id,
previous,
new_children: ChildList::new(),
new_children_membership: None,
attach_mode,
});
}
pub fn pop_parent(&self) {
let frame_opt = {
let mut stack = self.parent_stack();
stack.pop()
};
if let Some(frame) = frame_opt {
let ParentFrame {
id,
previous,
new_children,
new_children_membership: _new_children_membership,
attach_mode,
} = frame;
log::trace!(target: "cranpose::compose::parent", "pop_parent: node #{}", id);
log::trace!(
target: "cranpose::compose::parent",
"previous children: {:?}",
previous
);
log::trace!(
target: "cranpose::compose::parent",
"new children: {:?}",
new_children
);
if matches!(attach_mode, ParentAttachMode::DeferredSync) {
let _ = previous;
self.commands_mut().push(Command::SyncChildren {
parent_id: id,
expected_children: new_children,
});
}
}
}
pub(crate) fn take_commands(&self) -> CommandQueue {
std::mem::take(&mut *self.commands_mut())
}
pub fn apply_pending_commands(&self) -> Result<(), NodeError> {
let commands = self.take_commands();
let runtime_handle = self.runtime_handle();
let result = {
let mut applier = self.borrow_applier();
let mut result = commands.apply(&mut *applier);
if result.is_ok() {
for update in runtime_handle.take_updates() {
if let Err(err) = update.apply(&mut *applier) {
result = Err(err);
break;
}
}
}
result
};
if result.is_err() {
let host = self.active_slots_host();
if !host.has_active_pass() {
host.abandon_after_apply_failure();
}
}
result?;
runtime_handle.drain_ui();
Ok(())
}
pub fn register_side_effect(&self, effect: impl FnOnce() + 'static) {
self.side_effects_mut().push(Box::new(effect));
}
pub fn take_side_effects(&self) -> Vec<Box<dyn FnOnce()>> {
std::mem::take(&mut *self.side_effects_mut())
}
pub(crate) fn root(&self) -> Option<NodeId> {
self.core.root.get()
}
pub(crate) fn set_root(&self, node: Option<NodeId>) {
self.core.root.set(node);
}
}