Seregon/StratoSDK

StratoSDK is a framework with a declarative approach similar to Flutter/React, written and designed entirely for Rust.

Rust/27.3 KB/No license
crates/strato-ui-core/src/core/model/handle.rs
1use std::{
2 any::{type_name, TypeId},
3 fmt::{self, Debug},
4 hash::{Hash, Hasher},
5 marker::PhantomData,
6 sync::{Arc, Weak},
7};
8 
9use parking_lot::Mutex;
10 
11use crate::{core::RefCounts, AppContext, Entity, EntityId, EntityLocation, Handle, ModelContext};
12 
13/// A strong reference to a particular [`Entity`] instance within the application.
14///
15/// Handles structures are used in place of references (e.g.: `&Entity`) to avoid
16/// the complexity of reference lifetimes and appeasing the borrow checker. A
17/// handle can be combined with a reference to the application state (e.g.:
18/// [`AppContext`]) to get access to the actual [`Entity`] instance behind the
19/// handle.
20pub struct ModelHandle<T> {
21 model_id: EntityId,
22 model_type: PhantomData<T>,
23 ref_counts: Weak<Mutex<RefCounts>>,
24}
25 
26impl<T: Entity> ModelHandle<T> {
27 pub(in crate::core) fn new(model_id: EntityId, ref_counts: &Arc<Mutex<RefCounts>>) -> Self {
28 ref_counts.lock().inc_entity(model_id);
29 Self {
30 model_id,
31 model_type: PhantomData,
32 ref_counts: Arc::downgrade(ref_counts),
33 }
34 }
35 
36 pub fn downgrade(&self) -> WeakModelHandle<T> {
37 WeakModelHandle::new(self.model_id)
38 }
39 
40 pub fn id(&self) -> EntityId {
41 self.model_id
42 }
43 
44 pub fn as_ref<'a, A: ModelAsRef>(&self, app: &'a A) -> &'a T {
45 app.model(self)
46 }
47 
48 pub fn read<A, F, S>(&self, app: &A, read: F) -> S
49 where
50 A: ReadModel,
51 F: FnOnce(&T, &AppContext) -> S,
52 {
53 app.read_model(self, read)
54 }
55 
56 pub fn update<A, F, S>(&self, app: &mut A, update: F) -> S
57 where
58 A: UpdateModel,
59 F: FnOnce(&mut T, &mut ModelContext<T>) -> S,
60 {
61 app.update_model(self, update)
62 }
63}
64 
65impl<T> Clone for ModelHandle<T> {
66 fn clone(&self) -> Self {
67 if let Some(ref_counts) = self.ref_counts.upgrade() {
68 ref_counts.lock().inc_entity(self.model_id);
69 }
70 
71 Self {
72 model_id: self.model_id,
73 model_type: PhantomData,
74 ref_counts: self.ref_counts.clone(),
75 }
76 }
77}
78 
79impl<T> PartialEq for ModelHandle<T> {
80 fn eq(&self, other: &Self) -> bool {
81 self.model_id == other.model_id
82 }
83}
84 
85impl<T> Eq for ModelHandle<T> {}
86 
87impl<T> Hash for ModelHandle<T> {
88 fn hash<H: Hasher>(&self, state: &mut H) {
89 self.model_id.hash(state);
90 }
91}
92 
93impl<T> std::borrow::Borrow<EntityId> for ModelHandle<T> {
94 fn borrow(&self) -> &EntityId {
95 &self.model_id
96 }
97}
98 
99impl<T> Debug for ModelHandle<T> {
100 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
101 f.debug_tuple(&format!("ModelHandle<{}>", type_name::<T>()))
102 .field(&self.model_id)
103 .finish()
104 }
105}
106 
107unsafe impl<T> Send for ModelHandle<T> {}
108unsafe impl<T> Sync for ModelHandle<T> {}
109 
110impl<T> Drop for ModelHandle<T> {
111 fn drop(&mut self) {
112 if let Some(ref_counts) = self.ref_counts.upgrade() {
113 ref_counts.lock().dec_model(self.model_id);
114 }
115 }
116}
117 
118impl<T> Handle<T> for ModelHandle<T> {
119 fn id(&self) -> EntityId {
120 self.model_id
121 }
122 
123 fn location(&self) -> EntityLocation {
124 EntityLocation::Model(self.model_id)
125 }
126}
127 
128/// A type-erased strong reference to a particular [`Entity`] instance within the
129/// application.
130///
131/// `AnyModelHandle` is used within the core UI framework in places where we need
132/// to hold a strong reference to a `Entity`, but don't want to add a generic type
133/// parameter to the containing structure. See `singleton_models` in
134/// [`AppContext`](crate::core::AppContext) for an example.
135pub struct AnyModelHandle {
136 model_id: EntityId,
137 model_type: TypeId,
138 ref_counts: Weak<Mutex<RefCounts>>,
139}
140 
141impl AnyModelHandle {
142 pub fn id(&self) -> EntityId {
143 self.model_id
144 }
145 
146 pub fn is<T: 'static>(&self) -> bool {
147 TypeId::of::<T>() == self.model_type
148 }
149 
150 pub fn downcast<T: Entity>(self) -> Option<ModelHandle<T>> {
151 if self.is::<T>() {
152 if let Some(ref_counts) = self.ref_counts.upgrade() {
153 return Some(ModelHandle::new(self.model_id, &ref_counts));
154 }
155 }
156 None
157 }
158 
159 pub fn downcast_ref<'a, T: Entity>(&'a self, ctx: &'a AppContext) -> Option<&'a T> {
160 if self.is::<T>() {
161 return ctx.models.get(&self.model_id)?.as_any().downcast_ref();
162 }
163 None
164 }
165}
166 
167impl Clone for AnyModelHandle {
168 fn clone(&self) -> Self {
169 if let Some(ref_counts) = self.ref_counts.upgrade() {
170 ref_counts.lock().inc_entity(self.model_id);
171 }
172 
173 Self {
174 model_id: self.model_id,
175 model_type: self.model_type,
176 ref_counts: self.ref_counts.clone(),
177 }
178 }
179}
180 
181impl<T: Entity> From<ModelHandle<T>> for AnyModelHandle {
182 fn from(handle: ModelHandle<T>) -> Self {
183 if let Some(ref_counts) = handle.ref_counts.upgrade() {
184 ref_counts.lock().inc_entity(handle.model_id);
185 }
186 
187 Self {
188 model_id: handle.model_id,
189 model_type: TypeId::of::<T>(),
190 ref_counts: handle.ref_counts.clone(),
191 }
192 }
193}
194 
195impl Drop for AnyModelHandle {
196 fn drop(&mut self) {
197 if let Some(ref_counts) = self.ref_counts.upgrade() {
198 ref_counts.lock().dec_model(self.model_id);
199 }
200 }
201}
202 
203/// A weak reference to a particular [`Entity`] instance within the application.
204///
205/// `WeakModelHandle` is useful when a view wants to hold onto its own handle -
206/// holding a strong reference via [`ModelHandle`] would create a reference
207/// cycle that prevents the application from ever dropping the model.
208pub struct WeakModelHandle<T> {
209 model_id: EntityId,
210 model_type: PhantomData<T>,
211}
212 
213impl<T: Entity> WeakModelHandle<T> {
214 pub(super) fn new(model_id: EntityId) -> Self {
215 Self {
216 model_id,
217 model_type: PhantomData,
218 }
219 }
220 
221 pub fn upgrade(&self, app: &AppContext) -> Option<ModelHandle<T>> {
222 if app.models.contains_key(&self.model_id) {
223 Some(ModelHandle::new(self.model_id, &app.ref_counts))
224 } else {
225 None
226 }
227 }
228}
229 
230impl<T> Clone for WeakModelHandle<T> {
231 fn clone(&self) -> Self {
232 Self {
233 model_id: self.model_id,
234 model_type: PhantomData,
235 }
236 }
237}
238 
239pub trait ModelAsRef {
240 fn model<T: Entity>(&self, handle: &ModelHandle<T>) -> &T;
241}
242 
243pub trait ReadModel: ModelAsRef {
244 fn read_model<T, F, S>(&self, handle: &ModelHandle<T>, read: F) -> S
245 where
246 T: Entity,
247 F: FnOnce(&T, &AppContext) -> S;
248}
249 
250pub trait UpdateModel: ReadModel {
251 fn update_model<T, F, S>(&mut self, handle: &ModelHandle<T>, update: F) -> S
252 where
253 T: Entity,
254 F: FnOnce(&mut T, &mut ModelContext<T>) -> S;
255}
256