Skip to main content

amethystate_core/primitives/
field_core.rs

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