dioxus_stores/impls/btreemap.rs
1//! Additional utilities for `BTreeMap` stores.
2
3use std::{
4 borrow::Borrow, collections::BTreeMap, hash::Hash, iter::FusedIterator, panic::Location,
5};
6
7use crate::{store::Store, ReadStore};
8use dioxus_signals::{
9 AnyStorage, BorrowError, BorrowMutError, ReadSignal, Readable, ReadableExt, UnsyncStorage,
10 Writable, WriteLock, WriteSignal,
11};
12use generational_box::ValueDroppedError;
13
14impl<Lens: Readable<Target = BTreeMap<K, V>> + 'static, K: 'static, V: 'static>
15 Store<BTreeMap<K, V>, Lens>
16{
17 /// Get the length of the BTreeMap. This method will track the store shallowly and only cause
18 /// re-runs when items are added or removed from the map, not when existing values are modified.
19 ///
20 /// # Example
21 /// ```rust, no_run
22 /// use dioxus_stores::*;
23 /// use dioxus::prelude::*;
24 /// use std::collections::BTreeMap;
25 /// let mut store = use_store(|| BTreeMap::new());
26 /// assert_eq!(store.len(), 0);
27 /// store.insert(0, "value".to_string());
28 /// assert_eq!(store.len(), 1);
29 /// ```
30 pub fn len(&self) -> usize {
31 self.selector().track_shallow();
32 self.selector().peek().len()
33 }
34
35 /// Check if the BTreeMap is empty. This method will track the store shallowly and only cause
36 /// re-runs when items are added or removed from the map, not when existing values are modified.
37 ///
38 /// # Example
39 /// ```rust, no_run
40 /// use dioxus_stores::*;
41 /// use dioxus::prelude::*;
42 /// use std::collections::BTreeMap;
43 /// let mut store = use_store(|| BTreeMap::new());
44 /// assert!(store.is_empty());
45 /// store.insert(0, "value".to_string());
46 /// assert!(!store.is_empty());
47 /// ```
48 pub fn is_empty(&self) -> bool {
49 self.selector().track_shallow();
50 self.selector().peek().is_empty()
51 }
52
53 /// Iterate over the current entries in the BTreeMap, returning a tuple of the key and a store for the value. This method
54 /// will track the store shallowly and only cause re-runs when items are added or removed from the map, not when existing
55 /// values are modified.
56 ///
57 /// # Example
58 ///
59 /// ```rust, no_run
60 /// use dioxus_stores::*;
61 /// use dioxus::prelude::*;
62 /// use std::collections::BTreeMap;
63 /// let mut store = use_store(|| BTreeMap::new());
64 /// store.insert(0, "value1".to_string());
65 /// store.insert(1, "value2".to_string());
66 /// for (key, value_store) in store.iter() {
67 /// println!("{}: {}", key, value_store.read());
68 /// }
69 /// ```
70 pub fn iter(
71 &self,
72 ) -> impl ExactSizeIterator<Item = (K, Store<V, GetWrite<K, Lens>>)>
73 + DoubleEndedIterator
74 + FusedIterator
75 + '_
76 where
77 K: Hash + Ord + Clone,
78 Lens: Clone,
79 {
80 self.selector().track_shallow();
81 let keys: Vec<_> = self.selector().peek_unchecked().keys().cloned().collect();
82 keys.into_iter().map(move |key| {
83 let value = self.clone().get_unchecked(key.clone());
84 (key, value)
85 })
86 }
87
88 /// Get an iterator over the values in the BTreeMap. This method will track the store shallowly and only cause
89 /// re-runs when items are added or removed from the map, not when existing values are modified.
90 ///
91 /// # Example
92 /// ```rust, no_run
93 /// use dioxus_stores::*;
94 /// use dioxus::prelude::*;
95 /// use std::collections::BTreeMap;
96 /// let mut store = use_store(|| BTreeMap::new());
97 /// store.insert(0, "value1".to_string());
98 /// store.insert(1, "value2".to_string());
99 /// for value_store in store.values() {
100 /// println!("{}", value_store.read());
101 /// }
102 /// ```
103 pub fn values(
104 &self,
105 ) -> impl ExactSizeIterator<Item = Store<V, GetWrite<K, Lens>>>
106 + DoubleEndedIterator
107 + FusedIterator
108 + '_
109 where
110 K: Hash + Ord + Clone,
111 Lens: Clone,
112 {
113 self.selector().track_shallow();
114 let keys = self.selector().peek().keys().cloned().collect::<Vec<_>>();
115 keys.into_iter()
116 .map(move |key| self.clone().get_unchecked(key))
117 }
118
119 /// Insert a new key-value pair into the BTreeMap. This method will mark the store as shallowly dirty, causing
120 /// re-runs of any reactive scopes that depend on the shape of the map.
121 ///
122 /// # Example
123 /// ```rust, no_run
124 /// use dioxus_stores::*;
125 /// use dioxus::prelude::*;
126 /// use std::collections::BTreeMap;
127 /// let mut store = use_store(|| BTreeMap::new());
128 /// assert!(store.get(0).is_none());
129 /// store.insert(0, "value".to_string());
130 /// assert_eq!(store.get(0).unwrap().cloned(), "value".to_string());
131 /// ```
132 pub fn insert(&mut self, key: K, value: V)
133 where
134 K: Ord,
135 Lens: Writable,
136 {
137 // TODO: This method was released in 0.7 without the hash bound so we don't have a way
138 // to mark only the existing value as dirty. Instead we need to check if the value already exists
139 // in the map and mark the whole map as dirty if it does.
140 // In the 0.8 release, we should change this method to only mark the existing value as dirty.
141 if self.peek().contains_key(&key) {
142 self.selector().mark_dirty();
143 } else {
144 self.selector().mark_dirty_shallow();
145 }
146 self.selector().write_untracked().insert(key, value);
147 }
148
149 /// Remove a key-value pair from the BTreeMap. This method will mark the store as shallowly dirty, causing
150 /// re-runs of any reactive scopes that depend on the shape of the map or the value of the removed key.
151 ///
152 /// # Example
153 /// ```rust, no_run
154 /// use dioxus_stores::*;
155 /// use dioxus::prelude::*;
156 /// use std::collections::BTreeMap;
157 /// let mut store = use_store(|| BTreeMap::new());
158 /// store.insert(0, "value".to_string());
159 /// assert_eq!(store.get(0).unwrap().cloned(), "value".to_string());
160 /// let removed_value = store.remove(&0);
161 /// assert_eq!(removed_value, Some("value".to_string()));
162 /// assert!(store.get(0).is_none());
163 /// ```
164 pub fn remove<Q>(&mut self, key: &Q) -> Option<V>
165 where
166 Q: ?Sized + Ord + 'static,
167 K: Borrow<Q> + Ord,
168 Lens: Writable,
169 {
170 self.selector().mark_dirty_shallow();
171 self.selector().write_untracked().remove(key)
172 }
173
174 /// Clear the BTreeMap, removing all key-value pairs. This method will mark the store as shallowly dirty,
175 /// causing re-runs of any reactive scopes that depend on the shape of the map.
176 ///
177 /// # Example
178 /// ```rust, no_run
179 /// use dioxus_stores::*;
180 /// use dioxus::prelude::*;
181 /// use std::collections::BTreeMap;
182 /// let mut store = use_store(|| BTreeMap::new());
183 /// store.insert(1, "value1".to_string());
184 /// store.insert(2, "value2".to_string());
185 /// assert_eq!(store.len(), 2);
186 /// store.clear();
187 /// assert!(store.is_empty());
188 /// ```
189 pub fn clear(&mut self)
190 where
191 Lens: Writable,
192 {
193 self.selector().mark_dirty_shallow();
194 self.selector().write_untracked().clear();
195 }
196
197 /// Retain only the key-value pairs that satisfy the given predicate. This method will mark the store as shallowly dirty,
198 /// causing re-runs of any reactive scopes that depend on the shape of the map or the values retained.
199 ///
200 /// # Example
201 /// ```rust, no_run
202 /// use dioxus_stores::*;
203 /// use dioxus::prelude::*;
204 /// use std::collections::BTreeMap;
205 /// let mut store = use_store(|| BTreeMap::new());
206 /// store.insert(1, "value1".to_string());
207 /// store.insert(2, "value2".to_string());
208 /// store.retain(|key, value| *key == 1);
209 /// assert_eq!(store.len(), 1);
210 /// assert!(store.get(1).is_some());
211 /// assert!(store.get(2).is_none());
212 /// ```
213 pub fn retain(&mut self, mut f: impl FnMut(&K, &V) -> bool)
214 where
215 Lens: Writable,
216 K: Ord,
217 {
218 self.selector().mark_dirty_shallow();
219 self.selector().write_untracked().retain(|k, v| f(k, v));
220 }
221
222 /// Check if the BTreeMap contains a key. This method will track the store shallowly and only cause
223 /// re-runs when items are added or removed from the map, not when existing values are modified.
224 ///
225 /// # Example
226 /// ```rust, no_run
227 /// use dioxus_stores::*;
228 /// use dioxus::prelude::*;
229 /// use std::collections::BTreeMap;
230 /// let mut store = use_store(|| BTreeMap::new());
231 /// assert!(!store.contains_key(&0));
232 /// store.insert(0, "value".to_string());
233 /// assert!(store.contains_key(&0));
234 /// ```
235 pub fn contains_key<Q>(&self, key: &Q) -> bool
236 where
237 Q: ?Sized + Ord + 'static,
238 K: Borrow<Q> + Ord,
239 {
240 self.selector().track_shallow();
241 self.selector().peek().contains_key(key)
242 }
243
244 /// Get a store for the value associated with the given key. This method creates a new store scope
245 /// that tracks just changes to the value associated with the key.
246 ///
247 /// # Example
248 /// ```rust, no_run
249 /// use dioxus_stores::*;
250 /// use dioxus::prelude::*;
251 /// use std::collections::BTreeMap;
252 /// let mut store = use_store(|| BTreeMap::new());
253 /// assert!(store.get(0).is_none());
254 /// store.insert(0, "value".to_string());
255 /// assert_eq!(store.get(0).unwrap().cloned(), "value".to_string());
256 /// ```
257 pub fn get<Q>(self, key: Q) -> Option<Store<V, GetWrite<Q, Lens>>>
258 where
259 Q: Hash + Ord + 'static,
260 K: Borrow<Q> + Ord,
261 {
262 self.contains_key(&key).then(|| self.get_unchecked(key))
263 }
264
265 /// Get a store for the value associated with the given key without checking if the key exists.
266 /// This method creates a new store scope that tracks just changes to the value associated with the key.
267 ///
268 /// This is not unsafe, but it will panic when you try to read the value if it does not exist.
269 ///
270 /// # Example
271 /// ```rust, no_run
272 /// use dioxus_stores::*;
273 /// use dioxus::prelude::*;
274 /// use std::collections::BTreeMap;
275 /// let mut store = use_store(|| BTreeMap::new());
276 /// store.insert(0, "value".to_string());
277 /// assert_eq!(store.get_unchecked(0).cloned(), "value".to_string());
278 /// ```
279 #[track_caller]
280 pub fn get_unchecked<Q>(self, key: Q) -> Store<V, GetWrite<Q, Lens>>
281 where
282 Q: Hash + Ord + 'static,
283 K: Borrow<Q> + Ord,
284 {
285 let created = std::panic::Location::caller();
286 self.into_selector()
287 .hash_child_unmapped(key.borrow())
288 .map_writer(move |writer| GetWrite {
289 index: key,
290 write: writer,
291 created,
292 })
293 .into()
294 }
295}
296
297/// A specific index in a `Readable` / `Writable` BTreeMap
298#[derive(Clone, Copy)]
299pub struct GetWrite<Index, Write> {
300 index: Index,
301 write: Write,
302 created: &'static Location<'static>,
303}
304
305impl<Index, Write, K, V> Readable for GetWrite<Index, Write>
306where
307 Write: Readable<Target = BTreeMap<K, V>>,
308 Index: Ord + 'static,
309 K: Borrow<Index> + Ord + 'static,
310{
311 type Target = V;
312
313 type Storage = Write::Storage;
314
315 fn try_read_unchecked(&self) -> Result<dioxus_signals::ReadableRef<'static, Self>, BorrowError>
316 where
317 Self::Target: 'static,
318 {
319 self.write.try_read_unchecked().and_then(|value| {
320 Self::Storage::try_map(value, |value: &Write::Target| value.get(&self.index))
321 .ok_or_else(|| BorrowError::Dropped(ValueDroppedError::new(self.created)))
322 })
323 }
324
325 fn try_peek_unchecked(&self) -> Result<dioxus_signals::ReadableRef<'static, Self>, BorrowError>
326 where
327 Self::Target: 'static,
328 {
329 self.write.try_peek_unchecked().and_then(|value| {
330 Self::Storage::try_map(value, |value: &Write::Target| value.get(&self.index))
331 .ok_or_else(|| BorrowError::Dropped(ValueDroppedError::new(self.created)))
332 })
333 }
334
335 fn subscribers(&self) -> dioxus_core::Subscribers
336 where
337 Self::Target: 'static,
338 {
339 self.write.subscribers()
340 }
341}
342
343impl<Index, Write, K, V> Writable for GetWrite<Index, Write>
344where
345 Write: Writable<Target = BTreeMap<K, V>>,
346 Index: Ord + 'static,
347 K: Borrow<Index> + Ord + 'static,
348{
349 type WriteMetadata = Write::WriteMetadata;
350
351 fn try_write_unchecked(
352 &self,
353 ) -> Result<dioxus_signals::WritableRef<'static, Self>, BorrowMutError>
354 where
355 Self::Target: 'static,
356 {
357 self.write.try_write_unchecked().and_then(|value| {
358 WriteLock::filter_map(value, |value: &mut Write::Target| {
359 value.get_mut(&self.index)
360 })
361 .ok_or_else(|| BorrowMutError::Dropped(ValueDroppedError::new(self.created)))
362 })
363 }
364}
365
366impl<Index, Write, K, V> ::std::convert::From<Store<V, GetWrite<Index, Write>>>
367 for Store<V, WriteSignal<V>>
368where
369 Write::WriteMetadata: 'static,
370 Write: Writable<Target = BTreeMap<K, V>, Storage = UnsyncStorage> + 'static,
371 Index: Ord + 'static,
372 K: Borrow<Index> + Ord + 'static,
373 V: 'static,
374{
375 fn from(value: Store<V, GetWrite<Index, Write>>) -> Self {
376 value
377 .into_selector()
378 .map_writer(|writer| WriteSignal::new(writer))
379 .into()
380 }
381}
382
383impl<Index, Write, K, V> ::std::convert::From<Store<V, GetWrite<Index, Write>>> for ReadStore<V>
384where
385 Write: Readable<Target = BTreeMap<K, V>, Storage = UnsyncStorage> + 'static,
386 Index: Ord + 'static,
387 K: Borrow<Index> + Ord + 'static,
388 V: 'static,
389{
390 fn from(value: Store<V, GetWrite<Index, Write>>) -> Self {
391 value
392 .into_selector()
393 .map_writer(|writer| ReadSignal::new(writer))
394 .into()
395 }
396}