dioxus_stores/
scope.rs

1//! This module contains the `SelectorScope` type with raw access to the underlying store system. Most applications should
2//! use the [`Store`](dioxus_stores_macro::Store) macro to derive stores for their data structures, which provides a more ergonomic API.
3
4use std::{fmt::Debug, hash::Hash};
5
6use crate::subscriptions::{PathKey, StoreSubscriptions, TinyVec};
7use dioxus_core::Subscribers;
8use dioxus_signals::{
9    BorrowError, BorrowMutError, MappedMutSignal, Readable, ReadableRef, Writable, WritableExt,
10    WritableRef,
11};
12
13/// SelectorScope is the primitive that backs the store system.
14///
15/// Under the hood stores consist of two different parts:
16/// - The underlying lock that contains the data in the store.
17/// - A tree of subscriptions used to make the store reactive.
18///
19/// The `SelectorScope` contains a view into the lock (`Lens`) and a path into the subscription tree. When
20/// the selector is read to, it will track the current path in the subscription tree. When it it written to
21/// it marks itself and all its children as dirty.
22///
23/// When you derive the [`Store`](dioxus_stores_macro::Store) macro on your data structure,
24/// it generates methods that map the lock to a new type and scope the path to a specific part of the subscription structure.
25/// For example, a `Counter` store might look like this:
26///
27/// ```rust, ignore
28/// #[derive(Store)]
29/// struct Counter {
30///     count: i32,
31/// }
32///
33/// impl CounterStoreExt for Store<Counter> {
34///     fn count(
35///         self,
36///     ) -> dioxus_stores::Store<
37///         i32,
38///         dioxus_stores::macro_helpers::dioxus_signals::MappedMutSignal<i32, __W>,
39///     > {
40///         let __map_field: fn(&CounterTree) -> &i32 = |value| &value.count;
41///         let __map_mut_field: fn(&mut CounterTree) -> &mut i32 = |value| &mut value.count;
42///         let scope = self.selector().scope(0u32, __map_field, __map_mut_field);
43///         dioxus_stores::Store::new(scope)
44///     }
45/// }
46/// ```
47///
48/// The `count` method maps the lock to the `i32` type and creates a child `0` path in the subscription tree. Only writes
49/// to that `0` path or its parents will trigger a re-render of the components that read the `count` field.
50#[derive(PartialEq)]
51pub struct SelectorScope<Lens> {
52    path: TinyVec,
53    store: StoreSubscriptions,
54    write: Lens,
55}
56
57impl<Lens> Debug for SelectorScope<Lens> {
58    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
59        f.debug_struct("SelectorScope")
60            .field("path", &self.path)
61            .finish()
62    }
63}
64
65impl<Lens> Clone for SelectorScope<Lens>
66where
67    Lens: Clone,
68{
69    fn clone(&self) -> Self {
70        Self {
71            path: self.path,
72            store: self.store,
73            write: self.write.clone(),
74        }
75    }
76}
77
78impl<Lens> Copy for SelectorScope<Lens> where Lens: Copy {}
79
80impl<Lens> SelectorScope<Lens> {
81    pub(crate) fn new(path: TinyVec, store: StoreSubscriptions, write: Lens) -> Self {
82        Self { path, store, write }
83    }
84
85    /// Create a child selector scope for a hash key. The scope will only be marked as dirty when a
86    /// write occurs to that key or its parents.
87    ///
88    /// Note the hash is lossy, so there may rarely be collisions. If a collision does occur, it may
89    /// cause reruns in a part of the app that has not changed. As long as derived data is pure,
90    /// this should not cause issues.
91    pub fn hash_child<U: ?Sized, T, F, FMut>(
92        self,
93        index: &impl Hash,
94        map: F,
95        map_mut: FMut,
96    ) -> SelectorScope<MappedMutSignal<U, Lens, F, FMut>>
97    where
98        F: Fn(&T) -> &U,
99        FMut: Fn(&mut T) -> &mut U,
100    {
101        let hash = self.store.hash(index);
102        self.child(hash, map, map_mut)
103    }
104
105    /// Create a child selector scope for a specific index. The scope will only be marked as dirty when a
106    /// write occurs to that index or its parents.
107    pub fn child<U: ?Sized, T, F, FMut>(
108        self,
109        index: PathKey,
110        map: F,
111        map_mut: FMut,
112    ) -> SelectorScope<MappedMutSignal<U, Lens, F, FMut>>
113    where
114        F: Fn(&T) -> &U,
115        FMut: Fn(&mut T) -> &mut U,
116    {
117        self.child_unmapped(index).map(map, map_mut)
118    }
119
120    /// Create a hashed child selector scope for a specific index without mapping the writer. The scope will only
121    /// be marked as dirty when a write occurs to that index or its parents.
122    pub fn hash_child_unmapped(self, index: &impl Hash) -> SelectorScope<Lens> {
123        let hash = self.store.hash(index);
124        self.child_unmapped(hash)
125    }
126
127    /// Create a child selector scope for a specific index without mapping the writer. The scope will only
128    /// be marked as dirty when a write occurs to that index or its parents.
129    pub fn child_unmapped(mut self, index: PathKey) -> SelectorScope<Lens> {
130        self.path.push(index);
131        self
132    }
133
134    /// Map the view into the writable data without creating a child selector scope
135    pub fn map<U: ?Sized, T, F, FMut>(
136        self,
137        map: F,
138        map_mut: FMut,
139    ) -> SelectorScope<MappedMutSignal<U, Lens, F, FMut>>
140    where
141        F: Fn(&T) -> &U,
142        FMut: Fn(&mut T) -> &mut U,
143    {
144        self.map_writer(move |write| MappedMutSignal::new(write, map, map_mut))
145    }
146
147    /// Track this scope shallowly.
148    pub fn track_shallow(&self) {
149        self.store.track(&self.path);
150    }
151
152    /// Track this scope recursively.
153    pub fn track(&self) {
154        self.store.track_recursive(&self.path);
155    }
156
157    /// Mark this scope as dirty recursively.
158    pub fn mark_dirty(&self) {
159        self.store.mark_dirty(&self.path);
160    }
161
162    /// Mark this scope as dirty shallowly.
163    pub fn mark_dirty_shallow(&self) {
164        self.store.mark_dirty_shallow(&self.path);
165    }
166
167    /// Mark this scope as dirty at and after the given index.
168    pub fn mark_dirty_at_and_after_index(&self, index: usize) {
169        self.store.mark_dirty_at_and_after_index(&self.path, index);
170    }
171
172    /// Map the writer to a new type.
173    pub fn map_writer<W2>(self, map: impl FnOnce(Lens) -> W2) -> SelectorScope<W2> {
174        SelectorScope {
175            path: self.path,
176            store: self.store,
177            write: map(self.write),
178        }
179    }
180
181    /// Write without notifying subscribers.
182    pub fn write_untracked(&self) -> WritableRef<'static, Lens>
183    where
184        Lens: Writable,
185    {
186        self.write.write_unchecked()
187    }
188
189    /// Borrow the writer
190    pub(crate) fn as_ref(&self) -> SelectorScope<&Lens> {
191        SelectorScope {
192            path: self.path,
193            store: self.store,
194            write: &self.write,
195        }
196    }
197}
198
199impl<Lens: Readable> Readable for SelectorScope<Lens> {
200    type Target = Lens::Target;
201    type Storage = Lens::Storage;
202
203    fn try_read_unchecked(&self) -> Result<ReadableRef<'static, Lens>, BorrowError> {
204        self.track();
205        self.write.try_read_unchecked()
206    }
207
208    fn try_peek_unchecked(&self) -> Result<ReadableRef<'static, Lens>, BorrowError> {
209        self.write.try_peek_unchecked()
210    }
211
212    fn subscribers(&self) -> Subscribers {
213        self.store.subscribers(&self.path)
214    }
215}
216
217impl<Lens: Writable> Writable for SelectorScope<Lens> {
218    type WriteMetadata = Lens::WriteMetadata;
219
220    fn try_write_unchecked(&self) -> Result<WritableRef<'static, Lens>, BorrowMutError> {
221        self.mark_dirty();
222        self.write.try_write_unchecked()
223    }
224}