1use crate::{
2 meta::MetaTransform, Asset, AssetId, AssetIndexAllocator, AssetPath, InternalAssetId,
3 UntypedAssetId,
4};
5use bevy_ecs::prelude::*;
6use bevy_reflect::{std_traits::ReflectDefault, Reflect, TypePath};
7use bevy_utils::get_short_name;
8use crossbeam_channel::{Receiver, Sender};
9use std::{
10 any::TypeId,
11 hash::{Hash, Hasher},
12 sync::Arc,
13};
14use thiserror::Error;
15use uuid::Uuid;
16
17#[derive(Clone)]
20pub struct AssetHandleProvider {
21 pub(crate) allocator: Arc<AssetIndexAllocator>,
22 pub(crate) drop_sender: Sender<DropEvent>,
23 pub(crate) drop_receiver: Receiver<DropEvent>,
24 pub(crate) type_id: TypeId,
25}
26
27#[derive(Debug)]
28pub(crate) struct DropEvent {
29 pub(crate) id: InternalAssetId,
30 pub(crate) asset_server_managed: bool,
31}
32
33impl AssetHandleProvider {
34 pub(crate) fn new(type_id: TypeId, allocator: Arc<AssetIndexAllocator>) -> Self {
35 let (drop_sender, drop_receiver) = crossbeam_channel::unbounded();
36 Self {
37 type_id,
38 allocator,
39 drop_sender,
40 drop_receiver,
41 }
42 }
43
44 pub fn reserve_handle(&self) -> UntypedHandle {
47 let index = self.allocator.reserve();
48 UntypedHandle::Strong(self.get_handle(InternalAssetId::Index(index), false, None, None))
49 }
50
51 pub(crate) fn get_handle(
52 &self,
53 id: InternalAssetId,
54 asset_server_managed: bool,
55 path: Option<AssetPath<'static>>,
56 meta_transform: Option<MetaTransform>,
57 ) -> Arc<StrongHandle> {
58 Arc::new(StrongHandle {
59 id: id.untyped(self.type_id),
60 drop_sender: self.drop_sender.clone(),
61 meta_transform,
62 path,
63 asset_server_managed,
64 })
65 }
66
67 pub(crate) fn reserve_handle_internal(
68 &self,
69 asset_server_managed: bool,
70 path: Option<AssetPath<'static>>,
71 meta_transform: Option<MetaTransform>,
72 ) -> Arc<StrongHandle> {
73 let index = self.allocator.reserve();
74 self.get_handle(
75 InternalAssetId::Index(index),
76 asset_server_managed,
77 path,
78 meta_transform,
79 )
80 }
81}
82
83#[derive(TypePath)]
86pub struct StrongHandle {
87 pub(crate) id: UntypedAssetId,
88 pub(crate) asset_server_managed: bool,
89 pub(crate) path: Option<AssetPath<'static>>,
90 pub(crate) meta_transform: Option<MetaTransform>,
94 pub(crate) drop_sender: Sender<DropEvent>,
95}
96
97impl Drop for StrongHandle {
98 fn drop(&mut self) {
99 let _ = self.drop_sender.send(DropEvent {
100 id: self.id.internal(),
101 asset_server_managed: self.asset_server_managed,
102 });
103 }
104}
105
106impl std::fmt::Debug for StrongHandle {
107 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
108 f.debug_struct("StrongHandle")
109 .field("id", &self.id)
110 .field("asset_server_managed", &self.asset_server_managed)
111 .field("path", &self.path)
112 .field("drop_sender", &self.drop_sender)
113 .finish()
114 }
115}
116
117#[derive(Component, Reflect)]
126#[reflect(Default, Component, Debug, Hash, PartialEq)]
127pub enum Handle<A: Asset> {
128 Strong(Arc<StrongHandle>),
131 Weak(AssetId<A>),
134}
135
136impl<T: Asset> Clone for Handle<T> {
137 fn clone(&self) -> Self {
138 match self {
139 Handle::Strong(handle) => Handle::Strong(handle.clone()),
140 Handle::Weak(id) => Handle::Weak(*id),
141 }
142 }
143}
144
145impl<A: Asset> Handle<A> {
146 pub const fn weak_from_u128(value: u128) -> Self {
148 Handle::Weak(AssetId::Uuid {
149 uuid: Uuid::from_u128(value),
150 })
151 }
152
153 #[inline]
155 pub fn id(&self) -> AssetId<A> {
156 match self {
157 Handle::Strong(handle) => handle.id.typed_unchecked(),
158 Handle::Weak(id) => *id,
159 }
160 }
161
162 #[inline]
164 pub fn path(&self) -> Option<&AssetPath<'static>> {
165 match self {
166 Handle::Strong(handle) => handle.path.as_ref(),
167 Handle::Weak(_) => None,
168 }
169 }
170
171 #[inline]
173 pub fn is_weak(&self) -> bool {
174 matches!(self, Handle::Weak(_))
175 }
176
177 #[inline]
179 pub fn is_strong(&self) -> bool {
180 matches!(self, Handle::Strong(_))
181 }
182
183 #[inline]
185 pub fn clone_weak(&self) -> Self {
186 match self {
187 Handle::Strong(handle) => Handle::Weak(handle.id.typed_unchecked::<A>()),
188 Handle::Weak(id) => Handle::Weak(*id),
189 }
190 }
191
192 #[inline]
196 pub fn untyped(self) -> UntypedHandle {
197 self.into()
198 }
199}
200
201impl<A: Asset> Default for Handle<A> {
202 fn default() -> Self {
203 Handle::Weak(AssetId::default())
204 }
205}
206
207impl<A: Asset> std::fmt::Debug for Handle<A> {
208 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
209 let name = get_short_name(std::any::type_name::<A>());
210 match self {
211 Handle::Strong(handle) => {
212 write!(
213 f,
214 "StrongHandle<{name}>{{ id: {:?}, path: {:?} }}",
215 handle.id.internal(),
216 handle.path
217 )
218 }
219 Handle::Weak(id) => write!(f, "WeakHandle<{name}>({:?})", id.internal()),
220 }
221 }
222}
223
224impl<A: Asset> Hash for Handle<A> {
225 #[inline]
226 fn hash<H: Hasher>(&self, state: &mut H) {
227 self.id().hash(state);
228 }
229}
230
231impl<A: Asset> PartialOrd for Handle<A> {
232 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
233 Some(self.cmp(other))
234 }
235}
236
237impl<A: Asset> Ord for Handle<A> {
238 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
239 self.id().cmp(&other.id())
240 }
241}
242
243impl<A: Asset> PartialEq for Handle<A> {
244 #[inline]
245 fn eq(&self, other: &Self) -> bool {
246 self.id() == other.id()
247 }
248}
249
250impl<A: Asset> Eq for Handle<A> {}
251
252impl<A: Asset> From<&Handle<A>> for AssetId<A> {
253 #[inline]
254 fn from(value: &Handle<A>) -> Self {
255 value.id()
256 }
257}
258
259impl<A: Asset> From<&Handle<A>> for UntypedAssetId {
260 #[inline]
261 fn from(value: &Handle<A>) -> Self {
262 value.id().into()
263 }
264}
265
266impl<A: Asset> From<&mut Handle<A>> for AssetId<A> {
267 #[inline]
268 fn from(value: &mut Handle<A>) -> Self {
269 value.id()
270 }
271}
272
273impl<A: Asset> From<&mut Handle<A>> for UntypedAssetId {
274 #[inline]
275 fn from(value: &mut Handle<A>) -> Self {
276 value.id().into()
277 }
278}
279
280#[derive(Clone)]
286pub enum UntypedHandle {
287 Strong(Arc<StrongHandle>),
288 Weak(UntypedAssetId),
289}
290
291impl UntypedHandle {
292 #[inline]
294 pub fn id(&self) -> UntypedAssetId {
295 match self {
296 UntypedHandle::Strong(handle) => handle.id,
297 UntypedHandle::Weak(id) => *id,
298 }
299 }
300
301 #[inline]
303 pub fn path(&self) -> Option<&AssetPath<'static>> {
304 match self {
305 UntypedHandle::Strong(handle) => handle.path.as_ref(),
306 UntypedHandle::Weak(_) => None,
307 }
308 }
309
310 #[inline]
312 pub fn clone_weak(&self) -> UntypedHandle {
313 match self {
314 UntypedHandle::Strong(handle) => UntypedHandle::Weak(handle.id),
315 UntypedHandle::Weak(id) => UntypedHandle::Weak(*id),
316 }
317 }
318
319 #[inline]
321 pub fn type_id(&self) -> TypeId {
322 match self {
323 UntypedHandle::Strong(handle) => handle.id.type_id(),
324 UntypedHandle::Weak(id) => id.type_id(),
325 }
326 }
327
328 #[inline]
330 pub fn typed_unchecked<A: Asset>(self) -> Handle<A> {
331 match self {
332 UntypedHandle::Strong(handle) => Handle::Strong(handle),
333 UntypedHandle::Weak(id) => Handle::Weak(id.typed_unchecked::<A>()),
334 }
335 }
336
337 #[inline]
342 pub fn typed_debug_checked<A: Asset>(self) -> Handle<A> {
343 debug_assert_eq!(
344 self.type_id(),
345 TypeId::of::<A>(),
346 "The target Handle<A>'s TypeId does not match the TypeId of this UntypedHandle"
347 );
348 match self {
349 UntypedHandle::Strong(handle) => Handle::Strong(handle),
350 UntypedHandle::Weak(id) => Handle::Weak(id.typed_unchecked::<A>()),
351 }
352 }
353
354 #[inline]
356 pub fn typed<A: Asset>(self) -> Handle<A> {
357 let Ok(handle) = self.try_typed() else {
358 panic!(
359 "The target Handle<{}>'s TypeId does not match the TypeId of this UntypedHandle",
360 std::any::type_name::<A>()
361 )
362 };
363
364 handle
365 }
366
367 #[inline]
369 pub fn try_typed<A: Asset>(self) -> Result<Handle<A>, UntypedAssetConversionError> {
370 Handle::try_from(self)
371 }
372
373 #[inline]
376 pub fn meta_transform(&self) -> Option<&MetaTransform> {
377 match self {
378 UntypedHandle::Strong(handle) => handle.meta_transform.as_ref(),
379 UntypedHandle::Weak(_) => None,
380 }
381 }
382}
383
384impl PartialEq for UntypedHandle {
385 #[inline]
386 fn eq(&self, other: &Self) -> bool {
387 self.id() == other.id() && self.type_id() == other.type_id()
388 }
389}
390
391impl Eq for UntypedHandle {}
392
393impl Hash for UntypedHandle {
394 #[inline]
395 fn hash<H: Hasher>(&self, state: &mut H) {
396 self.id().hash(state);
397 }
398}
399
400impl std::fmt::Debug for UntypedHandle {
401 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
402 match self {
403 UntypedHandle::Strong(handle) => {
404 write!(
405 f,
406 "StrongHandle{{ type_id: {:?}, id: {:?}, path: {:?} }}",
407 handle.id.type_id(),
408 handle.id.internal(),
409 handle.path
410 )
411 }
412 UntypedHandle::Weak(id) => write!(
413 f,
414 "WeakHandle{{ type_id: {:?}, id: {:?} }}",
415 id.type_id(),
416 id.internal()
417 ),
418 }
419 }
420}
421
422impl PartialOrd for UntypedHandle {
423 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
424 if self.type_id() == other.type_id() {
425 self.id().partial_cmp(&other.id())
426 } else {
427 None
428 }
429 }
430}
431
432impl From<&UntypedHandle> for UntypedAssetId {
433 #[inline]
434 fn from(value: &UntypedHandle) -> Self {
435 value.id()
436 }
437}
438
439impl<A: Asset> PartialEq<UntypedHandle> for Handle<A> {
442 #[inline]
443 fn eq(&self, other: &UntypedHandle) -> bool {
444 TypeId::of::<A>() == other.type_id() && self.id() == other.id()
445 }
446}
447
448impl<A: Asset> PartialEq<Handle<A>> for UntypedHandle {
449 #[inline]
450 fn eq(&self, other: &Handle<A>) -> bool {
451 other.eq(self)
452 }
453}
454
455impl<A: Asset> PartialOrd<UntypedHandle> for Handle<A> {
456 #[inline]
457 fn partial_cmp(&self, other: &UntypedHandle) -> Option<std::cmp::Ordering> {
458 if TypeId::of::<A>() != other.type_id() {
459 None
460 } else {
461 self.id().partial_cmp(&other.id())
462 }
463 }
464}
465
466impl<A: Asset> PartialOrd<Handle<A>> for UntypedHandle {
467 #[inline]
468 fn partial_cmp(&self, other: &Handle<A>) -> Option<std::cmp::Ordering> {
469 Some(other.partial_cmp(self)?.reverse())
470 }
471}
472
473impl<A: Asset> From<Handle<A>> for UntypedHandle {
474 fn from(value: Handle<A>) -> Self {
475 match value {
476 Handle::Strong(handle) => UntypedHandle::Strong(handle),
477 Handle::Weak(id) => UntypedHandle::Weak(id.into()),
478 }
479 }
480}
481
482impl<A: Asset> TryFrom<UntypedHandle> for Handle<A> {
483 type Error = UntypedAssetConversionError;
484
485 fn try_from(value: UntypedHandle) -> Result<Self, Self::Error> {
486 let found = value.type_id();
487 let expected = TypeId::of::<A>();
488
489 if found != expected {
490 return Err(UntypedAssetConversionError::TypeIdMismatch { expected, found });
491 }
492
493 match value {
494 UntypedHandle::Strong(handle) => Ok(Handle::Strong(handle)),
495 UntypedHandle::Weak(id) => {
496 let Ok(id) = id.try_into() else {
497 return Err(UntypedAssetConversionError::TypeIdMismatch { expected, found });
498 };
499 Ok(Handle::Weak(id))
500 }
501 }
502 }
503}
504
505#[derive(Error, Debug, PartialEq, Clone)]
507#[non_exhaustive]
508pub enum UntypedAssetConversionError {
509 #[error(
511 "This UntypedHandle is for {found:?} and cannot be converted into a Handle<{expected:?}>"
512 )]
513 TypeIdMismatch { expected: TypeId, found: TypeId },
514}
515
516#[cfg(test)]
517mod tests {
518 use super::*;
519
520 type TestAsset = ();
521
522 const UUID_1: Uuid = Uuid::from_u128(123);
523 const UUID_2: Uuid = Uuid::from_u128(456);
524
525 fn hash<T: Hash>(data: &T) -> u64 {
527 let mut hasher = bevy_utils::AHasher::default();
528 data.hash(&mut hasher);
529 hasher.finish()
530 }
531
532 #[test]
534 fn equality() {
535 let typed = AssetId::<TestAsset>::Uuid { uuid: UUID_1 };
536 let untyped = UntypedAssetId::Uuid {
537 type_id: TypeId::of::<TestAsset>(),
538 uuid: UUID_1,
539 };
540
541 let typed = Handle::Weak(typed);
542 let untyped = UntypedHandle::Weak(untyped);
543
544 assert_eq!(
545 Ok(typed.clone()),
546 Handle::<TestAsset>::try_from(untyped.clone())
547 );
548 assert_eq!(UntypedHandle::from(typed.clone()), untyped);
549 assert_eq!(typed, untyped);
550 }
551
552 #[allow(clippy::cmp_owned)]
554 #[test]
555 fn ordering() {
556 assert!(UUID_1 < UUID_2);
557
558 let typed_1 = AssetId::<TestAsset>::Uuid { uuid: UUID_1 };
559 let typed_2 = AssetId::<TestAsset>::Uuid { uuid: UUID_2 };
560 let untyped_1 = UntypedAssetId::Uuid {
561 type_id: TypeId::of::<TestAsset>(),
562 uuid: UUID_1,
563 };
564 let untyped_2 = UntypedAssetId::Uuid {
565 type_id: TypeId::of::<TestAsset>(),
566 uuid: UUID_2,
567 };
568
569 let typed_1 = Handle::Weak(typed_1);
570 let typed_2 = Handle::Weak(typed_2);
571 let untyped_1 = UntypedHandle::Weak(untyped_1);
572 let untyped_2 = UntypedHandle::Weak(untyped_2);
573
574 assert!(typed_1 < typed_2);
575 assert!(untyped_1 < untyped_2);
576
577 assert!(UntypedHandle::from(typed_1.clone()) < untyped_2);
578 assert!(untyped_1 < UntypedHandle::from(typed_2.clone()));
579
580 assert!(Handle::<TestAsset>::try_from(untyped_1.clone()).unwrap() < typed_2);
581 assert!(typed_1 < Handle::<TestAsset>::try_from(untyped_2.clone()).unwrap());
582
583 assert!(typed_1 < untyped_2);
584 assert!(untyped_1 < typed_2);
585 }
586
587 #[test]
589 fn hashing() {
590 let typed = AssetId::<TestAsset>::Uuid { uuid: UUID_1 };
591 let untyped = UntypedAssetId::Uuid {
592 type_id: TypeId::of::<TestAsset>(),
593 uuid: UUID_1,
594 };
595
596 let typed = Handle::Weak(typed);
597 let untyped = UntypedHandle::Weak(untyped);
598
599 assert_eq!(
600 hash(&typed),
601 hash(&Handle::<TestAsset>::try_from(untyped.clone()).unwrap())
602 );
603 assert_eq!(hash(&UntypedHandle::from(typed.clone())), hash(&untyped));
604 assert_eq!(hash(&typed), hash(&untyped));
605 }
606
607 #[test]
609 fn conversion() {
610 let typed = AssetId::<TestAsset>::Uuid { uuid: UUID_1 };
611 let untyped = UntypedAssetId::Uuid {
612 type_id: TypeId::of::<TestAsset>(),
613 uuid: UUID_1,
614 };
615
616 let typed = Handle::Weak(typed);
617 let untyped = UntypedHandle::Weak(untyped);
618
619 assert_eq!(typed, Handle::try_from(untyped.clone()).unwrap());
620 assert_eq!(UntypedHandle::from(typed.clone()), untyped);
621 }
622}