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}