Skip to main content

hara_native/lang/data/
atom.rs

1use std::fmt;
2use std::sync::{Arc, Mutex};
3
4use crate::lang::protocol::{IDeref, IDisplay, IReset, IWatch};
5
6type Validator<V> = Arc<dyn Fn(&V) -> bool + Send + Sync>;
7type Watch<V> = Arc<dyn Fn(&WatchEntry<V>) + Send + Sync>;
8
9#[derive(Clone)]
10pub struct WatchEntry<V> {
11    pub key: String,
12    pub old_value: V,
13    pub new_value: V,
14}
15
16#[derive(Clone)]
17pub struct Atom<V> {
18    state: Arc<Mutex<V>>,
19    validator: Option<Validator<V>>,
20    watches: Arc<Mutex<Vec<(String, Watch<V>)>>>,
21}
22impl<V> Atom<V> {
23    pub fn new(value: V) -> Self {
24        Self {
25            state: Arc::new(Mutex::new(value)),
26            validator: None,
27            watches: Arc::new(Mutex::new(Vec::new())),
28        }
29    }
30    pub fn same_identity(&self, other: &Self) -> bool {
31        Arc::ptr_eq(&self.state, &other.state)
32    }
33    pub fn identity_address(&self) -> usize {
34        Arc::as_ptr(&self.state) as usize
35    }
36    pub fn with_validator(
37        value: V,
38        validator: impl Fn(&V) -> bool + Send + Sync + 'static,
39    ) -> Self {
40        Self {
41            validator: Some(Arc::new(validator)),
42            ..Self::new(value)
43        }
44    }
45    pub fn add_watch(
46        &self,
47        key: impl Into<String>,
48        watch: impl Fn(&WatchEntry<V>) + Send + Sync + 'static,
49    ) {
50        let key = key.into();
51        let mut watches = self.watches.lock().expect("atom watches");
52        watches.retain(|(candidate, _)| candidate != &key);
53        watches.push((key, Arc::new(watch)));
54    }
55    pub fn remove_watch(&self, key: &str) {
56        self.watches
57            .lock()
58            .expect("atom watches")
59            .retain(|(candidate, _)| candidate != key);
60    }
61    fn accepts(&self, value: &V) -> bool {
62        self.validator
63            .as_ref()
64            .map_or(true, |validator| validator(value))
65    }
66}
67impl<V: Clone> Atom<V> {
68    pub fn deref_value(&self) -> V {
69        self.state.lock().expect("atom state").clone()
70    }
71    pub fn reset(&self, new_value: V) -> Result<V, String> {
72        if !self.accepts(&new_value) {
73            return Err("atom validator rejected value".into());
74        }
75        let old_value = {
76            let mut state = self.state.lock().expect("atom state");
77            std::mem::replace(&mut *state, new_value.clone())
78        };
79        self.notify(old_value, new_value.clone());
80        Ok(new_value)
81    }
82    pub fn swap(&self, f: impl FnOnce(&V) -> V) -> Result<V, String> {
83        let (old_value, new_value) = {
84            let mut state = self.state.lock().expect("atom state");
85            let old = state.clone();
86            let new = f(&old);
87            if !self.accepts(&new) {
88                return Err("atom validator rejected value".into());
89            }
90            *state = new.clone();
91            (old, new)
92        };
93        self.notify(old_value, new_value.clone());
94        Ok(new_value)
95    }
96    fn notify(&self, old_value: V, new_value: V) {
97        for (key, watch) in self.watches.lock().expect("atom watches").iter() {
98            watch(&WatchEntry {
99                key: key.clone(),
100                old_value: old_value.clone(),
101                new_value: new_value.clone(),
102            });
103        }
104    }
105}
106impl<V: Clone + PartialEq> Atom<V> {
107    pub fn compare_and_set(&self, old: &V, new: V) -> Result<bool, String> {
108        if !self.accepts(&new) {
109            return Ok(false);
110        }
111        let prior = {
112            let mut state = self.state.lock().expect("atom state");
113            if &*state != old {
114                return Ok(false);
115            }
116            let prior = state.clone();
117            *state = new.clone();
118            prior
119        };
120        self.notify(prior, new);
121        Ok(true)
122    }
123}
124impl<V: Clone> IDeref for Atom<V> {
125    type Output = V;
126    fn deref(&self) -> V {
127        self.deref_value()
128    }
129}
130impl<V: Clone> IReset<V> for Atom<V> {
131    type Error = String;
132
133    fn reset(&self, value: V) -> Result<V, Self::Error> {
134        Atom::reset(self, value)
135    }
136}
137impl<V: Clone> IWatch<V> for Atom<V> {
138    type Key = String;
139    type WatchEntry = WatchEntry<V>;
140
141    fn add_watch(&self, key: Self::Key, watch: impl Fn(&Self::WatchEntry) + Send + Sync + 'static) {
142        Atom::add_watch(self, key, watch);
143    }
144
145    fn remove_watch(&self, key: &Self::Key) {
146        Atom::remove_watch(self, key);
147    }
148
149    fn notify_watches(&self, old_value: V, new_value: V) {
150        self.notify(old_value, new_value);
151    }
152}
153impl<V: fmt::Display + Clone> IDisplay for Atom<V> {
154    fn display(&self) -> String {
155        format!("#atom <{}>", self.deref_value())
156    }
157}
158impl<V: fmt::Display + Clone> fmt::Display for Atom<V> {
159    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
160        f.write_str(&self.display())
161    }
162}
163impl<V> fmt::Debug for Atom<V> {
164    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
165        f.debug_struct("Atom").finish_non_exhaustive()
166    }
167}
168
169#[cfg(test)]
170mod tests {
171    use super::Atom;
172    use crate::lang::protocol::{IReset, IWatch};
173    use std::sync::{Arc, Mutex};
174    #[test]
175    fn validates_swaps_and_notifies() {
176        let atom = Atom::with_validator(1, |v| *v >= 0);
177        let seen = Arc::new(Mutex::new(Vec::new()));
178        let output = seen.clone();
179        atom.add_watch("test", move |event| {
180            output
181                .lock()
182                .unwrap()
183                .push((event.old_value, event.new_value))
184        });
185        assert_eq!(atom.swap(|v| v + 2).unwrap(), 3);
186        assert!(atom.reset(-1).is_err());
187        assert_eq!(&*seen.lock().unwrap(), &[(1, 3)]);
188        assert_eq!(IReset::reset(&atom, 4).unwrap(), 4);
189        IWatch::remove_watch(&atom, &"test".to_string());
190        IWatch::notify_watches(&atom, 4, 5);
191        assert_eq!(seen.lock().unwrap().len(), 2);
192    }
193}