mirror of
https://github.com/leptos-rs/leptos.git
synced 2025-12-27 15:44:42 -05:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c317bc1e5c |
@@ -1,4 +1,5 @@
|
||||
use leptos::*;
|
||||
use leptos::{SignalWrite, *};
|
||||
use std::cell::{Ref, RefMut};
|
||||
|
||||
/// A simple counter component.
|
||||
///
|
||||
@@ -12,12 +13,20 @@ pub fn SimpleCounter(
|
||||
) -> impl IntoView {
|
||||
let (value, set_value) = create_signal(initial_value);
|
||||
|
||||
let something: Ref<'_, i32> = value.read();
|
||||
|
||||
spawn_local(async move {
|
||||
let mut something_else: RefMut<'_, i32> = set_value.write();
|
||||
async {}.await;
|
||||
*something_else = 30;
|
||||
});
|
||||
|
||||
view! {
|
||||
<div>
|
||||
<button on:click=move |_| set_value(0)>"Clear"</button>
|
||||
<button on:click=move |_| set_value.update(|value| *value -= step)>"-1"</button>
|
||||
<button on:click=move |_| *set_value.write() -= step>"-1"</button>
|
||||
<span>"Value: " {value} "!"</span>
|
||||
<button on:click=move |_| set_value.update(|value| *value += step)>"+1"</button>
|
||||
<button on:click=move |_| *set_value.write() += step>"+1"</button>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ repository = "https://github.com/leptos-rs/leptos"
|
||||
description = "Reactive system for the Leptos web framework."
|
||||
|
||||
[dependencies]
|
||||
elsa = "1"
|
||||
slotmap = { version = "1", features = ["serde"] }
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde-lite = { version = "0.4", optional = true }
|
||||
|
||||
161
leptos_reactive/src/arena.rs
Normal file
161
leptos_reactive/src/arena.rs
Normal file
@@ -0,0 +1,161 @@
|
||||
use elsa::FrozenVec;
|
||||
use std::{
|
||||
any::Any,
|
||||
cell::{Cell, Ref, RefCell, RefMut},
|
||||
fmt::Debug,
|
||||
marker::PhantomData,
|
||||
rc::Rc,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct Arena {
|
||||
#[allow(clippy::type_complexity)]
|
||||
values: &'static FrozenVec<Box<Slot>>,
|
||||
open: Rc<RefCell<Vec<u32>>>,
|
||||
}
|
||||
|
||||
impl Arena {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
values: Box::leak(Box::new(FrozenVec::new())),
|
||||
open: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
fn push<T: 'static>(&self, value: T) -> NodeId {
|
||||
self.push_erased(Box::new(value))
|
||||
}
|
||||
|
||||
pub fn get<T: 'static>(&self, id: &NodeId) -> Option<Ref<'_, T>> {
|
||||
let node = self.get_any(id)?;
|
||||
Ref::filter_map(node, |node| {
|
||||
let node = node.as_ref()?;
|
||||
match node.downcast_ref::<T>() {
|
||||
Some(t) => Some(t),
|
||||
None => None,
|
||||
}
|
||||
})
|
||||
.ok()
|
||||
}
|
||||
|
||||
pub fn get_mut<T: 'static>(&self, id: &NodeId) -> Option<RefMut<'_, T>> {
|
||||
let node = self.get_any_mut(id)?;
|
||||
RefMut::filter_map(node, |node| {
|
||||
let node = node.as_mut()?;
|
||||
match node.downcast_mut::<T>() {
|
||||
Some(t) => Some(t),
|
||||
None => None,
|
||||
}
|
||||
})
|
||||
.ok()
|
||||
}
|
||||
|
||||
pub fn remove(&self, id: &NodeId) -> Option<Box<dyn Any>> {
|
||||
self.recycle(id)
|
||||
}
|
||||
|
||||
fn get_any(&self, id: &NodeId) -> Option<Ref<'_, Option<Box<dyn Any>>>> {
|
||||
let node = self.values.get(id.idx as usize)?;
|
||||
if id.generation == node.generation.get() {
|
||||
Some(node.value.borrow())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_any_mut(
|
||||
&self,
|
||||
id: &NodeId,
|
||||
) -> Option<RefMut<'_, Option<Box<dyn Any>>>> {
|
||||
let node = self.values.get(id.idx as usize)?;
|
||||
if id.generation == node.generation.get() {
|
||||
Some(node.value.borrow_mut())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn recycle(&self, id: &NodeId) -> Option<Box<dyn Any>> {
|
||||
let node = self.values.get(id.idx as usize)?;
|
||||
node.value.borrow_mut().take()
|
||||
}
|
||||
|
||||
fn push_erased(&self, value: Box<dyn Any>) -> NodeId {
|
||||
if let Some(next_node) = self.open.borrow_mut().pop() {
|
||||
let slot = self.values.get(next_node as usize).unwrap();
|
||||
let generation = slot.generation.get() + 1;
|
||||
slot.generation.set(generation);
|
||||
*slot.value.borrow_mut() = Some(value);
|
||||
NodeId {
|
||||
idx: next_node,
|
||||
generation,
|
||||
}
|
||||
} else {
|
||||
self.values.push(Box::new(Slot {
|
||||
generation: Cell::new(0),
|
||||
value: RefCell::new(Some(value)),
|
||||
}));
|
||||
let idx = (self.values.len() - 1) as u32;
|
||||
NodeId { idx, generation: 0 }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for Arena {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("Arena")
|
||||
.field("values", &self.values.len())
|
||||
.field("open", &self.open)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Default, Debug, PartialEq, Eq, Hash)]
|
||||
/// TODO
|
||||
pub struct NodeId {
|
||||
idx: u32,
|
||||
generation: u32,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct Slot {
|
||||
generation: Cell<u32>,
|
||||
value: RefCell<Option<Box<dyn Any>>>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub(crate) struct Value<T> {
|
||||
id: NodeId,
|
||||
ty: PhantomData<T>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct ArenaOwner {
|
||||
pub(crate) arena: &'static Arena,
|
||||
pub(crate) owned: RefCell<Vec<NodeId>>,
|
||||
}
|
||||
|
||||
impl ArenaOwner {
|
||||
pub fn insert<T: 'static>(&self, value: T) -> NodeId {
|
||||
let id = self.arena.push(value);
|
||||
self.register(id)
|
||||
}
|
||||
|
||||
pub fn insert_boxed(&self, value: Box<dyn Any>) -> NodeId {
|
||||
let id = self.arena.push_erased(value);
|
||||
self.register(id)
|
||||
}
|
||||
|
||||
fn register(&self, id: NodeId) -> NodeId {
|
||||
self.owned.borrow_mut().push(id);
|
||||
id
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for ArenaOwner {
|
||||
fn drop(&mut self) {
|
||||
for location in self.owned.borrow().iter() {
|
||||
drop(self.arena.recycle(location));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -60,7 +60,7 @@ where
|
||||
let mut contexts = runtime.contexts.borrow_mut();
|
||||
let owner = runtime.owner.get();
|
||||
if let Some(owner) = owner {
|
||||
let context = contexts.entry(owner).unwrap().or_default();
|
||||
let context = contexts.entry(owner).or_default();
|
||||
context.insert(id, Box::new(value) as Box<dyn Any>);
|
||||
} else {
|
||||
crate::macros::debug_warn!(
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
use crate::{node::NodeId, with_runtime, Disposer, Runtime, SignalDispose};
|
||||
use crate::{
|
||||
arena::NodeId, runtime::ThreadArena, with_runtime, Disposer, Runtime,
|
||||
SignalDispose,
|
||||
};
|
||||
use cfg_if::cfg_if;
|
||||
use std::{any::Any, cell::RefCell, marker::PhantomData, rc::Rc};
|
||||
use std::marker::PhantomData;
|
||||
|
||||
/// Effects run a certain chunk of code whenever the signals they depend on change.
|
||||
/// `create_effect` immediately runs the given function once, tracks its dependence
|
||||
@@ -177,16 +180,8 @@ where
|
||||
&self,
|
||||
f: impl FnOnce(&mut Option<T>) -> U,
|
||||
) -> Option<U> {
|
||||
with_runtime(|runtime| {
|
||||
let nodes = runtime.nodes.borrow();
|
||||
let node = nodes.get(self.id)?;
|
||||
let value = node.value.clone()?;
|
||||
let mut value = value.borrow_mut();
|
||||
let value = value.downcast_mut()?;
|
||||
Some(f(value))
|
||||
})
|
||||
.ok()
|
||||
.flatten()
|
||||
let mut value = ThreadArena::get_mut::<Option<T>>(&self.id)?;
|
||||
Some(f(&mut *value))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -296,7 +291,7 @@ where
|
||||
}
|
||||
|
||||
pub(crate) trait AnyComputation {
|
||||
fn run(&self, value: Rc<RefCell<dyn Any>>) -> bool;
|
||||
fn run(&self, node: NodeId) -> bool;
|
||||
}
|
||||
|
||||
impl<T, F> AnyComputation for EffectState<T, F>
|
||||
@@ -316,15 +311,13 @@ where
|
||||
)
|
||||
)
|
||||
)]
|
||||
fn run(&self, value: Rc<RefCell<dyn Any>>) -> bool {
|
||||
fn run(&self, node: NodeId) -> bool {
|
||||
// we defensively take and release the BorrowMut twice here
|
||||
// in case a change during the effect running schedules a rerun
|
||||
// ideally this should never happen, but this guards against panic
|
||||
let curr_value = {
|
||||
// downcast value
|
||||
let mut value = value.borrow_mut();
|
||||
let value = value
|
||||
.downcast_mut::<Option<T>>()
|
||||
let mut value = ThreadArena::get_mut::<Option<T>>(&node)
|
||||
.expect("to downcast effect value");
|
||||
value.take()
|
||||
};
|
||||
@@ -333,9 +326,7 @@ where
|
||||
let new_value = (self.f)(curr_value);
|
||||
|
||||
// set new value
|
||||
let mut value = value.borrow_mut();
|
||||
let value = value
|
||||
.downcast_mut::<Option<T>>()
|
||||
let mut value = ThreadArena::get_mut::<Option<T>>(&node)
|
||||
.expect("to downcast effect value");
|
||||
*value = Some(new_value);
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#![deny(missing_docs)]
|
||||
#![cfg_attr(feature = "nightly", feature(fn_traits))]
|
||||
#![cfg_attr(feature = "nightly", feature(unboxed_closures))]
|
||||
#![cfg_attr(feature = "nightly", feature(type_name_of_val))]
|
||||
#![feature(type_name_of_val)]
|
||||
|
||||
//! The reactive system for the [Leptos](https://docs.rs/leptos/latest/leptos/) Web framework.
|
||||
//!
|
||||
@@ -76,6 +76,7 @@
|
||||
#[cfg_attr(any(debug_assertions, feature = "ssr"), macro_use)]
|
||||
extern crate tracing;
|
||||
|
||||
mod arena;
|
||||
#[macro_use]
|
||||
mod signal;
|
||||
mod context;
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
use crate::{
|
||||
create_effect, diagnostics::AccessDiagnostics, node::NodeId, on_cleanup,
|
||||
with_runtime, AnyComputation, Runtime, SignalDispose, SignalGet,
|
||||
SignalGetUntracked, SignalStream, SignalWith, SignalWithUntracked,
|
||||
arena::NodeId, create_effect, diagnostics::AccessDiagnostics, on_cleanup,
|
||||
runtime::ThreadArena, with_runtime, AnyComputation, Runtime, SignalDispose,
|
||||
SignalGet, SignalGetUntracked, SignalStream, SignalWith,
|
||||
SignalWithUntracked,
|
||||
};
|
||||
use std::{any::Any, cell::RefCell, fmt, marker::PhantomData, rc::Rc};
|
||||
use std::{fmt, marker::PhantomData};
|
||||
|
||||
// IMPLEMENTATION NOTE:
|
||||
// Memos are implemented "lazily," i.e., the inner computation is not run
|
||||
@@ -282,8 +283,8 @@ impl<T: Clone> SignalGetUntracked for Memo<T> {
|
||||
.expect("invariant: must have already been initialized")
|
||||
};
|
||||
match self.id.try_with_no_subscription(runtime, f) {
|
||||
Ok(t) => t,
|
||||
Err(_) => panic_getting_dead_memo(
|
||||
Some(t) => t,
|
||||
None => panic_getting_dead_memo(
|
||||
#[cfg(any(debug_assertions, feature = "ssr"))]
|
||||
self.defined_at,
|
||||
),
|
||||
@@ -330,8 +331,8 @@ impl<T> SignalWithUntracked for Memo<T> {
|
||||
fn with_untracked<O>(&self, f: impl FnOnce(&T) -> O) -> O {
|
||||
with_runtime(|runtime| {
|
||||
match self.id.try_with_no_subscription(runtime, forward_ref_to(f)) {
|
||||
Ok(t) => t,
|
||||
Err(_) => panic_getting_dead_memo(
|
||||
Some(t) => t,
|
||||
None => panic_getting_dead_memo(
|
||||
#[cfg(any(debug_assertions, feature = "ssr"))]
|
||||
self.defined_at,
|
||||
),
|
||||
@@ -356,7 +357,7 @@ impl<T> SignalWithUntracked for Memo<T> {
|
||||
#[inline]
|
||||
fn try_with_untracked<O>(&self, f: impl FnOnce(&T) -> O) -> Option<O> {
|
||||
with_runtime(|runtime| {
|
||||
self.id.try_with_no_subscription(runtime, |v: &T| f(v)).ok()
|
||||
self.id.try_with_no_subscription(runtime, |v: &T| f(v))
|
||||
})
|
||||
.ok()
|
||||
.flatten()
|
||||
@@ -468,9 +469,7 @@ impl<T> SignalWith for Memo<T> {
|
||||
|
||||
with_runtime(|runtime| {
|
||||
self.id.subscribe(runtime, diagnostics);
|
||||
self.id
|
||||
.try_with_no_subscription(runtime, forward_ref_to(f))
|
||||
.ok()
|
||||
self.id.try_with_no_subscription(runtime, forward_ref_to(f))
|
||||
})
|
||||
.ok()
|
||||
.flatten()
|
||||
@@ -544,12 +543,12 @@ where
|
||||
)
|
||||
)
|
||||
)]
|
||||
fn run(&self, value: Rc<RefCell<dyn Any>>) -> bool {
|
||||
fn run(&self, node: NodeId) -> bool {
|
||||
let (new_value, is_different) = {
|
||||
let value = value.borrow();
|
||||
let curr_value = value
|
||||
.downcast_ref::<Option<T>>()
|
||||
.expect("to downcast memo value");
|
||||
let curr_value = match ThreadArena::get::<Option<T>>(&node) {
|
||||
Some(t) => t,
|
||||
_ => return false,
|
||||
};
|
||||
|
||||
// run the effect
|
||||
let new_value = (self.f)(curr_value.as_ref());
|
||||
@@ -557,11 +556,8 @@ where
|
||||
(new_value, is_different)
|
||||
};
|
||||
if is_different {
|
||||
let mut value = value.borrow_mut();
|
||||
let curr_value = value
|
||||
.downcast_mut::<Option<T>>()
|
||||
.expect("to downcast memo value");
|
||||
*curr_value = Some(new_value);
|
||||
let mut value = ThreadArena::get_mut::<Option<T>>(&node).unwrap();
|
||||
*value = Some(new_value);
|
||||
}
|
||||
|
||||
is_different
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
use crate::{with_runtime, AnyComputation};
|
||||
use std::{any::Any, cell::RefCell, rc::Rc};
|
||||
|
||||
slotmap::new_key_type! {
|
||||
/// Unique ID assigned to a signal.
|
||||
pub struct NodeId;
|
||||
}
|
||||
use crate::{
|
||||
arena::NodeId, runtime::ThreadArena, with_runtime, AnyComputation,
|
||||
};
|
||||
use std::rc::Rc;
|
||||
|
||||
/// Handle to dispose of a reactive node.
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
@@ -14,6 +11,7 @@ impl Drop for Disposer {
|
||||
fn drop(&mut self) {
|
||||
let id = self.0;
|
||||
_ = with_runtime(|runtime| {
|
||||
ThreadArena::remove(&id);
|
||||
runtime.cleanup_node(id);
|
||||
runtime.dispose_node(id);
|
||||
});
|
||||
@@ -22,19 +20,10 @@ impl Drop for Disposer {
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct ReactiveNode {
|
||||
pub value: Option<Rc<RefCell<dyn Any>>>,
|
||||
pub state: ReactiveNodeState,
|
||||
pub node_type: ReactiveNodeType,
|
||||
}
|
||||
|
||||
impl ReactiveNode {
|
||||
pub fn value(&self) -> Rc<RefCell<dyn Any>> {
|
||||
self.value
|
||||
.clone()
|
||||
.expect("ReactiveNode.value to have a value")
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) enum ReactiveNodeType {
|
||||
Trigger,
|
||||
|
||||
@@ -1116,8 +1116,7 @@ where
|
||||
|
||||
let v = self
|
||||
.value
|
||||
.try_with(|n| n.as_ref().map(|n| Some(f(n))))
|
||||
.ok()?
|
||||
.try_with(|n| n.as_ref().map(|n| Some(f(n))))?
|
||||
.flatten();
|
||||
|
||||
self.handle_result(location, global_suspense_cx, suspense_cx, v)
|
||||
@@ -1132,7 +1131,7 @@ where
|
||||
let global_suspense_cx = use_context::<GlobalSuspenseContext>();
|
||||
let suspense_cx = use_context::<SuspenseContext>();
|
||||
|
||||
let v = self.value.try_with(|n| f(n)).ok();
|
||||
let v = self.value.try_with(|n| f(n));
|
||||
|
||||
self.handle_result(location, global_suspense_cx, suspense_cx, v)
|
||||
}
|
||||
|
||||
@@ -1,23 +1,22 @@
|
||||
#[cfg(debug_assertions)]
|
||||
use crate::SpecialNonReactiveZone;
|
||||
use crate::{
|
||||
arena::{Arena, ArenaOwner, NodeId},
|
||||
hydration::SharedContext,
|
||||
node::{
|
||||
Disposer, NodeId, ReactiveNode, ReactiveNodeState, ReactiveNodeType,
|
||||
},
|
||||
node::{Disposer, ReactiveNode, ReactiveNodeState, ReactiveNodeType},
|
||||
AnyComputation, AnyResource, EffectState, Memo, MemoState, ReadSignal,
|
||||
ResourceId, ResourceState, RwSignal, SerializableResource, StoredValueId,
|
||||
Trigger, UnserializableResource, WriteSignal,
|
||||
ResourceId, ResourceState, RwSignal, SerializableResource, Trigger,
|
||||
UnserializableResource, WriteSignal,
|
||||
};
|
||||
use cfg_if::cfg_if;
|
||||
use core::hash::BuildHasherDefault;
|
||||
use futures::stream::FuturesUnordered;
|
||||
use indexmap::IndexSet;
|
||||
use rustc_hash::{FxHashMap, FxHasher};
|
||||
use slotmap::{SecondaryMap, SlotMap, SparseSecondaryMap};
|
||||
use slotmap::SlotMap;
|
||||
use std::{
|
||||
any::{Any, TypeId},
|
||||
cell::{Cell, RefCell},
|
||||
cell::{Cell, Ref, RefCell, RefMut},
|
||||
fmt::Debug,
|
||||
future::Future,
|
||||
marker::PhantomData,
|
||||
@@ -30,12 +29,13 @@ 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 ARENA: &'static Arena = Box::leak(Box::new(Arena::new()));
|
||||
pub(crate) static RUNTIME: Runtime = Runtime::new();
|
||||
}
|
||||
} else {
|
||||
thread_local! {
|
||||
pub(crate) static ARENA: &'static Arena = Box::leak(Box::new(Arena::new()));
|
||||
pub(crate) static RUNTIMES: RefCell<SlotMap<RuntimeId, Runtime>> = Default::default();
|
||||
|
||||
pub(crate) static CURRENT_RUNTIME: Cell<Option<RuntimeId>> = Default::default();
|
||||
}
|
||||
}
|
||||
@@ -53,29 +53,54 @@ type FxIndexSet<T> = IndexSet<T, BuildHasherDefault<FxHasher>>;
|
||||
// and other data included in the reactive system.
|
||||
#[derive(Default)]
|
||||
pub(crate) struct Runtime {
|
||||
pub arena: ArenaOwner,
|
||||
pub shared_context: RefCell<SharedContext>,
|
||||
pub owner: Cell<Option<NodeId>>,
|
||||
pub observer: Cell<Option<NodeId>>,
|
||||
#[allow(clippy::type_complexity)]
|
||||
pub on_cleanups:
|
||||
RefCell<SparseSecondaryMap<NodeId, Vec<Box<dyn FnOnce()>>>>,
|
||||
pub stored_values: RefCell<SlotMap<StoredValueId, Rc<RefCell<dyn Any>>>>,
|
||||
pub nodes: RefCell<SlotMap<NodeId, ReactiveNode>>,
|
||||
pub on_cleanups: RefCell<FxHashMap<NodeId, Vec<Box<dyn FnOnce()>>>>,
|
||||
pub nodes: RefCell<FxHashMap<NodeId, ReactiveNode>>,
|
||||
pub node_subscribers:
|
||||
RefCell<SecondaryMap<NodeId, RefCell<FxIndexSet<NodeId>>>>,
|
||||
pub node_sources:
|
||||
RefCell<SecondaryMap<NodeId, RefCell<FxIndexSet<NodeId>>>>,
|
||||
pub node_owners: RefCell<SecondaryMap<NodeId, NodeId>>,
|
||||
pub node_properties:
|
||||
RefCell<SparseSecondaryMap<NodeId, Vec<ScopeProperty>>>,
|
||||
RefCell<FxHashMap<NodeId, RefCell<FxIndexSet<NodeId>>>>,
|
||||
pub node_sources: RefCell<FxHashMap<NodeId, RefCell<FxIndexSet<NodeId>>>>,
|
||||
pub node_owners: RefCell<FxHashMap<NodeId, NodeId>>,
|
||||
pub node_properties: RefCell<FxHashMap<NodeId, Vec<ScopeProperty>>>,
|
||||
#[allow(clippy::type_complexity)]
|
||||
pub contexts:
|
||||
RefCell<SparseSecondaryMap<NodeId, FxHashMap<TypeId, Box<dyn Any>>>>,
|
||||
pub contexts: RefCell<FxHashMap<NodeId, FxHashMap<TypeId, Box<dyn Any>>>>,
|
||||
pub pending_effects: RefCell<Vec<NodeId>>,
|
||||
pub resources: RefCell<SlotMap<ResourceId, AnyResource>>,
|
||||
pub batching: Cell<bool>,
|
||||
}
|
||||
|
||||
impl Default for ArenaOwner {
|
||||
fn default() -> ArenaOwner {
|
||||
ArenaOwner {
|
||||
arena: ARENA.with(|a| *a),
|
||||
owned: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct ThreadArena;
|
||||
|
||||
impl ThreadArena {
|
||||
fn this() -> &'static Arena {
|
||||
ARENA.with(|a| *a)
|
||||
}
|
||||
|
||||
pub fn get<T: 'static>(id: &NodeId) -> Option<Ref<'_, T>> {
|
||||
Self::this().get::<T>(id)
|
||||
}
|
||||
|
||||
pub fn get_mut<T: 'static>(id: &NodeId) -> Option<RefMut<'_, T>> {
|
||||
Self::this().get_mut::<T>(id)
|
||||
}
|
||||
|
||||
pub fn remove(id: &NodeId) -> Option<Box<dyn Any>> {
|
||||
Self::this().remove(id)
|
||||
}
|
||||
}
|
||||
|
||||
/// The current reactive runtime.
|
||||
pub fn current_runtime() -> RuntimeId {
|
||||
Runtime::current()
|
||||
@@ -142,7 +167,7 @@ impl Runtime {
|
||||
let sources = self.node_sources.borrow();
|
||||
|
||||
// rather than cloning the entire FxIndexSet, only allocate a `Vec` for the node ids
|
||||
sources.get(node_id).map(|n| {
|
||||
sources.get(&node_id).map(|n| {
|
||||
let sources = n.borrow();
|
||||
// in case Vec::from_iterator specialization doesn't work, do it manually
|
||||
let mut sources_vec = Vec::with_capacity(sources.len());
|
||||
@@ -175,7 +200,7 @@ impl Runtime {
|
||||
|
||||
pub(crate) fn cleanup_node(&self, node_id: NodeId) {
|
||||
// first, run our cleanups, if any
|
||||
let c = { self.on_cleanups.borrow_mut().remove(node_id) };
|
||||
let c = { self.on_cleanups.borrow_mut().remove(&node_id) };
|
||||
if let Some(cleanups) = c {
|
||||
for cleanup in cleanups {
|
||||
cleanup();
|
||||
@@ -183,7 +208,7 @@ impl Runtime {
|
||||
}
|
||||
|
||||
// dispose of any of our properties
|
||||
let properties = { self.node_properties.borrow_mut().remove(node_id) };
|
||||
let properties = { self.node_properties.borrow_mut().remove(&node_id) };
|
||||
if let Some(properties) = properties {
|
||||
for property in properties {
|
||||
self.cleanup_property(property);
|
||||
@@ -194,9 +219,8 @@ impl Runtime {
|
||||
pub(crate) fn update(&self, node_id: NodeId) {
|
||||
let node = {
|
||||
let nodes = self.nodes.borrow();
|
||||
nodes.get(node_id).cloned()
|
||||
nodes.get(&node_id).cloned()
|
||||
};
|
||||
|
||||
if let Some(node) = node {
|
||||
// memos and effects rerun
|
||||
// signals simply have their value
|
||||
@@ -204,13 +228,12 @@ impl Runtime {
|
||||
ReactiveNodeType::Signal | ReactiveNodeType::Trigger => true,
|
||||
ReactiveNodeType::Memo { ref f }
|
||||
| ReactiveNodeType::Effect { ref f } => {
|
||||
let value = node.value();
|
||||
// set this node as the observer
|
||||
self.with_observer(node_id, move || {
|
||||
// clean up sources of this memo/effect
|
||||
self.cleanup_sources(node_id);
|
||||
|
||||
f.run(value)
|
||||
f.run(node_id)
|
||||
})
|
||||
}
|
||||
};
|
||||
@@ -219,10 +242,10 @@ impl Runtime {
|
||||
if changed {
|
||||
let subs = self.node_subscribers.borrow();
|
||||
|
||||
if let Some(subs) = subs.get(node_id) {
|
||||
if let Some(subs) = subs.get(&node_id) {
|
||||
let mut nodes = self.nodes.borrow_mut();
|
||||
for sub_id in subs.borrow().iter() {
|
||||
if let Some(sub) = nodes.get_mut(*sub_id) {
|
||||
if let Some(sub) = nodes.get_mut(sub_id) {
|
||||
sub.state = ReactiveNodeState::Dirty;
|
||||
}
|
||||
}
|
||||
@@ -241,53 +264,53 @@ impl Runtime {
|
||||
| ScopeProperty::Trigger(node)
|
||||
| ScopeProperty::Effect(node) => {
|
||||
// run all cleanups for this node
|
||||
let cleanups = { self.on_cleanups.borrow_mut().remove(node) };
|
||||
let cleanups = { self.on_cleanups.borrow_mut().remove(&node) };
|
||||
for cleanup in cleanups.into_iter().flatten() {
|
||||
cleanup();
|
||||
}
|
||||
|
||||
// clean up all children
|
||||
let properties =
|
||||
{ self.node_properties.borrow_mut().remove(node) };
|
||||
{ self.node_properties.borrow_mut().remove(&node) };
|
||||
for property in properties.into_iter().flatten() {
|
||||
self.cleanup_property(property);
|
||||
}
|
||||
|
||||
// each of the subs needs to remove the node from its dependencies
|
||||
// so that it doesn't try to read the (now disposed) signal
|
||||
let subs = self.node_subscribers.borrow_mut().remove(node);
|
||||
let subs = self.node_subscribers.borrow_mut().remove(&node);
|
||||
|
||||
if let Some(subs) = subs {
|
||||
let source_map = self.node_sources.borrow();
|
||||
for effect in subs.borrow().iter() {
|
||||
if let Some(effect_sources) = source_map.get(*effect) {
|
||||
if let Some(effect_sources) = source_map.get(effect) {
|
||||
effect_sources.borrow_mut().remove(&node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// no longer needs to track its sources
|
||||
self.node_sources.borrow_mut().remove(node);
|
||||
self.node_sources.borrow_mut().remove(&node);
|
||||
|
||||
// remove the node from the graph
|
||||
let node = { self.nodes.borrow_mut().remove(node) };
|
||||
let node = ThreadArena::remove(&node);
|
||||
drop(node);
|
||||
}
|
||||
ScopeProperty::Resource(id) => {
|
||||
self.resources.borrow_mut().remove(id);
|
||||
}
|
||||
ScopeProperty::StoredValue(id) => {
|
||||
self.stored_values.borrow_mut().remove(id);
|
||||
ThreadArena::remove(&id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn cleanup_sources(&self, node_id: NodeId) {
|
||||
let sources = self.node_sources.borrow();
|
||||
if let Some(sources) = sources.get(node_id) {
|
||||
if let Some(sources) = sources.get(&node_id) {
|
||||
let subs = self.node_subscribers.borrow();
|
||||
for source in sources.borrow().iter() {
|
||||
if let Some(source) = subs.get(*source) {
|
||||
if let Some(source) = subs.get(source) {
|
||||
source.borrow_mut().remove(&node_id);
|
||||
}
|
||||
}
|
||||
@@ -295,7 +318,7 @@ impl Runtime {
|
||||
}
|
||||
|
||||
fn current_state(&self, node: NodeId) -> ReactiveNodeState {
|
||||
match self.nodes.borrow().get(node) {
|
||||
match self.nodes.borrow().get(&node) {
|
||||
None => ReactiveNodeState::Clean,
|
||||
Some(node) => node.state,
|
||||
}
|
||||
@@ -316,7 +339,7 @@ impl Runtime {
|
||||
|
||||
fn mark_clean(&self, node: NodeId) {
|
||||
let mut nodes = self.nodes.borrow_mut();
|
||||
if let Some(node) = nodes.get_mut(node) {
|
||||
if let Some(node) = nodes.get_mut(&node) {
|
||||
node.state = ReactiveNodeState::Clean;
|
||||
}
|
||||
}
|
||||
@@ -324,7 +347,7 @@ impl Runtime {
|
||||
pub(crate) fn mark_dirty(&self, node: NodeId) {
|
||||
let mut nodes = self.nodes.borrow_mut();
|
||||
|
||||
if let Some(current_node) = nodes.get_mut(node) {
|
||||
if let Some(current_node) = nodes.get_mut(&node) {
|
||||
if current_node.state == ReactiveNodeState::DirtyMarked {
|
||||
return;
|
||||
}
|
||||
@@ -376,7 +399,7 @@ impl Runtime {
|
||||
|
||||
let mut stack = Vec::new();
|
||||
|
||||
if let Some(children) = subscribers.get(node) {
|
||||
if let Some(children) = subscribers.get(&node) {
|
||||
stack.push(RefIter::new(children.borrow(), |children| {
|
||||
children.iter()
|
||||
}));
|
||||
@@ -388,7 +411,7 @@ impl Runtime {
|
||||
return IterResult::Empty;
|
||||
};
|
||||
|
||||
while let Some(node) = nodes.get_mut(child) {
|
||||
while let Some(node) = nodes.get_mut(&child) {
|
||||
if node.state == ReactiveNodeState::Check
|
||||
|| node.state == ReactiveNodeState::DirtyMarked
|
||||
{
|
||||
@@ -403,7 +426,7 @@ impl Runtime {
|
||||
current_observer,
|
||||
);
|
||||
|
||||
if let Some(children) = subscribers.get(child) {
|
||||
if let Some(children) = subscribers.get(&child) {
|
||||
let children = children.borrow();
|
||||
|
||||
if !children.is_empty() {
|
||||
@@ -470,9 +493,9 @@ impl Runtime {
|
||||
}
|
||||
|
||||
pub(crate) fn dispose_node(&self, node: NodeId) {
|
||||
self.node_sources.borrow_mut().remove(node);
|
||||
self.node_subscribers.borrow_mut().remove(node);
|
||||
self.nodes.borrow_mut().remove(node);
|
||||
self.node_sources.borrow_mut().remove(&node);
|
||||
self.node_subscribers.borrow_mut().remove(&node);
|
||||
ThreadArena::remove(&node);
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
@@ -485,10 +508,8 @@ impl Runtime {
|
||||
) {
|
||||
let mut properties = self.node_properties.borrow_mut();
|
||||
if let Some(owner) = self.owner.get() {
|
||||
if let Some(entry) = properties.entry(owner) {
|
||||
let entry = entry.or_default();
|
||||
entry.push(property);
|
||||
}
|
||||
let mut entry = properties.entry(owner).or_default();
|
||||
entry.push(property);
|
||||
|
||||
if let Some(node) = property.to_node_id() {
|
||||
let mut owners = self.node_owners.borrow_mut();
|
||||
@@ -509,7 +530,7 @@ impl Runtime {
|
||||
) -> Option<T> {
|
||||
let contexts = self.contexts.borrow();
|
||||
|
||||
let context = contexts.get(node);
|
||||
let context = contexts.get(&node);
|
||||
let local_value = context.and_then(|context| {
|
||||
context
|
||||
.get(&ty)
|
||||
@@ -521,7 +542,7 @@ impl Runtime {
|
||||
None => self
|
||||
.node_owners
|
||||
.borrow()
|
||||
.get(node)
|
||||
.get(&node)
|
||||
.and_then(|parent| self.get_context(*parent, ty)),
|
||||
}
|
||||
}
|
||||
@@ -552,7 +573,7 @@ impl Runtime {
|
||||
property: ScopeProperty,
|
||||
) {
|
||||
let mut properties = self.node_properties.borrow_mut();
|
||||
if let Some(properties) = properties.get_mut(owner) {
|
||||
if let Some(properties) = properties.get_mut(&owner) {
|
||||
// remove this property from the list, if found
|
||||
if let Some(index) = properties.iter().position(|p| p == &property)
|
||||
{
|
||||
@@ -564,7 +585,7 @@ impl Runtime {
|
||||
|
||||
if let Some(node) = property.to_node_id() {
|
||||
let mut owners = self.node_owners.borrow_mut();
|
||||
owners.remove(node);
|
||||
owners.remove(&node);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -658,11 +679,14 @@ where
|
||||
runtime.owner.set(owner);
|
||||
runtime.observer.set(owner);
|
||||
|
||||
let id = runtime.nodes.borrow_mut().insert(ReactiveNode {
|
||||
value: None,
|
||||
state: ReactiveNodeState::Clean,
|
||||
node_type: ReactiveNodeType::Trigger,
|
||||
});
|
||||
let id = runtime.arena.insert(None::<()>);
|
||||
runtime.nodes.borrow_mut().insert(
|
||||
id,
|
||||
ReactiveNode {
|
||||
state: ReactiveNodeState::Clean,
|
||||
node_type: ReactiveNodeType::Trigger,
|
||||
},
|
||||
);
|
||||
runtime.push_scope_property(ScopeProperty::Trigger(id));
|
||||
let disposer = Disposer(id);
|
||||
|
||||
@@ -809,11 +833,14 @@ impl RuntimeId {
|
||||
#[inline(always)] // only because it's placed here to fit in with the other create methods
|
||||
pub(crate) fn create_trigger(self) -> Trigger {
|
||||
let id = with_runtime(|runtime| {
|
||||
let id = runtime.nodes.borrow_mut().insert(ReactiveNode {
|
||||
value: None,
|
||||
state: ReactiveNodeState::Clean,
|
||||
node_type: ReactiveNodeType::Trigger,
|
||||
});
|
||||
let id = runtime.arena.insert(None::<()>);
|
||||
runtime.nodes.borrow_mut().insert(
|
||||
id,
|
||||
ReactiveNode {
|
||||
state: ReactiveNodeState::Clean,
|
||||
node_type: ReactiveNodeType::Trigger,
|
||||
},
|
||||
);
|
||||
runtime.push_scope_property(ScopeProperty::Trigger(id));
|
||||
id
|
||||
})
|
||||
@@ -828,16 +855,17 @@ impl RuntimeId {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn create_concrete_signal(
|
||||
self,
|
||||
value: Rc<RefCell<dyn Any>>,
|
||||
) -> NodeId {
|
||||
pub(crate) fn create_concrete_signal(self, value: Box<dyn Any>) -> NodeId {
|
||||
with_runtime(|runtime| {
|
||||
let id = runtime.nodes.borrow_mut().insert(ReactiveNode {
|
||||
value: Some(value),
|
||||
state: ReactiveNodeState::Clean,
|
||||
node_type: ReactiveNodeType::Signal,
|
||||
});
|
||||
let id = runtime.arena.insert_boxed(value);
|
||||
runtime.nodes.borrow_mut().insert(
|
||||
id,
|
||||
ReactiveNode {
|
||||
state: ReactiveNodeState::Clean,
|
||||
node_type: ReactiveNodeType::Signal,
|
||||
},
|
||||
);
|
||||
|
||||
runtime.push_scope_property(ScopeProperty::Signal(id));
|
||||
id
|
||||
})
|
||||
@@ -853,9 +881,7 @@ impl RuntimeId {
|
||||
where
|
||||
T: Any + 'static,
|
||||
{
|
||||
let id = self.create_concrete_signal(
|
||||
Rc::new(RefCell::new(value)) as Rc<RefCell<dyn Any>>
|
||||
);
|
||||
let id = self.create_concrete_signal(Box::new(value));
|
||||
|
||||
(
|
||||
ReadSignal {
|
||||
@@ -879,9 +905,7 @@ impl RuntimeId {
|
||||
where
|
||||
T: Any + 'static,
|
||||
{
|
||||
let id = self.create_concrete_signal(
|
||||
Rc::new(RefCell::new(value)) as Rc<RefCell<dyn Any>>
|
||||
);
|
||||
let id = self.create_concrete_signal(Box::new(value));
|
||||
RwSignal {
|
||||
id,
|
||||
ty: PhantomData,
|
||||
@@ -892,17 +916,20 @@ impl RuntimeId {
|
||||
|
||||
pub(crate) fn create_concrete_effect(
|
||||
self,
|
||||
value: Rc<RefCell<dyn Any>>,
|
||||
value: Box<dyn Any>,
|
||||
effect: Rc<dyn AnyComputation>,
|
||||
) -> NodeId {
|
||||
with_runtime(|runtime| {
|
||||
let id = runtime.nodes.borrow_mut().insert(ReactiveNode {
|
||||
value: Some(Rc::clone(&value)),
|
||||
state: ReactiveNodeState::Dirty,
|
||||
node_type: ReactiveNodeType::Effect {
|
||||
f: Rc::clone(&effect),
|
||||
let id = runtime.arena.insert_boxed(value);
|
||||
runtime.nodes.borrow_mut().insert(
|
||||
id,
|
||||
ReactiveNode {
|
||||
state: ReactiveNodeState::Dirty,
|
||||
node_type: ReactiveNodeType::Effect {
|
||||
f: Rc::clone(&effect),
|
||||
},
|
||||
},
|
||||
});
|
||||
);
|
||||
runtime.push_scope_property(ScopeProperty::Effect(id));
|
||||
id
|
||||
})
|
||||
@@ -911,17 +938,20 @@ impl RuntimeId {
|
||||
|
||||
pub(crate) fn create_concrete_memo(
|
||||
self,
|
||||
value: Rc<RefCell<dyn Any>>,
|
||||
value: Box<dyn Any>,
|
||||
computation: Rc<dyn AnyComputation>,
|
||||
) -> NodeId {
|
||||
with_runtime(|runtime| {
|
||||
let id = runtime.nodes.borrow_mut().insert(ReactiveNode {
|
||||
value: Some(value),
|
||||
// memos are lazy, so are dirty when created
|
||||
// will be run the first time we ask for it
|
||||
state: ReactiveNodeState::Dirty,
|
||||
node_type: ReactiveNodeType::Memo { f: computation },
|
||||
});
|
||||
let id = runtime.arena.insert_boxed(value);
|
||||
runtime.nodes.borrow_mut().insert(
|
||||
id,
|
||||
ReactiveNode {
|
||||
// memos are lazy, so are dirty when created
|
||||
// will be run the first time we ask for it
|
||||
state: ReactiveNodeState::Dirty,
|
||||
node_type: ReactiveNodeType::Memo { f: computation },
|
||||
},
|
||||
);
|
||||
runtime.push_scope_property(ScopeProperty::Effect(id));
|
||||
id
|
||||
})
|
||||
@@ -938,7 +968,7 @@ impl RuntimeId {
|
||||
T: Any + 'static,
|
||||
{
|
||||
self.create_concrete_effect(
|
||||
Rc::new(RefCell::new(None::<T>)),
|
||||
Box::new(None::<T>),
|
||||
Rc::new(EffectState {
|
||||
f,
|
||||
ty: PhantomData,
|
||||
@@ -1002,7 +1032,7 @@ impl RuntimeId {
|
||||
};
|
||||
|
||||
let id = self.create_concrete_effect(
|
||||
Rc::new(RefCell::new(None::<()>)),
|
||||
Box::new(None::<()>),
|
||||
Rc::new(EffectState {
|
||||
f: effect_fn,
|
||||
ty: PhantomData,
|
||||
@@ -1013,8 +1043,9 @@ impl RuntimeId {
|
||||
|
||||
(id, move || {
|
||||
with_runtime(|runtime| {
|
||||
runtime.nodes.borrow_mut().remove(id);
|
||||
runtime.node_sources.borrow_mut().remove(id);
|
||||
ThreadArena::remove(&id);
|
||||
runtime.nodes.borrow_mut().remove(&id);
|
||||
runtime.node_sources.borrow_mut().remove(&id);
|
||||
})
|
||||
.expect(
|
||||
"tried to stop a watch in a runtime that has been disposed",
|
||||
@@ -1033,7 +1064,7 @@ impl RuntimeId {
|
||||
{
|
||||
Memo {
|
||||
id: self.create_concrete_memo(
|
||||
Rc::new(RefCell::new(None::<T>)),
|
||||
Box::new(None::<T>),
|
||||
Rc::new(MemoState {
|
||||
f,
|
||||
t: PhantomData,
|
||||
@@ -1050,16 +1081,20 @@ impl RuntimeId {
|
||||
|
||||
impl Runtime {
|
||||
pub fn new() -> Self {
|
||||
let root = ReactiveNode {
|
||||
value: None,
|
||||
state: ReactiveNodeState::Clean,
|
||||
node_type: ReactiveNodeType::Trigger,
|
||||
};
|
||||
let mut nodes: SlotMap<NodeId, ReactiveNode> = SlotMap::default();
|
||||
let root_id = nodes.insert(root);
|
||||
let mut arena = ArenaOwner::default();
|
||||
let mut nodes: FxHashMap<NodeId, ReactiveNode> = Default::default();
|
||||
let root_id = arena.insert(None::<()>);
|
||||
nodes.insert(
|
||||
root_id,
|
||||
ReactiveNode {
|
||||
state: ReactiveNodeState::Clean,
|
||||
node_type: ReactiveNodeType::Trigger,
|
||||
},
|
||||
);
|
||||
|
||||
Self {
|
||||
owner: Cell::new(Some(root_id)),
|
||||
arena,
|
||||
nodes: RefCell::new(nodes),
|
||||
..Self::default()
|
||||
}
|
||||
@@ -1159,15 +1194,6 @@ impl Runtime {
|
||||
}
|
||||
f
|
||||
}
|
||||
|
||||
/// Do not call on triggers
|
||||
pub(crate) fn get_value(
|
||||
&self,
|
||||
node_id: NodeId,
|
||||
) -> Option<Rc<RefCell<dyn Any>>> {
|
||||
let signals = self.nodes.borrow();
|
||||
signals.get(node_id).map(|node| node.value())
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for Runtime {
|
||||
@@ -1259,7 +1285,7 @@ fn push_cleanup(cleanup_fn: Box<dyn FnOnce()>) {
|
||||
_ = with_runtime(|runtime| {
|
||||
if let Some(owner) = runtime.owner.get() {
|
||||
let mut cleanups = runtime.on_cleanups.borrow_mut();
|
||||
if let Some(entries) = cleanups.get_mut(owner) {
|
||||
if let Some(entries) = cleanups.get_mut(&owner) {
|
||||
entries.push(cleanup_fn);
|
||||
} else {
|
||||
cleanups.insert(owner, vec![cleanup_fn]);
|
||||
@@ -1274,7 +1300,7 @@ pub(crate) enum ScopeProperty {
|
||||
Signal(NodeId),
|
||||
Effect(NodeId),
|
||||
Resource(ResourceId),
|
||||
StoredValue(StoredValueId),
|
||||
StoredValue(NodeId),
|
||||
}
|
||||
|
||||
impl ScopeProperty {
|
||||
|
||||
@@ -1,19 +1,20 @@
|
||||
use crate::{
|
||||
console_warn, create_effect, diagnostics, diagnostics::*,
|
||||
macros::debug_warn, node::NodeId, on_cleanup, runtime::with_runtime,
|
||||
arena::NodeId,
|
||||
console_warn, create_effect, diagnostics,
|
||||
diagnostics::*,
|
||||
macros::debug_warn,
|
||||
on_cleanup, queue_microtask,
|
||||
runtime::{with_runtime, ThreadArena},
|
||||
Runtime,
|
||||
};
|
||||
use futures::Stream;
|
||||
use std::{
|
||||
any::Any,
|
||||
cell::RefCell,
|
||||
cell::{Ref, RefMut},
|
||||
fmt,
|
||||
hash::{Hash, Hasher},
|
||||
marker::PhantomData,
|
||||
pin::Pin,
|
||||
rc::Rc,
|
||||
};
|
||||
use thiserror::Error;
|
||||
|
||||
macro_rules! impl_get_fn_traits {
|
||||
($($ty:ident $(($method_name:ident))?),*) => {
|
||||
@@ -103,6 +104,43 @@ pub mod prelude {
|
||||
};
|
||||
}
|
||||
|
||||
/// This trait allows getting a reference to a signal's inner type.
|
||||
pub trait SignalRead {
|
||||
/// The value held by the signal.
|
||||
type Value;
|
||||
|
||||
/// Returns a [Ref] to the current value held by the signal, and subscribes
|
||||
/// the running effect to this signal.
|
||||
///
|
||||
/// # Panics
|
||||
/// Panics if you try to access a signal that is owned by a reactive node that has been disposed.
|
||||
#[track_caller]
|
||||
fn read(&self) -> Ref<'_, Self::Value>;
|
||||
|
||||
/// Returns a [Ref] to the current value held by the signal, and subscribes
|
||||
/// the running effect to this signal, returning [`Some`] if the signal
|
||||
/// is still alive, and [`None`] otherwise.
|
||||
fn try_read(&self) -> Option<Ref<'_, Self::Value>>;
|
||||
}
|
||||
|
||||
/// This trait allows getting a mutable reference to a signal's inner type.
|
||||
pub trait SignalWrite {
|
||||
/// The value held by the signal.
|
||||
type Value;
|
||||
|
||||
/// Returns a [RefMut] to the current value held by the signal,
|
||||
/// and notifies subscribers that the signal has changed.
|
||||
///
|
||||
/// # Panics
|
||||
/// Panics if you try to access a signal that is owned by a reactive node that has been disposed.
|
||||
#[track_caller]
|
||||
fn write(&self) -> RefMut<'_, Self::Value>;
|
||||
|
||||
/// Returns a [Ref] to the current value held by the signal, and notifies subscribers that the
|
||||
/// signal has changed, returning [`Some`] if the signal is still alive, and [`None`] otherwise.
|
||||
fn try_write(&self) -> Option<RefMut<'_, Self::Value>>;
|
||||
}
|
||||
|
||||
/// This trait allows getting an owned value of the signals
|
||||
/// inner type.
|
||||
pub trait SignalGet {
|
||||
@@ -441,6 +479,24 @@ where
|
||||
pub(crate) defined_at: &'static std::panic::Location<'static>,
|
||||
}
|
||||
|
||||
impl<T> SignalRead for ReadSignal<T> {
|
||||
type Value = T;
|
||||
|
||||
fn read(&self) -> Ref<'_, Self::Value> {
|
||||
self.id.read(
|
||||
#[cfg(debug_assesrtions)]
|
||||
self.defined_at,
|
||||
)
|
||||
}
|
||||
|
||||
fn try_read(&self) -> Option<Ref<'_, Self::Value>> {
|
||||
self.id.try_read(
|
||||
#[cfg(debug_assertions)]
|
||||
self.defined_at,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Clone> SignalGetUntracked for ReadSignal<T> {
|
||||
type Value = T;
|
||||
|
||||
@@ -463,8 +519,8 @@ impl<T: Clone> SignalGetUntracked for ReadSignal<T> {
|
||||
})
|
||||
.expect("runtime to be alive")
|
||||
{
|
||||
Ok(t) => t,
|
||||
Err(_) => panic_getting_dead_signal(
|
||||
Some(t) => t,
|
||||
None => panic_getting_dead_signal(
|
||||
#[cfg(any(debug_assertions, feature = "ssr"))]
|
||||
self.defined_at,
|
||||
),
|
||||
@@ -487,7 +543,7 @@ impl<T: Clone> SignalGetUntracked for ReadSignal<T> {
|
||||
#[track_caller]
|
||||
fn try_get_untracked(&self) -> Option<T> {
|
||||
with_runtime(|runtime| {
|
||||
self.id.try_with_no_subscription(runtime, Clone::clone).ok()
|
||||
self.id.try_with_no_subscription(runtime, Clone::clone)
|
||||
})
|
||||
.ok()
|
||||
.flatten()
|
||||
@@ -535,7 +591,7 @@ impl<T> SignalWithUntracked for ReadSignal<T> {
|
||||
|
||||
match with_runtime(|runtime| self.id.try_with(runtime, f, diagnostics))
|
||||
{
|
||||
Ok(Ok(o)) => Some(o),
|
||||
Ok(Some(o)) => Some(o),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@@ -583,8 +639,8 @@ impl<T> SignalWith for ReadSignal<T> {
|
||||
match with_runtime(|runtime| self.id.try_with(runtime, f, diagnostics))
|
||||
.expect("runtime to be alive")
|
||||
{
|
||||
Ok(o) => o,
|
||||
Err(_) => panic_getting_dead_signal(
|
||||
Some(o) => o,
|
||||
None => panic_getting_dead_signal(
|
||||
#[cfg(any(debug_assertions, feature = "ssr"))]
|
||||
self.defined_at,
|
||||
),
|
||||
@@ -609,7 +665,7 @@ impl<T> SignalWith for ReadSignal<T> {
|
||||
fn try_with<O>(&self, f: impl FnOnce(&T) -> O) -> Option<O> {
|
||||
let diagnostics = diagnostics!(self);
|
||||
|
||||
with_runtime(|runtime| self.id.try_with(runtime, f, diagnostics).ok())
|
||||
with_runtime(|runtime| self.id.try_with(runtime, f, diagnostics))
|
||||
.ok()
|
||||
.flatten()
|
||||
}
|
||||
@@ -653,8 +709,8 @@ impl<T: Clone> SignalGet for ReadSignal<T> {
|
||||
})
|
||||
.expect("runtime to be alive")
|
||||
{
|
||||
Ok(t) => t,
|
||||
Err(_) => panic_getting_dead_signal(
|
||||
Some(t) => t,
|
||||
None => panic_getting_dead_signal(
|
||||
#[cfg(any(debug_assertions, feature = "ssr"))]
|
||||
self.defined_at,
|
||||
),
|
||||
@@ -675,7 +731,7 @@ impl<T: Clone> SignalGet for ReadSignal<T> {
|
||||
)
|
||||
)]
|
||||
fn try_get(&self) -> Option<T> {
|
||||
self.try_with(Clone::clone).ok()
|
||||
self.try_with(Clone::clone)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -728,21 +784,11 @@ where
|
||||
|
||||
self.id
|
||||
.try_with_no_subscription_by_id(f)
|
||||
.unwrap_or_else(|_| {
|
||||
#[cfg(not(debug_assertions))]
|
||||
{
|
||||
panic!("tried to access ReadSignal that has been disposed")
|
||||
}
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
panic!(
|
||||
"at {}, tried to access ReadSignal<{}> defined at {}, \
|
||||
but it has already been disposed",
|
||||
caller,
|
||||
std::any::type_name::<T>(),
|
||||
self.defined_at
|
||||
)
|
||||
}
|
||||
.unwrap_or_else(|| {
|
||||
panic_getting_dead_signal(
|
||||
#[cfg(debug_assertions)]
|
||||
self.defined_at,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -750,17 +796,13 @@ where
|
||||
/// the running effect.
|
||||
#[track_caller]
|
||||
#[inline(always)]
|
||||
pub(crate) fn try_with<U>(
|
||||
&self,
|
||||
f: impl FnOnce(&T) -> U,
|
||||
) -> Result<U, SignalError> {
|
||||
pub(crate) fn try_with<U>(&self, f: impl FnOnce(&T) -> U) -> Option<U> {
|
||||
let diagnostics = diagnostics!(self);
|
||||
|
||||
match with_runtime(|runtime| self.id.try_with(runtime, f, diagnostics))
|
||||
{
|
||||
Ok(Ok(v)) => Ok(v),
|
||||
Ok(Err(e)) => Err(e),
|
||||
Err(_) => Err(SignalError::RuntimeDisposed),
|
||||
Ok(Some(v)) => Some(v),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -855,6 +897,24 @@ where
|
||||
pub(crate) defined_at: &'static std::panic::Location<'static>,
|
||||
}
|
||||
|
||||
impl<T> SignalWrite for WriteSignal<T> {
|
||||
type Value = T;
|
||||
|
||||
fn write(&self) -> RefMut<'_, Self::Value> {
|
||||
self.id.write(
|
||||
#[cfg(debug_assertions)]
|
||||
self.defined_at,
|
||||
)
|
||||
}
|
||||
|
||||
fn try_write(&self) -> Option<RefMut<'_, Self::Value>> {
|
||||
self.id.try_write(
|
||||
#[cfg(debug_assertions)]
|
||||
self.defined_at,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> SignalSetUntracked<T> for WriteSignal<T>
|
||||
where
|
||||
T: 'static,
|
||||
@@ -1274,21 +1334,11 @@ impl<T: Clone> SignalGetUntracked for RwSignal<T> {
|
||||
|
||||
self.id
|
||||
.try_with_no_subscription_by_id(Clone::clone)
|
||||
.unwrap_or_else(|_| {
|
||||
#[cfg(not(debug_assertions))]
|
||||
{
|
||||
panic!("tried to access RwSignal that has been disposed")
|
||||
}
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
panic!(
|
||||
"at {}, tried to access RwSignal<{}> defined at {}, \
|
||||
but it has already been disposed",
|
||||
caller,
|
||||
std::any::type_name::<T>(),
|
||||
self.defined_at
|
||||
)
|
||||
}
|
||||
.unwrap_or_else(|| {
|
||||
panic_getting_dead_signal(
|
||||
#[cfg(debug_assertions)]
|
||||
self.defined_at,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1308,7 +1358,7 @@ impl<T: Clone> SignalGetUntracked for RwSignal<T> {
|
||||
#[track_caller]
|
||||
fn try_get_untracked(&self) -> Option<T> {
|
||||
with_runtime(|runtime| {
|
||||
self.id.try_with_no_subscription(runtime, Clone::clone).ok()
|
||||
self.id.try_with_no_subscription(runtime, Clone::clone)
|
||||
})
|
||||
.ok()
|
||||
.flatten()
|
||||
@@ -1335,20 +1385,11 @@ impl<T> SignalWithUntracked for RwSignal<T> {
|
||||
fn with_untracked<O>(&self, f: impl FnOnce(&T) -> O) -> O {
|
||||
self.id
|
||||
.try_with_no_subscription_by_id(f)
|
||||
.unwrap_or_else(|_| {
|
||||
#[cfg(not(debug_assertions))]
|
||||
{
|
||||
panic!("tried to access RwSignal that has been disposed")
|
||||
}
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
panic!(
|
||||
"tried to access RwSignal<{}> defined at {}, but it \
|
||||
has already been disposed",
|
||||
std::any::type_name::<T>(),
|
||||
self.defined_at
|
||||
)
|
||||
}
|
||||
.unwrap_or_else(|| {
|
||||
panic_getting_dead_signal(
|
||||
#[cfg(debug_assertions)]
|
||||
self.defined_at,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1372,7 +1413,7 @@ impl<T> SignalWithUntracked for RwSignal<T> {
|
||||
|
||||
match with_runtime(|runtime| self.id.try_with(runtime, f, diagnostics))
|
||||
{
|
||||
Ok(Ok(o)) => Some(o),
|
||||
Ok(Some(o)) => Some(o),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@@ -1518,8 +1559,8 @@ impl<T> SignalWith for RwSignal<T> {
|
||||
match with_runtime(|runtime| self.id.try_with(runtime, f, diagnostics))
|
||||
.expect("runtime to be alive")
|
||||
{
|
||||
Ok(o) => o,
|
||||
Err(_) => panic_getting_dead_signal(
|
||||
Some(o) => o,
|
||||
None => panic_getting_dead_signal(
|
||||
#[cfg(any(debug_assertions, feature = "ssr"))]
|
||||
self.defined_at,
|
||||
),
|
||||
@@ -1544,7 +1585,7 @@ impl<T> SignalWith for RwSignal<T> {
|
||||
fn try_with<O>(&self, f: impl FnOnce(&T) -> O) -> Option<O> {
|
||||
let diagnostics = diagnostics!(self);
|
||||
|
||||
with_runtime(|runtime| self.id.try_with(runtime, f, diagnostics).ok())
|
||||
with_runtime(|runtime| self.id.try_with(runtime, f, diagnostics))
|
||||
.ok()
|
||||
.flatten()
|
||||
}
|
||||
@@ -1592,8 +1633,8 @@ impl<T: Clone> SignalGet for RwSignal<T> {
|
||||
})
|
||||
.expect("runtime to be alive")
|
||||
{
|
||||
Ok(t) => t,
|
||||
Err(_) => panic_getting_dead_signal(
|
||||
Some(t) => t,
|
||||
None => panic_getting_dead_signal(
|
||||
#[cfg(any(debug_assertions, feature = "ssr"))]
|
||||
self.defined_at,
|
||||
),
|
||||
@@ -1618,7 +1659,7 @@ impl<T: Clone> SignalGet for RwSignal<T> {
|
||||
let diagnostics = diagnostics!(self);
|
||||
|
||||
with_runtime(|runtime| {
|
||||
self.id.try_with(runtime, Clone::clone, diagnostics).ok()
|
||||
self.id.try_with(runtime, Clone::clone, diagnostics)
|
||||
})
|
||||
.ok()
|
||||
.flatten()
|
||||
@@ -1940,16 +1981,6 @@ impl<T> RwSignal<T> {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub(crate) enum SignalError {
|
||||
#[error("tried to access a signal in a runtime that had been disposed")]
|
||||
RuntimeDisposed,
|
||||
#[error("tried to access a signal that had been disposed")]
|
||||
Disposed,
|
||||
#[error("error casting signal to type {0}")]
|
||||
Type(&'static str),
|
||||
}
|
||||
|
||||
impl NodeId {
|
||||
#[track_caller]
|
||||
pub(crate) fn subscribe(
|
||||
@@ -1961,16 +1992,15 @@ impl NodeId {
|
||||
if let Some(observer) = runtime.observer.get() {
|
||||
// add this observer to this node's dependencies (to allow notification)
|
||||
let mut subs = runtime.node_subscribers.borrow_mut();
|
||||
if let Some(subs) = subs.entry(*self) {
|
||||
subs.or_default().borrow_mut().insert(observer);
|
||||
}
|
||||
subs.entry(*self).or_default().borrow_mut().insert(observer);
|
||||
|
||||
// add this node to the observer's sources (to allow cleanup)
|
||||
let mut sources = runtime.node_sources.borrow_mut();
|
||||
if let Some(sources) = sources.entry(observer) {
|
||||
let sources = sources.or_default();
|
||||
sources.borrow_mut().insert(*self);
|
||||
}
|
||||
sources
|
||||
.entry(observer)
|
||||
.or_default()
|
||||
.borrow_mut()
|
||||
.insert(*self);
|
||||
} else {
|
||||
#[cfg(all(debug_assertions, not(feature = "ssr")))]
|
||||
{
|
||||
@@ -2000,21 +2030,11 @@ impl NodeId {
|
||||
}
|
||||
}
|
||||
|
||||
fn try_with_no_subscription_inner(
|
||||
&self,
|
||||
runtime: &Runtime,
|
||||
) -> Result<Rc<RefCell<dyn Any>>, SignalError> {
|
||||
runtime.update_if_necessary(*self);
|
||||
let nodes = runtime.nodes.borrow();
|
||||
let node = nodes.get(*self).ok_or(SignalError::Disposed)?;
|
||||
Ok(node.value())
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub(crate) fn try_with_no_subscription_by_id<T, U>(
|
||||
&self,
|
||||
f: impl FnOnce(&T) -> U,
|
||||
) -> Result<U, SignalError>
|
||||
) -> Option<U>
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
@@ -2028,17 +2048,13 @@ impl NodeId {
|
||||
&self,
|
||||
runtime: &Runtime,
|
||||
f: impl FnOnce(&T) -> U,
|
||||
) -> Result<U, SignalError>
|
||||
) -> Option<U>
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
let value = self.try_with_no_subscription_inner(runtime)?;
|
||||
let value = value.borrow();
|
||||
let value = value
|
||||
.downcast_ref::<T>()
|
||||
.ok_or_else(|| SignalError::Type(std::any::type_name::<T>()))
|
||||
.expect("to downcast signal type");
|
||||
Ok(f(value))
|
||||
runtime.update_if_necessary(*self);
|
||||
let value = ThreadArena::get::<T>(self)?;
|
||||
Some(f(&value))
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
@@ -2048,12 +2064,11 @@ impl NodeId {
|
||||
runtime: &Runtime,
|
||||
f: impl FnOnce(&T) -> U,
|
||||
diagnostics: AccessDiagnostics,
|
||||
) -> Result<U, SignalError>
|
||||
) -> Option<U>
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
self.subscribe(runtime, diagnostics);
|
||||
|
||||
self.try_with_no_subscription(runtime, f)
|
||||
}
|
||||
|
||||
@@ -2061,7 +2076,6 @@ impl NodeId {
|
||||
#[track_caller]
|
||||
fn update_value<T, U>(
|
||||
&self,
|
||||
|
||||
f: impl FnOnce(&mut T) -> U,
|
||||
#[cfg(debug_assertions)] defined_at: Option<
|
||||
&'static std::panic::Location<'static>,
|
||||
@@ -2074,18 +2088,8 @@ impl NodeId {
|
||||
let location = std::panic::Location::caller();
|
||||
|
||||
with_runtime(|runtime| {
|
||||
if let Some(value) = runtime.get_value(*self) {
|
||||
let mut value = value.borrow_mut();
|
||||
if let Some(value) = value.downcast_mut::<T>() {
|
||||
Some(f(value))
|
||||
} else {
|
||||
debug_warn!(
|
||||
"[Signal::update] failed when downcasting to \
|
||||
Signal<{}>",
|
||||
std::any::type_name::<T>()
|
||||
);
|
||||
None
|
||||
}
|
||||
if let Some(ref mut value) = ThreadArena::get_mut::<T>(self) {
|
||||
Some(f(value))
|
||||
} else {
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
@@ -2109,6 +2113,73 @@ impl NodeId {
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
pub(crate) fn try_read<T: 'static>(
|
||||
&self,
|
||||
#[cfg(debug_assertions)] defined_at: &'static std::panic::Location<
|
||||
'static,
|
||||
>,
|
||||
) -> Option<Ref<'_, T>> {
|
||||
with_runtime(|runtime| {
|
||||
let diagnostics = diagnostics!(self);
|
||||
self.subscribe(runtime, diagnostics);
|
||||
});
|
||||
ThreadArena::get::<T>(self)
|
||||
}
|
||||
|
||||
pub(crate) fn read<T: 'static>(
|
||||
&self,
|
||||
#[cfg(debug_assertions)] defined_at: &'static std::panic::Location<
|
||||
'static,
|
||||
>,
|
||||
) -> Ref<'_, T> {
|
||||
self.try_read(
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at,
|
||||
)
|
||||
.unwrap_or_else(|| {
|
||||
panic_getting_dead_signal(
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn try_write<T: 'static>(
|
||||
&self,
|
||||
#[cfg(debug_assertions)] defined_at: &'static std::panic::Location<
|
||||
'static,
|
||||
>,
|
||||
) -> Option<RefMut<'_, T>> {
|
||||
// delay notification a tick, until you've hopefully done whatever work
|
||||
let id = *self;
|
||||
queue_microtask(move || {
|
||||
with_runtime(|runtime| {
|
||||
runtime.mark_dirty(id);
|
||||
runtime.run_effects();
|
||||
});
|
||||
});
|
||||
|
||||
ThreadArena::get_mut::<T>(self)
|
||||
}
|
||||
|
||||
pub(crate) fn write<T: 'static>(
|
||||
&self,
|
||||
#[cfg(debug_assertions)] defined_at: &'static std::panic::Location<
|
||||
'static,
|
||||
>,
|
||||
) -> RefMut<'_, T> {
|
||||
self.try_write(
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at,
|
||||
)
|
||||
.unwrap_or_else(|| {
|
||||
panic_getting_dead_signal(
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
#[track_caller]
|
||||
pub(crate) fn update<T, U>(
|
||||
@@ -2125,18 +2196,10 @@ impl NodeId {
|
||||
let location = std::panic::Location::caller();
|
||||
|
||||
with_runtime(|runtime| {
|
||||
let updated = if let Some(value) = runtime.get_value(*self) {
|
||||
let mut value = value.borrow_mut();
|
||||
if let Some(value) = value.downcast_mut::<T>() {
|
||||
Some(f(value))
|
||||
} else {
|
||||
debug_warn!(
|
||||
"[Signal::update] failed when downcasting to \
|
||||
Signal<{}>",
|
||||
std::any::type_name::<T>()
|
||||
);
|
||||
None
|
||||
}
|
||||
let updated = if let Some(ref mut value) =
|
||||
ThreadArena::get_mut::<T>(self)
|
||||
{
|
||||
Some(f(value))
|
||||
} else {
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
|
||||
@@ -273,7 +273,7 @@ impl<T> SignalWith for Signal<T> {
|
||||
)]
|
||||
fn try_with<O>(&self, f: impl FnOnce(&T) -> O) -> Option<O> {
|
||||
match self.inner {
|
||||
SignalTypes::ReadSignal(r) => r.try_with(f).ok(),
|
||||
SignalTypes::ReadSignal(r) => r.try_with(f),
|
||||
|
||||
SignalTypes::Memo(m) => m.try_with(f),
|
||||
SignalTypes::DerivedSignal(s) => s.try_with_value(|t| f(&t())),
|
||||
|
||||
@@ -1,17 +1,12 @@
|
||||
use crate::{with_runtime, Runtime, ScopeProperty};
|
||||
use crate::{
|
||||
arena::NodeId, runtime::ThreadArena, with_runtime, Runtime, ScopeProperty,
|
||||
};
|
||||
use std::{
|
||||
cell::RefCell,
|
||||
fmt,
|
||||
hash::{Hash, Hasher},
|
||||
marker::PhantomData,
|
||||
rc::Rc,
|
||||
};
|
||||
|
||||
slotmap::new_key_type! {
|
||||
/// Unique ID assigned to a [`StoredValue`].
|
||||
pub(crate) struct StoredValueId;
|
||||
}
|
||||
|
||||
/// A **non-reactive** wrapper for any value, which can be created with [`store_value`].
|
||||
///
|
||||
/// If you want a reactive wrapper, use [`create_signal`](crate::create_signal).
|
||||
@@ -25,7 +20,7 @@ pub struct StoredValue<T>
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
id: StoredValueId,
|
||||
id: NodeId,
|
||||
ty: PhantomData<T>,
|
||||
}
|
||||
|
||||
@@ -89,7 +84,9 @@ impl<T> StoredValue<T> {
|
||||
where
|
||||
T: Clone,
|
||||
{
|
||||
self.try_get_value().expect("could not get stored value")
|
||||
ThreadArena::get::<T>(&self.id)
|
||||
.expect("could not get stored value")
|
||||
.clone()
|
||||
}
|
||||
|
||||
/// Same as [`StoredValue::get_value`] but will not panic by default.
|
||||
@@ -98,7 +95,7 @@ impl<T> StoredValue<T> {
|
||||
where
|
||||
T: Clone,
|
||||
{
|
||||
self.try_with_value(T::clone)
|
||||
ThreadArena::get::<T>(&self.id).map(|n| n.clone())
|
||||
}
|
||||
|
||||
/// Applies a function to the current stored value and returns the result.
|
||||
@@ -130,17 +127,8 @@ impl<T> StoredValue<T> {
|
||||
/// Same as [`StoredValue::with_value`] but returns [`Some(O)]` only if
|
||||
/// the stored value has not yet been disposed. [`None`] otherwise.
|
||||
pub fn try_with_value<O>(&self, f: impl FnOnce(&T) -> O) -> Option<O> {
|
||||
with_runtime(|runtime| {
|
||||
let value = {
|
||||
let values = runtime.stored_values.borrow();
|
||||
values.get(self.id)?.clone()
|
||||
};
|
||||
let value = value.borrow();
|
||||
let value = value.downcast_ref::<T>()?;
|
||||
Some(f(value))
|
||||
})
|
||||
.ok()
|
||||
.flatten()
|
||||
let value = ThreadArena::get(&self.id)?;
|
||||
Some(f(&value))
|
||||
}
|
||||
|
||||
/// Updates the stored value.
|
||||
@@ -190,17 +178,8 @@ impl<T> StoredValue<T> {
|
||||
/// Same as [`Self::update_value`], but returns [`Some(O)`] if the
|
||||
/// stored value has not yet been disposed, [`None`] otherwise.
|
||||
pub fn try_update_value<O>(self, f: impl FnOnce(&mut T) -> O) -> Option<O> {
|
||||
with_runtime(|runtime| {
|
||||
let value = {
|
||||
let values = runtime.stored_values.borrow();
|
||||
values.get(self.id)?.clone()
|
||||
};
|
||||
let mut value = value.borrow_mut();
|
||||
let value = value.downcast_mut::<T>()?;
|
||||
Some(f(value))
|
||||
})
|
||||
.ok()
|
||||
.flatten()
|
||||
let mut value = ThreadArena::get_mut::<T>(&self.id)?;
|
||||
Some(f(&mut *value))
|
||||
}
|
||||
|
||||
/// Sets the stored value.
|
||||
@@ -226,27 +205,13 @@ impl<T> StoredValue<T> {
|
||||
/// Same as [`Self::set_value`], but returns [`None`] if the
|
||||
/// stored value has not yet been disposed, [`Some(T)`] otherwise.
|
||||
pub fn try_set_value(&self, value: T) -> Option<T> {
|
||||
with_runtime(|runtime| {
|
||||
let n = {
|
||||
let values = runtime.stored_values.borrow();
|
||||
values.get(self.id).map(Rc::clone)
|
||||
};
|
||||
|
||||
if let Some(n) = n {
|
||||
let mut n = n.borrow_mut();
|
||||
let n = n.downcast_mut::<T>();
|
||||
if let Some(n) = n {
|
||||
*n = value;
|
||||
None
|
||||
} else {
|
||||
Some(value)
|
||||
}
|
||||
} else {
|
||||
Some(value)
|
||||
match ThreadArena::get_mut::<T>(&self.id) {
|
||||
Some(mut curr) => {
|
||||
*curr = value;
|
||||
None
|
||||
}
|
||||
})
|
||||
.ok()
|
||||
.flatten()
|
||||
None => Some(value),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -294,10 +259,7 @@ where
|
||||
T: 'static,
|
||||
{
|
||||
let id = with_runtime(|runtime| {
|
||||
let id = runtime
|
||||
.stored_values
|
||||
.borrow_mut()
|
||||
.insert(Rc::new(RefCell::new(value)));
|
||||
let id = runtime.arena.insert(value);
|
||||
runtime.push_scope_property(ScopeProperty::StoredValue(id));
|
||||
id
|
||||
})
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::{
|
||||
arena::NodeId,
|
||||
diagnostics,
|
||||
diagnostics::*,
|
||||
node::NodeId,
|
||||
runtime::{with_runtime, Runtime},
|
||||
SignalGet, SignalSet, SignalUpdate,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user