Compare commits

...

1 Commits
2693 ... arena

Author SHA1 Message Date
Greg Johnston
c317bc1e5c feat: add .write() and .read() for signals 2023-09-08 11:30:09 -04:00
14 changed files with 579 additions and 381 deletions

View File

@@ -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>
}
}

View File

@@ -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 }

View 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));
}
}
}

View File

@@ -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!(

View File

@@ -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);

View File

@@ -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;

View File

@@ -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

View File

@@ -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,

View File

@@ -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)
}

View File

@@ -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 {

View File

@@ -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)]
{

View File

@@ -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())),

View File

@@ -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
})

View File

@@ -1,7 +1,7 @@
use crate::{
arena::NodeId,
diagnostics,
diagnostics::*,
node::NodeId,
runtime::{with_runtime, Runtime},
SignalGet, SignalSet, SignalUpdate,
};