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#[derive(Debug, Eq, Hash, PartialEq, Ord, PartialOrd, Clone, Copy)]
18pub enum HandleId {
19 FromPath(TypeId, u64),
21 FromU64(TypeId, u64),
23}
24
25impl HandleId {
26 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 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 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 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#[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 pub fn weak(id: HandleId) -> Self {
168 Self {
169 id,
170 handle_type: HandleType::Weak,
171 _phantom: PhantomData::default(),
172 }
173 }
174
175 pub fn get_id(&self) -> HandleId {
177 self.id
178 }
179
180 pub fn clone_as_weak(&self) -> Self {
182 Handle::weak(self.id)
183 }
184
185 pub fn as_weak(&self) -> Self {
187 Handle::weak(self.id)
188 }
189
190 pub fn is_strong(&self) -> bool {
192 matches!(self.handle_type, HandleType::Strong(_))
193 }
194
195 pub fn is_weak(&self) -> bool {
197 matches!(self.handle_type, HandleType::Weak)
198 }
199
200 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}