//! This module contains the `SelectorScope` type with raw access to the underlying store system. Most applications should //! use the [`Store`](dioxus_stores_macro::Store) macro to derive stores for their data structures, which provides a more ergonomic API. use std::{fmt::Debug, hash::Hash}; use crate::subscriptions::{PathKey, StoreSubscriptions, TinyVec}; use dioxus_core::Subscribers; use dioxus_signals::{ BorrowError, BorrowMutError, MappedMutSignal, Readable, ReadableRef, Writable, WritableExt, WritableRef, }; /// SelectorScope is the primitive that backs the store system. /// /// Under the hood stores consist of two different parts: /// - The underlying lock that contains the data in the store. /// - A tree of subscriptions used to make the store reactive. /// /// The `SelectorScope` contains a view into the lock (`Lens`) and a path into the subscription tree. When /// the selector is read to, it will track the current path in the subscription tree. When it is written to /// it marks itself and all its children as dirty. /// /// When you derive the [`Store`](dioxus_stores_macro::Store) macro on your data structure, /// it generates methods that map the lock to a new type and scope the path to a specific part of the subscription structure. /// For example, a `Counter` store might look like this: /// /// ```rust, ignore /// #[derive(Store)] /// struct Counter { /// count: i32, /// } /// /// impl CounterStoreExt for Store { /// fn count( /// self, /// ) -> dioxus_stores::Store< /// i32, /// dioxus_stores::macro_helpers::dioxus_signals::MappedMutSignal, /// > { /// let __map_field: fn(&CounterTree) -> &i32 = |value| &value.count; /// let __map_mut_field: fn(&mut CounterTree) -> &mut i32 = |value| &mut value.count; /// let scope = self.selector().scope(0u32, __map_field, __map_mut_field); /// dioxus_stores::Store::new(scope) /// } /// } /// ``` /// /// The `count` method maps the lock to the `i32` type and creates a child `0` path in the subscription tree. Only writes /// to that `0` path or its parents will trigger a re-render of the components that read the `count` field. #[derive(PartialEq)] pub struct SelectorScope { path: TinyVec, store: StoreSubscriptions, write: Lens, } impl Debug for SelectorScope { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { f.debug_struct("SelectorScope") .field("path", &self.path) .finish() } } impl Clone for SelectorScope where Lens: Clone, { fn clone(&self) -> Self { Self { path: self.path, store: self.store, write: self.write.clone(), } } } impl Copy for SelectorScope where Lens: Copy {} impl SelectorScope { pub(crate) fn new(path: TinyVec, store: StoreSubscriptions, write: Lens) -> Self { Self { path, store, write } } /// Create a child selector scope for a hash key. The scope will only be marked as dirty when a /// write occurs to that key or its parents. /// /// Note the hash is lossy, so there may rarely be collisions. If a collision does occur, it may /// cause reruns in a part of the app that has not changed. As long as derived data is pure, /// this should not cause issues. pub fn hash_child( self, index: &impl Hash, map: F, map_mut: FMut, ) -> SelectorScope> where F: Fn(&T) -> &U, FMut: Fn(&mut T) -> &mut U, { let hash = self.store.hash(index); self.child(hash, map, map_mut) } /// Create a child selector scope for a specific index. The scope will only be marked as dirty when a /// write occurs to that index or its parents. pub fn child( self, index: PathKey, map: F, map_mut: FMut, ) -> SelectorScope> where F: Fn(&T) -> &U, FMut: Fn(&mut T) -> &mut U, { self.child_unmapped(index).map(map, map_mut) } /// Create a hashed child selector scope for a specific index without mapping the writer. The scope will only /// be marked as dirty when a write occurs to that index or its parents. pub fn hash_child_unmapped(self, index: &impl Hash) -> SelectorScope { let hash = self.store.hash(index); self.child_unmapped(hash) } /// Create a child selector scope for a specific index without mapping the writer. The scope will only /// be marked as dirty when a write occurs to that index or its parents. pub fn child_unmapped(mut self, index: PathKey) -> SelectorScope { self.path.push(index); self } /// Map the view into the writable data without creating a child selector scope pub fn map( self, map: F, map_mut: FMut, ) -> SelectorScope> where F: Fn(&T) -> &U, FMut: Fn(&mut T) -> &mut U, { self.map_writer(move |write| MappedMutSignal::new(write, map, map_mut)) } /// Track this scope shallowly. pub fn track_shallow(&self) { self.store.track(&self.path); } /// Track this scope recursively. pub fn track(&self) { self.store.track_recursive(&self.path); } /// Mark this scope as dirty recursively. pub fn mark_dirty(&self) { self.store.mark_dirty(&self.path); } /// Mark this scope as dirty shallowly. pub fn mark_dirty_shallow(&self) { self.store.mark_dirty_shallow(&self.path); } /// Mark this scope as dirty at and after the given index. pub fn mark_dirty_at_and_after_index(&self, index: usize) { self.store.mark_dirty_at_and_after_index(&self.path, index); } /// Map the writer to a new type. pub fn map_writer(self, map: impl FnOnce(Lens) -> W2) -> SelectorScope { SelectorScope { path: self.path, store: self.store, write: map(self.write), } } /// Write without notifying subscribers. pub fn write_untracked(&self) -> WritableRef<'static, Lens> where Lens: Writable, { self.write.write_unchecked() } /// Borrow the writer pub(crate) fn as_ref(&self) -> SelectorScope<&Lens> { SelectorScope { path: self.path, store: self.store, write: &self.write, } } } impl Readable for SelectorScope { type Target = Lens::Target; type Storage = Lens::Storage; fn try_read_unchecked(&self) -> Result, BorrowError> { self.track(); self.write.try_read_unchecked() } fn try_peek_unchecked(&self) -> Result, BorrowError> { self.write.try_peek_unchecked() } fn subscribers(&self) -> Subscribers { self.store.subscribers(&self.path) } } impl Writable for SelectorScope { type WriteMetadata = Lens::WriteMetadata; fn try_write_unchecked(&self) -> Result, BorrowMutError> { self.mark_dirty(); self.write.try_write_unchecked() } }