use super::{JsValue, Scope};
use crate::{ContextKey, OwnerHandle};
use std::{
cell::{Cell, RefCell},
rc::{Rc, Weak},
};
struct Action {
owner: OwnerHandle,
ready: Weak<Cell<bool>>,
setup: Box<dyn FnOnce() -> Result<(), JsValue>>,
}
#[derive(Default)]
pub(super) struct CommitQueue(RefCell<Vec<Action>>);
struct MountContext;
impl ContextKey for MountContext {
type Value = CommitQueue;
}
impl Scope {
pub(super) fn prepare_queue(&mut self, parent: Option<&OwnerHandle>) {
self.mount_parent = parent.cloned();
if self.owner().is_disposed() {
self.mount_queue = None;
return;
}
self.mount_queue = Some(
match parent.and_then(|parent| parent.context::<MountContext>()) {
Some(queue) => queue,
None => {
self.owner()
.provide::<MountContext>(CommitQueue::default())
.expect("fresh component owner");
self.owner()
.context::<MountContext>()
.expect("just provided")
}
},
);
}
#[doc(hidden)]
pub fn before_commit(
&mut self,
setup: impl FnOnce() -> Result<(), JsValue> + 'static,
) -> Result<(), JsValue> {
if self.owner().is_disposed() {
return Err(JsValue::from_str("cannot initialize a disposed component"));
}
if self.owner().is_active() {
return setup();
}
let queue = self.mount_queue.as_ref().expect("prepared component queue");
queue.0.borrow_mut().push(Action {
owner: self.owner(),
ready: Rc::downgrade(&self.mount_ready),
setup: Box::new(setup),
});
Ok(())
}
#[doc(hidden)]
pub fn finish_prepare(&self) -> Result<(), JsValue> {
if self.owner().is_disposed() {
return Err(JsValue::from_str("cannot commit a disposed component"));
}
self.mount_ready.set(true);
if self
.mount_parent
.as_ref()
.is_some_and(|parent| !parent.is_active())
{
return Ok(());
}
self.finish_prepare_subtree()
}
pub fn finish_prepare_subtree(&self) -> Result<(), JsValue> {
if self.owner().is_disposed() {
return Err(JsValue::from_str("cannot prepare a disposed subtree"));
}
self.mount_ready.set(true);
if let Some(queue) = &self.mount_queue {
let pending = queue.0.take();
let (ready, waiting): (Vec<_>, Vec<_>) = pending
.into_iter()
.filter(|action| !action.owner.is_disposed() && action.ready.strong_count() > 0)
.partition(|action| {
action.owner.is_within(&self.owner())
&& action.ready.upgrade().is_some_and(|ready| ready.get())
});
queue.0.borrow_mut().extend(waiting);
for action in ready {
if !action.owner.is_disposed() {
(action.setup)()?;
}
}
}
if self.owner().is_disposed() {
return Err(JsValue::from_str(
"component disposed during initialization",
));
}
Ok(())
}
pub fn try_commit(&self) -> Result<(), JsValue> {
if self.owner().is_active()
&& self
.mount_queue
.as_ref()
.is_none_or(|queue| queue.0.borrow().is_empty())
{
return Ok(());
}
self.finish_prepare()?;
self.owner.commit();
if self.owner().is_disposed() {
return Err(JsValue::from_str("component disposed during activation"));
}
Ok(())
}
}