diff --git a/packages/core/src/events.rs b/packages/core/src/events.rs index 90511a8e6..b85e0394d 100644 --- a/packages/core/src/events.rs +++ b/packages/core/src/events.rs @@ -135,7 +135,7 @@ impl std::fmt::Debug for Event { /// } /// /// ``` -pub struct EventHandler<'bump, T: ?Sized = ()> { +pub struct EventHandler<'bump, T = ()> { pub(crate) origin: ScopeId, pub(super) callback: RefCell>>, } diff --git a/packages/core/src/runtime.rs b/packages/core/src/runtime.rs index 9f031f287..256b46909 100644 --- a/packages/core/src/runtime.rs +++ b/packages/core/src/runtime.rs @@ -41,7 +41,7 @@ where .flatten() } -pub struct Runtime { +pub(crate) struct Runtime { pub(crate) scope_contexts: RefCell>>, pub(crate) scheduler: Rc, @@ -93,10 +93,10 @@ impl Runtime { } } -pub struct RuntimeGuard(Rc); +pub(crate) struct RuntimeGuard(Rc); impl RuntimeGuard { - pub fn new(runtime: Rc) -> Self { + pub(crate) fn new(runtime: Rc) -> Self { push_runtime(runtime.clone()); Self(runtime) } diff --git a/packages/core/src/scope_context.rs b/packages/core/src/scope_context.rs index d627eea5f..14366eefb 100644 --- a/packages/core/src/scope_context.rs +++ b/packages/core/src/scope_context.rs @@ -16,7 +16,7 @@ use std::{ /// A component's state separate from its props. /// /// This struct exists to provide a common interface for all scopes without relying on generics. -pub struct ScopeContext { +pub(crate) struct ScopeContext { pub(crate) name: &'static str, pub(crate) id: ScopeId, diff --git a/packages/core/src/scopes.rs b/packages/core/src/scopes.rs index f255c4175..8469781c4 100644 --- a/packages/core/src/scopes.rs +++ b/packages/core/src/scopes.rs @@ -195,7 +195,7 @@ impl<'src> ScopeState { /// /// assert_eq!(base.parent(), None); /// ``` - pub fn parent_id(&self) -> Option { + pub fn parent(&self) -> Option { // safety: the pointer to our parent is *always* valid thanks to the bump arena self.context().parent_id() } diff --git a/packages/hooks/src/computed.rs b/packages/hooks/src/computed.rs new file mode 100644 index 000000000..94011d37d --- /dev/null +++ b/packages/hooks/src/computed.rs @@ -0,0 +1,210 @@ +use dioxus_core::{ScopeId, ScopeState}; +use slab::Slab; +use std::{ + cell::{RefCell, RefMut}, + collections::HashSet, + ops::{Deref, DerefMut}, + rc::Rc, +}; + +/// Create a new tracked state. +/// Tracked state is state that can drive Selector state +/// +/// It will efficiently update any Selector state that is reading from it, but it is not readable on its own. +/// +/// ```rust +/// use dioxus::prelude::*; +/// +/// fn Parent(cx: Scope) -> Element { +/// let count = use_tracked_state(cx, || 0); +/// +/// render! { +/// Child { +/// count: count.clone(), +/// } +/// } +/// } +/// +/// #[inline_props] +/// fn Child(cx: Scope, count: Tracked) -> Element { +/// let less_than_five = use_selector(cx, count, |count| *count < 5); +/// +/// render! { +/// "{less_than_five}" +/// } +/// } +/// ``` +pub fn use_tracked_state(cx: &ScopeState, init: impl FnOnce() -> T) -> &Tracked { + cx.use_hook(|| { + let init = init(); + Tracked::new(cx, init) + }) +} + +/// Tracked state is state that can drive Selector state +/// +/// Tracked state will efficiently update any Selector state that is reading from it, but it is not readable on it's own. +#[derive(Clone)] +pub struct Tracked { + state: Rc>, + update_any: std::sync::Arc, + subscribers: SubscribedCallbacks, +} + +impl PartialEq for Tracked { + fn eq(&self, other: &Self) -> bool { + self.state == other.state + } +} + +impl Tracked { + /// Create a new tracked state + pub fn new(cx: &ScopeState, state: I) -> Self { + let subscribers = std::rc::Rc::new(std::cell::RefCell::new(Slab::new())); + Self { + state: Rc::new(RefCell::new(state)), + subscribers, + update_any: cx.schedule_update_any(), + } + } + + /// Create a new Selector state from this tracked state + pub fn compute( + &self, + mut compute: impl FnMut(&I) -> O + 'static, + ) -> Selector { + let subscribers = Rc::new(RefCell::new(HashSet::new())); + let state = Rc::new(RefCell::new(compute(&self.state.borrow()))); + let update_any = self.update_any.clone(); + + Selector { + value: state.clone(), + subscribers: subscribers.clone(), + _tracker: Rc::new(self.track(move |input_state| { + let new = compute(input_state); + let different = { + let state = state.borrow(); + *state != new + }; + if different { + let mut state = state.borrow_mut(); + *state = new; + for id in subscribers.borrow().iter().copied() { + (update_any)(id); + } + } + })), + } + } + + pub(crate) fn track(&self, update: impl FnMut(&I) + 'static) -> Tracker { + let mut subscribers = self.subscribers.borrow_mut(); + let id = subscribers.insert(Box::new(update)); + Tracker { + subscribers: self.subscribers.clone(), + id, + } + } + + /// Write to the tracked state + pub fn write(&self) -> TrackedMut<'_, I> { + TrackedMut { + state: self.state.borrow_mut(), + subscribers: self.subscribers.clone(), + } + } +} + +/// A mutable reference to tracked state +pub struct TrackedMut<'a, I> { + state: RefMut<'a, I>, + subscribers: SubscribedCallbacks, +} + +impl<'a, I> Deref for TrackedMut<'a, I> { + type Target = I; + + fn deref(&self) -> &Self::Target { + &self.state + } +} + +impl<'a, I> DerefMut for TrackedMut<'a, I> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.state + } +} + +impl<'a, I> Drop for TrackedMut<'a, I> { + fn drop(&mut self) { + let state = self.state.deref(); + for (_, sub) in &mut *self.subscribers.borrow_mut() { + sub(state); + } + } +} + +type SubscribedCallbacks = std::rc::Rc>>>; + +pub(crate) struct Tracker { + subscribers: SubscribedCallbacks, + id: usize, +} + +impl Drop for Tracker { + fn drop(&mut self) { + let _ = self.subscribers.borrow_mut().remove(self.id); + } +} + +pub fn use_selector( + cx: &ScopeState, + tracked: &Tracked, + init: impl FnMut(&I) -> O + 'static, +) -> O { + let selector = cx.use_hook(|| tracked.compute(init)); + selector.use_state(cx) +} + +/// Selector state is state that is derived from tracked state +/// +/// Whenever the tracked state changes, the Selector state will be updated and any components reading from it will be rerun +#[derive(Clone)] +pub struct Selector { + _tracker: Rc>, + value: Rc>, + subscribers: Rc>>, +} + +impl PartialEq for Selector { + fn eq(&self, other: &Self) -> bool { + std::rc::Rc::ptr_eq(&self.value, &other.value) + } +} + +impl Selector { + /// Read the Selector state and subscribe to updates + pub fn use_state(&self, cx: &ScopeState) -> T { + cx.use_hook(|| { + let id = cx.scope_id(); + self.subscribers.borrow_mut().insert(id); + + ComputedRead { + scope: cx.scope_id(), + subscribers: self.subscribers.clone(), + } + }); + self.value.borrow().clone() + } +} + +struct ComputedRead { + scope: ScopeId, + subscribers: std::rc::Rc>>, +} + +impl Drop for ComputedRead { + fn drop(&mut self) { + self.subscribers.borrow_mut().remove(&self.scope); + } +} diff --git a/packages/hooks/src/lib.rs b/packages/hooks/src/lib.rs index 65f390eda..7e5798167 100644 --- a/packages/hooks/src/lib.rs +++ b/packages/hooks/src/lib.rs @@ -52,6 +52,9 @@ macro_rules! to_owned { }; } +mod computed; +pub use computed::*; + mod use_on_unmount; pub use use_on_unmount::*;