Skip to main content

amethystate_core/primitives/
field_core.rs

1use crate::change::Change;
2use crate::path::StorePath;
3use crate::primitives::intercept::{InterceptDepth, InterceptDisposer, InterceptGuard};
4use crate::primitives::signal::{Signal, SignalSubscription, held};
5use serde::Serialize;
6use serde::de::DeserializeOwned;
7use std::sync::atomic::{AtomicUsize, Ordering};
8use std::sync::{Arc, Mutex};
9use uuid::Uuid;
10
11pub trait FieldValue: DeserializeOwned + Serialize + Clone + Send + Sync + 'static {}
12impl<T: DeserializeOwned + Serialize + Clone + Send + Sync + 'static> FieldValue for T {}
13
14/// A rule a field was declared with: it may put the value right, or turn it
15/// down in its own words.
16pub type DeclaredRule<T> = Arc<dyn Fn(&mut T) -> Result<(), String> + Send + Sync + 'static>;
17
18pub struct FieldCore<T> {
19    pub signal: Signal<T>,
20    pub interceptors: Arc<
21        Mutex<
22            Vec<(
23                u64,
24                Arc<dyn Fn(Change<T>) -> Option<Change<T>> + Send + Sync + 'static>,
25            )>,
26        >,
27    >,
28    pub next_interceptor_id: Arc<AtomicUsize>,
29    pub intercept_depth: InterceptDepth,
30    pub rules: Arc<Mutex<Vec<DeclaredRule<T>>>>,
31}
32
33impl<T> Clone for FieldCore<T> {
34    fn clone(&self) -> Self {
35        Self {
36            signal: self.signal.clone(),
37            interceptors: self.interceptors.clone(),
38            next_interceptor_id: self.next_interceptor_id.clone(),
39            intercept_depth: self.intercept_depth.clone(),
40            rules: self.rules.clone(),
41        }
42    }
43}
44
45impl<T: Clone + 'static> FieldCore<T> {
46    pub fn new_with_signal(initial: Signal<T>) -> Self {
47        Self {
48            signal: initial,
49            interceptors: Arc::new(Mutex::new(Vec::new())),
50            next_interceptor_id: Arc::new(AtomicUsize::new(0)),
51            intercept_depth: InterceptDepth::default(),
52            rules: Arc::new(Mutex::new(Vec::new())),
53        }
54    }
55
56    pub fn new(initial: T) -> Self {
57        Self {
58            signal: Signal::new(initial),
59            interceptors: Arc::new(Mutex::new(Vec::new())),
60            next_interceptor_id: Arc::new(AtomicUsize::new(0)),
61            intercept_depth: InterceptDepth::default(),
62            rules: Arc::new(Mutex::new(Vec::new())),
63        }
64    }
65
66    /// Adds a rule every write has to pass, after the interceptors and for as
67    /// long as the field lives: there is no handle to take it off.
68    pub fn rule<F>(&self, rule: F)
69    where
70        F: Fn(&mut T) -> Result<(), String> + Send + Sync + 'static,
71    {
72        held(&self.rules).push(Arc::new(rule));
73    }
74
75    /// Puts `value` through the declared rules, in the order they were added.
76    pub fn ruled(&self, value: &mut T) -> Result<(), String> {
77        let rules = held(&self.rules).clone();
78        for rule in rules {
79            rule(value)?;
80        }
81        Ok(())
82    }
83
84    pub fn get(&self) -> T {
85        self.signal.get()
86    }
87
88    #[track_caller]
89    pub fn subscribe<F>(&self, callback: F) -> SignalSubscription
90    where
91        F: for<'a> Fn(&'a T) + Send + Sync + 'static,
92    {
93        self.signal.subscribe(callback)
94    }
95
96    #[track_caller]
97    pub fn subscribe_with_source<F>(&self, callback: F) -> SignalSubscription
98    where
99        F: for<'a> Fn(&'a T, Option<Uuid>) + Send + Sync + 'static,
100    {
101        self.signal.subscribe_with_source(callback)
102    }
103
104    pub fn intercept<F>(&self, path: StorePath, callback: F) -> InterceptDisposer
105    where
106        F: Fn(Change<T>) -> Option<Change<T>> + Send + Sync + 'static,
107    {
108        let id = self.next_interceptor_id.fetch_add(1, Ordering::Relaxed);
109        held(&self.interceptors).push((id as u64, Arc::new(callback)));
110
111        let interceptors = self.interceptors.clone();
112        InterceptDisposer {
113            id: id as u64,
114            path: path.clone(),
115            cleanup: Arc::new(move |id| {
116                held(&interceptors).retain(|(i, _)| *i != id);
117            }),
118        }
119    }
120
121    pub fn run_interceptors(
122        &self,
123        path: StorePath,
124        value: T,
125        source: Option<Uuid>,
126    ) -> Result<Change<T>, crate::primitives::intercept::Refusal> {
127        use crate::primitives::intercept::Refusal;
128
129        let mut change = Change {
130            source,
131            old_value: self.get(),
132            new_value: value,
133        };
134
135        let Some(_guard) = InterceptGuard::enter(&self.intercept_depth, path) else {
136            return Err(Refusal::Recursed);
137        };
138
139        let interceptors = held(&self.interceptors).clone();
140        for (_, interceptor) in interceptors {
141            if let Some(new_change) = interceptor(change.clone()) {
142                change = new_change;
143            } else {
144                return Err(Refusal::Said(
145                    "refused by an interceptor on the field".to_string(),
146                ));
147            }
148        }
149
150        self.ruled(&mut change.new_value).map_err(Refusal::Ruled)?;
151
152        Ok(change)
153    }
154}