#[cfg(feature = "transition")]
use crate::TransitionState;
use crate::{
debug_warn, AnyEffect, AnySignal, EffectId, ResourceId, ResourceState, Scope, ScopeDisposer,
ScopeId, ScopeState, SignalId, SignalState, StreamingResourceId, Subscriber,
};
use slotmap::SlotMap;
use std::cell::RefCell;
use std::fmt::Debug;
use std::rc::Rc;
use thiserror::Error;
use crate::hydration::SharedContext;
#[derive(Default, Debug)]
pub(crate) struct Runtime {
pub(crate) shared_context: RefCell<Option<SharedContext>>,
pub(crate) stack: RefCell<Vec<Subscriber>>,
pub(crate) scopes: RefCell<SlotMap<ScopeId, Rc<ScopeState>>>,
#[cfg(feature = "transition")]
pub(crate) transition: RefCell<Option<Rc<TransitionState>>>,
}
#[derive(Error, Debug)]
pub(crate) enum ReactiveSystemErr {
#[error("tried to access a scope that had already been disposed: {0:?}")]
ScopeDisposed(ScopeId),
#[error("tried to access an error that was not available: {0:?} {1:?}")]
Effect(ScopeId, EffectId),
}
impl Runtime {
pub fn new() -> Self {
Self::default()
}
pub fn scope<T>(&self, id: ScopeId, f: impl FnOnce(&ScopeState) -> T) -> T {
let scope = { self.scopes.borrow().get(id).cloned() };
if let Some(scope) = scope {
(f)(&scope)
} else {
debug_warn!("(scope) couldn't locate {id:?}");
panic!("couldn't locate {id:?}");
}
}
pub fn try_scope<T>(
&self,
id: ScopeId,
f: impl FnOnce(&ScopeState) -> T,
) -> Result<T, ReactiveSystemErr> {
let scope = { self.scopes.borrow().get(id).cloned() };
if let Some(scope) = scope {
Ok((f)(&scope))
} else {
debug_warn!("(scope) couldn't locate {id:?}");
Err(ReactiveSystemErr::ScopeDisposed(id))
}
}
pub fn any_effect<T>(&self, id: (ScopeId, EffectId), f: impl FnOnce(&dyn AnyEffect) -> T) -> T {
self.scope(id.0, |scope| {
if let Some(n) = scope.effects.get(id.1 .0) {
(f)(n)
} else {
debug_warn!("(any_effect) couldn't locate {id:?}");
panic!("couldn't locate {id:?}");
}
})
}
pub fn try_any_effect<T>(
&self,
id: (ScopeId, EffectId),
f: impl FnOnce(&dyn AnyEffect) -> T,
) -> Result<Result<T, ReactiveSystemErr>, ReactiveSystemErr> {
self.try_scope(id.0, |scope| {
if let Some(n) = scope.effects.get(id.1 .0) {
Ok((f)(n))
} else {
debug_warn!("(try_any_effect) couldn't locate {id:?}");
Err(ReactiveSystemErr::Effect(id.0, id.1))
}
})
}
pub fn any_signal<T>(&self, id: (ScopeId, SignalId), f: impl FnOnce(&dyn AnySignal) -> T) -> T {
self.scope(id.0, |scope| {
if let Some(n) = scope.signals.get(id.1 .0) {
(f)(n)
} else {
debug_warn!("(any_signal) couldn't locate {id:?}");
panic!("couldn't locate {id:?}");
}
})
}
pub fn signal<T, U>(&self, id: (ScopeId, SignalId), f: impl FnOnce(&SignalState<T>) -> U) -> U
where
T: 'static,
{
self.any_signal(id, |n| {
if let Some(n) = n.as_any().downcast_ref::<SignalState<T>>() {
f(n)
} else {
panic!(
"couldn't convert {id:?} to SignalState<{}>",
std::any::type_name::<T>()
);
}
})
}
pub fn resource<S, T, U>(
&self,
id: (ScopeId, ResourceId),
f: impl FnOnce(&ResourceState<S, T>) -> U,
) -> U
where
S: Debug + Clone + 'static,
T: Debug + Clone + 'static,
{
self.scope(id.0, |scope| {
if let Some(n) = scope.resources.get(id.1 .0) {
if let Some(n) = n.as_any().downcast_ref::<ResourceState<S, T>>() {
f(n)
} else {
panic!(
"couldn't convert {id:?} to ResourceState<{}, {}>",
std::any::type_name::<S>(),
std::any::type_name::<T>(),
);
}
} else {
panic!("couldn't locate {id:?}");
}
})
}
pub fn running_effect(&self) -> Option<Subscriber> {
self.stack.borrow().last().cloned()
}
#[cfg(feature = "transition")]
pub fn running_transition(&self) -> Option<Rc<TransitionState>> {
self.transition.borrow().as_ref().and_then(|t| {
if t.running.get() {
Some(Rc::clone(t))
} else {
None
}
})
}
#[cfg(feature = "transition")]
pub fn transition(&self) -> Option<Rc<TransitionState>> {
self.transition.borrow().as_ref().map(Rc::clone)
}
pub fn create_scope(
&'static self,
f: impl FnOnce(Scope),
parent: Option<Scope>,
) -> ScopeDisposer {
let id = {
self.scopes
.borrow_mut()
.insert(Rc::new(ScopeState::new(parent)))
};
let scope = Scope { runtime: self, id };
f(scope);
ScopeDisposer(Box::new(move || scope.dispose()))
}
pub fn run_scope<T>(&'static self, f: impl FnOnce(Scope) -> T, parent: Option<Scope>) -> T {
let id = {
self.scopes
.borrow_mut()
.insert(Rc::new(ScopeState::new(parent)))
};
let scope = Scope { runtime: self, id };
let ret = f(scope);
scope.dispose();
ret
}
pub fn run_scope_undisposed<T>(
&'static self,
f: impl FnOnce(Scope) -> T,
parent: Option<Scope>,
) -> (T, ScopeDisposer) {
let id = {
self.scopes
.borrow_mut()
.insert(Rc::new(ScopeState::new(parent)))
};
let scope = Scope { runtime: self, id };
let ret = f(scope);
(ret, ScopeDisposer(Box::new(move || scope.dispose())))
}
pub fn push_stack(&self, id: Subscriber) {
self.stack.borrow_mut().push(id);
}
pub fn pop_stack(&self) {
self.stack.borrow_mut().pop();
}
pub fn remove_scope(&self, scope: &ScopeId) {
let scope = self.scopes.borrow_mut().remove(*scope);
drop(scope); }
pub fn untrack<T>(&self, f: impl FnOnce() -> T) -> T {
let prev_stack = self.stack.replace(Vec::new());
let untracked_result = f();
self.stack.replace(prev_stack);
untracked_result
}
#[cfg(feature = "hydrate")]
pub fn start_hydration(&self, element: &web_sys::Element) {
use std::collections::HashMap;
use wasm_bindgen::{JsCast, UnwrapThrowExt};
let mut registry = HashMap::new();
if let Ok(templates) = element.query_selector_all("*[data-hk]") {
for i in 0..templates.length() {
let node = templates
.item(i)
.unwrap_throw()
.unchecked_into::<web_sys::Element>();
let key = node.get_attribute("data-hk").unwrap_throw();
registry.insert(key, node);
}
}
*self.shared_context.borrow_mut() = Some(SharedContext::new_with_registry(registry));
}
#[cfg(feature = "hydrate")]
pub fn end_hydration(&self) {
if let Some(ref mut sc) = *self.shared_context.borrow_mut() {
sc.context = None;
}
}
pub(crate) fn all_resources(&self) -> Vec<StreamingResourceId> {
self.scopes
.borrow()
.iter()
.flat_map(|(scope_id, scope)| {
scope
.resources
.iter()
.enumerate()
.map(move |(resource_id, _)| {
StreamingResourceId(scope_id, ResourceId(resource_id))
})
})
.collect()
}
#[cfg(feature = "ssr")]
pub(crate) fn serialization_resolvers(
&self,
) -> futures::stream::futures_unordered::FuturesUnordered<
std::pin::Pin<Box<dyn futures::Future<Output = (StreamingResourceId, String)>>>,
> {
let f = futures::stream::futures_unordered::FuturesUnordered::new();
for (id, resource) in self.scopes.borrow().iter().flat_map(|(scope_id, scope)| {
scope
.resources
.iter()
.enumerate()
.map(move |(idx, resource)| {
(StreamingResourceId(scope_id, ResourceId(idx)), resource)
})
}) {
f.push(resource.to_serialization_resolver(id));
}
f
}
}
impl PartialEq for Runtime {
fn eq(&self, other: &Self) -> bool {
std::ptr::eq(self, other)
}
}
impl Eq for Runtime {}
impl std::hash::Hash for Runtime {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
std::ptr::hash(&self, state);
}
}