use std::rc::Rc;
use super::runtime::Owner;
use super::{untrack, with_runtime};
use crate::view::Element;
pub fn mount_component<R>(fn_ptr: *const (), body: impl FnOnce() -> R) -> (Owner, R) {
let owner = Owner::new(None);
with_runtime(|rt| {
if let Some(o) = rt.owners.get_mut(owner) {
o.mount_fn = Some(fn_ptr);
}
rt.component_owners.entry(fn_ptr).or_default().push(owner);
});
let result = untrack(|| owner.with(body));
(owner, result)
}
pub fn unmount_component(owner: Owner) {
let fn_ptr = with_runtime(|rt| rt.owners.get(owner).and_then(|o| o.mount_fn));
if let Some(fp) = fn_ptr {
with_runtime(|rt| {
if let Some(list) = rt.component_owners.get_mut(&fp) {
list.retain(|o| *o != owner);
if list.is_empty() {
rt.component_owners.remove(&fp);
}
}
});
}
owner.dispose();
}
pub fn on_mount(f: impl FnOnce() + 'static) {
let registered = with_runtime(|rt| {
if rt.current_owner().is_none() {
return false;
}
rt.pending_mounts.push(Box::new(f));
true
});
if !registered {
super::warn_no_owner("on_mount");
}
}
pub fn flush_mounts() {
let queue: Vec<Box<dyn FnOnce()>> = with_runtime(|rt| std::mem::take(&mut rt.pending_mounts));
for cb in queue {
untrack(cb);
}
}
#[doc(hidden)]
pub fn owners_for_fn(fn_ptr: *const ()) -> Vec<Owner> {
with_runtime(|rt| {
rt.component_owners
.get(&fn_ptr)
.cloned()
.unwrap_or_default()
})
}
use std::cell::Cell;
thread_local! {
static PENDING_MOUNT: Cell<Option<(MountId, Element)>> = const { Cell::new(None) };
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct MountId(pub(crate) u64);
pub(crate) struct MountSite {
pub fn_ptr: *const (),
pub body: Rc<dyn Fn() -> Element + 'static>,
pub owner: Option<Owner>,
pub body_root: Option<Element>,
pub parent: Option<Element>,
pub anchor: Option<Element>,
}
pub fn on_component_root_attached(parent: Element, child: Element) {
let pending = PENDING_MOUNT.with(|cell| cell.take());
let Some((mount_id, root)) = pending else {
return;
};
if root != child {
PENDING_MOUNT.with(|cell| cell.set(Some((mount_id, root))));
return;
}
let anchor = crate::view::previous_sibling(parent, child);
super::with_runtime(|rt| {
if let Some(site) = rt.mount_sites.get_mut(&mount_id) {
site.parent = Some(parent);
site.anchor = anchor;
}
});
}
#[doc(hidden)]
pub fn __reset_pending_mount_for_tests() {
PENDING_MOUNT.with(|cell| cell.set(None));
}
pub fn mount_component_remountable<F>(fn_ptr: *const (), body: F) -> Element
where
F: Fn() -> Element + 'static,
{
let body: Rc<dyn Fn() -> Element + 'static> = Rc::new(body);
let body_for_first = body.clone();
let owner = Owner::new(None);
with_runtime(|rt| {
if let Some(o) = rt.owners.get_mut(owner) {
o.mount_fn = Some(fn_ptr);
}
rt.component_owners.entry(fn_ptr).or_default().push(owner);
});
let body_root = untrack(|| owner.with(|| (*body_for_first)()));
let mount_id = with_runtime(|rt| {
rt.mount_id_counter += 1;
let id = MountId(rt.mount_id_counter);
rt.mount_sites.insert(
id,
MountSite {
fn_ptr,
body,
owner: Some(owner),
body_root: Some(body_root),
parent: None,
anchor: None,
},
);
rt.fn_ptr_mounts.entry(fn_ptr).or_default().push(id);
id
});
PENDING_MOUNT.with(|cell| cell.set(Some((mount_id, body_root))));
body_root
}
pub fn remount_components_for(patched_fns: &[*const ()]) {
if patched_fns.is_empty() {
return;
}
let patched_set: std::collections::HashSet<*const ()> = patched_fns.iter().copied().collect();
let ids: Vec<MountId> = with_runtime(|rt| {
let mut candidates: Vec<MountId> = Vec::new();
for fp in patched_fns {
if let Some(list) = rt.fn_ptr_mounts.get(fp) {
for id in list {
if !candidates.contains(id) {
candidates.push(*id);
}
}
}
}
candidates
.into_iter()
.filter(|mount_id| {
let site = match rt.mount_sites.get(mount_id) {
Some(s) => s,
None => return false,
};
let mut cursor = match site.owner {
Some(o) => o,
None => return false,
};
while let Some(parent) = rt.owners.get(cursor).and_then(|o| o.parent) {
if let Some(mf) = rt.owners.get(parent).and_then(|o| o.mount_fn) {
if patched_set.contains(&mf) {
return false;
}
}
cursor = parent;
}
true
})
.collect()
});
if ids.is_empty() {
return;
}
struct RemountInfo {
mount_id: MountId,
parent: Element,
old_body_root: Element,
body: Rc<dyn Fn() -> Element + 'static>,
fn_ptr: *const (),
}
let infos: Vec<RemountInfo> = with_runtime(|rt| {
ids.iter()
.filter_map(|mid| {
let site = rt.mount_sites.get(mid)?;
Some(RemountInfo {
mount_id: *mid,
parent: site.parent?,
old_body_root: site.body_root?,
body: site.body.clone(),
fn_ptr: site.fn_ptr,
})
})
.collect()
});
if infos.is_empty() {
return;
}
let mut parent_snapshot: std::collections::HashMap<Element, Vec<Element>> =
std::collections::HashMap::new();
for info in &infos {
parent_snapshot
.entry(info.parent)
.or_insert_with(|| crate::view::children_of(info.parent));
}
let mut by_parent: std::collections::HashMap<Element, Vec<(Element, Option<Element>)>> =
std::collections::HashMap::new();
for info in &infos {
crate::view::remove_child(info.parent, info.old_body_root);
by_parent
.entry(info.parent)
.or_default()
.push((info.old_body_root, None));
}
let mut results: Vec<(MountId, Element, Element, Element, Owner)> =
Vec::with_capacity(infos.len());
for info in infos {
let old_owner = with_runtime(|rt| {
let site = rt.mount_sites.get_mut(&info.mount_id)?;
site.body_root.take();
site.owner.take()
});
if let Some(o) = old_owner {
o.dispose();
}
let new_owner = Owner::new(None);
with_runtime(|rt| {
if let Some(o) = rt.owners.get_mut(new_owner) {
o.mount_fn = Some(info.fn_ptr);
}
rt.component_owners
.entry(info.fn_ptr)
.or_default()
.push(new_owner);
});
let new_body_root = untrack(|| new_owner.with(|| (*info.body)()));
PENDING_MOUNT.with(|cell| cell.set(None));
if let Some(list) = by_parent.get_mut(&info.parent) {
if let Some(entry) = list
.iter_mut()
.find(|(o, n)| *o == info.old_body_root && n.is_none())
{
entry.1 = Some(new_body_root);
}
}
results.push((
info.mount_id,
info.parent,
info.old_body_root,
new_body_root,
new_owner,
));
}
for (parent, pairs) in &by_parent {
let snapshot = parent_snapshot.get(parent).cloned().unwrap_or_default();
let old_to_new: std::collections::HashMap<Element, Element> = pairs
.iter()
.filter_map(|(o, n)| n.map(|new_root| (*o, new_root)))
.collect();
let desired: Vec<Element> = snapshot
.iter()
.map(|c| old_to_new.get(c).copied().unwrap_or(*c))
.collect();
let new_set: std::collections::HashSet<Element> =
pairs.iter().filter_map(|(_, n)| *n).collect();
for (idx, child) in desired.iter().enumerate() {
if new_set.contains(child) {
crate::view::insert_child_at(*parent, *child, idx);
}
}
}
for (mount_id, _, _, new_root, new_owner) in &results {
with_runtime(|rt| {
if let Some(site) = rt.mount_sites.get_mut(mount_id) {
site.owner = Some(*new_owner);
site.body_root = Some(*new_root);
}
});
}
for (mount_id, parent, _, new_root, _) in &results {
let new_anchor = crate::view::previous_sibling(*parent, *new_root);
with_runtime(|rt| {
if let Some(site) = rt.mount_sites.get_mut(mount_id) {
site.anchor = new_anchor;
}
});
}
}