use cfg_if::cfg_if;
use crate::runtime::{with_runtime, RuntimeId};
use crate::{hydration::SharedContext, EffectId, ResourceId, SignalId};
use crate::{PinnedFuture, SuspenseContext};
use futures::stream::FuturesUnordered;
use std::collections::HashMap;
use std::fmt::Debug;
use std::{future::Future, pin::Pin};
#[doc(hidden)]
#[must_use = "Scope will leak memory if the disposer function is never called"]
pub fn create_scope(runtime: RuntimeId, f: impl FnOnce(Scope) + 'static) -> ScopeDisposer {
runtime.run_scope_undisposed(f, None).2
}
#[doc(hidden)]
#[must_use = "Scope will leak memory if the disposer function is never called"]
pub fn raw_scope_and_disposer(runtime: RuntimeId) -> (Scope, ScopeDisposer) {
runtime.raw_scope_and_disposer()
}
#[doc(hidden)]
pub fn run_scope<T>(runtime: RuntimeId, f: impl FnOnce(Scope) -> T + 'static) -> T {
runtime.run_scope(f, None)
}
#[doc(hidden)]
#[must_use = "Scope will leak memory if the disposer function is never called"]
pub fn run_scope_undisposed<T>(
runtime: RuntimeId,
f: impl FnOnce(Scope) -> T + 'static,
) -> (T, ScopeId, ScopeDisposer) {
runtime.run_scope_undisposed(f, None)
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct Scope {
pub(crate) runtime: RuntimeId,
pub(crate) id: ScopeId,
}
impl Scope {
pub fn id(&self) -> ScopeId {
self.id
}
pub fn child_scope(self, f: impl FnOnce(Scope)) -> ScopeDisposer {
let (_, disposer) = self.run_child_scope(f);
disposer
}
pub fn run_child_scope<T>(self, f: impl FnOnce(Scope) -> T) -> (T, ScopeDisposer) {
let (res, child_id, disposer) = self.runtime.run_scope_undisposed(f, Some(self));
with_runtime(self.runtime, |runtime| {
let mut children = runtime.scope_children.borrow_mut();
children
.entry(self.id)
.expect("trying to add a child to a Scope that has already been disposed")
.or_default()
.push(child_id);
});
(res, disposer)
}
pub fn untrack<T>(&self, f: impl FnOnce() -> T) -> T {
with_runtime(self.runtime, |runtime| {
let prev_observer = runtime.observer.take();
let untracked_result = f();
runtime.observer.set(prev_observer);
untracked_result
})
}
}
impl Scope {
pub(crate) fn dispose(self) {
with_runtime(self.runtime, |runtime| {
let children = {
let mut children = runtime.scope_children.borrow_mut();
children.remove(self.id)
};
if let Some(children) = children {
for id in children {
Scope {
runtime: self.runtime,
id,
}
.dispose();
}
}
if let Some(cleanups) = runtime.scope_cleanups.borrow_mut().remove(self.id) {
for cleanup in cleanups {
cleanup();
}
}
let owned = {
let owned = runtime.scopes.borrow_mut().remove(self.id);
owned.map(|owned| owned.take())
};
if let Some(owned) = owned {
for property in owned {
match property {
ScopeProperty::Signal(id) => {
runtime.signals.borrow_mut().remove(id);
let subs = runtime.signal_subscribers.borrow_mut().remove(id);
if let Some(subs) = subs {
let source_map = runtime.effect_sources.borrow();
for effect in subs.borrow().iter() {
if let Some(effect_sources) = source_map.get(*effect) {
effect_sources.borrow_mut().remove(&id);
}
}
}
}
ScopeProperty::Effect(id) => {
runtime.effects.borrow_mut().remove(id);
runtime.effect_sources.borrow_mut().remove(id);
}
ScopeProperty::Resource(id) => {
runtime.resources.borrow_mut().remove(id);
}
}
}
}
})
}
pub(crate) fn with_scope_property(&self, f: impl FnOnce(&mut Vec<ScopeProperty>)) {
with_runtime(self.runtime, |runtime| {
let scopes = runtime.scopes.borrow();
let scope = scopes
.get(self.id)
.expect("tried to add property to a scope that has been disposed");
f(&mut scope.borrow_mut());
})
}
}
pub fn on_cleanup(cx: Scope, cleanup_fn: impl FnOnce() + 'static) {
with_runtime(cx.runtime, |runtime| {
let mut cleanups = runtime.scope_cleanups.borrow_mut();
let cleanups = cleanups
.entry(cx.id)
.expect("trying to clean up a Scope that has already been disposed")
.or_insert_with(Default::default);
cleanups.push(Box::new(cleanup_fn));
})
}
slotmap::new_key_type! {
pub struct ScopeId;
}
#[derive(Debug)]
pub(crate) enum ScopeProperty {
Signal(SignalId),
Effect(EffectId),
Resource(ResourceId),
}
pub struct ScopeDisposer(pub(crate) Box<dyn FnOnce()>);
impl ScopeDisposer {
pub fn dispose(self) {
(self.0)()
}
}
impl Scope {
cfg_if! {
if #[cfg(any(feature = "hydrate", doc))] {
pub fn is_hydrating(&self) -> bool {
with_runtime(self.runtime, |runtime| {
runtime.shared_context.borrow().is_some()
})
}
pub fn start_hydration(&self, element: &web_sys::Element) {
with_runtime(self.runtime, |runtime| {
runtime.start_hydration(element);
})
}
pub fn end_hydration(&self) {
with_runtime(self.runtime, |runtime| {
runtime.end_hydration();
})
}
pub fn get_next_element(&self, template: &web_sys::Element) -> web_sys::Element {
use wasm_bindgen::{JsCast, UnwrapThrowExt};
let cloned_template = |t: &web_sys::Element| {
let t = t
.unchecked_ref::<web_sys::HtmlTemplateElement>()
.content()
.clone_node_with_deep(true)
.expect_throw("(get_next_element) could not clone template")
.unchecked_into::<web_sys::Element>()
.first_element_child()
.expect_throw("(get_next_element) could not get first child of template");
t
};
with_runtime(self.runtime, |runtime| {
if let Some(ref mut shared_context) = &mut *runtime.shared_context.borrow_mut() {
if shared_context.context.is_some() {
let key = shared_context.next_hydration_key();
let node = shared_context.registry.remove(&key);
if let Some(node) = node {
shared_context.completed.push(node.clone());
node
} else {
cloned_template(template)
}
} else {
cloned_template(template)
}
} else {
cloned_template(template)
}
})
}
}
}
#[cfg(any(feature = "csr", feature = "hydrate", doc))]
pub fn get_next_marker(&self, start: &web_sys::Node) -> (web_sys::Node, Vec<web_sys::Node>) {
let mut end = Some(start.clone());
let mut count = 0;
let mut current = Vec::new();
let mut start = start.clone();
with_runtime(self.runtime, |runtime| {
if runtime
.shared_context
.borrow()
.as_ref()
.map(|sc| sc.context.as_ref())
.is_some()
{
while let Some(curr) = end {
start = curr.clone();
if curr.node_type() == 8 {
let v = curr.node_value();
if v == Some("#".to_string()) {
count += 1;
} else if v == Some("/".to_string()) {
count -= 1;
if count == 0 {
current.push(curr.clone());
return (curr, current);
}
}
}
current.push(curr.clone());
end = curr.next_sibling();
}
}
(start, current)
})
}
pub fn next_hydration_key(&self) -> String {
with_runtime(self.runtime, |runtime| {
let mut sc = runtime.shared_context.borrow_mut();
if let Some(ref mut sc) = *sc {
sc.next_hydration_key()
} else {
let mut new_sc = SharedContext::default();
let id = new_sc.next_hydration_key();
*sc = Some(new_sc);
id
}
})
}
pub fn with_next_context<T>(&self, f: impl FnOnce() -> T) -> T {
with_runtime(self.runtime, |runtime| {
if runtime
.shared_context
.borrow()
.as_ref()
.and_then(|sc| sc.context.as_ref())
.is_some()
{
let c = {
if let Some(ref mut sc) = *runtime.shared_context.borrow_mut() {
if let Some(ref mut context) = sc.context {
let next = context.next_hydration_context();
Some(std::mem::replace(context, next))
} else {
None
}
} else {
None
}
};
let res = self.untrack(f);
if let Some(ref mut sc) = *runtime.shared_context.borrow_mut() {
sc.context = c;
}
res
} else {
self.untrack(f)
}
})
}
pub fn all_resources(&self) -> Vec<ResourceId> {
with_runtime(self.runtime, |runtime| runtime.all_resources())
}
pub fn current_fragment_key(&self) -> String {
with_runtime(self.runtime, |runtime| {
runtime
.shared_context
.borrow()
.as_ref()
.map(|context| context.current_fragment_key())
.unwrap_or_else(|| String::from("0f"))
})
}
pub fn serialization_resolvers(&self) -> FuturesUnordered<PinnedFuture<(ResourceId, String)>> {
with_runtime(self.runtime, |runtime| runtime.serialization_resolvers())
}
pub fn register_suspense(
&self,
context: SuspenseContext,
key: &str,
resolver: impl FnOnce() -> String + 'static,
) {
use crate::create_isomorphic_effect;
use futures::StreamExt;
with_runtime(self.runtime, |runtime| {
if let Some(ref mut shared_context) = *runtime.shared_context.borrow_mut() {
let (tx, mut rx) = futures::channel::mpsc::unbounded();
create_isomorphic_effect(*self, move |_| {
let pending = context.pending_resources.try_with(|n| *n).unwrap_or(0);
if pending == 0 {
_ = tx.unbounded_send(());
}
});
shared_context.pending_fragments.insert(
key.to_string(),
Box::pin(async move {
rx.next().await;
resolver()
}),
);
}
})
}
pub fn pending_fragments(&self) -> HashMap<String, Pin<Box<dyn Future<Output = String>>>> {
with_runtime(self.runtime, |runtime| {
if let Some(ref mut shared_context) = *runtime.shared_context.borrow_mut() {
std::mem::take(&mut shared_context.pending_fragments)
} else {
HashMap::new()
}
})
}
}
impl Debug for ScopeDisposer {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("ScopeDisposer").finish()
}
}