zengine_asset/
handle.rs

1use std::{
2    any::TypeId,
3    cmp::Ordering,
4    hash::{Hash, Hasher},
5    marker::PhantomData,
6};
7
8use crossbeam_channel::{Receiver, Sender};
9use zengine_engine::log::debug;
10
11use crate::{
12    assets::{Asset, Assets},
13    AssetPath,
14};
15
16/// A unique asset id
17#[derive(Debug, Eq, Hash, PartialEq, Ord, PartialOrd, Clone, Copy)]
18pub enum HandleId {
19    /// A handle id from a asset path
20    FromPath(TypeId, u64),
21    /// A handle id from an u64.
22    FromU64(TypeId, u64),
23}
24
25impl HandleId {
26    /// Creates a new HandleId from a given [AssetPath]
27    pub fn new_from_path<T: Asset>(asset_path: &AssetPath) -> Self {
28        let type_id = TypeId::of::<T>();
29
30        let mut hasher = ahash::AHasher::default();
31        asset_path.path.hash(&mut hasher);
32        let id: u64 = hasher.finish();
33
34        Self::FromPath(type_id, id)
35    }
36
37    /// Creates a new HandleId from a given u64
38    pub fn new_from_u64<T: Asset>(id: u64) -> Self {
39        let type_id = TypeId::of::<T>();
40
41        Self::FromU64(type_id, id)
42    }
43
44    /// Clone and HandleId using a different [TypeId]
45    pub fn clone_with_different_type<T: Asset>(&self) -> Self {
46        let type_id = TypeId::of::<T>();
47
48        match self {
49            Self::FromPath(_, id) => Self::FromPath(type_id, *id),
50            Self::FromU64(_, id) => Self::FromU64(type_id, *id),
51        }
52    }
53
54    /// Get the [TypeId] of the HandleId
55    pub fn get_type(&self) -> TypeId {
56        match self {
57            Self::FromPath(type_id, _) => *type_id,
58            Self::FromU64(type_id, _) => *type_id,
59        }
60    }
61}
62
63impl<T: Asset> From<Handle<T>> for HandleId {
64    fn from(value: Handle<T>) -> Self {
65        value.id
66    }
67}
68
69#[derive(PartialEq, Debug)]
70pub(crate) enum HandleRef {
71    Increment(HandleId),
72    Decrement(HandleId),
73}
74
75#[derive(Default, Debug)]
76pub(crate) enum HandleType {
77    Strong(Sender<HandleRef>),
78    #[default]
79    Weak,
80}
81
82/// A handle into a specific [`Asset`] of type `T`
83///
84/// Handles contain a unique id that corresponds to a specific asset in the [`Assets`] collection.
85///
86/// # Accessing the Asset
87///
88/// A handle is _not_ the asset itself, it's mora like a pointer to the asset.
89/// To get the actual asset, you should use [`Assets::get`] or [`Assets::get_mut`].
90///
91/// # Strong and Weak
92///
93/// A handle can be either "Strong" or "Weak".
94/// A Strong handles keep the asset loaded.
95///
96/// A Weak handles do not affect the loaded status of assets.
97///
98/// This is due to a type of_reference counting_.
99/// When the number of Strong handles that exist for any given asset reach
100/// zero, the asset is dropped and will be unloaded.
101///
102/// If you want a reference to an asset but don't want to take the responsibility of
103/// keeping it loaded that comes with a Strong handle then you need a Weak handle.
104#[derive(Debug)]
105pub struct Handle<T> {
106    pub(crate) id: HandleId,
107    handle_type: HandleType,
108    _phantom: PhantomData<T>,
109}
110
111impl<T: Asset> Hash for Handle<T> {
112    fn hash<H: Hasher>(&self, state: &mut H) {
113        Hash::hash(&self.id, state);
114    }
115}
116
117impl<T: Asset> PartialEq for Handle<T> {
118    fn eq(&self, other: &Self) -> bool {
119        self.id == other.id
120    }
121}
122
123impl<T: Asset> Eq for Handle<T> {}
124
125impl<T: Asset> PartialOrd for Handle<T> {
126    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
127        Some(self.id.cmp(&other.id))
128    }
129}
130
131impl<T: Asset> Ord for Handle<T> {
132    fn cmp(&self, other: &Self) -> Ordering {
133        self.id.cmp(&other.id)
134    }
135}
136
137impl<T> Drop for Handle<T> {
138    fn drop(&mut self) {
139        if let HandleType::Strong(sender) = &self.handle_type {
140            let _res = sender.send(HandleRef::Decrement(self.id));
141            debug!("Drop a strong handle id: {:?}", self.id);
142        }
143    }
144}
145
146impl<T: Asset> Clone for Handle<T> {
147    fn clone(&self) -> Self {
148        match self.handle_type {
149            HandleType::Strong(ref sender) => Handle::strong(self.id, sender.clone()),
150            HandleType::Weak => Handle::weak(self.id),
151        }
152    }
153}
154
155impl<T: Asset> Handle<T> {
156    pub(crate) fn strong(id: HandleId, handle_ref_sender: Sender<HandleRef>) -> Self {
157        handle_ref_sender.send(HandleRef::Increment(id)).unwrap();
158        debug!("Create a strong handle id: {:?}", id);
159        Self {
160            id,
161            handle_type: HandleType::Strong(handle_ref_sender),
162            _phantom: PhantomData::default(),
163        }
164    }
165
166    /// Creates a weak handle for the Asset identified by `id`
167    pub fn weak(id: HandleId) -> Self {
168        Self {
169            id,
170            handle_type: HandleType::Weak,
171            _phantom: PhantomData::default(),
172        }
173    }
174
175    /// Get the handle id
176    pub fn get_id(&self) -> HandleId {
177        self.id
178    }
179
180    /// Clone the handle producing a weak one that point to the same asset
181    pub fn clone_as_weak(&self) -> Self {
182        Handle::weak(self.id)
183    }
184
185    /// Convert an handle to a weak one
186    pub fn as_weak(&self) -> Self {
187        Handle::weak(self.id)
188    }
189
190    /// Checks if the handle is a strong one
191    pub fn is_strong(&self) -> bool {
192        matches!(self.handle_type, HandleType::Strong(_))
193    }
194
195    /// Checks if the handle is a weak one
196    pub fn is_weak(&self) -> bool {
197        matches!(self.handle_type, HandleType::Weak)
198    }
199
200    /// Makes this handle Strong if it wasn’t already
201    ///
202    /// This method requires the corresponding [Assets] storage
203    pub fn make_strong(&mut self, assets: &Assets<T>) {
204        if self.is_weak() {
205            debug!("Create a strong handle from a weak one id: {:?}", self.id);
206            let sender = assets.sender.clone();
207            sender.send(HandleRef::Increment(self.id)).unwrap();
208            self.handle_type = HandleType::Strong(sender)
209        }
210    }
211}
212
213#[derive(Debug, Clone)]
214pub(crate) struct HandleRefChannel {
215    pub sender: Sender<HandleRef>,
216    pub receiver: Receiver<HandleRef>,
217}
218
219impl Default for HandleRefChannel {
220    fn default() -> Self {
221        let (sender, receiver) = crossbeam_channel::unbounded();
222        Self { sender, receiver }
223    }
224}
225
226#[cfg(test)]
227mod tests {
228    use crossbeam_channel::TryRecvError;
229
230    use crate::{Asset, Assets, Handle, HandleId, HandleRef};
231
232    #[derive(Debug)]
233    pub struct TestAsset1 {}
234    impl Asset for TestAsset1 {
235        fn next_counter() -> u64
236        where
237            Self: Sized,
238        {
239            0
240        }
241    }
242
243    #[derive(Debug)]
244    pub struct TestAsset2 {}
245    impl Asset for TestAsset2 {
246        fn next_counter() -> u64
247        where
248            Self: Sized,
249        {
250            0
251        }
252    }
253
254    #[test]
255    fn handle_id_unique_constrain() {
256        let id = HandleId::new_from_path::<TestAsset1>(&"path1.txt".into());
257        let same_id = HandleId::new_from_path::<TestAsset1>(&"path1.txt".into());
258        let different_id = HandleId::new_from_path::<TestAsset1>(&"path2.txt".into());
259
260        assert_eq!(id, same_id);
261        assert_ne!(id, different_id);
262
263        let different_id = HandleId::new_from_path::<TestAsset2>(&"path1.txt".into());
264        assert_ne!(id, different_id);
265    }
266
267    #[test]
268    fn strong_handle_increment_ref_counter() {
269        let (sender, receiver) = crossbeam_channel::unbounded::<HandleRef>();
270
271        let id = HandleId::new_from_path::<TestAsset1>(&"path1.txt".into());
272        let _handle: Handle<TestAsset1> = Handle::strong(id, sender);
273
274        let handle_ref = receiver.try_recv();
275
276        assert_eq!(handle_ref, Ok(HandleRef::Increment(id)));
277    }
278
279    #[test]
280    fn strong_handle_is_a_strong_one() {
281        let (sender, _receiver) = crossbeam_channel::unbounded::<HandleRef>();
282
283        let id = HandleId::new_from_path::<TestAsset1>(&"path1.txt".into());
284        let handle: Handle<TestAsset1> = Handle::strong(id, sender);
285
286        assert!(handle.is_strong());
287        assert!(!handle.is_weak());
288    }
289
290    #[test]
291    fn weak_handle_is_a_weak_one() {
292        let (sender, _receiver) = crossbeam_channel::unbounded::<HandleRef>();
293
294        let id = HandleId::new_from_path::<TestAsset1>(&"path1.txt".into());
295        let handle: Handle<TestAsset1> = Handle::strong(id, sender).as_weak();
296
297        assert!(handle.is_weak());
298        assert!(!handle.is_strong());
299    }
300
301    #[test]
302    fn weak_handle_do_not_increment_ref_counter() {
303        let (sender, receiver) = crossbeam_channel::unbounded::<HandleRef>();
304
305        let id = HandleId::new_from_path::<TestAsset1>(&"path1.txt".into());
306        let handle: Handle<TestAsset1> = Handle::strong(id, sender);
307        let _handle2 = handle.as_weak();
308
309        let handle_ref = receiver.try_recv();
310        assert_eq!(handle_ref, Ok(HandleRef::Increment(id)));
311
312        let handle_ref = receiver.try_recv();
313        assert_eq!(handle_ref, Err(TryRecvError::Empty));
314    }
315
316    #[test]
317    fn cloning_a_strong_handle_increment_ref_counter() {
318        let (sender, receiver) = crossbeam_channel::unbounded::<HandleRef>();
319
320        let id = HandleId::new_from_path::<TestAsset1>(&"path1.txt".into());
321        let handle: Handle<TestAsset1> = Handle::strong(id, sender);
322
323        let handle_ref = receiver.try_recv();
324        assert_eq!(handle_ref, Ok(HandleRef::Increment(id)));
325
326        #[allow(clippy::redundant_clone)]
327        let _cloned_handle: Handle<TestAsset1> = handle.clone();
328
329        let handle_ref = receiver.try_recv();
330        assert_eq!(handle_ref, Ok(HandleRef::Increment(id)));
331    }
332
333    #[test]
334    fn cloning_a_weak_handle_do_not_increment_ref_counter() {
335        let (sender, receiver) = crossbeam_channel::unbounded::<HandleRef>();
336
337        let id = HandleId::new_from_path::<TestAsset1>(&"path1.txt".into());
338        let handle: Handle<TestAsset1> = Handle::strong(id, sender);
339
340        let handle_ref = receiver.try_recv();
341        assert_eq!(handle_ref, Ok(HandleRef::Increment(id)));
342
343        #[allow(clippy::redundant_clone)]
344        let _cloned_handle: Handle<TestAsset1> = handle.as_weak().clone();
345
346        let handle_ref = receiver.try_recv();
347        assert_eq!(handle_ref, Err(TryRecvError::Empty));
348    }
349
350    #[test]
351    fn drop_a_strong_handle_decrement_ref_counter() {
352        let (sender, receiver) = crossbeam_channel::unbounded::<HandleRef>();
353
354        let id = HandleId::new_from_path::<TestAsset1>(&"path1.txt".into());
355        {
356            let _handle: Handle<TestAsset1> = Handle::strong(id, sender);
357
358            let handle_ref = receiver.try_recv();
359            assert_eq!(handle_ref, Ok(HandleRef::Increment(id)));
360        }
361
362        let handle_ref = receiver.try_recv();
363        assert_eq!(handle_ref, Ok(HandleRef::Decrement(id)));
364    }
365
366    #[test]
367    fn drop_a_weak_handle_do_not_decrement_ref_counter() {
368        let (sender, receiver) = crossbeam_channel::unbounded::<HandleRef>();
369
370        let id = HandleId::new_from_path::<TestAsset1>(&"path1.txt".into());
371        let handle: Handle<TestAsset1> = Handle::strong(id, sender);
372
373        let handle_ref = receiver.try_recv();
374        assert_eq!(handle_ref, Ok(HandleRef::Increment(id)));
375
376        {
377            let _weak_handle = handle.as_weak();
378        }
379
380        let handle_ref = receiver.try_recv();
381        assert_eq!(handle_ref, Err(TryRecvError::Empty));
382    }
383
384    #[test]
385    fn making_a_weak_handle_a_strong_one_increment_ref_counter() {
386        let (sender, receiver) = crossbeam_channel::unbounded::<HandleRef>();
387
388        let assets: Assets<TestAsset1> = Assets::new(sender);
389
390        let id = HandleId::new_from_path::<TestAsset1>(&"path1.txt".into());
391        let mut handle: Handle<TestAsset1> = Handle::weak(id);
392
393        handle.make_strong(&assets);
394
395        assert!(handle.is_strong());
396
397        let handle_ref = receiver.try_recv();
398        assert_eq!(handle_ref, Ok(HandleRef::Increment(id)));
399    }
400
401    #[test]
402    fn making_a_strong_handle_a_strong_one_do_not_increment_ref_counter() {
403        let (sender, receiver) = crossbeam_channel::unbounded::<HandleRef>();
404
405        let assets: Assets<TestAsset1> = Assets::new(sender.clone());
406
407        let id = HandleId::new_from_path::<TestAsset1>(&"path1.txt".into());
408        let mut handle: Handle<TestAsset1> = Handle::strong(id, sender);
409
410        let handle_ref = receiver.try_recv();
411        assert_eq!(handle_ref, Ok(HandleRef::Increment(id)));
412
413        handle.make_strong(&assets);
414
415        assert!(handle.is_strong());
416
417        let handle_ref = receiver.try_recv();
418        assert_eq!(handle_ref, Err(TryRecvError::Empty));
419    }
420}