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
21pub trait MapEntryPath {
23 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
40pub enum EntryOf<K> {
58 Took(K),
60
61 ThePathItself,
64
65 NameWillNotRead,
67
68 NotThisMaps,
72}
73
74pub 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 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#[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 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
166impl<T> Borrow<str> for Id<T> {
170 fn borrow(&self) -> &str {
171 &self.spelled
172 }
173}
174
175impl<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
203impl<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
231pub 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#[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#[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 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 pub fn view(&self) -> Entries<K, V> {
404 Entries {
405 held: self.entries.load_full(),
406 }
407 }
408
409 pub fn keys(&self) -> Walk<K, V, K> {
411 Walk::new(self.entries.load_full(), |key, _| key.0.clone())
412 }
413
414 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;
425pub 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
451pub 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 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
512pub 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 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 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 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 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 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 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}