pax_runtime_api/properties/
shared_property.rs1use super::{LocalProperty, PropertyBinding, PropertyValue, UntypedProperty};
2use crate::Interpolatable;
3use serde::{Deserialize, Serialize};
4use std::{
5 cell::Cell,
6 collections::HashMap,
7 sync::{
8 atomic::{AtomicU64, Ordering},
9 Arc, Mutex, MutexGuard, Weak,
10 },
11};
12
13use super::shared_graph::{with_current_graph, GraphInbox};
14
15pub trait SharedPropertyValue: PropertyValue + Send + Sync {}
19impl<T: PropertyValue + Send + Sync> SharedPropertyValue for T {}
20
21static NEXT_ID: AtomicU64 = AtomicU64::new(1);
22
23pub(super) fn next_id() -> u64 {
24 NEXT_ID
25 .fetch_update(Ordering::Relaxed, Ordering::Relaxed, |id| id.checked_add(1))
26 .expect("property identity space exhausted")
27}
28
29pub(super) fn lock<T>(mutex: &Mutex<T>) -> MutexGuard<'_, T> {
30 mutex
33 .lock()
34 .unwrap_or_else(|poisoned| poisoned.into_inner())
35}
36
37thread_local! {
38 static MUTATING: Cell<bool> = const { Cell::new(false) };
39}
40
41struct MutationGuard;
42
43impl MutationGuard {
44 fn enter() -> Self {
45 MUTATING.with(|active| {
46 assert!(
47 !active.replace(true),
48 "property mutation callbacks must not mutate another property recursively"
49 );
50 });
51 Self
52 }
53}
54
55impl Drop for MutationGuard {
56 fn drop(&mut self) {
57 MUTATING.with(|active| active.set(false));
58 }
59}
60
61pub(super) struct Snapshot<T> {
62 pub revision: u64,
63 pub value: Arc<T>,
64}
65
66pub(super) struct SharedCell<T> {
67 pub id: u64,
68 value: Mutex<Snapshot<T>>,
69 writer: Mutex<()>,
70 subscribers: Mutex<HashMap<u64, Weak<GraphInbox>>>,
71}
72
73impl<T> Drop for SharedCell<T> {
74 fn drop(&mut self) {
75 let subscribers = self
78 .subscribers
79 .get_mut()
80 .unwrap_or_else(|e| e.into_inner());
81 for inbox in subscribers.values().filter_map(Weak::upgrade) {
82 inbox.publish(self.id);
83 }
84 }
85}
86
87impl<T: SharedPropertyValue> SharedCell<T> {
88 pub fn snapshot(&self) -> Snapshot<T> {
89 let snapshot = lock(&self.value);
90 Snapshot {
91 revision: snapshot.revision,
92 value: snapshot.value.clone(),
93 }
94 }
95
96 fn change(&self, origin: Option<u64>, update: impl FnOnce(&T) -> Option<T>) -> (bool, u64) {
97 let mutation = MutationGuard::enter();
98 let writer = lock(&self.writer);
99 let before = self.snapshot();
100 let Some(value) = update(&before.value) else {
101 return (false, before.revision);
102 };
103 let revision = before
104 .revision
105 .checked_add(1)
106 .expect("property revision exhausted");
107 let value = Arc::new(value);
108 let previous = {
109 let mut current = lock(&self.value);
110 std::mem::replace(&mut *current, Snapshot { revision, value })
111 };
112 drop(writer);
113 drop(mutation);
114 drop(previous);
117 drop(before);
118 self.notify(origin);
119 (true, revision)
120 }
121
122 fn notify(&self, origin: Option<u64>) {
123 let subscribers: Vec<_> = {
124 let mut subscribers = lock(&self.subscribers);
125 subscribers.retain(|_, inbox| inbox.strong_count() != 0);
126 subscribers
127 .iter()
128 .filter(|(id, _)| Some(**id) != origin)
129 .filter_map(|(_, inbox)| inbox.upgrade())
130 .collect()
131 };
132 for inbox in subscribers {
133 inbox.publish(self.id);
134 }
135 }
136
137 pub fn publish_local(&self, value: T, graph: u64) -> u64 {
138 self.change(Some(graph), |_| Some(value)).1
139 }
140
141 pub fn subscribe(&self, id: u64, inbox: &Arc<GraphInbox>) {
142 lock(&self.subscribers).insert(id, Arc::downgrade(inbox));
143 }
144
145 pub fn unsubscribe(&self, id: u64) {
146 lock(&self.subscribers).remove(&id);
147 }
148}
149
150pub struct Property<T> {
159 pub(super) cell: Arc<SharedCell<T>>,
160}
161
162impl<T> Clone for Property<T> {
163 fn clone(&self) -> Self {
164 Self {
165 cell: self.cell.clone(),
166 }
167 }
168}
169
170impl<T: SharedPropertyValue> Property<T> {
171 pub fn new(value: T) -> Self {
173 Self {
174 cell: Arc::new(SharedCell {
175 id: next_id(),
176 value: Mutex::new(Snapshot {
177 revision: 0,
178 value: Arc::new(value),
179 }),
180 writer: Mutex::new(()),
181 subscribers: Mutex::new(HashMap::new()),
182 }),
183 }
184 }
185
186 pub fn published(&self) -> Published<T> {
188 Published(self.clone())
189 }
190
191 pub fn get(&self) -> T {
193 self.read(Clone::clone)
194 }
195
196 pub fn read<R>(&self, f: impl FnOnce(&T) -> R) -> R {
199 let snapshot = self.cell.snapshot();
200 f(&snapshot.value)
201 }
202
203 pub fn observed_revision(&self) -> Option<u64> {
207 with_current_graph(|graph| graph.observed_revision(self))
208 }
209
210 pub fn set(&self, value: T) {
212 self.cell.change(None, |_| Some(value));
213 }
214
215 pub(crate) fn publish_guarded(
216 &self,
217 value: T,
218 authority: &crate::async_runtime::Authority,
219 ) -> Result<(), crate::async_runtime::Closed<T>> {
220 let mutation = MutationGuard::enter();
221 let Some(authority) = authority.acquire() else {
222 return Err(crate::async_runtime::Closed(value));
223 };
224 let writer = lock(&self.cell.writer);
225 let previous = {
226 let mut current = lock(&self.cell.value);
227 let revision = current
228 .revision
229 .checked_add(1)
230 .expect("property revision exhausted");
231 std::mem::replace(
232 &mut *current,
233 Snapshot {
234 revision,
235 value: Arc::new(value),
236 },
237 )
238 };
239 drop(writer);
240 drop(authority);
241 drop(mutation);
242 drop(previous);
244 self.cell.notify(None);
245 Ok(())
246 }
247
248 pub fn update(&self, f: impl FnOnce(&mut T)) {
256 self.cell.change(None, |old| {
257 let mut draft = old.clone();
258 f(&mut draft);
259 Some(draft)
260 });
261 }
262
263 pub fn set_if_neq(&self, value: T) -> bool
265 where
266 T: PartialEq,
267 {
268 self.cell
269 .change(None, |old| (old != &value).then_some(value))
270 .0
271 }
272
273 pub fn revision(&self) -> u64 {
275 self.cell.snapshot().revision
276 }
277
278 pub fn local(&self) -> LocalProperty<T> {
284 with_current_graph(|graph| graph.attach(self))
285 }
286
287 pub fn untyped(&self) -> UntypedProperty {
289 self.local().untyped()
290 }
291
292 pub fn replace_with(&self, source: LocalProperty<T>) {
294 let local = self.local();
295 local.replace_with(source);
296 }
297}
298
299impl<T: SharedPropertyValue> Default for Property<T> {
300 fn default() -> Self {
301 Self::new(T::default())
302 }
303}
304
305impl<T: SharedPropertyValue + std::fmt::Debug> std::fmt::Debug for Property<T> {
306 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
307 self.read(|value| f.debug_tuple("Property").field(value).finish())
308 }
309}
310
311impl<T: SharedPropertyValue> Interpolatable for Property<T> {
312 fn interpolate(&self, other: &Self, t: f64) -> Self {
313 Self::new(self.get().interpolate(&other.get(), t))
314 }
315}
316
317impl<T: SharedPropertyValue + Serialize> Serialize for Property<T> {
318 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
319 self.read(|value| value.serialize(serializer))
320 }
321}
322
323impl<'de, T: SharedPropertyValue + Deserialize<'de>> Deserialize<'de> for Property<T> {
324 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
325 T::deserialize(deserializer).map(Self::new)
326 }
327}
328
329impl<T: SharedPropertyValue> PropertyBinding<T> for Property<T> {
330 fn from_value(value: T) -> Self {
331 Self::new(value)
332 }
333 fn local(&self) -> LocalProperty<T> {
334 self.local()
335 }
336 fn from_local(source: LocalProperty<T>) -> Self {
337 with_current_graph(|graph| graph.share(source))
338 }
339 fn value_for_conversion(&self) -> T {
340 if super::GRAPH_CONVERSION.with(Cell::get) {
341 self.local().get()
342 } else {
343 self.get()
344 }
345 }
346}
347
348#[derive(Clone)]
357pub struct Published<T>(Property<T>);
358impl<T: SharedPropertyValue> Published<T> {
359 pub fn get(&self) -> T {
360 self.0.get()
361 }
362 pub fn read<R>(&self, callback: impl FnOnce(&T) -> R) -> R {
363 self.0.read(callback)
364 }
365 pub fn revision(&self) -> u64 {
366 self.0.revision()
367 }
368 pub fn local(&self) -> LocalProperty<T> {
369 let source = self.0.local();
370 let deps = [source.untyped()];
371 LocalProperty::computed(move || source.get(), &deps)
372 }
373}