#![forbid(unsafe_code)]
use crate::{
runtime::{with_runtime, RuntimeId},
EffectId, PinnedFuture, ResourceId, SignalId, SuspenseContext,
};
use futures::stream::FuturesUnordered;
use std::{collections::HashMap, fmt};
#[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 {
#[doc(hidden)]
pub runtime: RuntimeId,
#[doc(hidden)]
pub 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
})
.expect("tried to run untracked function in a runtime that has been disposed")
}
}
impl Scope {
pub 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 parent(&self) -> Option<Scope> {
with_runtime(self.runtime, |runtime| {
runtime.scope_parents.borrow().get(self.id).copied()
})
.ok()
.flatten()
.map(|id| Scope {
runtime: self.runtime,
id,
})
}
}
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 {
pub fn all_resources(&self) -> Vec<ResourceId> {
with_runtime(self.runtime, |runtime| runtime.all_resources()).unwrap_or_default()
}
pub fn pending_resources(&self) -> Vec<ResourceId> {
with_runtime(self.runtime, |runtime| runtime.pending_resources()).unwrap_or_default()
}
pub fn serialization_resolvers(&self) -> FuturesUnordered<PinnedFuture<(ResourceId, String)>> {
with_runtime(self.runtime, |runtime| runtime.serialization_resolvers()).unwrap_or_default()
}
pub fn register_suspense(
&self,
context: SuspenseContext,
key_before_suspense: &str,
key: &str,
resolver: impl FnOnce() -> String + 'static,
) {
use crate::create_isomorphic_effect;
use futures::StreamExt;
_ = with_runtime(self.runtime, |runtime| {
let 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(),
(
key_before_suspense.to_string(),
Box::pin(async move {
rx.next().await;
resolver()
}),
),
);
})
}
pub fn pending_fragments(&self) -> HashMap<String, (String, PinnedFuture<String>)> {
with_runtime(self.runtime, |runtime| {
let mut shared_context = runtime.shared_context.borrow_mut();
std::mem::take(&mut shared_context.pending_fragments)
})
.unwrap_or_default()
}
}
impl fmt::Debug for ScopeDisposer {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("ScopeDisposer").finish()
}
}