Skip to main content

fret_runtime/model/
handle.rs

1use std::{
2    any::Any,
3    marker::PhantomData,
4    panic::{AssertUnwindSafe, Location, catch_unwind, resume_unwind},
5    rc::{Rc, Weak},
6};
7
8use super::ModelId;
9use super::error::ModelUpdateError;
10use super::host::{ModelCx, ModelHost};
11use super::store::{ModelStore, ModelStoreInner};
12
13/// A reference-counted handle to a typed model stored in a [`ModelStore`].
14///
15/// This is intentionally gpui-like (`Entity<T>`):
16/// - `Model<T>` is a strong handle (cloning increments a per-model strong count).
17/// - `WeakModel<T>` can be upgraded back to `Model<T>` if the model is still alive.
18/// - When the last strong handle is dropped, the model is removed from the store.
19pub struct Model<T> {
20    store: ModelStore,
21    id: ModelId,
22    _phantom: PhantomData<fn() -> T>,
23}
24
25impl<T> Model<T> {
26    pub(super) fn from_store_id(store: ModelStore, id: ModelId) -> Self {
27        Self {
28            store,
29            id,
30            _phantom: PhantomData,
31        }
32    }
33
34    pub fn id(&self) -> ModelId {
35        self.id
36    }
37
38    /// Returns a weak handle that can be upgraded if the model is still alive.
39    pub fn downgrade(&self) -> WeakModel<T> {
40        WeakModel {
41            store: Rc::downgrade(&self.store.inner),
42            id: self.id,
43            _phantom: PhantomData,
44        }
45    }
46
47    /// Reads the model value using the host's read path.
48    ///
49    /// This acquires a temporary lease and ensures the lease is released even if the closure
50    /// panics (when `panic=unwind`).
51    pub fn read<H: ModelHost, R>(
52        &self,
53        host: &mut H,
54        f: impl FnOnce(&mut H, &T) -> R,
55    ) -> Result<R, ModelUpdateError>
56    where
57        T: Any,
58    {
59        host.read(self, f)
60    }
61
62    /// Updates the model value using the host's update path.
63    ///
64    /// The provided [`ModelCx`] exposes the host while the lease is active, allowing updates that
65    /// coordinate multiple models.
66    #[track_caller]
67    pub fn update<H: ModelHost, R>(
68        &self,
69        host: &mut H,
70        f: impl FnOnce(&mut T, &mut ModelCx<'_, H>) -> R,
71    ) -> Result<R, ModelUpdateError>
72    where
73        T: Any,
74    {
75        let changed_at = Location::caller();
76
77        let mut lease = host.models_mut().lease(self)?;
78        let result = if cfg!(panic = "unwind") {
79            catch_unwind(AssertUnwindSafe(|| {
80                let mut cx = ModelCx { host };
81                f(lease.value_mut(), &mut cx)
82            }))
83        } else {
84            Ok({
85                let mut cx = ModelCx { host };
86                f(lease.value_mut(), &mut cx)
87            })
88        };
89
90        match result {
91            Ok(value) => {
92                lease.mark_dirty();
93                host.models_mut()
94                    .end_lease_with_changed_at(&mut lease, changed_at);
95                Ok(value)
96            }
97            Err(panic) => {
98                host.models_mut().end_lease(&mut lease);
99                resume_unwind(panic)
100            }
101        }
102    }
103
104    pub fn revision<H: ModelHost>(&self, host: &H) -> Option<u64>
105    where
106        T: Any,
107    {
108        host.model_revision(self)
109    }
110
111    /// Marks the model as changed without mutating its value.
112    ///
113    /// This is useful for "derived invalidation" cases where the model's internal state changes
114    /// via interior mutability or external resources and you still want observers to re-run.
115    #[track_caller]
116    pub fn notify<H: ModelHost>(&self, host: &mut H) -> Result<(), ModelUpdateError>
117    where
118        T: Any,
119    {
120        host.models_mut()
121            .notify_with_changed_at(self, Location::caller())
122    }
123
124    pub fn read_ref<H: ModelHost, R>(
125        &self,
126        host: &H,
127        f: impl FnOnce(&T) -> R,
128    ) -> Result<R, ModelUpdateError>
129    where
130        T: Any,
131    {
132        host.models().read(self, f)
133    }
134}
135
136impl<T> std::fmt::Debug for Model<T> {
137    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
138        f.debug_struct("Model").field("id", &self.id).finish()
139    }
140}
141
142impl<T> PartialEq for Model<T> {
143    fn eq(&self, other: &Self) -> bool {
144        Rc::ptr_eq(&self.store.inner, &other.store.inner) && self.id == other.id
145    }
146}
147
148impl<T> Eq for Model<T> {}
149
150impl<T> std::hash::Hash for Model<T> {
151    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
152        (Rc::as_ptr(&self.store.inner) as usize).hash(state);
153        self.id.hash(state);
154    }
155}
156
157impl<T> Clone for Model<T> {
158    fn clone(&self) -> Self {
159        self.store.inc_strong(self.id);
160        Self {
161            store: self.store.clone(),
162            id: self.id,
163            _phantom: PhantomData,
164        }
165    }
166}
167
168impl<T> Drop for Model<T> {
169    fn drop(&mut self) {
170        self.store.dec_strong(self.id);
171    }
172}
173
174pub struct WeakModel<T> {
175    store: Weak<ModelStoreInner>,
176    id: ModelId,
177    _phantom: PhantomData<fn() -> T>,
178}
179
180impl<T> std::fmt::Debug for WeakModel<T> {
181    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
182        f.debug_struct("WeakModel").field("id", &self.id).finish()
183    }
184}
185
186impl<T> PartialEq for WeakModel<T> {
187    fn eq(&self, other: &Self) -> bool {
188        Weak::ptr_eq(&self.store, &other.store) && self.id == other.id
189    }
190}
191
192impl<T> Eq for WeakModel<T> {}
193
194impl<T> std::hash::Hash for WeakModel<T> {
195    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
196        (Weak::as_ptr(&self.store) as usize).hash(state);
197        self.id.hash(state);
198    }
199}
200
201impl<T> Clone for WeakModel<T> {
202    fn clone(&self) -> Self {
203        Self {
204            store: self.store.clone(),
205            id: self.id,
206            _phantom: PhantomData,
207        }
208    }
209}
210
211impl<T> WeakModel<T> {
212    pub fn id(&self) -> ModelId {
213        self.id
214    }
215
216    pub fn upgrade(&self) -> Option<Model<T>> {
217        let store = ModelStore {
218            inner: self.store.upgrade()?,
219            _not_send: PhantomData,
220        };
221        store.upgrade_strong(self.id).then(|| Model {
222            store,
223            id: self.id,
224            _phantom: PhantomData,
225        })
226    }
227}