Skip to main content

amethystate_core/primitives/
field_core.rs

1use crate::change::Change;
2use crate::path::StorePath;
3use crate::primitives::intercept::{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
14pub struct FieldCore<T> {
15    pub signal: Signal<T>,
16    pub interceptors: Arc<
17        Mutex<
18            Vec<(
19                u64,
20                Arc<dyn Fn(Change<T>) -> Option<Change<T>> + Send + Sync + 'static>,
21            )>,
22        >,
23    >,
24    pub next_interceptor_id: Arc<AtomicUsize>,
25    pub intercept_depth: Arc<AtomicUsize>,
26}
27
28impl<T> Clone for FieldCore<T> {
29    fn clone(&self) -> Self {
30        Self {
31            signal: self.signal.clone(),
32            interceptors: self.interceptors.clone(),
33            next_interceptor_id: self.next_interceptor_id.clone(),
34            intercept_depth: self.intercept_depth.clone(),
35        }
36    }
37}
38
39impl<T: Clone + 'static> FieldCore<T> {
40    pub fn new_with_signal(initial: Signal<T>) -> Self {
41        Self {
42            signal: initial,
43            interceptors: Arc::new(Mutex::new(Vec::new())),
44            next_interceptor_id: Arc::new(AtomicUsize::new(0)),
45            intercept_depth: Arc::new(AtomicUsize::new(0)),
46        }
47    }
48
49    pub fn new(initial: T) -> Self {
50        Self {
51            signal: Signal::new(initial),
52            interceptors: Arc::new(Mutex::new(Vec::new())),
53            next_interceptor_id: Arc::new(AtomicUsize::new(0)),
54            intercept_depth: Arc::new(AtomicUsize::new(0)),
55        }
56    }
57
58    pub fn get(&self) -> T {
59        self.signal.get()
60    }
61
62    #[track_caller]
63    pub fn subscribe<F>(&self, callback: F) -> SignalSubscription
64    where
65        F: for<'a> Fn(&'a T) + Send + Sync + 'static,
66    {
67        self.signal.subscribe(callback)
68    }
69
70    #[track_caller]
71    pub fn subscribe_with_source<F>(&self, callback: F) -> SignalSubscription
72    where
73        F: for<'a> Fn(&'a T, Option<Uuid>) + Send + Sync + 'static,
74    {
75        self.signal.subscribe_with_source(callback)
76    }
77
78    pub fn intercept<F>(&self, path: StorePath, callback: F) -> InterceptDisposer
79    where
80        F: Fn(Change<T>) -> Option<Change<T>> + Send + Sync + 'static,
81    {
82        let id = self.next_interceptor_id.fetch_add(1, Ordering::Relaxed);
83        held(&self.interceptors).push((id as u64, Arc::new(callback)));
84
85        let interceptors = self.interceptors.clone();
86        InterceptDisposer {
87            id: id as u64,
88            path: path.clone(),
89            cleanup: Arc::new(move |id| {
90                held(&interceptors).retain(|(i, _)| *i != id);
91            }),
92        }
93    }
94
95    pub fn run_interceptors(
96        &self,
97        path: StorePath,
98        value: T,
99        source: Option<Uuid>,
100    ) -> Result<Change<T>, crate::primitives::intercept::Refusal> {
101        use crate::primitives::intercept::Refusal;
102
103        let mut change = Change {
104            source,
105            old_value: self.get(),
106            new_value: value,
107        };
108
109        let Some(_guard) = InterceptGuard::enter(&self.intercept_depth, path) else {
110            return Err(Refusal::Recursed);
111        };
112
113        let interceptors = held(&self.interceptors).clone();
114        for (_, interceptor) in interceptors {
115            if let Some(new_change) = interceptor(change.clone()) {
116                change = new_change;
117            } else {
118                return Err(Refusal::Said(
119                    "refused by an interceptor on the field".to_string(),
120                ));
121            }
122        }
123
124        Ok(change)
125    }
126}