mirror of
https://github.com/leptos-rs/leptos
synced 2024-11-10 06:44:17 +00:00
Support for multiple, independent Runtimes on a single thread without leaking
This commit is contained in:
parent
7c79cb1b1f
commit
739e7db49d
14 changed files with 651 additions and 501 deletions
|
@ -3,7 +3,7 @@ use std::{
|
|||
collections::HashMap,
|
||||
};
|
||||
|
||||
use crate::Scope;
|
||||
use crate::{runtime::with_runtime, Scope};
|
||||
|
||||
/// Provides a context value of type `T` to the current reactive [Scope](crate::Scope)
|
||||
/// and all of its descendants. This can be consumed using [use_context](crate::use_context).
|
||||
|
@ -14,7 +14,7 @@ use crate::Scope;
|
|||
///
|
||||
/// ```
|
||||
/// # use leptos_reactive::*;
|
||||
/// # create_scope(|cx| {
|
||||
/// # create_scope(create_runtime(), |cx| {
|
||||
///
|
||||
/// // Note: this example doesn’t use Leptos’s DOM model or component structure,
|
||||
/// // so it ends up being a little silly.
|
||||
|
@ -48,9 +48,11 @@ where
|
|||
T: Clone + 'static,
|
||||
{
|
||||
let id = value.type_id();
|
||||
let mut contexts = cx.runtime.scope_contexts.borrow_mut();
|
||||
let context = contexts.entry(cx.id).unwrap().or_insert_with(HashMap::new);
|
||||
context.insert(id, Box::new(value) as Box<dyn Any>);
|
||||
with_runtime(cx.runtime, |runtime| {
|
||||
let mut contexts = runtime.scope_contexts.borrow_mut();
|
||||
let context = contexts.entry(cx.id).unwrap().or_insert_with(HashMap::new);
|
||||
context.insert(id, Box::new(value) as Box<dyn Any>);
|
||||
});
|
||||
}
|
||||
|
||||
/// Extracts a context value of type `T` from the reactive system by traversing
|
||||
|
@ -64,7 +66,7 @@ where
|
|||
///
|
||||
/// ```
|
||||
/// # use leptos_reactive::*;
|
||||
/// # create_scope(|cx| {
|
||||
/// # create_scope(create_runtime(), |cx| {
|
||||
///
|
||||
/// // Note: this example doesn’t use Leptos’s DOM model or component structure,
|
||||
/// // so it ends up being a little silly.
|
||||
|
@ -98,25 +100,26 @@ where
|
|||
T: Clone + 'static,
|
||||
{
|
||||
let id = TypeId::of::<T>();
|
||||
let local_value = {
|
||||
let contexts = cx.runtime.scope_contexts.borrow();
|
||||
let context = contexts.get(cx.id);
|
||||
context
|
||||
.and_then(|context| context.get(&id).and_then(|val| val.downcast_ref::<T>()))
|
||||
.cloned()
|
||||
};
|
||||
match local_value {
|
||||
Some(val) => Some(val),
|
||||
None => cx
|
||||
.runtime
|
||||
.scope_parents
|
||||
.borrow()
|
||||
.get(cx.id)
|
||||
.and_then(|parent| {
|
||||
use_context::<T>(Scope {
|
||||
runtime: cx.runtime,
|
||||
id: *parent,
|
||||
})
|
||||
}),
|
||||
}
|
||||
with_runtime(cx.runtime, |runtime| {
|
||||
let local_value = {
|
||||
let contexts = runtime.scope_contexts.borrow();
|
||||
let context = contexts.get(cx.id);
|
||||
context
|
||||
.and_then(|context| context.get(&id).and_then(|val| val.downcast_ref::<T>()))
|
||||
.cloned()
|
||||
};
|
||||
match local_value {
|
||||
Some(val) => Some(val),
|
||||
None => runtime
|
||||
.scope_parents
|
||||
.borrow()
|
||||
.get(cx.id)
|
||||
.and_then(|parent| {
|
||||
use_context::<T>(Scope {
|
||||
runtime: cx.runtime,
|
||||
id: *parent,
|
||||
})
|
||||
}),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
use crate::runtime::{with_runtime, RuntimeId};
|
||||
use crate::{debug_warn, Runtime, Scope, ScopeProperty};
|
||||
use cfg_if::cfg_if;
|
||||
use std::cell::RefCell;
|
||||
|
@ -22,7 +23,7 @@ use std::fmt::Debug;
|
|||
/// ```
|
||||
/// # use leptos_reactive::*;
|
||||
/// # use log::*;
|
||||
/// # create_scope(|cx| {
|
||||
/// # create_scope(create_runtime(), |cx| {
|
||||
/// let (a, set_a) = create_signal(cx, 0);
|
||||
/// let (b, set_b) = create_signal(cx, 0);
|
||||
///
|
||||
|
@ -66,7 +67,7 @@ where
|
|||
/// ```
|
||||
/// # use leptos_reactive::*;
|
||||
/// # use log::*;
|
||||
/// # create_scope(|cx| {
|
||||
/// # create_scope(create_runtime(), |cx| {
|
||||
/// let (a, set_a) = create_signal(cx, 0);
|
||||
/// let (b, set_b) = create_signal(cx, 0);
|
||||
///
|
||||
|
@ -118,7 +119,7 @@ where
|
|||
}
|
||||
|
||||
pub(crate) trait AnyEffect {
|
||||
fn run(&self, id: EffectId, runtime: &Runtime);
|
||||
fn run(&self, id: EffectId, runtime: RuntimeId);
|
||||
}
|
||||
|
||||
impl<T, F> AnyEffect for Effect<T, F>
|
||||
|
@ -126,35 +127,39 @@ where
|
|||
T: 'static,
|
||||
F: Fn(Option<T>) -> T,
|
||||
{
|
||||
fn run(&self, id: EffectId, runtime: &Runtime) {
|
||||
// clear previous dependencies
|
||||
id.cleanup(runtime);
|
||||
fn run(&self, id: EffectId, runtime: RuntimeId) {
|
||||
with_runtime(runtime, |runtime| {
|
||||
// clear previous dependencies
|
||||
id.cleanup(runtime);
|
||||
|
||||
// set this as the current observer
|
||||
let prev_observer = runtime.observer.take();
|
||||
runtime.observer.set(Some(id));
|
||||
// set this as the current observer
|
||||
let prev_observer = runtime.observer.take();
|
||||
runtime.observer.set(Some(id));
|
||||
|
||||
// run the effect
|
||||
let value = self.value.take();
|
||||
let new_value = (self.f)(value);
|
||||
*self.value.borrow_mut() = Some(new_value);
|
||||
// run the effect
|
||||
let value = self.value.take();
|
||||
let new_value = (self.f)(value);
|
||||
*self.value.borrow_mut() = Some(new_value);
|
||||
|
||||
// restore the previous observer
|
||||
runtime.observer.set(prev_observer);
|
||||
// restore the previous observer
|
||||
runtime.observer.set(prev_observer);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl EffectId {
|
||||
pub(crate) fn run<T>(&self, runtime: &Runtime) {
|
||||
let effect = {
|
||||
let effects = runtime.effects.borrow();
|
||||
effects.get(*self).cloned()
|
||||
};
|
||||
if let Some(effect) = effect {
|
||||
effect.run(*self, runtime);
|
||||
} else {
|
||||
debug_warn!("[Effect] Trying to run an Effect that has been disposed. This is probably either a logic error in a component that creates and disposes of scopes, or a Resource resolving after its scope has been dropped without having been cleaned up.")
|
||||
}
|
||||
pub(crate) fn run<T>(&self, runtime_id: RuntimeId) {
|
||||
with_runtime(runtime_id, |runtime| {
|
||||
let effect = {
|
||||
let effects = runtime.effects.borrow();
|
||||
effects.get(*self).cloned()
|
||||
};
|
||||
if let Some(effect) = effect {
|
||||
effect.run(*self, runtime_id);
|
||||
} else {
|
||||
debug_warn!("[Effect] Trying to run an Effect that has been disposed. This is probably either a logic error in a component that creates and disposes of scopes, or a Resource resolving after its scope has been dropped without having been cleaned up.")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn cleanup(&self, runtime: &Runtime) {
|
||||
|
|
|
@ -37,7 +37,7 @@
|
|||
//! // creates a new reactive Scope
|
||||
//! // this is omitted from most of the examples in the docs
|
||||
//! // you usually won't need to call it yourself
|
||||
//! create_scope(|cx| {
|
||||
//! create_scope(create_runtime(), |cx| {
|
||||
//! // a signal: returns a (getter, setter) pair
|
||||
//! let (count, set_count) = create_signal(cx, 0);
|
||||
//!
|
||||
|
@ -85,6 +85,7 @@ pub use effect::*;
|
|||
pub use memo::*;
|
||||
pub use resource::*;
|
||||
use runtime::*;
|
||||
pub use runtime::{create_runtime, RuntimeId};
|
||||
pub use scope::*;
|
||||
pub use selector::*;
|
||||
pub use serialization::*;
|
||||
|
|
|
@ -20,7 +20,7 @@ use std::fmt::Debug;
|
|||
/// ```
|
||||
/// # use leptos_reactive::*;
|
||||
/// # fn really_expensive_computation(value: i32) -> i32 { value };
|
||||
/// # create_scope(|cx| {
|
||||
/// # create_scope(create_runtime(), |cx| {
|
||||
/// let (value, set_value) = create_signal(cx, 0);
|
||||
///
|
||||
/// // 🆗 we could create a derived signal with a simple function
|
||||
|
@ -80,7 +80,7 @@ where
|
|||
/// ```
|
||||
/// # use leptos_reactive::*;
|
||||
/// # fn really_expensive_computation(value: i32) -> i32 { value };
|
||||
/// # create_scope(|cx| {
|
||||
/// # create_scope(create_runtime(), |cx| {
|
||||
/// let (value, set_value) = create_signal(cx, 0);
|
||||
///
|
||||
/// // 🆗 we could create a derived signal with a simple function
|
||||
|
@ -155,7 +155,7 @@ where
|
|||
/// the running effect to the memo.
|
||||
/// ```
|
||||
/// # use leptos_reactive::*;
|
||||
/// # create_scope(|cx| {
|
||||
/// # create_scope(create_runtime(), |cx| {
|
||||
/// let (count, set_count) = create_signal(cx, 0);
|
||||
/// let double_count = create_memo(cx, move |_| count() * 2);
|
||||
///
|
||||
|
@ -178,7 +178,7 @@ where
|
|||
/// the running effect to this memo.
|
||||
/// ```
|
||||
/// # use leptos_reactive::*;
|
||||
/// # create_scope(|cx| {
|
||||
/// # create_scope(create_runtime(), |cx| {
|
||||
/// let (name, set_name) = create_signal(cx, "Alice".to_string());
|
||||
/// let name_upper = create_memo(cx, move |_| name().to_uppercase());
|
||||
///
|
||||
|
|
|
@ -11,8 +11,10 @@ use std::{
|
|||
|
||||
use crate::{
|
||||
create_effect, create_isomorphic_effect, create_memo, create_signal, queue_microtask,
|
||||
runtime::Runtime, serialization::Serializable, spawn::spawn_local, use_context, Memo,
|
||||
ReadSignal, Scope, ScopeProperty, SuspenseContext, WriteSignal,
|
||||
runtime::{with_runtime, RuntimeId},
|
||||
serialization::Serializable,
|
||||
spawn::spawn_local,
|
||||
use_context, Memo, ReadSignal, Scope, ScopeProperty, SuspenseContext, WriteSignal,
|
||||
};
|
||||
|
||||
/// Creates [Resource](crate::Resource), which is a signal that reflects the
|
||||
|
@ -31,7 +33,7 @@ use crate::{
|
|||
///
|
||||
/// ```
|
||||
/// # use leptos_reactive::*;
|
||||
/// # create_scope(|cx| {
|
||||
/// # create_scope(create_runtime(), |cx| {
|
||||
/// // any old async function; maybe this is calling a REST API or something
|
||||
/// async fn fetch_cat_picture_urls(how_many: i32) -> Vec<String> {
|
||||
/// // pretend we're fetching cat pics
|
||||
|
@ -121,7 +123,9 @@ where
|
|||
suspense_contexts: Default::default(),
|
||||
});
|
||||
|
||||
let id = cx.runtime.create_serializable_resource(Rc::clone(&r));
|
||||
let id = with_runtime(cx.runtime, |runtime| {
|
||||
runtime.create_serializable_resource(Rc::clone(&r))
|
||||
});
|
||||
|
||||
create_isomorphic_effect(cx, {
|
||||
let r = Rc::clone(&r);
|
||||
|
@ -153,7 +157,7 @@ where
|
|||
///
|
||||
/// ```
|
||||
/// # use leptos_reactive::*;
|
||||
/// # create_scope(|cx| {
|
||||
/// # create_scope(create_runtime(), |cx| {
|
||||
/// #[derive(Debug, Clone)] // doesn't implement Serialize, Deserialize
|
||||
/// struct ComplicatedUnserializableStruct {
|
||||
/// // something here that can't be serialized
|
||||
|
@ -223,7 +227,9 @@ where
|
|||
suspense_contexts: Default::default(),
|
||||
});
|
||||
|
||||
let id = cx.runtime.create_unserializable_resource(Rc::clone(&r));
|
||||
let id = with_runtime(cx.runtime, |runtime| {
|
||||
runtime.create_unserializable_resource(Rc::clone(&r))
|
||||
});
|
||||
|
||||
create_effect(cx, {
|
||||
let r = Rc::clone(&r);
|
||||
|
@ -259,61 +265,63 @@ where
|
|||
{
|
||||
use wasm_bindgen::{JsCast, UnwrapThrowExt};
|
||||
|
||||
if let Some(ref mut context) = *cx.runtime.shared_context.borrow_mut() {
|
||||
if let Some(data) = context.resolved_resources.remove(&id) {
|
||||
// The server already sent us the serialized resource value, so
|
||||
// deserialize & set it now
|
||||
context.pending_resources.remove(&id); // no longer pending
|
||||
r.resolved.set(true);
|
||||
with_runtime(cx.runtime, |runtime| {
|
||||
if let Some(ref mut context) = *runtime.shared_context.borrow_mut() {
|
||||
if let Some(data) = context.resolved_resources.remove(&id) {
|
||||
// The server already sent us the serialized resource value, so
|
||||
// deserialize & set it now
|
||||
context.pending_resources.remove(&id); // no longer pending
|
||||
r.resolved.set(true);
|
||||
|
||||
let res = T::from_json(&data).expect_throw("could not deserialize Resource JSON");
|
||||
r.set_value.update(|n| *n = Some(res));
|
||||
r.set_loading.update(|n| *n = false);
|
||||
let res = T::from_json(&data).expect_throw("could not deserialize Resource JSON");
|
||||
r.set_value.update(|n| *n = Some(res));
|
||||
r.set_loading.update(|n| *n = false);
|
||||
|
||||
// for reactivity
|
||||
r.source.subscribe();
|
||||
} else if context.pending_resources.remove(&id) {
|
||||
// We're still waiting for the resource, add a "resolver" closure so
|
||||
// that it will be set as soon as the server sends the serialized
|
||||
// value
|
||||
r.set_loading.update(|n| *n = true);
|
||||
// for reactivity
|
||||
r.source.subscribe();
|
||||
} else if context.pending_resources.remove(&id) {
|
||||
// We're still waiting for the resource, add a "resolver" closure so
|
||||
// that it will be set as soon as the server sends the serialized
|
||||
// value
|
||||
r.set_loading.update(|n| *n = true);
|
||||
|
||||
let resolve = {
|
||||
let resolved = r.resolved.clone();
|
||||
let set_value = r.set_value;
|
||||
let set_loading = r.set_loading;
|
||||
move |res: String| {
|
||||
let res =
|
||||
T::from_json(&res).expect_throw("could not deserialize Resource JSON");
|
||||
resolved.set(true);
|
||||
set_value.update(|n| *n = Some(res));
|
||||
set_loading.update(|n| *n = false);
|
||||
}
|
||||
};
|
||||
let resolve =
|
||||
wasm_bindgen::closure::Closure::wrap(Box::new(resolve) as Box<dyn Fn(String)>);
|
||||
let resource_resolvers = js_sys::Reflect::get(
|
||||
&web_sys::window().unwrap(),
|
||||
&wasm_bindgen::JsValue::from_str("__LEPTOS_RESOURCE_RESOLVERS"),
|
||||
)
|
||||
.expect_throw("no __LEPTOS_RESOURCE_RESOLVERS found in the JS global scope");
|
||||
let id = serde_json::to_string(&id).expect_throw("could not serialize Resource ID");
|
||||
_ = js_sys::Reflect::set(
|
||||
&resource_resolvers,
|
||||
&wasm_bindgen::JsValue::from_str(&id),
|
||||
resolve.as_ref().unchecked_ref(),
|
||||
);
|
||||
let resolve = {
|
||||
let resolved = r.resolved.clone();
|
||||
let set_value = r.set_value;
|
||||
let set_loading = r.set_loading;
|
||||
move |res: String| {
|
||||
let res =
|
||||
T::from_json(&res).expect_throw("could not deserialize Resource JSON");
|
||||
resolved.set(true);
|
||||
set_value.update(|n| *n = Some(res));
|
||||
set_loading.update(|n| *n = false);
|
||||
}
|
||||
};
|
||||
let resolve =
|
||||
wasm_bindgen::closure::Closure::wrap(Box::new(resolve) as Box<dyn Fn(String)>);
|
||||
let resource_resolvers = js_sys::Reflect::get(
|
||||
&web_sys::window().unwrap(),
|
||||
&wasm_bindgen::JsValue::from_str("__LEPTOS_RESOURCE_RESOLVERS"),
|
||||
)
|
||||
.expect_throw("no __LEPTOS_RESOURCE_RESOLVERS found in the JS global scope");
|
||||
let id = serde_json::to_string(&id).expect_throw("could not serialize Resource ID");
|
||||
_ = js_sys::Reflect::set(
|
||||
&resource_resolvers,
|
||||
&wasm_bindgen::JsValue::from_str(&id),
|
||||
resolve.as_ref().unchecked_ref(),
|
||||
);
|
||||
|
||||
// for reactivity
|
||||
r.source.subscribe()
|
||||
// for reactivity
|
||||
r.source.subscribe()
|
||||
} else {
|
||||
// Server didn't mark the resource as pending, so load it on the
|
||||
// client
|
||||
r.load(false);
|
||||
}
|
||||
} else {
|
||||
// Server didn't mark the resource as pending, so load it on the
|
||||
// client
|
||||
r.load(false);
|
||||
r.load(false)
|
||||
}
|
||||
} else {
|
||||
r.load(false)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
impl<S, T> Resource<S, T>
|
||||
|
@ -331,8 +339,9 @@ where
|
|||
where
|
||||
T: Clone,
|
||||
{
|
||||
self.runtime
|
||||
.resource(self.id, |resource: &ResourceState<S, T>| resource.read())
|
||||
with_runtime(self.runtime, |runtime| {
|
||||
runtime.resource(self.id, |resource: &ResourceState<S, T>| resource.read())
|
||||
})
|
||||
}
|
||||
|
||||
/// Applies a function to the current value of the resource, and subscribes
|
||||
|
@ -343,20 +352,23 @@ where
|
|||
/// If you want to get the value by cloning it, you can use
|
||||
/// [Resource::read].
|
||||
pub fn with<U>(&self, f: impl FnOnce(&T) -> U) -> Option<U> {
|
||||
self.runtime
|
||||
.resource(self.id, |resource: &ResourceState<S, T>| resource.with(f))
|
||||
with_runtime(self.runtime, |runtime| {
|
||||
runtime.resource(self.id, |resource: &ResourceState<S, T>| resource.with(f))
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns a signal that indicates whether the resource is currently loading.
|
||||
pub fn loading(&self) -> ReadSignal<bool> {
|
||||
self.runtime
|
||||
.resource(self.id, |resource: &ResourceState<S, T>| resource.loading)
|
||||
with_runtime(self.runtime, |runtime| {
|
||||
runtime.resource(self.id, |resource: &ResourceState<S, T>| resource.loading)
|
||||
})
|
||||
}
|
||||
|
||||
/// Re-runs the async function with the current source data.
|
||||
pub fn refetch(&self) {
|
||||
self.runtime
|
||||
.resource(self.id, |resource: &ResourceState<S, T>| resource.refetch())
|
||||
with_runtime(self.runtime, |runtime| {
|
||||
runtime.resource(self.id, |resource: &ResourceState<S, T>| resource.refetch())
|
||||
});
|
||||
}
|
||||
|
||||
/// Returns a [std::future::Future] that will resolve when the resource has loaded,
|
||||
|
@ -366,11 +378,12 @@ where
|
|||
where
|
||||
T: Serializable,
|
||||
{
|
||||
self.runtime
|
||||
.resource(self.id, |resource: &ResourceState<S, T>| {
|
||||
with_runtime(self.runtime, |runtime| {
|
||||
runtime.resource(self.id, |resource: &ResourceState<S, T>| {
|
||||
resource.to_serialization_resolver(self.id)
|
||||
})
|
||||
.await
|
||||
})
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -390,7 +403,7 @@ where
|
|||
///
|
||||
/// ```
|
||||
/// # use leptos_reactive::*;
|
||||
/// # create_scope(|cx| {
|
||||
/// # create_scope(create_runtime(), |cx| {
|
||||
/// // any old async function; maybe this is calling a REST API or something
|
||||
/// async fn fetch_cat_picture_urls(how_many: i32) -> Vec<String> {
|
||||
/// // pretend we're fetching cat pics
|
||||
|
@ -423,7 +436,7 @@ where
|
|||
S: Debug + 'static,
|
||||
T: Debug + 'static,
|
||||
{
|
||||
runtime: &'static Runtime,
|
||||
runtime: RuntimeId,
|
||||
pub(crate) id: ResourceId,
|
||||
pub(crate) source_ty: PhantomData<S>,
|
||||
pub(crate) out_ty: PhantomData<T>,
|
||||
|
|
|
@ -3,6 +3,7 @@ use crate::{
|
|||
EffectId, Memo, ReadSignal, ResourceId, ResourceState, RwSignal, Scope, ScopeDisposer, ScopeId,
|
||||
ScopeProperty, SignalId, WriteSignal,
|
||||
};
|
||||
use cfg_if::cfg_if;
|
||||
use futures::stream::FuturesUnordered;
|
||||
use slotmap::{SecondaryMap, SlotMap, SparseSecondaryMap};
|
||||
use std::{
|
||||
|
@ -18,6 +19,185 @@ use std::{
|
|||
|
||||
pub(crate) type PinnedFuture<T> = Pin<Box<dyn Future<Output = T>>>;
|
||||
|
||||
cfg_if! {
|
||||
if #[cfg(any(feature = "csr", feature = "hydrate"))] {
|
||||
thread_local! {
|
||||
pub(crate) static RUNTIME: Runtime = Default::default();
|
||||
}
|
||||
} else {
|
||||
thread_local! {
|
||||
pub(crate) static RUNTIMES: RefCell<SlotMap<RuntimeId, Runtime>> = Default::default();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the selected runtime from the thread-local set of runtimes. On the server,
|
||||
/// this will return the correct runtime. In the browser, there should only be one runtime.
|
||||
pub(crate) fn with_runtime<T>(id: RuntimeId, f: impl FnOnce(&Runtime) -> T) -> T {
|
||||
// in the browser, everything should exist under one runtime
|
||||
cfg_if! {
|
||||
if #[cfg(any(feature = "csr", feature = "hydrate"))] {
|
||||
_ = id;
|
||||
RUNTIME.with(|runtime| f(runtime))
|
||||
} else {
|
||||
RUNTIMES.with(|runtimes| {
|
||||
let runtimes = runtimes.borrow();
|
||||
let runtime = runtimes
|
||||
.get(id)
|
||||
.expect("Tried to access a Runtime that no longer exists.");
|
||||
f(runtime)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[must_use = "Runtime will leak memory if Runtime::dispose() is never called."]
|
||||
/// Creates a new reactive [Runtime]. This should almost always be handled by the framework.
|
||||
pub fn create_runtime() -> RuntimeId {
|
||||
cfg_if! {
|
||||
if #[cfg(any(feature = "csr", feature = "hydrate"))] {
|
||||
Default::default()
|
||||
} else {
|
||||
RUNTIMES.with(|runtimes| runtimes.borrow_mut().insert(Runtime::new()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
slotmap::new_key_type! {
|
||||
/// Unique ID assigned to a [Runtime](crate::Runtime).
|
||||
pub struct RuntimeId;
|
||||
}
|
||||
|
||||
impl RuntimeId {
|
||||
/// Removes the runtime, disposing all its child [Scope](crate::Scope)s.
|
||||
pub fn dispose(self) {
|
||||
cfg_if! {
|
||||
if #[cfg(not(any(feature = "csr", feature = "hydrate")))] {
|
||||
let runtime = RUNTIMES.with(move |runtimes| runtimes.borrow_mut().remove(self));
|
||||
if let Some(runtime) = runtime {
|
||||
for (scope_id, _) in runtime.scopes.borrow().iter() {
|
||||
let scope = Scope {
|
||||
runtime: self,
|
||||
id: scope_id,
|
||||
};
|
||||
scope.dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn raw_scope_and_disposer(self) -> (Scope, ScopeDisposer) {
|
||||
with_runtime(self, |runtime| {
|
||||
let id = { runtime.scopes.borrow_mut().insert(Default::default()) };
|
||||
let scope = Scope { runtime: self, id };
|
||||
let disposer = ScopeDisposer(Box::new(move || scope.dispose()));
|
||||
(scope, disposer)
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn run_scope_undisposed<T>(
|
||||
self,
|
||||
f: impl FnOnce(Scope) -> T,
|
||||
parent: Option<Scope>,
|
||||
) -> (T, ScopeId, ScopeDisposer) {
|
||||
with_runtime(self, |runtime| {
|
||||
let id = { runtime.scopes.borrow_mut().insert(Default::default()) };
|
||||
if let Some(parent) = parent {
|
||||
runtime.scope_parents.borrow_mut().insert(id, parent.id);
|
||||
}
|
||||
let scope = Scope { runtime: self, id };
|
||||
let val = f(scope);
|
||||
let disposer = ScopeDisposer(Box::new(move || scope.dispose()));
|
||||
(val, id, disposer)
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn run_scope<T>(self, f: impl FnOnce(Scope) -> T, parent: Option<Scope>) -> T {
|
||||
let (ret, _, disposer) = self.run_scope_undisposed(f, parent);
|
||||
disposer.dispose();
|
||||
ret
|
||||
}
|
||||
|
||||
pub(crate) fn create_signal<T>(self, value: T) -> (ReadSignal<T>, WriteSignal<T>)
|
||||
where
|
||||
T: Any + 'static,
|
||||
{
|
||||
let id = with_runtime(self, |runtime| {
|
||||
runtime
|
||||
.signals
|
||||
.borrow_mut()
|
||||
.insert(Rc::new(RefCell::new(value)))
|
||||
});
|
||||
(
|
||||
ReadSignal {
|
||||
runtime: self,
|
||||
id,
|
||||
ty: PhantomData,
|
||||
},
|
||||
WriteSignal {
|
||||
runtime: self,
|
||||
id,
|
||||
ty: PhantomData,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn create_rw_signal<T>(self, value: T) -> RwSignal<T>
|
||||
where
|
||||
T: Any + 'static,
|
||||
{
|
||||
let id = with_runtime(self, |runtime| {
|
||||
runtime
|
||||
.signals
|
||||
.borrow_mut()
|
||||
.insert(Rc::new(RefCell::new(value)))
|
||||
});
|
||||
RwSignal {
|
||||
runtime: self,
|
||||
id,
|
||||
ty: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn create_effect<T>(self, f: impl Fn(Option<T>) -> T + 'static) -> EffectId
|
||||
where
|
||||
T: Any + 'static,
|
||||
{
|
||||
with_runtime(self, |runtime| {
|
||||
let effect = Effect {
|
||||
f,
|
||||
value: RefCell::new(None),
|
||||
};
|
||||
let id = { runtime.effects.borrow_mut().insert(Rc::new(effect)) };
|
||||
id.run::<T>(self);
|
||||
id
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn create_memo<T>(self, f: impl Fn(Option<&T>) -> T + 'static) -> Memo<T>
|
||||
where
|
||||
T: PartialEq + Any + 'static,
|
||||
{
|
||||
let (read, write) = self.create_signal(None);
|
||||
|
||||
self.create_effect(move |_| {
|
||||
let (new, changed) = read.with_no_subscription(|p| {
|
||||
let new = f(p.as_ref());
|
||||
let changed = Some(&new) != p.as_ref();
|
||||
(new, changed)
|
||||
});
|
||||
|
||||
if changed {
|
||||
write.update(|n| *n = Some(new));
|
||||
}
|
||||
});
|
||||
|
||||
Memo(read)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub(crate) struct Runtime {
|
||||
pub shared_context: RefCell<Option<SharedContext>>,
|
||||
|
@ -57,105 +237,6 @@ impl Runtime {
|
|||
Self::default()
|
||||
}
|
||||
|
||||
pub fn raw_scope_and_disposer(&'static self) -> (Scope, ScopeDisposer) {
|
||||
let id = { self.scopes.borrow_mut().insert(Default::default()) };
|
||||
let scope = Scope { runtime: self, id };
|
||||
let disposer = ScopeDisposer(Box::new(move || scope.dispose()));
|
||||
(scope, disposer)
|
||||
}
|
||||
|
||||
pub fn run_scope_undisposed<T>(
|
||||
&'static self,
|
||||
f: impl FnOnce(Scope) -> T,
|
||||
parent: Option<Scope>,
|
||||
) -> (T, ScopeId, ScopeDisposer) {
|
||||
let id = { self.scopes.borrow_mut().insert(Default::default()) };
|
||||
if let Some(parent) = parent {
|
||||
self.scope_parents.borrow_mut().insert(id, parent.id);
|
||||
}
|
||||
let scope = Scope { runtime: self, id };
|
||||
let val = f(scope);
|
||||
let disposer = ScopeDisposer(Box::new(move || scope.dispose()));
|
||||
(val, id, disposer)
|
||||
}
|
||||
|
||||
pub fn run_scope<T>(&'static self, f: impl FnOnce(Scope) -> T, parent: Option<Scope>) -> T {
|
||||
let (ret, _, disposer) = self.run_scope_undisposed(f, parent);
|
||||
disposer.dispose();
|
||||
ret
|
||||
}
|
||||
|
||||
pub(crate) fn create_signal<T>(&'static self, value: T) -> (ReadSignal<T>, WriteSignal<T>)
|
||||
where
|
||||
T: Any + 'static,
|
||||
{
|
||||
let id = self
|
||||
.signals
|
||||
.borrow_mut()
|
||||
.insert(Rc::new(RefCell::new(value)));
|
||||
(
|
||||
ReadSignal {
|
||||
runtime: self,
|
||||
id,
|
||||
ty: PhantomData,
|
||||
},
|
||||
WriteSignal {
|
||||
runtime: self,
|
||||
id,
|
||||
ty: PhantomData,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn create_rw_signal<T>(&'static self, value: T) -> RwSignal<T>
|
||||
where
|
||||
T: Any + 'static,
|
||||
{
|
||||
let id = self
|
||||
.signals
|
||||
.borrow_mut()
|
||||
.insert(Rc::new(RefCell::new(value)));
|
||||
RwSignal {
|
||||
runtime: self,
|
||||
id,
|
||||
ty: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn create_effect<T>(&'static self, f: impl Fn(Option<T>) -> T + 'static) -> EffectId
|
||||
where
|
||||
T: Any + 'static,
|
||||
{
|
||||
let effect = Effect {
|
||||
f,
|
||||
value: RefCell::new(None),
|
||||
};
|
||||
let id = { self.effects.borrow_mut().insert(Rc::new(effect)) };
|
||||
id.run::<T>(self);
|
||||
id
|
||||
}
|
||||
|
||||
pub(crate) fn create_memo<T>(&'static self, f: impl Fn(Option<&T>) -> T + 'static) -> Memo<T>
|
||||
where
|
||||
T: PartialEq + Any + 'static,
|
||||
{
|
||||
let (read, write) = self.create_signal(None);
|
||||
|
||||
self.create_effect(move |_| {
|
||||
let (new, changed) = read.with_no_subscription(|p| {
|
||||
let new = f(p.as_ref());
|
||||
let changed = Some(&new) != p.as_ref();
|
||||
(new, changed)
|
||||
});
|
||||
|
||||
if changed {
|
||||
write.update(|n| *n = Some(new));
|
||||
}
|
||||
});
|
||||
|
||||
Memo(read)
|
||||
}
|
||||
|
||||
pub(crate) fn create_unserializable_resource<S, T>(
|
||||
&self,
|
||||
state: Rc<ResourceState<S, T>>,
|
||||
|
|
|
@ -1,52 +1,58 @@
|
|||
use cfg_if::cfg_if;
|
||||
|
||||
use crate::{hydration::SharedContext, EffectId, ResourceId, Runtime, SignalId};
|
||||
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"]
|
||||
/// Creates a new reactive system and root reactive scope and runs the function within it.
|
||||
///
|
||||
/// This should usually only be used once, at the root of an application, because its reactive
|
||||
/// values will not have access to values created under another `create_scope`.
|
||||
pub fn create_scope(f: impl FnOnce(Scope) + 'static) -> ScopeDisposer {
|
||||
// TODO leak
|
||||
let runtime = Box::leak(Box::new(Runtime::new()));
|
||||
///
|
||||
/// You usually don't need to call this manually.
|
||||
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"]
|
||||
/// Creates a new reactive system and root reactive scope, and returns them.
|
||||
///
|
||||
/// This should usually only be used once, at the root of an application, because its reactive
|
||||
/// values will not have access to values created under another `create_scope`.
|
||||
pub fn raw_scope_and_disposer() -> (Scope, ScopeDisposer) {
|
||||
// TODO leak
|
||||
let runtime = Box::leak(Box::new(Runtime::new()));
|
||||
///
|
||||
/// You usually don't need to call this manually.
|
||||
pub fn raw_scope_and_disposer(runtime: RuntimeId) -> (Scope, ScopeDisposer) {
|
||||
runtime.raw_scope_and_disposer()
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
/// Creates a temporary scope, runs the given function, disposes of the scope,
|
||||
/// and returns the value returned from the function. This is very useful for short-lived
|
||||
/// applications like SSR, where actual reactivity is not required beyond the end
|
||||
/// of the synchronous operation.
|
||||
pub fn run_scope<T>(f: impl FnOnce(Scope) -> T + 'static) -> T {
|
||||
// TODO this leaks the runtime — should unsafely upgrade the lifetime, and then drop it after the scope is run
|
||||
let runtime = Box::leak(Box::new(Runtime::new()));
|
||||
///
|
||||
/// You usually don't need to call this manually.
|
||||
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"]
|
||||
/// Creates a temporary scope and run the given function without disposing of the scope.
|
||||
/// If you do not dispose of the scope on your own, memory will leak.
|
||||
///
|
||||
/// You usually don't need to call this manually.
|
||||
pub fn run_scope_undisposed<T>(
|
||||
runtime: RuntimeId,
|
||||
f: impl FnOnce(Scope) -> T + 'static,
|
||||
) -> (T, ScopeId, ScopeDisposer) {
|
||||
// TODO this leaks the runtime — should unsafely upgrade the lifetime, and then drop it after the scope is run
|
||||
let runtime = Box::leak(Box::new(Runtime::new()));
|
||||
runtime.run_scope_undisposed(f, None)
|
||||
}
|
||||
|
||||
|
@ -67,7 +73,7 @@ pub fn run_scope_undisposed<T>(
|
|||
/// is [Copy] and `'static` this does not add much overhead or lifetime complexity.
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
pub struct Scope {
|
||||
pub(crate) runtime: &'static Runtime,
|
||||
pub(crate) runtime: RuntimeId,
|
||||
pub(crate) id: ScopeId,
|
||||
}
|
||||
|
||||
|
@ -87,13 +93,15 @@ impl Scope {
|
|||
/// has navigated away from the route.)
|
||||
pub fn child_scope(self, f: impl FnOnce(Scope)) -> ScopeDisposer {
|
||||
let (_, child_id, disposer) = self.runtime.run_scope_undisposed(f, Some(self));
|
||||
let mut children = self.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);
|
||||
disposer
|
||||
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);
|
||||
disposer
|
||||
})
|
||||
}
|
||||
|
||||
/// Suspends reactive tracking while running the given function.
|
||||
|
@ -102,7 +110,7 @@ impl Scope {
|
|||
///
|
||||
/// ```
|
||||
/// # use leptos_reactive::*;
|
||||
/// # run_scope(|cx| {
|
||||
/// # run_scope(create_runtime(), |cx| {
|
||||
/// let (a, set_a) = create_signal(cx, 0);
|
||||
/// let (b, set_b) = create_signal(cx, 0);
|
||||
/// let c = create_memo(cx, move |_| {
|
||||
|
@ -122,10 +130,12 @@ impl Scope {
|
|||
/// # });
|
||||
/// ```
|
||||
pub fn untrack<T>(&self, f: impl FnOnce() -> T) -> T {
|
||||
let prev_observer = self.runtime.observer.take();
|
||||
let untracked_result = f();
|
||||
self.runtime.observer.set(prev_observer);
|
||||
untracked_result
|
||||
with_runtime(self.runtime, |runtime| {
|
||||
let prev_observer = runtime.observer.take();
|
||||
let untracked_result = f();
|
||||
runtime.observer.set(prev_observer);
|
||||
untracked_result
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -133,71 +143,75 @@ impl Scope {
|
|||
|
||||
impl Scope {
|
||||
pub(crate) fn dispose(self) {
|
||||
// dispose of all child scopes
|
||||
let children = {
|
||||
let mut children = self.runtime.scope_children.borrow_mut();
|
||||
children.remove(self.id)
|
||||
};
|
||||
with_runtime(self.runtime, |runtime| {
|
||||
// dispose of all child scopes
|
||||
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,
|
||||
if let Some(children) = children {
|
||||
for id in children {
|
||||
Scope {
|
||||
runtime: self.runtime,
|
||||
id,
|
||||
}
|
||||
.dispose();
|
||||
}
|
||||
.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
// run cleanups
|
||||
if let Some(cleanups) = self.runtime.scope_cleanups.borrow_mut().remove(self.id) {
|
||||
for cleanup in cleanups {
|
||||
cleanup();
|
||||
// run cleanups
|
||||
if let Some(cleanups) = runtime.scope_cleanups.borrow_mut().remove(self.id) {
|
||||
for cleanup in cleanups {
|
||||
cleanup();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// remove everything we own and run cleanups
|
||||
let owned = {
|
||||
let owned = self.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) => {
|
||||
// remove the signal
|
||||
self.runtime.signals.borrow_mut().remove(id);
|
||||
let subs = self.runtime.signal_subscribers.borrow_mut().remove(id);
|
||||
// remove everything we own and run cleanups
|
||||
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) => {
|
||||
// remove the signal
|
||||
runtime.signals.borrow_mut().remove(id);
|
||||
let subs = runtime.signal_subscribers.borrow_mut().remove(id);
|
||||
|
||||
// each of the subs needs to remove the signal from its dependencies
|
||||
// so that it doesn't try to read the (now disposed) signal
|
||||
if let Some(subs) = subs {
|
||||
let source_map = self.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);
|
||||
// each of the subs needs to remove the signal from its dependencies
|
||||
// so that it doesn't try to read the (now disposed) signal
|
||||
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) => {
|
||||
self.runtime.effects.borrow_mut().remove(id);
|
||||
self.runtime.effect_sources.borrow_mut().remove(id);
|
||||
}
|
||||
ScopeProperty::Resource(id) => {
|
||||
self.runtime.resources.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>)) {
|
||||
let scopes = self.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());
|
||||
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());
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -206,12 +220,14 @@ impl Scope {
|
|||
/// It runs after child scopes have been disposed, but before signals, effects, and resources
|
||||
/// are invalidated.
|
||||
pub fn on_cleanup(cx: Scope, cleanup_fn: impl FnOnce() + 'static) {
|
||||
let mut cleanups = cx.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));
|
||||
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! {
|
||||
|
@ -253,17 +269,23 @@ impl Scope {
|
|||
if #[cfg(any(feature = "hydrate", doc))] {
|
||||
/// `hydrate` only: Whether we're currently hydrating the page.
|
||||
pub fn is_hydrating(&self) -> bool {
|
||||
self.runtime.shared_context.borrow().is_some()
|
||||
with_runtime(self.runtime, |runtime| {
|
||||
runtime.shared_context.borrow().is_some()
|
||||
})
|
||||
}
|
||||
|
||||
/// `hydrate` only: Begins the hydration process.
|
||||
pub fn start_hydration(&self, element: &web_sys::Element) {
|
||||
self.runtime.start_hydration(element);
|
||||
with_runtime(self.runtime, |runtime| {
|
||||
runtime.start_hydration(element);
|
||||
})
|
||||
}
|
||||
|
||||
/// `hydrate` only: Ends the hydration process.
|
||||
pub fn end_hydration(&self) {
|
||||
self.runtime.end_hydration();
|
||||
with_runtime(self.runtime, |runtime| {
|
||||
runtime.end_hydration();
|
||||
})
|
||||
}
|
||||
|
||||
/// `hydrate` only: Gets the next element in the hydration queue, either from the
|
||||
|
@ -283,27 +305,29 @@ impl Scope {
|
|||
t
|
||||
};
|
||||
|
||||
if let Some(ref mut shared_context) = &mut *self.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);
|
||||
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);
|
||||
|
||||
//log::debug!("(hy) searching for {key}");
|
||||
//log::debug!("(hy) searching for {key}");
|
||||
|
||||
if let Some(node) = node {
|
||||
//log::debug!("(hy) found {key}");
|
||||
shared_context.completed.push(node.clone());
|
||||
node
|
||||
if let Some(node) = node {
|
||||
//log::debug!("(hy) found {key}");
|
||||
shared_context.completed.push(node.clone());
|
||||
node
|
||||
} else {
|
||||
//log::debug!("(hy) did NOT find {key}");
|
||||
cloned_template(template)
|
||||
}
|
||||
} else {
|
||||
//log::debug!("(hy) did NOT find {key}");
|
||||
cloned_template(template)
|
||||
}
|
||||
} else {
|
||||
cloned_template(template)
|
||||
}
|
||||
} else {
|
||||
cloned_template(template)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -317,102 +341,108 @@ impl Scope {
|
|||
let mut current = Vec::new();
|
||||
let mut start = start.clone();
|
||||
|
||||
if self
|
||||
.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 {
|
||||
// COMMENT
|
||||
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);
|
||||
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 {
|
||||
// COMMENT
|
||||
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();
|
||||
}
|
||||
current.push(curr.clone());
|
||||
end = curr.next_sibling();
|
||||
}
|
||||
}
|
||||
|
||||
(start, current)
|
||||
(start, current)
|
||||
})
|
||||
}
|
||||
|
||||
/// On either the server side or the browser side, generates the next key in the hydration process.
|
||||
pub fn next_hydration_key(&self) -> String {
|
||||
let mut sc = self.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
|
||||
}
|
||||
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
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Runs the given function with the next hydration context.
|
||||
pub fn with_next_context<T>(&self, f: impl FnOnce() -> T) -> T {
|
||||
if self
|
||||
.runtime
|
||||
.shared_context
|
||||
.borrow()
|
||||
.as_ref()
|
||||
.and_then(|sc| sc.context.as_ref())
|
||||
.is_some()
|
||||
{
|
||||
let c = {
|
||||
if let Some(ref mut sc) = *self.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))
|
||||
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
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let res = self.untrack(f);
|
||||
|
||||
if let Some(ref mut sc) = *runtime.shared_context.borrow_mut() {
|
||||
sc.context = c;
|
||||
}
|
||||
};
|
||||
|
||||
let res = self.untrack(f);
|
||||
|
||||
if let Some(ref mut sc) = *self.runtime.shared_context.borrow_mut() {
|
||||
sc.context = c;
|
||||
res
|
||||
} else {
|
||||
self.untrack(f)
|
||||
}
|
||||
res
|
||||
} else {
|
||||
self.untrack(f)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns IDs for all [Resource](crate::Resource)s found on any scope.
|
||||
pub fn all_resources(&self) -> Vec<ResourceId> {
|
||||
self.runtime.all_resources()
|
||||
with_runtime(self.runtime, |runtime| runtime.all_resources())
|
||||
}
|
||||
|
||||
/// The current key for an HTML fragment created by server-rendering a `<Suspense/>` component.
|
||||
pub fn current_fragment_key(&self) -> String {
|
||||
self.runtime
|
||||
.shared_context
|
||||
.borrow()
|
||||
.as_ref()
|
||||
.map(|context| context.current_fragment_key())
|
||||
.unwrap_or_else(|| String::from("0f"))
|
||||
with_runtime(self.runtime, |runtime| {
|
||||
runtime
|
||||
.shared_context
|
||||
.borrow()
|
||||
.as_ref()
|
||||
.map(|context| context.current_fragment_key())
|
||||
.unwrap_or_else(|| String::from("0f"))
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns IDs for all [Resource](crate::Resource)s found on any scope.
|
||||
pub fn serialization_resolvers(&self) -> FuturesUnordered<PinnedFuture<(ResourceId, String)>> {
|
||||
self.runtime.serialization_resolvers()
|
||||
with_runtime(self.runtime, |runtime| runtime.serialization_resolvers())
|
||||
}
|
||||
|
||||
/// Registers the given [SuspenseContext](crate::SuspenseContext) with the current scope,
|
||||
|
@ -426,33 +456,37 @@ impl Scope {
|
|||
use crate::create_isomorphic_effect;
|
||||
use futures::StreamExt;
|
||||
|
||||
if let Some(ref mut shared_context) = *self.runtime.shared_context.borrow_mut() {
|
||||
let (tx, mut rx) = futures::channel::mpsc::unbounded();
|
||||
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(());
|
||||
}
|
||||
});
|
||||
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()
|
||||
}),
|
||||
);
|
||||
}
|
||||
shared_context.pending_fragments.insert(
|
||||
key.to_string(),
|
||||
Box::pin(async move {
|
||||
rx.next().await;
|
||||
resolver()
|
||||
}),
|
||||
);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// The set of all HTML fragments current pending, by their keys (see [Self::current_fragment_key]).
|
||||
pub fn pending_fragments(&self) -> HashMap<String, Pin<Box<dyn Future<Output = String>>>> {
|
||||
if let Some(ref mut shared_context) = *self.runtime.shared_context.borrow_mut() {
|
||||
std::mem::take(&mut shared_context.pending_fragments)
|
||||
} else {
|
||||
HashMap::new()
|
||||
}
|
||||
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()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -11,10 +11,10 @@ use crate::{create_isomorphic_effect, create_signal, ReadSignal, Scope, WriteSig
|
|||
/// because it reduces them from `O(n)` to `O(1)`.
|
||||
///
|
||||
/// ```
|
||||
/// # use leptos_reactive::{create_isomorphic_effect, create_scope, create_selector, create_signal};
|
||||
/// # use leptos_reactive::*;
|
||||
/// # use std::rc::Rc;
|
||||
/// # use std::cell::RefCell;
|
||||
/// # create_scope(|cx| {
|
||||
/// # create_scope(create_runtime(), |cx| {
|
||||
/// let (a, set_a) = create_signal(cx, 0);
|
||||
/// let is_selected = create_selector(cx, a);
|
||||
/// let total_notifications = Rc::new(RefCell::new(0));
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use crate::{
|
||||
debug_warn, spawn_local, Runtime, Scope, ScopeProperty, UntrackedGettableSignal,
|
||||
UntrackedSettableSignal,
|
||||
debug_warn,
|
||||
runtime::{with_runtime, RuntimeId},
|
||||
spawn_local, Runtime, Scope, ScopeProperty, UntrackedGettableSignal, UntrackedSettableSignal,
|
||||
};
|
||||
use futures::Stream;
|
||||
use std::{fmt::Debug, marker::PhantomData};
|
||||
|
@ -18,7 +19,7 @@ use thiserror::Error;
|
|||
///
|
||||
/// ```
|
||||
/// # use leptos_reactive::*;
|
||||
/// # create_scope(|cx| {
|
||||
/// # create_scope(create_runtime(), |cx| {
|
||||
/// let (count, set_count) = create_signal(cx, 0);
|
||||
///
|
||||
/// // ✅ calling the getter clones and returns the value
|
||||
|
@ -85,7 +86,7 @@ pub fn create_signal_from_stream<T>(
|
|||
///
|
||||
/// ```
|
||||
/// # use leptos_reactive::*;
|
||||
/// # create_scope(|cx| {
|
||||
/// # create_scope(create_runtime(), |cx| {
|
||||
/// let (count, set_count) = create_signal(cx, 0);
|
||||
///
|
||||
/// // ✅ calling the getter clones and returns the value
|
||||
|
@ -116,7 +117,7 @@ pub struct ReadSignal<T>
|
|||
where
|
||||
T: 'static,
|
||||
{
|
||||
pub(crate) runtime: &'static Runtime,
|
||||
pub(crate) runtime: RuntimeId,
|
||||
pub(crate) id: SignalId,
|
||||
pub(crate) ty: PhantomData<T>,
|
||||
}
|
||||
|
@ -142,7 +143,7 @@ where
|
|||
/// the running effect to this signal.
|
||||
/// ```
|
||||
/// # use leptos_reactive::*;
|
||||
/// # create_scope(|cx| {
|
||||
/// # create_scope(create_runtime(), |cx| {
|
||||
/// let (name, set_name) = create_signal(cx, "Alice".to_string());
|
||||
///
|
||||
/// // ❌ unnecessarily clones the string
|
||||
|
@ -166,7 +167,7 @@ where
|
|||
|
||||
#[cfg(feature = "hydrate")]
|
||||
pub(crate) fn subscribe(&self) {
|
||||
self.id.subscribe(self.runtime);
|
||||
with_runtime(self.runtime, |runtime| self.id.subscribe(runtime))
|
||||
}
|
||||
|
||||
/// Clones and returns the current value of the signal, and subscribes
|
||||
|
@ -176,7 +177,7 @@ where
|
|||
/// (`value.get()` is equivalent to `value.with(T::clone)`.)
|
||||
/// ```
|
||||
/// # use leptos_reactive::*;
|
||||
/// # create_scope(|cx| {
|
||||
/// # create_scope(create_runtime(), |cx| {
|
||||
/// let (count, set_count) = create_signal(cx, 0);
|
||||
///
|
||||
/// // calling the getter clones and returns the value
|
||||
|
@ -193,7 +194,7 @@ where
|
|||
/// Applies the function to the current Signal, if it exists, and subscribes
|
||||
/// the running effect.
|
||||
pub(crate) fn try_with<U>(&self, f: impl FnOnce(&T) -> U) -> Result<U, SignalError> {
|
||||
self.id.try_with(self.runtime, f)
|
||||
with_runtime(self.runtime, |runtime| self.id.try_with(runtime, f))
|
||||
}
|
||||
|
||||
/// Generates a [Stream] that emits the new value of the signal whenever it changes.
|
||||
|
@ -273,7 +274,7 @@ where
|
|||
///
|
||||
/// ```
|
||||
/// # use leptos_reactive::*;
|
||||
/// # create_scope(|cx| {
|
||||
/// # create_scope(create_runtime(), |cx| {
|
||||
/// let (count, set_count) = create_signal(cx, 0);
|
||||
///
|
||||
/// // ✅ calling the setter sets the value
|
||||
|
@ -294,7 +295,7 @@ pub struct WriteSignal<T>
|
|||
where
|
||||
T: 'static,
|
||||
{
|
||||
pub(crate) runtime: &'static Runtime,
|
||||
pub(crate) runtime: RuntimeId,
|
||||
pub(crate) id: SignalId,
|
||||
pub(crate) ty: PhantomData<T>,
|
||||
}
|
||||
|
@ -324,7 +325,7 @@ where
|
|||
/// even if the value has not actually changed.
|
||||
/// ```
|
||||
/// # use leptos_reactive::*;
|
||||
/// # create_scope(|cx| {
|
||||
/// # create_scope(create_runtime(), |cx| {
|
||||
/// let (count, set_count) = create_signal(cx, 0);
|
||||
///
|
||||
/// // notifies subscribers
|
||||
|
@ -347,7 +348,7 @@ where
|
|||
/// even if the value has not actually changed.
|
||||
/// ```
|
||||
/// # use leptos_reactive::*;
|
||||
/// # create_scope(|cx| {
|
||||
/// # create_scope(create_runtime(), |cx| {
|
||||
/// let (count, set_count) = create_signal(cx, 0);
|
||||
///
|
||||
/// // notifies subscribers
|
||||
|
@ -414,7 +415,7 @@ where
|
|||
/// or as a function argument.
|
||||
/// ```
|
||||
/// # use leptos_reactive::*;
|
||||
/// # create_scope(|cx| {
|
||||
/// # create_scope(create_runtime(), |cx| {
|
||||
/// let count = create_rw_signal(cx, 0);
|
||||
///
|
||||
/// // ✅ set the value
|
||||
|
@ -441,7 +442,7 @@ pub fn create_rw_signal<T>(cx: Scope, value: T) -> RwSignal<T> {
|
|||
/// its style, or it may be easier to pass around in a context or as a function argument.
|
||||
/// ```
|
||||
/// # use leptos_reactive::*;
|
||||
/// # create_scope(|cx| {
|
||||
/// # create_scope(create_runtime(), |cx| {
|
||||
/// let count = create_rw_signal(cx, 0);
|
||||
///
|
||||
/// // ✅ set the value
|
||||
|
@ -462,7 +463,7 @@ pub struct RwSignal<T>
|
|||
where
|
||||
T: 'static,
|
||||
{
|
||||
pub(crate) runtime: &'static Runtime,
|
||||
pub(crate) runtime: RuntimeId,
|
||||
pub(crate) id: SignalId,
|
||||
pub(crate) ty: PhantomData<T>,
|
||||
}
|
||||
|
@ -496,11 +497,11 @@ impl<T> UntrackedGettableSignal<T> for RwSignal<T> {
|
|||
impl<T> UntrackedSettableSignal<T> for RwSignal<T> {
|
||||
fn set_untracked(&self, new_value: T) {
|
||||
self.id
|
||||
.update_with_no_effect(self.runtime, |v| *v = new_value);
|
||||
.update_with_no_effect(self.runtime, |v| *v = new_value)
|
||||
}
|
||||
|
||||
fn update_untracked(&self, f: impl FnOnce(&mut T)) {
|
||||
self.id.update_with_no_effect(self.runtime, f);
|
||||
self.id.update_with_no_effect(self.runtime, f)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -512,7 +513,7 @@ where
|
|||
/// the running effect to this signal.
|
||||
/// ```
|
||||
/// # use leptos_reactive::*;
|
||||
/// # create_scope(|cx| {
|
||||
/// # create_scope(create_runtime(), |cx| {
|
||||
/// let name = create_rw_signal(cx, "Alice".to_string());
|
||||
///
|
||||
/// // ❌ unnecessarily clones the string
|
||||
|
@ -535,7 +536,7 @@ where
|
|||
/// the running effect to this signal.
|
||||
/// ```
|
||||
/// # use leptos_reactive::*;
|
||||
/// # create_scope(|cx| {
|
||||
/// # create_scope(create_runtime(), |cx| {
|
||||
/// let count = create_rw_signal(cx, 0);
|
||||
///
|
||||
/// assert_eq!(count.get(), 0);
|
||||
|
@ -556,7 +557,7 @@ where
|
|||
/// and notifies subscribers that the signal has changed.
|
||||
/// ```
|
||||
/// # use leptos_reactive::*;
|
||||
/// # create_scope(|cx| {
|
||||
/// # create_scope(create_runtime(), |cx| {
|
||||
/// let count = create_rw_signal(cx, 0);
|
||||
///
|
||||
/// // notifies subscribers
|
||||
|
@ -579,7 +580,7 @@ where
|
|||
/// even if the value has not actually changed.
|
||||
/// ```
|
||||
/// # use leptos_reactive::*;
|
||||
/// # create_scope(|cx| {
|
||||
/// # create_scope(create_runtime(), |cx| {
|
||||
/// let count = create_rw_signal(cx, 0);
|
||||
///
|
||||
/// assert_eq!(count(), 0);
|
||||
|
@ -597,7 +598,7 @@ where
|
|||
/// to the signal and cause other parts of the DOM to update.
|
||||
/// ```
|
||||
/// # use leptos_reactive::*;
|
||||
/// # create_scope(|cx| {
|
||||
/// # create_scope(create_runtime(), |cx| {
|
||||
/// let count = create_rw_signal(cx, 0);
|
||||
/// let read_count = count.read_only();
|
||||
/// assert_eq!(count(), 0);
|
||||
|
@ -726,78 +727,88 @@ impl SignalId {
|
|||
self.try_with_no_subscription(runtime, f)
|
||||
}
|
||||
|
||||
pub(crate) fn with_no_subscription<T, U>(&self, runtime: &Runtime, f: impl FnOnce(&T) -> U) -> U
|
||||
pub(crate) fn with_no_subscription<T, U>(
|
||||
&self,
|
||||
runtime: RuntimeId,
|
||||
f: impl FnOnce(&T) -> U,
|
||||
) -> U
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
self.try_with_no_subscription(runtime, f).unwrap()
|
||||
with_runtime(runtime, |runtime| {
|
||||
self.try_with_no_subscription(runtime, f).unwrap()
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn with<T, U>(&self, runtime: &Runtime, f: impl FnOnce(&T) -> U) -> U
|
||||
pub(crate) fn with<T, U>(&self, runtime: RuntimeId, f: impl FnOnce(&T) -> U) -> U
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
self.try_with(runtime, f).unwrap()
|
||||
with_runtime(runtime, |runtime| self.try_with(runtime, f).unwrap())
|
||||
}
|
||||
|
||||
fn update_value<T>(&self, runtime: &Runtime, f: impl FnOnce(&mut T)) -> bool
|
||||
fn update_value<T>(&self, runtime: RuntimeId, f: impl FnOnce(&mut T)) -> bool
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
let value = {
|
||||
let signals = runtime.signals.borrow();
|
||||
signals.get(*self).cloned()
|
||||
};
|
||||
if let Some(value) = value {
|
||||
let mut value = value.borrow_mut();
|
||||
if let Some(value) = value.downcast_mut::<T>() {
|
||||
f(value);
|
||||
true
|
||||
with_runtime(runtime, |runtime| {
|
||||
let value = {
|
||||
let signals = runtime.signals.borrow();
|
||||
signals.get(*self).cloned()
|
||||
};
|
||||
if let Some(value) = value {
|
||||
let mut value = value.borrow_mut();
|
||||
if let Some(value) = value.downcast_mut::<T>() {
|
||||
f(value);
|
||||
true
|
||||
} else {
|
||||
debug_warn!(
|
||||
"[Signal::update] failed when downcasting to Signal<{}>",
|
||||
std::any::type_name::<T>()
|
||||
);
|
||||
false
|
||||
}
|
||||
} else {
|
||||
debug_warn!(
|
||||
"[Signal::update] failed when downcasting to Signal<{}>",
|
||||
"[Signal::update] You’re trying to update a Signal<{}> that has already been disposed of. This is probably either a logic error in a component that creates and disposes of scopes, or a Resource resolving after its scope has been dropped without having been cleaned up.",
|
||||
std::any::type_name::<T>()
|
||||
);
|
||||
false
|
||||
}
|
||||
} else {
|
||||
debug_warn!(
|
||||
"[Signal::update] You’re trying to update a Signal<{}> that has already been disposed of. This is probably either a logic error in a component that creates and disposes of scopes, or a Resource resolving after its scope has been dropped without having been cleaned up.",
|
||||
std::any::type_name::<T>()
|
||||
);
|
||||
false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn update<T>(&self, runtime: &Runtime, f: impl FnOnce(&mut T))
|
||||
pub(crate) fn update<T>(&self, runtime_id: RuntimeId, f: impl FnOnce(&mut T))
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
// update the value
|
||||
let updated = self.update_value(runtime, f);
|
||||
with_runtime(runtime_id, |runtime| {
|
||||
// update the value
|
||||
let updated = self.update_value(runtime_id, f);
|
||||
|
||||
// notify subscribers
|
||||
if updated {
|
||||
let subs = {
|
||||
let subs = runtime.signal_subscribers.borrow();
|
||||
let subs = subs.get(*self);
|
||||
subs.map(|subs| subs.borrow().clone())
|
||||
};
|
||||
if let Some(subs) = subs {
|
||||
for sub in subs {
|
||||
let effect = {
|
||||
let effects = runtime.effects.borrow();
|
||||
effects.get(sub).cloned()
|
||||
};
|
||||
if let Some(effect) = effect {
|
||||
effect.run(sub, runtime);
|
||||
// notify subscribers
|
||||
if updated {
|
||||
let subs = {
|
||||
let subs = runtime.signal_subscribers.borrow();
|
||||
let subs = subs.get(*self);
|
||||
subs.map(|subs| subs.borrow().clone())
|
||||
};
|
||||
if let Some(subs) = subs {
|
||||
for sub in subs {
|
||||
let effect = {
|
||||
let effects = runtime.effects.borrow();
|
||||
effects.get(sub).cloned()
|
||||
};
|
||||
if let Some(effect) = effect {
|
||||
effect.run(sub, runtime_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn update_with_no_effect<T>(&self, runtime: &Runtime, f: impl FnOnce(&mut T))
|
||||
pub(crate) fn update_with_no_effect<T>(&self, runtime: RuntimeId, f: impl FnOnce(&mut T))
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
|
|
|
@ -10,8 +10,8 @@ use crate::{Memo, ReadSignal, RwSignal, Scope, UntrackedGettableSignal};
|
|||
/// function call, `with()`, and `get()` APIs as over signals.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use leptos_reactive::{create_scope, create_signal, create_rw_signal, create_memo, Signal};
|
||||
/// # create_scope(|cx| {
|
||||
/// # use leptos_reactive::*;
|
||||
/// # create_scope(create_runtime(), |cx| {
|
||||
/// let (count, set_count) = create_signal(cx, 2);
|
||||
/// let double_count = Signal::derive(cx, move || count() * 2);
|
||||
/// let memoized_double_count = create_memo(cx, move |_| count() * 2);
|
||||
|
@ -73,8 +73,8 @@ where
|
|||
/// Wraps a derived signal, i.e., any computation that accesses one or more
|
||||
/// reactive signals.
|
||||
/// ```rust
|
||||
/// # use leptos_reactive::{create_scope, create_signal, create_rw_signal, create_memo, Signal};
|
||||
/// # create_scope(|cx| {
|
||||
/// # use leptos_reactive::*;
|
||||
/// # create_scope(create_runtime(), |cx| {
|
||||
/// let (count, set_count) = create_signal(cx, 2);
|
||||
/// let double_count = Signal::derive(cx, move || count() * 2);
|
||||
///
|
||||
|
@ -95,7 +95,7 @@ where
|
|||
/// the running effect to this signal.
|
||||
/// ```
|
||||
/// # use leptos_reactive::*;
|
||||
/// # create_scope(|cx| {
|
||||
/// # create_scope(create_runtime(), |cx| {
|
||||
/// let (name, set_name) = create_signal(cx, "Alice".to_string());
|
||||
/// let name_upper = Signal::derive(cx, move || name.with(|n| n.to_uppercase()));
|
||||
/// let memoized_lower = create_memo(cx, move |_| name.with(|n| n.to_lowercase()));
|
||||
|
@ -134,8 +134,8 @@ where
|
|||
/// If you want to get the value without cloning it, use [ReadSignal::with].
|
||||
/// (There’s no difference in behavior for derived signals: they re-run in any case.)
|
||||
/// ```
|
||||
/// # use leptos_reactive::{create_scope, create_signal, create_rw_signal, create_memo, Signal};
|
||||
/// # create_scope(|cx| {
|
||||
/// # use leptos_reactive::*;
|
||||
/// # create_scope(create_runtime(), |cx| {
|
||||
/// let (count, set_count) = create_signal(cx, 2);
|
||||
/// let double_count = Signal::derive(cx, move || count() * 2);
|
||||
/// let memoized_double_count = create_memo(cx, move |_| count() * 2);
|
||||
|
@ -258,7 +258,7 @@ where
|
|||
///
|
||||
/// ```rust
|
||||
/// # use leptos_reactive::*;
|
||||
/// # create_scope(|cx| {
|
||||
/// # create_scope(create_runtime(), |cx| {
|
||||
/// let (count, set_count) = create_signal(cx, 2);
|
||||
/// let double_count = MaybeSignal::derive(cx, move || count() * 2);
|
||||
/// let memoized_double_count = create_memo(cx, move |_| count() * 2);
|
||||
|
@ -317,8 +317,8 @@ where
|
|||
/// Wraps a derived signal, i.e., any computation that accesses one or more
|
||||
/// reactive signals.
|
||||
/// ```rust
|
||||
/// # use leptos_reactive::{create_scope, create_signal, create_rw_signal, create_memo, Signal};
|
||||
/// # create_scope(|cx| {
|
||||
/// # use leptos_reactive::*;
|
||||
/// # create_scope(create_runtime(), |cx| {
|
||||
/// let (count, set_count) = create_signal(cx, 2);
|
||||
/// let double_count = Signal::derive(cx, move || count() * 2);
|
||||
///
|
||||
|
@ -339,7 +339,7 @@ where
|
|||
/// the running effect to this signal.
|
||||
/// ```
|
||||
/// # use leptos_reactive::*;
|
||||
/// # create_scope(|cx| {
|
||||
/// # create_scope(create_runtime(), |cx| {
|
||||
/// let (name, set_name) = create_signal(cx, "Alice".to_string());
|
||||
/// let name_upper = MaybeSignal::derive(cx, move || name.with(|n| n.to_uppercase()));
|
||||
/// let memoized_lower = create_memo(cx, move |_| name.with(|n| n.to_lowercase()));
|
||||
|
@ -381,7 +381,7 @@ where
|
|||
/// (There’s no difference in behavior for derived signals: they re-run in any case.)
|
||||
/// ```
|
||||
/// # use leptos_reactive::*;
|
||||
/// # create_scope(|cx| {
|
||||
/// # create_scope(create_runtime(), |cx| {
|
||||
/// let (count, set_count) = create_signal(cx, 2);
|
||||
/// let double_count = MaybeSignal::derive(cx, move || count() * 2);
|
||||
/// let memoized_double_count = create_memo(cx, move |_| count() * 2);
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
#[cfg(not(feature = "stable"))]
|
||||
use leptos_reactive::{create_isomorphic_effect, create_memo, create_scope, create_signal};
|
||||
use leptos_reactive::{
|
||||
create_isomorphic_effect, create_memo, create_runtime, create_scope, create_signal,
|
||||
};
|
||||
|
||||
#[cfg(not(feature = "stable"))]
|
||||
#[test]
|
||||
|
@ -7,7 +9,7 @@ fn effect_runs() {
|
|||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
|
||||
create_scope(|cx| {
|
||||
create_scope(create_runtime(), |cx| {
|
||||
let (a, set_a) = create_signal(cx, -1);
|
||||
|
||||
// simulate an arbitrary side effect
|
||||
|
@ -36,7 +38,7 @@ fn effect_tracks_memo() {
|
|||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
|
||||
create_scope(|cx| {
|
||||
create_scope(create_runtime(), |cx| {
|
||||
let (a, set_a) = create_signal(cx, -1);
|
||||
let b = create_memo(cx, move |_| format!("Value is {}", a()));
|
||||
|
||||
|
@ -67,7 +69,7 @@ fn untrack_mutes_effect() {
|
|||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
|
||||
create_scope(|cx| {
|
||||
create_scope(create_runtime(), |cx| {
|
||||
let (a, set_a) = create_signal(cx, -1);
|
||||
|
||||
// simulate an arbitrary side effect
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
#[cfg(not(feature = "stable"))]
|
||||
use leptos_reactive::{create_memo, create_scope, create_signal};
|
||||
use leptos_reactive::{create_memo, create_runtime, create_scope, create_signal};
|
||||
|
||||
#[cfg(not(feature = "stable"))]
|
||||
#[test]
|
||||
fn basic_memo() {
|
||||
create_scope(|cx| {
|
||||
create_scope(create_runtime(), |cx| {
|
||||
let a = create_memo(cx, |_| 5);
|
||||
assert_eq!(a(), 5);
|
||||
})
|
||||
|
@ -14,7 +14,7 @@ fn basic_memo() {
|
|||
#[cfg(not(feature = "stable"))]
|
||||
#[test]
|
||||
fn memo_with_computed_value() {
|
||||
create_scope(|cx| {
|
||||
create_scope(create_runtime(), |cx| {
|
||||
let (a, set_a) = create_signal(cx, 0);
|
||||
let (b, set_b) = create_signal(cx, 0);
|
||||
let c = create_memo(cx, move |_| a() + b());
|
||||
|
@ -30,7 +30,7 @@ fn memo_with_computed_value() {
|
|||
#[cfg(not(feature = "stable"))]
|
||||
#[test]
|
||||
fn nested_memos() {
|
||||
create_scope(|cx| {
|
||||
create_scope(create_runtime(), |cx| {
|
||||
let (a, set_a) = create_signal(cx, 0);
|
||||
let (b, set_b) = create_signal(cx, 0);
|
||||
let c = create_memo(cx, move |_| a() + b());
|
||||
|
@ -54,7 +54,7 @@ fn nested_memos() {
|
|||
fn memo_runs_only_when_inputs_change() {
|
||||
use std::{cell::Cell, rc::Rc};
|
||||
|
||||
create_scope(|cx| {
|
||||
create_scope(create_runtime(), |cx| {
|
||||
let call_count = Rc::new(Cell::new(0));
|
||||
let (a, set_a) = create_signal(cx, 0);
|
||||
let (b, _) = create_signal(cx, 0);
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
#[cfg(not(feature = "stable"))]
|
||||
use leptos_reactive::{create_scope, create_signal};
|
||||
use leptos_reactive::{create_runtime, create_scope, create_signal};
|
||||
|
||||
#[cfg(not(feature = "stable"))]
|
||||
#[test]
|
||||
fn basic_signal() {
|
||||
create_scope(|cx| {
|
||||
create_scope(create_runtime(), |cx| {
|
||||
let (a, set_a) = create_signal(cx, 0);
|
||||
assert_eq!(a(), 0);
|
||||
set_a(5);
|
||||
|
@ -16,7 +16,7 @@ fn basic_signal() {
|
|||
#[cfg(not(feature = "stable"))]
|
||||
#[test]
|
||||
fn derived_signals() {
|
||||
create_scope(|cx| {
|
||||
create_scope(create_runtime(), |cx| {
|
||||
let (a, set_a) = create_signal(cx, 0);
|
||||
let (b, set_b) = create_signal(cx, 0);
|
||||
let c = move || a() + b();
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
//#[cfg(not(feature = "stable"))]
|
||||
use leptos_reactive::{
|
||||
create_isomorphic_effect, create_scope, create_signal, UntrackedGettableSignal,
|
||||
create_isomorphic_effect, create_runtime, create_scope, create_signal, UntrackedGettableSignal,
|
||||
UntrackedSettableSignal,
|
||||
};
|
||||
|
||||
|
@ -10,7 +10,7 @@ fn untracked_set_doesnt_trigger_effect() {
|
|||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
|
||||
create_scope(|cx| {
|
||||
create_scope(create_runtime(), |cx| {
|
||||
let (a, set_a) = create_signal(cx, -1);
|
||||
|
||||
// simulate an arbitrary side effect
|
||||
|
@ -42,7 +42,7 @@ fn untracked_get_doesnt_trigger_effect() {
|
|||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
|
||||
create_scope(|cx| {
|
||||
create_scope(create_runtime(), |cx| {
|
||||
let (a, set_a) = create_signal(cx, -1);
|
||||
let (a2, set_a2) = create_signal(cx, 1);
|
||||
|
||||
|
|
Loading…
Reference in a new issue