1use crate::{Asset, AssetIndex, Handle, UntypedHandle};
2use bevy_reflect::{std_traits::ReflectDefault, Reflect};
3use serde::{Deserialize, Serialize};
4use uuid::Uuid;
5
6use core::{
7 any::TypeId,
8 fmt::{Debug, Display},
9 hash::Hash,
10 marker::PhantomData,
11};
12use derive_more::derive::From;
13use thiserror::Error;
14
15#[derive(Reflect, Serialize, Deserialize, From)]
22#[reflect(Clone, Default, Debug, PartialEq, Hash)]
23pub enum AssetId<A: Asset> {
24 Index {
30 index: AssetIndex,
32 #[reflect(ignore, clone)]
34 marker: PhantomData<fn() -> A>,
35 },
36 Uuid {
41 uuid: Uuid,
43 },
44}
45
46impl<A: Asset> AssetId<A> {
47 pub const DEFAULT_UUID: Uuid = Uuid::from_u128(200809721996911295814598172825939264631);
50
51 pub const INVALID_UUID: Uuid = Uuid::from_u128(108428345662029828789348721013522787528);
54
55 #[inline]
57 pub const fn invalid() -> Self {
58 Self::Uuid {
59 uuid: Self::INVALID_UUID,
60 }
61 }
62
63 #[inline]
66 pub fn untyped(self) -> UntypedAssetId {
67 self.into()
68 }
69
70 #[inline]
71 fn internal(self) -> InternalAssetId {
72 match self {
73 AssetId::Index { index, .. } => InternalAssetId::Index(index),
74 AssetId::Uuid { uuid } => InternalAssetId::Uuid(uuid),
75 }
76 }
77}
78
79impl<A: Asset> Default for AssetId<A> {
80 fn default() -> Self {
81 AssetId::Uuid {
82 uuid: Self::DEFAULT_UUID,
83 }
84 }
85}
86
87impl<A: Asset> Clone for AssetId<A> {
88 fn clone(&self) -> Self {
89 *self
90 }
91}
92
93impl<A: Asset> Copy for AssetId<A> {}
94
95impl<A: Asset> Display for AssetId<A> {
96 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
97 Debug::fmt(self, f)
98 }
99}
100
101impl<A: Asset> Debug for AssetId<A> {
102 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
103 match self {
104 AssetId::Index { index, .. } => {
105 write!(
106 f,
107 "AssetId<{}>{{ index: {}, generation: {}}}",
108 core::any::type_name::<A>(),
109 index.index,
110 index.generation
111 )
112 }
113 AssetId::Uuid { uuid } => {
114 write!(
115 f,
116 "AssetId<{}>{{uuid: {}}}",
117 core::any::type_name::<A>(),
118 uuid
119 )
120 }
121 }
122 }
123}
124
125impl<A: Asset> Hash for AssetId<A> {
126 #[inline]
127 fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
128 self.internal().hash(state);
129 TypeId::of::<A>().hash(state);
130 }
131}
132
133impl<A: Asset> PartialEq for AssetId<A> {
134 #[inline]
135 fn eq(&self, other: &Self) -> bool {
136 self.internal().eq(&other.internal())
137 }
138}
139
140impl<A: Asset> Eq for AssetId<A> {}
141
142impl<A: Asset> PartialOrd for AssetId<A> {
143 fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
144 Some(self.cmp(other))
145 }
146}
147
148impl<A: Asset> Ord for AssetId<A> {
149 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
150 self.internal().cmp(&other.internal())
151 }
152}
153
154impl<A: Asset> From<AssetIndex> for AssetId<A> {
155 #[inline]
156 fn from(value: AssetIndex) -> Self {
157 Self::Index {
158 index: value,
159 marker: PhantomData,
160 }
161 }
162}
163
164#[derive(Debug, Copy, Clone, Reflect)]
168pub enum UntypedAssetId {
169 Index {
175 type_id: TypeId,
177 index: AssetIndex,
179 },
180 Uuid {
185 type_id: TypeId,
187 uuid: Uuid,
189 },
190}
191
192impl UntypedAssetId {
193 #[inline]
197 pub fn typed_unchecked<A: Asset>(self) -> AssetId<A> {
198 match self {
199 UntypedAssetId::Index { index, .. } => AssetId::Index {
200 index,
201 marker: PhantomData,
202 },
203 UntypedAssetId::Uuid { uuid, .. } => AssetId::Uuid { uuid },
204 }
205 }
206
207 #[inline]
214 pub fn typed_debug_checked<A: Asset>(self) -> AssetId<A> {
215 debug_assert_eq!(
216 self.type_id(),
217 TypeId::of::<A>(),
218 "The target AssetId<{}>'s TypeId does not match the TypeId of this UntypedAssetId",
219 core::any::type_name::<A>()
220 );
221 self.typed_unchecked()
222 }
223
224 #[inline]
230 pub fn typed<A: Asset>(self) -> AssetId<A> {
231 let Ok(id) = self.try_typed() else {
232 panic!(
233 "The target AssetId<{}>'s TypeId does not match the TypeId of this UntypedAssetId",
234 core::any::type_name::<A>()
235 )
236 };
237
238 id
239 }
240
241 #[inline]
243 pub fn try_typed<A: Asset>(self) -> Result<AssetId<A>, UntypedAssetIdConversionError> {
244 AssetId::try_from(self)
245 }
246
247 #[inline]
249 pub fn type_id(&self) -> TypeId {
250 match self {
251 UntypedAssetId::Index { type_id, .. } | UntypedAssetId::Uuid { type_id, .. } => {
252 *type_id
253 }
254 }
255 }
256
257 #[inline]
258 fn internal(self) -> InternalAssetId {
259 match self {
260 UntypedAssetId::Index { index, .. } => InternalAssetId::Index(index),
261 UntypedAssetId::Uuid { uuid, .. } => InternalAssetId::Uuid(uuid),
262 }
263 }
264}
265
266impl Display for UntypedAssetId {
267 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
268 let mut writer = f.debug_struct("UntypedAssetId");
269 match self {
270 UntypedAssetId::Index { index, type_id } => {
271 writer
272 .field("type_id", type_id)
273 .field("index", &index.index)
274 .field("generation", &index.generation);
275 }
276 UntypedAssetId::Uuid { uuid, type_id } => {
277 writer.field("type_id", type_id).field("uuid", uuid);
278 }
279 }
280 writer.finish()
281 }
282}
283
284impl PartialEq for UntypedAssetId {
285 #[inline]
286 fn eq(&self, other: &Self) -> bool {
287 self.type_id() == other.type_id() && self.internal().eq(&other.internal())
288 }
289}
290
291impl Eq for UntypedAssetId {}
292
293impl Hash for UntypedAssetId {
294 #[inline]
295 fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
296 self.internal().hash(state);
297 self.type_id().hash(state);
298 }
299}
300
301impl Ord for UntypedAssetId {
302 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
303 self.type_id()
304 .cmp(&other.type_id())
305 .then_with(|| self.internal().cmp(&other.internal()))
306 }
307}
308
309impl PartialOrd for UntypedAssetId {
310 fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
311 Some(self.cmp(other))
312 }
313}
314
315#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, PartialOrd, Ord, From)]
319enum InternalAssetId {
320 Index(AssetIndex),
321 Uuid(Uuid),
322}
323
324#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)]
326pub(crate) struct ErasedAssetIndex {
327 pub(crate) index: AssetIndex,
328 pub(crate) type_id: TypeId,
329}
330
331impl ErasedAssetIndex {
332 pub(crate) fn new(index: AssetIndex, type_id: TypeId) -> Self {
333 Self { index, type_id }
334 }
335}
336
337impl Display for ErasedAssetIndex {
338 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
339 f.debug_struct("ErasedAssetIndex")
340 .field("type_id", &self.type_id)
341 .field("index", &self.index.index)
342 .field("generation", &self.index.generation)
343 .finish()
344 }
345}
346
347impl<A: Asset> PartialEq<UntypedAssetId> for AssetId<A> {
350 #[inline]
351 fn eq(&self, other: &UntypedAssetId) -> bool {
352 TypeId::of::<A>() == other.type_id() && self.internal().eq(&other.internal())
353 }
354}
355
356impl<A: Asset> PartialEq<AssetId<A>> for UntypedAssetId {
357 #[inline]
358 fn eq(&self, other: &AssetId<A>) -> bool {
359 other.eq(self)
360 }
361}
362
363impl<A: Asset> PartialOrd<UntypedAssetId> for AssetId<A> {
364 #[inline]
365 fn partial_cmp(&self, other: &UntypedAssetId) -> Option<core::cmp::Ordering> {
366 if TypeId::of::<A>() != other.type_id() {
367 None
368 } else {
369 Some(self.internal().cmp(&other.internal()))
370 }
371 }
372}
373
374impl<A: Asset> PartialOrd<AssetId<A>> for UntypedAssetId {
375 #[inline]
376 fn partial_cmp(&self, other: &AssetId<A>) -> Option<core::cmp::Ordering> {
377 Some(other.partial_cmp(self)?.reverse())
378 }
379}
380
381impl<A: Asset> From<AssetId<A>> for UntypedAssetId {
382 #[inline]
383 fn from(value: AssetId<A>) -> Self {
384 let type_id = TypeId::of::<A>();
385
386 match value {
387 AssetId::Index { index, .. } => UntypedAssetId::Index { type_id, index },
388 AssetId::Uuid { uuid } => UntypedAssetId::Uuid { type_id, uuid },
389 }
390 }
391}
392
393impl<A: Asset> TryFrom<UntypedAssetId> for AssetId<A> {
394 type Error = UntypedAssetIdConversionError;
395
396 #[inline]
397 fn try_from(value: UntypedAssetId) -> Result<Self, Self::Error> {
398 let found = value.type_id();
399 let expected = TypeId::of::<A>();
400
401 match value {
402 UntypedAssetId::Index { index, type_id } if type_id == expected => Ok(AssetId::Index {
403 index,
404 marker: PhantomData,
405 }),
406 UntypedAssetId::Uuid { uuid, type_id } if type_id == expected => {
407 Ok(AssetId::Uuid { uuid })
408 }
409 _ => Err(UntypedAssetIdConversionError::TypeIdMismatch { expected, found }),
410 }
411 }
412}
413
414impl TryFrom<UntypedAssetId> for ErasedAssetIndex {
415 type Error = UuidNotSupportedError;
416
417 fn try_from(asset_id: UntypedAssetId) -> Result<Self, Self::Error> {
418 match asset_id {
419 UntypedAssetId::Index { type_id, index } => Ok(ErasedAssetIndex { index, type_id }),
420 UntypedAssetId::Uuid { .. } => Err(UuidNotSupportedError),
421 }
422 }
423}
424
425impl<A: Asset> TryFrom<&Handle<A>> for ErasedAssetIndex {
426 type Error = UuidNotSupportedError;
427
428 fn try_from(handle: &Handle<A>) -> Result<Self, Self::Error> {
429 match handle {
430 Handle::Strong(handle) => Ok(Self::new(handle.index, handle.type_id)),
431 Handle::Uuid(..) => Err(UuidNotSupportedError),
432 }
433 }
434}
435
436impl TryFrom<&UntypedHandle> for ErasedAssetIndex {
437 type Error = UuidNotSupportedError;
438
439 fn try_from(handle: &UntypedHandle) -> Result<Self, Self::Error> {
440 match handle {
441 UntypedHandle::Strong(handle) => Ok(Self::new(handle.index, handle.type_id)),
442 UntypedHandle::Uuid { .. } => Err(UuidNotSupportedError),
443 }
444 }
445}
446
447impl From<ErasedAssetIndex> for UntypedAssetId {
448 fn from(value: ErasedAssetIndex) -> Self {
449 Self::Index {
450 type_id: value.type_id,
451 index: value.index,
452 }
453 }
454}
455
456#[derive(Error, Debug)]
457#[error("Attempted to create a TypedAssetIndex from a Uuid")]
458pub(crate) struct UuidNotSupportedError;
459
460#[derive(Error, Debug, PartialEq, Clone)]
462#[non_exhaustive]
463pub enum UntypedAssetIdConversionError {
464 #[error("This UntypedAssetId is for {found:?} and cannot be converted into an AssetId<{expected:?}>")]
466 TypeIdMismatch {
467 expected: TypeId,
469 found: TypeId,
471 },
472}
473
474#[cfg(test)]
475mod tests {
476 use super::*;
477
478 type TestAsset = ();
479
480 const UUID_1: Uuid = Uuid::from_u128(123);
481 const UUID_2: Uuid = Uuid::from_u128(456);
482
483 fn hash<T: Hash>(data: &T) -> u64 {
485 use core::hash::BuildHasher;
486
487 bevy_platform::hash::FixedHasher.hash_one(data)
488 }
489
490 #[test]
492 fn equality() {
493 let typed = AssetId::<TestAsset>::Uuid { uuid: UUID_1 };
494 let untyped = UntypedAssetId::Uuid {
495 type_id: TypeId::of::<TestAsset>(),
496 uuid: UUID_1,
497 };
498
499 assert_eq!(Ok(typed), AssetId::try_from(untyped));
500 assert_eq!(UntypedAssetId::from(typed), untyped);
501 assert_eq!(typed, untyped);
502 }
503
504 #[test]
506 fn ordering() {
507 assert!(UUID_1 < UUID_2);
508
509 let typed_1 = AssetId::<TestAsset>::Uuid { uuid: UUID_1 };
510 let typed_2 = AssetId::<TestAsset>::Uuid { uuid: UUID_2 };
511 let untyped_1 = UntypedAssetId::Uuid {
512 type_id: TypeId::of::<TestAsset>(),
513 uuid: UUID_1,
514 };
515 let untyped_2 = UntypedAssetId::Uuid {
516 type_id: TypeId::of::<TestAsset>(),
517 uuid: UUID_2,
518 };
519
520 assert!(typed_1 < typed_2);
521 assert!(untyped_1 < untyped_2);
522
523 assert!(UntypedAssetId::from(typed_1) < untyped_2);
524 assert!(untyped_1 < UntypedAssetId::from(typed_2));
525
526 assert!(AssetId::try_from(untyped_1).unwrap() < typed_2);
527 assert!(typed_1 < AssetId::try_from(untyped_2).unwrap());
528
529 assert!(typed_1 < untyped_2);
530 assert!(untyped_1 < typed_2);
531 }
532
533 #[test]
535 fn hashing() {
536 let typed = AssetId::<TestAsset>::Uuid { uuid: UUID_1 };
537 let untyped = UntypedAssetId::Uuid {
538 type_id: TypeId::of::<TestAsset>(),
539 uuid: UUID_1,
540 };
541
542 assert_eq!(
543 hash(&typed),
544 hash(&AssetId::<TestAsset>::try_from(untyped).unwrap())
545 );
546 assert_eq!(hash(&UntypedAssetId::from(typed)), hash(&untyped));
547 assert_eq!(hash(&typed), hash(&untyped));
548 }
549
550 #[test]
552 fn conversion() {
553 let typed = AssetId::<TestAsset>::Uuid { uuid: UUID_1 };
554 let untyped = UntypedAssetId::Uuid {
555 type_id: TypeId::of::<TestAsset>(),
556 uuid: UUID_1,
557 };
558
559 assert_eq!(Ok(typed), AssetId::try_from(untyped));
560 assert_eq!(UntypedAssetId::from(typed), untyped);
561 }
562}