amethystate_core/primitives/
field_core.rs1use 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}