pax_runtime_api/properties/
shared_graph.rs1use super::{
2 properties_table::PROPERTY_TABLE,
3 shared_property::{lock, next_id, SharedCell},
4 LocalProperty, Property, SharedPropertyValue,
5};
6use std::{
7 any::Any,
8 cell::{Cell, RefCell},
9 collections::{HashMap, HashSet, VecDeque},
10 rc::Rc,
11 sync::{Arc, Mutex, Weak},
12};
13
14#[derive(Default)]
15struct InboxState {
16 attached: HashSet<u64>,
17 pending: HashSet<u64>,
18 order: VecDeque<u64>,
19 closed: bool,
20}
21
22pub(super) struct GraphInbox {
23 state: Mutex<InboxState>,
24 wake: Mutex<Arc<dyn Fn() + Send + Sync>>,
25}
26
27impl GraphInbox {
28 pub fn publish(&self, id: u64) {
29 let wake = {
30 let mut state = lock(&self.state);
31 if state.closed || !state.attached.contains(&id) {
32 return;
33 }
34 let wake = state.pending.is_empty();
35 if state.pending.insert(id) {
36 state.order.push_back(id);
37 }
38 wake
39 };
40 if wake {
41 self.wake();
42 }
43 }
44
45 fn wake(&self) {
46 let wake = lock(&self.wake).clone();
47 wake();
48 }
49
50 fn attach(&self, id: u64) {
51 lock(&self.state).attached.insert(id);
52 }
53
54 fn detach(&self, id: u64) {
55 let mut state = lock(&self.state);
56 state.attached.remove(&id);
57 state.pending.remove(&id);
58 state.order.retain(|queued| *queued != id);
59 }
60}
61
62thread_local! {
63 static ACTIVE_GRAPH: RefCell<Option<PropertyGraph>> = const { RefCell::new(None) };
64 static ACTIVE_SCOPE: RefCell<Option<std::rc::Weak<BindingScopeInner>>> = const { RefCell::new(None) };
65}
66
67pub(super) fn with_current_graph<R>(f: impl FnOnce(&PropertyGraph) -> R) -> R {
68 let graph = ACTIVE_GRAPH
69 .with(|active| active.borrow().clone())
70 .expect("shared property graph access requires PropertyGraph::enter on its owner thread");
71 f(&graph)
72}
73
74pub(super) fn current_graph_id() -> Option<u64> {
75 ACTIVE_GRAPH.with(|active| active.borrow().as_ref().map(|graph| graph.inner.id))
76}
77
78pub(super) fn current_binding_scope() -> Option<std::rc::Weak<BindingScopeInner>> {
79 ACTIVE_SCOPE.with(|active| active.borrow().clone())
80}
81
82pub(super) fn owner_evaluator<T: 'static>(evaluator: impl Fn() -> T + 'static) -> impl Fn() -> T {
85 let owner = ACTIVE_GRAPH.with(|active| {
86 active
87 .borrow()
88 .as_ref()
89 .map(|graph| Rc::downgrade(&graph.inner))
90 });
91 let scope = ACTIVE_SCOPE.with(|active| active.borrow().clone());
92 move || {
93 let graph = owner.as_ref().map(|owner| PropertyGraph {
94 inner: owner
95 .upgrade()
96 .expect("cannot evaluate a binding after its graph has been destroyed"),
97 });
98 let _entered = graph.as_ref().map(PropertyGraph::enter);
99 let previous = ACTIVE_SCOPE.with(|active| active.replace(scope.clone()));
103 let _scope = BindingScopeGuard { previous };
104 evaluator()
105 }
106}
107
108trait Attachment {
109 fn as_any(&self) -> &dyn Any;
110 fn prepare_import(&self) -> Option<Box<dyn FnOnce()>>;
111 fn live(&self) -> bool;
112 fn dormant(&self) -> Box<dyn DormantAttachment>;
113}
114
115trait DormantAttachment {
116 fn live(&self) -> bool;
117 fn restore(&self, graph: &PropertyGraph);
118 fn local_id(&self) -> super::private::PropertyId;
119}
120
121struct Dormant<T: SharedPropertyValue> {
122 shared: Weak<SharedCell<T>>,
123 local: super::private::PropertyId,
124}
125
126impl<T: SharedPropertyValue> DormantAttachment for Dormant<T> {
127 fn local_id(&self) -> super::private::PropertyId {
128 self.local
129 }
130 fn live(&self) -> bool {
131 self.shared.strong_count() != 0
132 && PROPERTY_TABLE.with(|table| table.has_live_entry(self.local))
133 }
134 fn restore(&self, graph: &PropertyGraph) {
135 let Some(cell) = self.shared.upgrade() else {
136 return;
137 };
138 let Some(untyped) = super::UntypedProperty::try_from_id(self.local) else {
139 return;
140 };
141 let local = LocalProperty::new_from_untyped(untyped);
142 let property = Property { cell };
143 graph.inner.inbox.attach(property.cell.id);
144 property.cell.subscribe(graph.inner.id, &graph.inner.inbox);
145 let snapshot = property.cell.snapshot();
146 PROPERTY_TABLE.with(|table| table.import_shared(self.local, (*snapshot.value).clone()));
147 PROPERTY_TABLE.with(|table| table.invalidate(self.local));
148 graph.insert(&property, local, snapshot.revision);
149 }
150}
151
152struct TypedAttachment<T: SharedPropertyValue> {
153 graph: u64,
154 shared: Weak<SharedCell<T>>,
155 local: LocalProperty<T>,
156 revision: Rc<Cell<u64>>,
157 _publication_effect: LocalProperty<()>,
158}
159
160impl<T: SharedPropertyValue> Attachment for TypedAttachment<T> {
161 fn as_any(&self) -> &dyn Any {
162 self
163 }
164 fn live(&self) -> bool {
165 self.shared.strong_count() != 0
166 }
167 fn dormant(&self) -> Box<dyn DormantAttachment> {
168 Box::new(Dormant::<T> {
169 shared: self.shared.clone(),
170 local: self.local.untyped.get_id(),
171 })
172 }
173 fn prepare_import(&self) -> Option<Box<dyn FnOnce()>> {
174 let snapshot = self.shared.upgrade()?.snapshot();
175 if snapshot.revision <= self.revision.get() {
176 return None;
177 }
178 let local = self.local.clone();
179 let revision = self.revision.clone();
180 Some(Box::new(move || {
181 PROPERTY_TABLE.with(|table| {
182 table.import_shared(local.untyped.get_id(), (*snapshot.value).clone())
183 });
184 revision.set(snapshot.revision);
185 }))
186 }
187}
188
189impl<T: SharedPropertyValue> Drop for TypedAttachment<T> {
190 fn drop(&mut self) {
191 if let Some(shared) = self.shared.upgrade() {
192 shared.unsubscribe(self.graph);
193 }
194 let _ =
195 PROPERTY_TABLE.try_with(|table| table.clear_shared::<T>(self.local.untyped.get_id()));
196 }
197}
198
199struct GraphInner {
200 id: u64,
201 inbox: Arc<GraphInbox>,
202 attachments: RefCell<HashMap<u64, Box<dyn Attachment>>>,
203 dormant: RefCell<HashMap<u64, Box<dyn DormantAttachment>>>,
204 owners: RefCell<HashMap<u64, HashSet<u64>>>,
205 scopes: RefCell<HashMap<u64, std::rc::Weak<BindingScopeInner>>>,
206 closed: Cell<bool>,
207}
208
209impl Drop for GraphInner {
210 fn drop(&mut self) {
211 let mut state = lock(&self.inbox.state);
212 state.closed = true;
213 state.attached.clear();
214 state.pending.clear();
215 state.order.clear();
216 }
217}
218
219#[derive(Clone)]
227pub struct PropertyGraph {
228 inner: Rc<GraphInner>,
229}
230
231impl PropertyGraph {
232 pub fn new(wake: impl Fn() + Send + Sync + 'static) -> Self {
234 Self {
235 inner: Rc::new(GraphInner {
236 id: next_id(),
237 inbox: Arc::new(GraphInbox {
238 state: Mutex::new(InboxState::default()),
239 wake: Mutex::new(Arc::new(wake)),
240 }),
241 attachments: RefCell::new(HashMap::new()),
242 dormant: RefCell::new(HashMap::new()),
243 owners: RefCell::new(HashMap::new()),
244 scopes: RefCell::new(HashMap::new()),
245 closed: Cell::new(false),
246 }),
247 }
248 }
249
250 pub fn id(&self) -> u64 {
253 self.inner.id
254 }
255
256 pub fn has_pending(&self) -> bool {
259 let state = lock(&self.inner.inbox.state);
260 !state.closed && !state.pending.is_empty()
261 }
262
263 pub fn set_waker(&self, wake: impl Fn() + Send + Sync + 'static) {
266 *lock(&self.inner.inbox.wake) = Arc::new(wake);
267 let pending = {
268 let state = lock(&self.inner.inbox.state);
269 !state.closed && !state.pending.is_empty()
270 };
271 if pending {
272 self.inner.inbox.wake();
273 }
274 }
275
276 pub fn enter(&self) -> PropertyGraphGuard {
278 assert!(!self.inner.closed.get(), "property graph is closed");
279 let previous = ACTIVE_GRAPH.with(|active| active.replace(Some(self.clone())));
280 let previous_scope = ACTIVE_SCOPE.with(|active| active.take());
281 PropertyGraphGuard {
282 previous,
283 previous_scope,
284 }
285 }
286
287 pub(super) fn attach<T: SharedPropertyValue>(
288 &self,
289 property: &Property<T>,
290 ) -> LocalProperty<T> {
291 assert!(!self.inner.closed.get(), "property graph is closed");
292 let detached = ACTIVE_SCOPE.with(|active| {
293 active
294 .borrow()
295 .as_ref()
296 .is_some_and(|scope| scope.upgrade().is_none_or(|scope| scope.closed.get()))
297 });
298 if detached {
299 if let Some(attachment) = self.inner.attachments.borrow().get(&property.cell.id) {
302 return attachment
303 .as_any()
304 .downcast_ref::<TypedAttachment<T>>()
305 .unwrap()
306 .local
307 .clone();
308 }
309 if let Some(dormant) = self.inner.dormant.borrow().get(&property.cell.id) {
310 if let Some(local) = super::UntypedProperty::try_from_id(dormant.local_id()) {
311 return LocalProperty::new_from_untyped(local);
312 }
313 }
314 return LocalProperty::new(property.get());
315 }
316 self.record_owner(property.cell.id);
317 self.restore(property.cell.id);
318 if let Some(attachment) = self.inner.attachments.borrow().get(&property.cell.id) {
319 return attachment
320 .as_any()
321 .downcast_ref::<TypedAttachment<T>>()
322 .expect("shared property type changed")
323 .local
324 .clone();
325 }
326 self.inner.inbox.attach(property.cell.id);
329 property.cell.subscribe(self.inner.id, &self.inner.inbox);
330 let snapshot = property.cell.snapshot();
331 let local = LocalProperty::new((*snapshot.value).clone());
332 self.insert(property, local.clone(), snapshot.revision);
333 local
334 }
335
336 pub(super) fn observed_revision<T: SharedPropertyValue>(
337 &self,
338 property: &Property<T>,
339 ) -> Option<u64> {
340 self.inner
341 .attachments
342 .borrow()
343 .get(&property.cell.id)
344 .map(|entry| {
345 entry
346 .as_any()
347 .downcast_ref::<TypedAttachment<T>>()
348 .expect("shared property type changed")
349 .revision
350 .get()
351 })
352 }
353
354 pub(super) fn share<T: SharedPropertyValue>(&self, local: LocalProperty<T>) -> Property<T> {
355 if let Some(cell) =
356 PROPERTY_TABLE.with(|table| table.shared_storage::<T>(local.untyped.get_id()))
357 {
358 self.record_owner(cell.id);
359 return Property { cell };
360 }
361 let property = Property::new(local.get());
362 self.record_owner(property.cell.id);
363 self.inner.inbox.attach(property.cell.id);
364 property.cell.subscribe(self.inner.id, &self.inner.inbox);
365 self.insert(&property, local, 0);
366 property
367 }
368
369 fn insert<T: SharedPropertyValue>(
370 &self,
371 property: &Property<T>,
372 local: LocalProperty<T>,
373 revision: u64,
374 ) {
375 let seen = Rc::new(Cell::new(revision));
376 let weak = Arc::downgrade(&property.cell);
377 let weak_writer = weak.clone();
378 let writer_seen = seen.clone();
379 let graph = self.inner.id;
380 PROPERTY_TABLE.with(|table| {
381 table.set_shared(
382 local.untyped.get_id(),
383 weak.clone(),
384 Rc::new(move |value: &T| {
385 if let Some(shared) = weak_writer.upgrade() {
386 writer_seen.set(shared.publish_local(value.clone(), graph));
387 }
388 }),
389 )
390 });
391 let _graph = self.enter();
394 let observed = local.clone();
395 let effect = LocalProperty::computed(
396 move || {
397 observed.get();
398 },
399 &[local.untyped()],
400 );
401 super::register_effect_property(&effect);
402 self.inner.attachments.borrow_mut().insert(
403 property.cell.id,
404 Box::new(TypedAttachment {
405 graph,
406 shared: weak,
407 local,
408 revision: seen,
409 _publication_effect: effect,
410 }),
411 );
412 }
413
414 pub fn import(&self) -> usize {
418 self.import_batch(1024)
419 }
420
421 pub fn import_batch(&self, limit: usize) -> usize {
424 if self.inner.closed.get() {
425 return 0;
426 }
427 let _entered = self.enter();
428 let pending: Vec<_> = {
429 let mut state = lock(&self.inner.inbox.state);
430 let count = limit.min(state.order.len());
431 let pending: Vec<_> = state.order.drain(..count).collect();
432 for id in &pending {
433 state.pending.remove(id);
434 }
435 pending
436 };
437 let actions: Vec<_> = {
438 let attachments = self.inner.attachments.borrow();
439 pending
440 .into_iter()
441 .filter_map(|id| attachments.get(&id)?.prepare_import())
442 .collect()
443 };
444 let count = actions.len();
445 for action in actions {
446 action();
447 }
448 self.collect();
449 if self.has_pending() {
450 self.inner.inbox.wake();
451 }
452 count
453 }
454
455 pub fn collect(&self) {
457 self.inner
458 .dormant
459 .borrow_mut()
460 .retain(|_, attachment| attachment.live());
461 let dead: Vec<_> = self
462 .inner
463 .attachments
464 .borrow()
465 .iter()
466 .filter_map(|(id, attachment)| (!attachment.live()).then_some(*id))
467 .collect();
468 let retired: Vec<_> = {
469 let mut attachments = self.inner.attachments.borrow_mut();
470 dead.into_iter()
471 .filter_map(|id| {
472 self.inner.inbox.detach(id);
473 if let Some(owners) = self.inner.owners.borrow_mut().remove(&id) {
474 let scopes = self.inner.scopes.borrow();
475 for owner in owners {
476 if let Some(scope) = scopes.get(&owner).and_then(std::rc::Weak::upgrade)
477 {
478 scope.properties.borrow_mut().remove(&id);
479 }
480 }
481 }
482 attachments.remove(&id)
483 })
484 .collect()
485 };
486 drop(retired);
487 }
488
489 #[doc(hidden)]
492 pub fn stop_publications(&self) {
493 let mut state = lock(&self.inner.inbox.state);
494 state.closed = true;
495 state.attached.clear();
496 state.pending.clear();
497 state.order.clear();
498 }
499
500 pub fn close(&self) {
501 self.inner.closed.set(true);
502 {
503 let mut state = lock(&self.inner.inbox.state);
504 state.closed = true;
505 state.attached.clear();
506 state.pending.clear();
507 state.order.clear();
508 }
509 let retired = std::mem::take(&mut *self.inner.attachments.borrow_mut());
510 self.inner.dormant.borrow_mut().clear();
511 self.inner.owners.borrow_mut().clear();
512 self.inner.scopes.borrow_mut().clear();
513 drop(retired);
514 }
515
516 #[doc(hidden)]
519 pub fn binding_scope(&self) -> BindingScope {
520 let scope = BindingScope {
521 inner: Rc::new(BindingScopeInner {
522 graph: Rc::downgrade(&self.inner),
523 id: next_id(),
524 closed: Cell::new(false),
525 properties: RefCell::new(HashSet::new()),
526 }),
527 };
528 self.inner
529 .scopes
530 .borrow_mut()
531 .insert(scope.inner.id, Rc::downgrade(&scope.inner));
532 scope
533 }
534
535 fn restore(&self, id: u64) {
536 let dormant = self.inner.dormant.borrow_mut().remove(&id);
537 if let Some(dormant) = dormant {
538 dormant.restore(self);
539 }
540 }
541
542 fn record_owner(&self, id: u64) {
543 let scope =
544 ACTIVE_SCOPE.with(|scope| scope.borrow().as_ref().and_then(std::rc::Weak::upgrade));
545 let mut owners = self.inner.owners.borrow_mut();
546 let owners = owners.entry(id).or_default();
547 if let Some(scope) = scope.filter(|scope| scope.graph.ptr_eq(&Rc::downgrade(&self.inner))) {
548 assert!(
549 !scope.closed.get(),
550 "cannot attach properties for an unmounted view"
551 );
552 scope.properties.borrow_mut().insert(id);
553 owners.insert(scope.id);
554 } else if owners.is_empty() {
555 owners.insert(0);
556 }
557 }
558
559 fn release_owner(&self, scope: &BindingScopeInner) {
560 let retired: Vec<_> = scope
561 .properties
562 .borrow()
563 .iter()
564 .filter_map(|id| {
565 let mut owners = self.inner.owners.borrow_mut();
566 let set = owners.get_mut(id)?;
567 set.remove(&scope.id);
568 if !set.is_empty() {
569 return None;
570 }
571 owners.remove(id);
572 self.inner
573 .attachments
574 .borrow_mut()
575 .remove(id)
576 .map(|attachment| (*id, attachment))
577 })
578 .collect();
579 for (id, attachment) in retired {
580 self.inner
581 .dormant
582 .borrow_mut()
583 .insert(id, attachment.dormant());
584 self.inner.inbox.detach(id);
585 drop(attachment);
586 }
587 }
588}
589
590pub(super) struct BindingScopeInner {
591 graph: std::rc::Weak<GraphInner>,
592 id: u64,
593 pub(super) closed: Cell<bool>,
594 properties: RefCell<HashSet<u64>>,
595}
596
597impl Drop for BindingScopeInner {
598 fn drop(&mut self) {
599 if let Some(inner) = self.graph.upgrade() {
600 inner.scopes.borrow_mut().remove(&self.id);
601 PropertyGraph { inner }.release_owner(self);
602 }
603 }
604}
605
606#[doc(hidden)]
608#[derive(Clone)]
609pub struct BindingScope {
610 inner: Rc<BindingScopeInner>,
611}
612
613impl BindingScope {
614 pub fn with_graph<R>(&self, body: impl FnOnce() -> R) -> R {
616 let graph = PropertyGraph {
617 inner: self
618 .inner
619 .graph
620 .upgrade()
621 .expect("binding graph was destroyed"),
622 };
623 let _graph = graph.enter();
624 let previous = ACTIVE_SCOPE.with(|active| active.replace(Some(Rc::downgrade(&self.inner))));
625 let _scope = BindingScopeGuard { previous };
626 body()
627 }
628
629 pub fn enter(&self) -> BindingScopeGuard {
630 assert!(
631 !self.inner.closed.get(),
632 "cannot evaluate bindings for an unmounted view"
633 );
634 let previous = ACTIVE_SCOPE.with(|active| active.replace(Some(Rc::downgrade(&self.inner))));
635 BindingScopeGuard { previous }
636 }
637
638 pub fn close(&self) {
639 if self.inner.closed.replace(true) {
640 return;
641 }
642 if let Some(inner) = self.inner.graph.upgrade() {
643 PropertyGraph { inner }.release_owner(&self.inner);
644 }
645 }
646
647 pub fn resume(&self) {
648 if !self.inner.closed.replace(false) {
649 return;
650 }
651 if let Some(inner) = self.inner.graph.upgrade() {
652 let graph = PropertyGraph { inner };
653 let _entered = graph.enter();
654 let _scope = self.enter();
655 self.inner.properties.borrow_mut().retain(|id| {
656 graph.inner.attachments.borrow().contains_key(id)
657 || graph
658 .inner
659 .dormant
660 .borrow()
661 .get(id)
662 .is_some_and(|entry| entry.live())
663 });
664 let properties: Vec<_> = self.inner.properties.borrow().iter().copied().collect();
665 for id in properties {
666 graph.record_owner(id);
667 graph.restore(id);
668 }
669 }
670 }
671}
672
673#[doc(hidden)]
674pub struct BindingScopeGuard {
675 previous: Option<std::rc::Weak<BindingScopeInner>>,
676}
677impl Drop for BindingScopeGuard {
678 fn drop(&mut self) {
679 ACTIVE_SCOPE.with(|active| {
680 active.replace(self.previous.take());
681 });
682 }
683}
684
685#[must_use]
687pub struct PropertyGraphGuard {
688 previous: Option<PropertyGraph>,
689 previous_scope: Option<std::rc::Weak<BindingScopeInner>>,
690}
691
692impl Drop for PropertyGraphGuard {
693 fn drop(&mut self) {
694 ACTIVE_GRAPH.with(|active| {
695 active.replace(self.previous.take());
696 });
697 ACTIVE_SCOPE.with(|active| {
698 active.replace(self.previous_scope.take());
699 });
700 }
701}