tessera_ui/runtime/
state_owner.rs1use std::{
8 any::TypeId,
9 hash::{Hash, Hasher},
10 sync::{
11 Arc,
12 atomic::{AtomicBool, Ordering},
13 },
14};
15
16use rustc_hash::FxHasher;
17
18use super::{SlotKey, State, resolve_state_key, with_slot_table_mut};
19
20struct OwnerLease {
21 id: u128,
22 session: u128,
23 closed: AtomicBool,
24 dispatcher: crate::UiDispatcher,
25}
26
27impl OwnerLease {
28 fn close(&self) {
29 if self.closed.swap(true, Ordering::AcqRel) {
30 return;
31 }
32 let owner = self.id;
33 if crate::session::current_session_identity() == Some(self.session) {
34 release_slots(owner);
35 } else {
36 self.dispatcher.dispatch(move || release_slots(owner));
37 }
38 }
39}
40
41impl Drop for OwnerLease {
42 fn drop(&mut self) {
43 self.close();
44 }
45}
46
47#[derive(Clone)]
53pub struct StateOwner(Arc<OwnerLease>);
54
55impl Default for StateOwner {
56 fn default() -> Self {
57 Self::new()
58 }
59}
60
61impl PartialEq for StateOwner {
62 fn eq(&self, other: &Self) -> bool {
63 Arc::ptr_eq(&self.0, &other.0)
64 }
65}
66
67impl Eq for StateOwner {}
68
69impl StateOwner {
70 pub fn new() -> Self {
72 let owner = Arc::new(OwnerLease {
73 id: uuid::Uuid::new_v4().as_u128(),
74 session: crate::session::current_session_identity()
75 .expect("state owners require an active UI session"),
76 closed: AtomicBool::new(false),
77 dispatcher: crate::dispatcher::current(),
78 });
79 Self(owner)
80 }
81
82 pub fn is_alive(&self) -> bool {
84 !self.0.closed.load(Ordering::Acquire) && self.0.dispatcher.is_alive()
85 }
86
87 pub fn close(&self) {
89 self.0.close();
90 }
91
92 pub fn dispatcher(&self) -> crate::UiDispatcher {
94 let owner = Arc::downgrade(&self.0);
95 self.0.dispatcher.clone().scoped(move || {
96 owner
97 .upgrade()
98 .is_some_and(|owner| !owner.closed.load(Ordering::Acquire))
99 })
100 }
101
102 pub fn state_with_key<K, T>(&self, key: K, init: impl FnOnce() -> T) -> State<T>
107 where
108 K: Hash,
109 T: Send + Sync + 'static,
110 {
111 assert_eq!(
112 crate::session::current_session_identity(),
113 Some(self.0.session),
114 "state owner belongs to a different UI session"
115 );
116 assert!(self.is_alive(), "state owner is closed");
117 let mut hasher = FxHasher::default();
118 key.hash(&mut hasher);
119 let key = SlotKey {
120 owned_owner: Some(self.0.id),
121 instance_logic_id: 0,
122 slot_hash: hasher.finish(),
123 type_id: TypeId::of::<T>(),
124 };
125 resolve_state_key(key, true, || {
126 let value = init();
127 assert!(self.is_alive(), "state owner closed during initialization");
128 value
129 })
130 }
131}
132
133fn release_slots(owner: u128) {
134 let removed = with_slot_table_mut(|table| {
135 let slots = table.owned_slots.remove(&owner).unwrap_or_default();
136 slots
137 .into_iter()
138 .filter_map(|slot| table.remove_slot(slot))
139 .collect::<Vec<_>>()
140 });
141 drop(removed);
142}