Skip to main content

amethystate_core/primitives/
map_core.rs

1use crate::SignalSubscription;
2use crate::change::MapChange;
3use crate::path::{PathRef, StorePath, Under};
4use crate::primitives::intercept::{InterceptDepth, InterceptDisposer, InterceptGuard};
5use crate::primitives::signal::{SubscriptionMeta, forget, held, label};
6use arc_swap::ArcSwap;
7use dashmap::DashMap;
8use rpds::RedBlackTreeMapSync;
9use serde::de::DeserializeOwned;
10use serde::{Deserialize, Serialize};
11use smol_str::{SmolStr, SmolStrBuilder};
12use std::any;
13use std::borrow::Borrow;
14use std::fmt::{self, Debug, Display, Write as _};
15use std::hash::Hash;
16use std::panic::Location;
17use std::str::FromStr;
18use std::sync::atomic::{AtomicU64, Ordering};
19use std::sync::{Arc, Mutex};
20
21/// Where a map's entries live, relative to the map itself.
22pub trait MapEntryPath {
23    /// The path of the entry `name` sits at.
24    fn entry(&self, name: &str) -> StorePath;
25}
26
27impl MapEntryPath for StorePath {
28    fn entry(&self, name: &str) -> StorePath {
29        self.push(name)
30    }
31}
32
33pub type InterceptorAny<K, V> =
34    Arc<dyn Fn(MapChange<K, V>) -> Option<MapChange<K, V>> + Send + Sync + 'static>;
35pub type InterceptorKey<K, V> =
36    Arc<dyn Fn(MapChange<K, V>) -> Option<MapChange<K, V>> + Send + Sync + 'static>;
37pub type SubscriberAny<K, V> = Arc<dyn Fn(&MapChange<K, V>) + Send + Sync + 'static>;
38pub type SubscriberKey<K, V> = Arc<dyn Fn(&MapChange<K, V>) + Send + Sync + 'static>;
39
40/// What a map may be keyed by: a key that already *is* the name its entry sits
41/// at.
42///
43/// An entry lives at one level under the map, and that level is named by the
44/// key. A key that borrows its own name needs no spelling and no parsing: the
45/// store is addressed with what the caller already holds, a listing hands back
46/// what is already there, and neither can disagree with the other, because
47/// there is only one of them.
48///
49/// A key that is not a string is spelled by [`Id`], which renders it once and
50/// holds the rendering.
51/// What one scanned key turned out to be, to the map it was scanned from.
52///
53/// A map is loaded from two places - a store that reads now and a backend that
54/// reads later - and each has its own vocabulary for what went wrong. What they
55/// may not have is their own opinion about *which* of these four a key is, and
56/// each working it out from the path by hand is how they came to differ.
57pub enum EntryOf<K> {
58    /// One level below the map, and the name reads as `K`.
59    Took(K),
60
61    /// The map's own path. Not an entry, and not a fault: a scan of a prefix
62    /// hands the prefix back.
63    ThePathItself,
64
65    /// One level below, and the name does not read as `K`.
66    NameWillNotRead,
67
68    /// Deeper than the level the map owns, or not under it at all. A map owns
69    /// the level below it and nothing further, so a key past that belongs to
70    /// whatever claimed that level.
71    NotThisMaps,
72}
73
74/// Which of the four `stored` is, to a map at `prefix`.
75pub fn entry_of<K>(prefix: &StorePath, stored: PathRef<'_>) -> EntryOf<K>
76where
77    K: ReactiveMapKey,
78{
79    match stored.level_under(prefix) {
80        Under::Prefix => EntryOf::ThePathItself,
81        Under::Deeper(_) | Under::Outside => EntryOf::NotThisMaps,
82        Under::Entry(name) => match K::read(name.as_str()) {
83            Some(key) => EntryOf::Took(key),
84            None => EntryOf::NameWillNotRead,
85        },
86    }
87}
88
89pub trait ReactiveMapKey: AsRef<str> + Clone + Hash + Eq + Send + Sync + 'static {
90    /// The key a stored name stands for, or `None` where that name is not one.
91    ///
92    /// The only direction that can fail, and the only one that allocates. It is
93    /// walked once per entry when a map is built from the store, and never on a
94    /// read.
95    fn read(name: &str) -> Option<Self>;
96}
97
98impl ReactiveMapKey for String {
99    fn read(name: &str) -> Option<Self> {
100        Some(name.to_string())
101    }
102}
103
104impl ReactiveMapKey for SmolStr {
105    fn read(name: &str) -> Option<Self> {
106        Some(SmolStr::new(name))
107    }
108}
109
110impl<T> ReactiveMapKey for Id<T>
111where
112    T: Display + FromStr + Clone + Send + Sync + 'static,
113{
114    fn read(name: &str) -> Option<Self> {
115        T::from_str(name).ok().map(Id::new)
116    }
117}
118
119/// A key that is not a string, spelled once and kept that way.
120///
121/// `Id::new(9u16)` names its entry `9`, `Id::new(some_uuid)` names it by the
122/// uuid's own spelling. The rendering is done when the `Id` is built rather
123/// than at every lookup, which is what a `Uuid` wants most: 36 characters do
124/// not fit beside the value, so spelling one costs the heap, and this pays it
125/// once for the life of the key instead of once per read.
126///
127/// Entries are listed in the order of the *spelling*, so `Id<u16>` lists `10`
128/// before `9`. A number's decimal spelling does not order like the number, and
129/// nothing here pretends otherwise - padding it would make the name in the file
130/// something nobody typed.
131#[derive(Clone)]
132pub struct Id<T> {
133    spelled: SmolStr,
134    value: T,
135}
136
137impl<T: Display> Id<T> {
138    pub fn new(value: T) -> Self {
139        let mut out = SmolStrBuilder::new();
140        let _ = write!(out, "{value}");
141
142        Self {
143            spelled: out.finish(),
144            value,
145        }
146    }
147}
148
149impl<T> Id<T> {
150    /// What was spelled, for a caller that wants the number back.
151    pub fn get(&self) -> &T {
152        &self.value
153    }
154
155    pub fn into_inner(self) -> T {
156        self.value
157    }
158}
159
160impl<T> AsRef<str> for Id<T> {
161    fn as_ref(&self) -> &str {
162        &self.spelled
163    }
164}
165
166/// So a map keyed by an id can be looked up by the spelling alone, without
167/// building one. Sound because `Eq` and `Hash` below answer from the same
168/// spelling, which is what `Borrow` asks.
169impl<T> Borrow<str> for Id<T> {
170    fn borrow(&self) -> &str {
171        &self.spelled
172    }
173}
174
175/// By the spelling, which is what the store is addressed by and what a listing
176/// is ordered by. Two ids that spell alike are one entry whatever they hold.
177impl<T> PartialEq for Id<T> {
178    fn eq(&self, other: &Self) -> bool {
179        self.spelled == other.spelled
180    }
181}
182
183impl<T> Eq for Id<T> {}
184
185impl<T> Hash for Id<T> {
186    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
187        self.spelled.hash(state);
188    }
189}
190
191impl<T> Display for Id<T> {
192    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
193        f.write_str(&self.spelled)
194    }
195}
196
197impl<T: Debug> Debug for Id<T> {
198    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
199        f.debug_tuple("Id").field(&self.value).finish()
200    }
201}
202
203/// As the spelling, so a snapshot holding one for a key is a document with
204/// string keys - which is what every format this library writes needs a map's
205/// keys to be.
206impl<T> Serialize for Id<T> {
207    fn serialize<S: serde::Serializer>(&self, out: S) -> Result<S::Ok, S::Error> {
208        out.serialize_str(&self.spelled)
209    }
210}
211
212impl<'de, T: Display + FromStr> Deserialize<'de> for Id<T> {
213    fn deserialize<D: serde::Deserializer<'de>>(from: D) -> Result<Self, D::Error> {
214        let spelled = <std::borrow::Cow<'de, str>>::deserialize(from)?;
215
216        T::from_str(&spelled).map(Id::new).map_err(|_| {
217            serde::de::Error::custom(format!("{spelled:?} is not a {}", any::type_name::<T>()))
218        })
219    }
220}
221
222pub trait ReactiveMapValue:
223    Serialize + DeserializeOwned + Clone + Send + Sync + 'static + Default
224{
225}
226impl<T: Serialize + DeserializeOwned + Clone + Send + Sync + 'static + Default> ReactiveMapValue
227    for T
228{
229}
230
231/// A map's entries, held in the order the store lists them.
232///
233/// Keyed by the key itself, ordered by the name it borrows. A store orders its
234/// keys by their levels, and an entry is one level, so the name's own order is
235/// the store's - the two cannot come apart, because the cache is not holding a
236/// second thing to compare.
237///
238/// A read takes a version and holds nothing, so a walk neither blocks a writer
239/// nor waits for one, whatever thread either is on. A write publishes a new
240/// version that shares every node it did not touch, so it stays O(log n)
241/// rather than copying the map.
242///
243/// It is a trade, not a free win. Writes and memory both cost more than a
244/// `RwLock<BTreeMap>` would, and
245/// `crates/main/amethystate/benches/map_snapshot_bench.rs` measures against
246/// one.
247///
248/// What it buys is that writing during a walk cannot deadlock. It does not
249/// make it correct: the walk goes on yielding its own version, so a write made
250/// inside the loop is invisible to the rest of it. `for k in keys { remove(k) }`
251/// wants exactly that; a loop that writes and reads the same key back gets a
252/// stale answer and nothing says so. Loud failure was traded for quiet
253/// staleness.
254///
255/// The shape is not selectable per map, though it could be - the backing type
256/// is private either way. Nothing would pick the locking one: it wins only
257/// where a map is written in bulk and never walked while it is written, and a
258/// map nobody observes is the store's job.
259pub struct MapCache<K, V> {
260    entries: ArcSwap<Snapshot<K, V>>,
261    settled: Mutex<Settled>,
262}
263
264impl<K: AsRef<str>, V> Default for MapCache<K, V> {
265    fn default() -> Self {
266        Self {
267            entries: ArcSwap::from_pointee(RedBlackTreeMapSync::new_sync()),
268            settled: Mutex::new(Settled::default()),
269        }
270    }
271}
272
273/// Where in the store's order the cache last took a change to each key, and
274/// to the whole map.
275///
276/// A store numbers its writes as it settles them, and tells its subscribers on
277/// the thread each write came from. Two threads writing one key can be told in
278/// the other order, and a cache taking whatever it is told last would hold the
279/// earlier value over the later one until the next write. A key removed keeps
280/// its number, so an insert settled before the removal cannot bring it back.
281///
282/// A removed key's number is let go of once the store has settled
283/// [`FORGET_AFTER`] changes since, and `forgotten` keeps the newest number let
284/// go of: a change to a key with no number of its own is taken only if it was
285/// settled after that. A change told that late is not one a store delivers.
286#[derive(Default)]
287struct Settled {
288    keys: std::collections::HashMap<String, u64>,
289    cleared: u64,
290    forgotten: u64,
291    latest: u64,
292}
293
294const FORGET_AFTER: u64 = 4096;
295
296impl Settled {
297    fn admits(&self, name: &str, at: u64) -> bool {
298        at >= self.cleared
299            && match self.keys.get(name) {
300                Some(held) => at >= *held,
301                None => at > self.forgotten || self.forgotten == 0,
302            }
303    }
304
305    fn took(&mut self, name: &str, at: u64) {
306        self.keys.insert(name.to_string(), at);
307        self.latest = self.latest.max(at);
308    }
309
310    fn let_go_of_the_long_removed(&mut self, live: impl Fn(&str) -> bool) {
311        if self.keys.len() as u64 <= 2 * FORGET_AFTER {
312            return;
313        }
314
315        let horizon = self.latest.saturating_sub(FORGET_AFTER);
316        let mut forgotten = self.forgotten;
317        self.keys.retain(|name, held| {
318            let kept = *held > horizon || live(name);
319            if !kept {
320                forgotten = forgotten.max(*held);
321            }
322            kept
323        });
324        self.forgotten = forgotten;
325    }
326}
327
328/// A key held as the cache orders it: by the name it borrows, never by anything
329/// the key type decides for itself.
330///
331/// The invariant the cache rests on is that its order is the store's. Requiring
332/// `K: Ord` would ask the key type to agree and have no way to check; this
333/// takes the ordering away from it instead, so a key whose own `Ord` says
334/// something else cannot make a listing disagree with a scan.
335#[derive(Clone)]
336struct ByName<K>(K);
337
338impl<K: AsRef<str>> Borrow<str> for ByName<K> {
339    fn borrow(&self) -> &str {
340        self.0.as_ref()
341    }
342}
343
344impl<K: AsRef<str>> PartialEq for ByName<K> {
345    fn eq(&self, other: &Self) -> bool {
346        self.0.as_ref() == other.0.as_ref()
347    }
348}
349
350impl<K: AsRef<str>> Eq for ByName<K> {}
351
352impl<K: AsRef<str>> Ord for ByName<K> {
353    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
354        self.0.as_ref().cmp(other.0.as_ref())
355    }
356}
357
358impl<K: AsRef<str>> PartialOrd for ByName<K> {
359    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
360        Some(self.cmp(other))
361    }
362}
363
364impl<K: AsRef<str> + Clone, V: Clone> MapCache<K, V> {
365    pub fn new() -> Self {
366        Self::default()
367    }
368
369    pub fn get(&self, name: &str) -> Option<V> {
370        self.entries.load().get(name).cloned()
371    }
372
373    pub fn contains_key(&self, name: &str) -> bool {
374        self.entries.load().contains_key(name)
375    }
376
377    /// The key as the map holds it, for a caller that looked one up by the name
378    /// it borrows and needs the owned form back.
379    pub fn owned_key(&self, name: &str) -> Option<K> {
380        self.entries
381            .load()
382            .get_key_value(name)
383            .map(|(key, _)| key.0.clone())
384    }
385
386    pub fn len(&self) -> usize {
387        self.entries.load().size()
388    }
389
390    pub fn is_empty(&self) -> bool {
391        self.entries.load().is_empty()
392    }
393
394    pub fn clear(&self) {
395        self.entries
396            .store(Arc::new(RedBlackTreeMapSync::new_sync()));
397    }
398
399    /// The entries themselves, in order, without copying any of them.
400    ///
401    /// The version is taken when the view is, so writes that land afterwards
402    /// are not in it however long it is held.
403    pub fn view(&self) -> Entries<K, V> {
404        Entries {
405            held: self.entries.load_full(),
406        }
407    }
408
409    /// Every key, in the order the contract promises, one at a time.
410    pub fn keys(&self) -> Walk<K, V, K> {
411        Walk::new(self.entries.load_full(), |key, _| key.0.clone())
412    }
413
414    /// Every entry, in that order, one at a time.
415    pub fn entries(&self) -> Walk<K, V, (K, V)> {
416        Walk::new(self.entries.load_full(), |key, value| {
417            (key.0.clone(), value.clone())
418        })
419    }
420}
421
422type Snapshot<K, V> = RedBlackTreeMapSync<ByName<K>, V>;
423type Held<K, V> = Arc<Snapshot<K, V>>;
424type Pairs<'a, K, V> = <&'a Snapshot<K, V> as IntoIterator>::IntoIter;
425/// A borrowed walk of one version, handing back the key beside its value.
426///
427/// Its own type rather than a mapped iterator, so how the cache holds a key
428/// stays inside this module.
429pub struct Values<'a, K: AsRef<str>, V> {
430    pairs: Pairs<'a, K, V>,
431}
432
433impl<'a, K: AsRef<str>, V> Iterator for Values<'a, K, V> {
434    type Item = (&'a K, &'a V);
435
436    fn next(&mut self) -> Option<Self::Item> {
437        self.pairs.next().map(|(key, value)| (&key.0, value))
438    }
439
440    fn size_hint(&self) -> (usize, Option<usize>) {
441        self.pairs.size_hint()
442    }
443}
444
445impl<K: AsRef<str>, V> DoubleEndedIterator for Values<'_, K, V> {
446    fn next_back(&mut self) -> Option<Self::Item> {
447        self.pairs.next_back().map(|(key, value)| (&key.0, value))
448    }
449}
450
451/// A walk of a [`MapCache`], in order, taking nothing it is not asked for.
452///
453/// The position is the iterator's own, so a whole pass costs one descent and
454/// `take(n)` costs `n`.
455///
456/// The walk owns the version it started on. Writing to the same map while it is
457/// alive is allowed from any thread, the walk included, and the walk keeps
458/// handing back what its own version holds.
459pub struct Walk<K: AsRef<str> + 'static, V: 'static, T> {
460    pairs: Pairs<'static, K, V>,
461    take: fn(&ByName<K>, &V) -> T,
462    _held: Held<K, V>,
463}
464
465impl<K: AsRef<str>, V, T> Walk<K, V, T> {
466    fn new(held: Held<K, V>, take: fn(&ByName<K>, &V) -> T) -> Self {
467        // SAFETY: two invariants, both of which this module has to keep.
468        //
469        // The map lives in the `Arc`'s allocation and `_held` owns a strong
470        // reference to it, so it outlives every borrow taken here however the
471        // walk is moved. That is what lets the type carry no lifetime.
472        //
473        // The `'static` never reaches a caller: `new` is private, and `take` is
474        // `for<'x> fn(&'x ByName<K>, &'x V) -> T`, which cannot return either
475        // argument. So no `T` can be a reference into the map, and the only
476        // `&'static` that exists is the temporary inside `next` and `next_back`.
477        let pairs =
478            unsafe { std::mem::transmute::<Pairs<'_, K, V>, Pairs<'static, K, V>>(held.iter()) };
479
480        Self {
481            pairs,
482            take,
483            _held: held,
484        }
485    }
486}
487
488impl<K: AsRef<str>, V, T> Iterator for Walk<K, V, T> {
489    type Item = T;
490
491    fn next(&mut self) -> Option<T> {
492        self.pairs
493            .next()
494            .map(|(key, value)| (self.take)(key, value))
495    }
496
497    fn size_hint(&self) -> (usize, Option<usize>) {
498        self.pairs.size_hint()
499    }
500}
501
502impl<K: AsRef<str>, V, T> DoubleEndedIterator for Walk<K, V, T> {
503    fn next_back(&mut self) -> Option<T> {
504        self.pairs
505            .next_back()
506            .map(|(key, value)| (self.take)(key, value))
507    }
508}
509
510impl<K: AsRef<str>, V, T> ExactSizeIterator for Walk<K, V, T> {}
511
512/// One version of a [`MapCache`], in order, borrowed rather than copied.
513pub struct Entries<K, V> {
514    held: Held<K, V>,
515}
516
517impl<'e, K: AsRef<str>, V> IntoIterator for &'e Entries<K, V> {
518    type Item = (&'e K, &'e V);
519    type IntoIter = Values<'e, K, V>;
520
521    fn into_iter(self) -> Self::IntoIter {
522        Values {
523            pairs: self.held.iter(),
524        }
525    }
526}
527
528impl<K: AsRef<str>, V> Entries<K, V> {
529    pub fn iter(&self) -> impl DoubleEndedIterator<Item = (&K, &V)> {
530        self.held.iter().map(|(key, value)| (&key.0, value))
531    }
532
533    pub fn len(&self) -> usize {
534        self.held.size()
535    }
536
537    pub fn is_empty(&self) -> bool {
538        self.held.is_empty()
539    }
540}
541
542impl<K: AsRef<str> + Clone, V: Clone> MapCache<K, V> {
543    pub fn insert(&self, key: K, value: V) -> Option<V> {
544        let replaced = self
545            .entries
546            .rcu(|current| current.insert(ByName(key.clone()), value.clone()));
547
548        replaced.get(key.as_ref()).cloned()
549    }
550
551    pub fn remove(&self, name: &str) -> Option<(K, V)> {
552        let previous = self.entries.rcu(|current| current.remove(name));
553
554        previous
555            .get_key_value(name)
556            .map(|(key, value)| (key.0.clone(), value.clone()))
557    }
558
559    /// Puts `value` at `key` unless a change to it the store settled later
560    /// has been taken already. Answers whether it was put.
561    pub fn insert_settled(&self, key: K, value: V, at: u64) -> bool {
562        let mut settled = held(&self.settled);
563        if !settled.admits(key.as_ref(), at) {
564            return false;
565        }
566
567        settled.took(key.as_ref(), at);
568        self.insert(key, value);
569        true
570    }
571
572    /// Takes `name` away unless a change to it the store settled later has
573    /// been taken already. Answers whether it was.
574    pub fn remove_settled(&self, name: &str, at: u64) -> bool {
575        let mut settled = held(&self.settled);
576        if !settled.admits(name, at) {
577            return false;
578        }
579
580        settled.took(name, at);
581        self.remove(name);
582        let held = self.entries.load();
583        settled.let_go_of_the_long_removed(|name| held.contains_key(name));
584        true
585    }
586
587    /// Empties the map of everything the store settled up to `at`, keeping
588    /// the entries it settled after. Answers whether it did, which it does not
589    /// for a clear settled before one already taken.
590    pub fn clear_settled(&self, at: u64) -> bool {
591        let mut settled = held(&self.settled);
592        if at < settled.cleared {
593            return false;
594        }
595
596        settled.cleared = at;
597        settled.latest = settled.latest.max(at);
598        settled.keys.retain(|_, held| *held > at);
599
600        let later = &settled.keys;
601        self.entries.rcu(|current| {
602            current
603                .iter()
604                .filter(|(key, _)| later.contains_key(key.0.as_ref()))
605                .fold(RedBlackTreeMapSync::new_sync(), |kept, (key, value)| {
606                    kept.insert(key.clone(), value.clone())
607                })
608        });
609        true
610    }
611}
612
613impl<K: AsRef<str> + Debug, V: Debug> Debug for MapCache<K, V> {
614    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
615        let held = self.entries.load();
616
617        f.debug_map()
618            .entries(held.iter().map(|(key, value)| (&key.0, value)))
619            .finish()
620    }
621}
622
623pub struct ReactiveMapCore<K, V> {
624    pub interceptors_any: Arc<Mutex<Vec<(u64, InterceptorAny<K, V>)>>>,
625    pub interceptors_key: Arc<DashMap<K, Vec<(u64, InterceptorKey<K, V>)>>>,
626    pub subscribers_any: Arc<Mutex<Vec<(u64, SubscriberAny<K, V>, SubscriptionMeta)>>>,
627    pub subscribers_key: Arc<DashMap<K, Vec<(u64, SubscriberKey<K, V>, SubscriptionMeta)>>>,
628    pub next_id: Arc<AtomicU64>,
629    pub intercept_depth: InterceptDepth,
630    pub cache: Arc<MapCache<K, V>>,
631}
632
633impl<K, V> Clone for ReactiveMapCore<K, V> {
634    fn clone(&self) -> Self {
635        Self {
636            interceptors_any: self.interceptors_any.clone(),
637            interceptors_key: self.interceptors_key.clone(),
638            subscribers_any: self.subscribers_any.clone(),
639            subscribers_key: self.subscribers_key.clone(),
640            next_id: self.next_id.clone(),
641            intercept_depth: self.intercept_depth.clone(),
642            cache: self.cache.clone(),
643        }
644    }
645}
646
647struct Counted<'a, T>(&'a Mutex<Vec<T>>);
648
649impl<T> Debug for Counted<'_, T> {
650    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
651        match self.0.try_lock() {
652            Ok(list) => write!(f, "{}", list.len()),
653            Err(_) => f.write_str("<locked>"),
654        }
655    }
656}
657
658impl<K: AsRef<str> + Debug + Hash + Eq, V: Debug> Debug for ReactiveMapCore<K, V> {
659    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
660        f.debug_struct("ReactiveMapCore")
661            .field("cache", &self.cache)
662            .field("interceptors_any", &Counted(&self.interceptors_any))
663            .field("subscribers_any", &Counted(&self.subscribers_any))
664            .finish()
665    }
666}
667
668impl<K: ReactiveMapKey, V: ReactiveMapValue> Default for ReactiveMapCore<K, V> {
669    fn default() -> Self {
670        Self::new()
671    }
672}
673
674impl<K: ReactiveMapKey, V: ReactiveMapValue> ReactiveMapCore<K, V> {
675    pub fn new() -> Self {
676        Self::with_capacity(0)
677    }
678
679    /// The same. A tree has nothing to reserve, so `entries` is ignored; the
680    /// signature stays because a caller loading a store knows the size and
681    /// should not have to know that.
682    pub fn with_capacity(_entries: usize) -> Self {
683        Self {
684            interceptors_any: Arc::new(Mutex::new(Vec::new())),
685            interceptors_key: Arc::new(DashMap::new()),
686            subscribers_any: Arc::new(Mutex::new(Vec::new())),
687            subscribers_key: Arc::new(DashMap::new()),
688            next_id: Arc::new(AtomicU64::new(0)),
689            intercept_depth: InterceptDepth::default(),
690            cache: Arc::new(MapCache::new()),
691        }
692    }
693
694    #[track_caller]
695    pub fn subscribe_any<F>(&self, callback: F) -> SignalSubscription
696    where
697        F: Fn(&MapChange<K, V>) + Send + Sync + 'static,
698    {
699        let location = Location::caller();
700        let id = self.next_id.fetch_add(1, Ordering::Relaxed);
701        let meta = SubscriptionMeta {
702            id,
703            location,
704            name: None,
705        };
706        held(&self.subscribers_any).push((id, Arc::new(callback), meta));
707
708        let subs_for_name = self.subscribers_any.clone();
709        let set_name = Arc::new(move |name: &'static str| {
710            label(&mut held(&subs_for_name), id, name);
711        });
712        let subs_for_cleanup = self.subscribers_any.clone();
713        SignalSubscription::new(
714            id,
715            location,
716            set_name,
717            Arc::new(move |id| {
718                forget(&mut held(&subs_for_cleanup), id);
719            }),
720        )
721    }
722
723    #[track_caller]
724    pub fn subscribe_key<F>(&self, key: K, callback: F) -> SignalSubscription
725    where
726        F: Fn(&MapChange<K, V>) + Send + Sync + 'static,
727    {
728        let location = Location::caller();
729        let id = self.next_id.fetch_add(1, Ordering::Relaxed);
730        let meta = SubscriptionMeta {
731            id,
732            location,
733            name: None,
734        };
735        self.subscribers_key
736            .entry(key.clone())
737            .or_default()
738            .push((id, Arc::new(callback), meta));
739
740        let subs_for_name = self.subscribers_key.clone();
741        let key_for_name = key.clone();
742        let set_name = Arc::new(move |name: &'static str| {
743            if let Some(mut list) = subs_for_name.get_mut(&key_for_name) {
744                label(&mut list, id, name);
745            }
746        });
747        let subs_for_cleanup = self.subscribers_key.clone();
748        SignalSubscription::new(
749            id,
750            location,
751            set_name,
752            Arc::new(move |id| {
753                if let Some(mut list) = subs_for_cleanup.get_mut(&key) {
754                    forget(&mut list, id);
755                }
756                subs_for_cleanup.remove_if(&key, |_, list| list.is_empty());
757            }),
758        )
759    }
760
761    pub fn intercept<F>(&self, path: StorePath, callback: F) -> InterceptDisposer
762    where
763        F: Fn(MapChange<K, V>) -> Option<MapChange<K, V>> + Send + Sync + 'static,
764    {
765        let id = self.next_id.fetch_add(1, Ordering::Relaxed);
766        held(&self.interceptors_any).push((id, Arc::new(callback)));
767        let subs = self.interceptors_any.clone();
768        InterceptDisposer {
769            id,
770            path,
771            cleanup: Arc::new(move |id| {
772                held(&subs).retain(|(i, _)| *i != id);
773            }),
774        }
775    }
776
777    pub fn intercept_key<F>(&self, key: K, callback: F) -> InterceptDisposer
778    where
779        F: Fn(MapChange<K, V>) -> Option<MapChange<K, V>> + Send + Sync + 'static,
780    {
781        let id = self.next_id.fetch_add(1, Ordering::Relaxed);
782        self.interceptors_key
783            .entry(key.clone())
784            .or_default()
785            .push((id, Arc::new(callback)));
786        let subs = self.interceptors_key.clone();
787        InterceptDisposer {
788            id,
789            path: StorePath::root(),
790            cleanup: Arc::new(move |id| {
791                if let Some(mut list) = subs.get_mut(&key) {
792                    list.retain(|(i, _)| *i != id);
793                }
794                subs.remove_if(&key, |_, list| list.is_empty());
795            }),
796        }
797    }
798
799    pub fn run_interceptors(
800        &self,
801        path: StorePath,
802        mut change: MapChange<K, V>,
803    ) -> Result<MapChange<K, V>, crate::primitives::intercept::Refusal> {
804        use crate::primitives::intercept::Refusal;
805
806        let Some(_guard) = InterceptGuard::enter(&self.intercept_depth, path) else {
807            return Err(Refusal::Recursed);
808        };
809
810        if let Some(key) = change.key().cloned() {
811            let interceptors = self
812                .interceptors_key
813                .get(&key)
814                .map(|entry| entry.clone())
815                .unwrap_or_default();
816            for (_, interceptor) in interceptors {
817                if let Some(new_change) = interceptor(change.clone()) {
818                    change = new_change;
819                } else {
820                    return Err(Refusal::Said(
821                        "refused by an interceptor on that key".to_string(),
822                    ));
823                }
824            }
825        }
826
827        let interceptors_any = held(&self.interceptors_any).clone();
828        for (_, interceptor) in interceptors_any {
829            if let Some(new_change) = interceptor(change.clone()) {
830                change = new_change;
831            } else {
832                return Err(Refusal::Said(
833                    "refused by an interceptor on the map".to_string(),
834                ));
835            }
836        }
837
838        Ok(change)
839    }
840
841    /// Fires every subscriber interested in `change`.
842    ///
843    /// The callbacks are collected before any of them runs, and every guard is
844    /// released first. A subscriber reacting to a change by writing to the same
845    /// map is ordinary, and neither a `Mutex` nor a `DashMap` shard is
846    /// reentrant, so holding either across the calls deadlocks the thread.
847    pub fn notify(&self, change: &MapChange<K, V>) {
848        let keyed: Vec<_> = match change.key() {
849            Some(k) => self
850                .subscribers_key
851                .get(k)
852                .map(|entries| {
853                    entries
854                        .iter()
855                        .map(|(_, cb, meta)| (cb.clone(), *meta))
856                        .collect()
857                })
858                .unwrap_or_default(),
859
860            // A clear is about every key at once, so everyone watching a key
861            // hears it. Told about nothing, an entry cell goes on reporting a
862            // value the map no longer has, and has no later change to correct
863            // it.
864            None => self
865                .subscribers_key
866                .iter()
867                .flat_map(|entries| {
868                    entries
869                        .value()
870                        .iter()
871                        .map(|(_, cb, meta)| (cb.clone(), *meta))
872                        .collect::<Vec<_>>()
873                })
874                .collect(),
875        };
876
877        let any: Vec<_> = held(&self.subscribers_any)
878            .iter()
879            .map(|(_, cb, meta)| (cb.clone(), *meta))
880            .collect();
881
882        for (cb, meta) in keyed {
883            tracing::trace!(
884                target: "amethystate",
885                subscription_id = meta.id,
886                name = meta.name,
887                location = format!("{}:{}", meta.location.file(), meta.location.line()),
888                "map signal emit → key subscription fire",
889            );
890            cb(change);
891        }
892
893        for (cb, meta) in any {
894            tracing::trace!(
895                target: "amethystate",
896                subscription_id = meta.id,
897                name = meta.name,
898                location = format!("{}:{}", meta.location.file(), meta.location.line()),
899                "map signal emit → any subscription fire",
900            );
901            cb(change);
902        }
903    }
904}
905
906#[cfg(test)]
907mod tests {
908    use super::*;
909
910    #[test]
911    fn the_cache_lists_entries_in_the_order_the_store_keys_them() {
912        let cache = MapCache::<String, u8>::new();
913        let names = ["a1b", "a.b", "10", "9", "100", "a\\b", "cpu", "Cpu"];
914
915        for (n, name) in names.iter().enumerate() {
916            cache.insert((*name).to_string(), n as u8);
917        }
918
919        let under = StorePath::segment("m");
920        let mut by_store = names.to_vec();
921        by_store.sort_by_key(|name| under.push(name).key().as_bytes().to_vec());
922
923        assert_eq!(cache.keys().collect::<Vec<_>>(), by_store);
924    }
925
926    #[test]
927    fn keys_that_come_and_go_leave_a_bounded_record_behind() {
928        let cache = MapCache::<String, u8>::new();
929
930        for n in 0..10_000u64 {
931            cache.insert_settled(format!("pid{n}"), 1, 2 * n);
932            cache.remove_settled(&format!("pid{n}"), 2 * n + 1);
933        }
934
935        assert!(held(&cache.settled).keys.len() <= 2 * FORGET_AFTER as usize);
936    }
937
938    #[test]
939    fn a_removal_long_settled_still_keeps_a_late_insert_out() {
940        let cache = MapCache::<String, u8>::new();
941
942        cache.insert_settled("gone".to_string(), 1, 1);
943        cache.remove_settled("gone", 2);
944        for n in 0..3 * FORGET_AFTER {
945            cache.insert_settled(format!("other{n}"), 1, 3 + 2 * n);
946            cache.remove_settled(&format!("other{n}"), 4 + 2 * n);
947        }
948
949        assert_eq!(
950            (
951                cache.insert_settled("gone".to_string(), 7, 1),
952                cache.get("gone")
953            ),
954            (false, None)
955        );
956    }
957
958    #[test]
959    fn a_name_holding_the_separator_is_one_entry() {
960        let cache = MapCache::<String, u8>::new();
961
962        cache.insert("dark.mode".to_string(), 1);
963
964        assert_eq!(cache.len(), 1);
965        assert_eq!(cache.get("dark.mode"), Some(1));
966        assert_eq!(cache.get("dark"), None);
967        assert_eq!(cache.get("dark\\.mode"), None);
968        assert_eq!(cache.keys().collect::<Vec<_>>(), ["dark.mode"]);
969    }
970
971    #[test]
972    fn a_dropped_key_subscription_leaves_no_entry_behind() {
973        let core = ReactiveMapCore::<String, u64>::new();
974
975        for i in 0..64u64 {
976            let key = format!("col{i}");
977            let sub = core.subscribe_key(key.clone(), |_| {});
978            let held = core.intercept_key(key, Some);
979            drop(sub);
980            held.remove();
981        }
982
983        assert_eq!(core.subscribers_key.len(), 0);
984        assert_eq!(core.interceptors_key.len(), 0);
985    }
986
987    #[test]
988    fn a_key_still_watched_by_someone_keeps_its_entry() {
989        let core = ReactiveMapCore::<String, u64>::new();
990
991        let first = core.subscribe_key("cpu".to_string(), |_| {});
992        let second = core.subscribe_key("cpu".to_string(), |_| {});
993
994        drop(first);
995        assert_eq!(core.subscribers_key.len(), 1);
996
997        drop(second);
998        assert_eq!(core.subscribers_key.len(), 0);
999    }
1000}