1use crate::{Asset, AssetIndex};
2use bevy_reflect::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::{Display, Error, From};
13
14#[derive(Reflect, Serialize, Deserialize, From)]
21pub enum AssetId<A: Asset> {
22 Index {
28 index: AssetIndex,
29 #[reflect(ignore)]
30 marker: PhantomData<fn() -> A>,
31 },
32 Uuid { uuid: Uuid },
37}
38
39impl<A: Asset> AssetId<A> {
40 pub const DEFAULT_UUID: Uuid = Uuid::from_u128(200809721996911295814598172825939264631);
43
44 pub const INVALID_UUID: Uuid = Uuid::from_u128(108428345662029828789348721013522787528);
47
48 #[inline]
50 pub const fn invalid() -> Self {
51 Self::Uuid {
52 uuid: Self::INVALID_UUID,
53 }
54 }
55
56 #[inline]
59 pub fn untyped(self) -> UntypedAssetId {
60 self.into()
61 }
62
63 #[inline]
64 pub(crate) fn internal(self) -> InternalAssetId {
65 match self {
66 AssetId::Index { index, .. } => InternalAssetId::Index(index),
67 AssetId::Uuid { uuid } => InternalAssetId::Uuid(uuid),
68 }
69 }
70}
71
72impl<A: Asset> Default for AssetId<A> {
73 fn default() -> Self {
74 AssetId::Uuid {
75 uuid: Self::DEFAULT_UUID,
76 }
77 }
78}
79
80impl<A: Asset> Clone for AssetId<A> {
81 fn clone(&self) -> Self {
82 *self
83 }
84}
85
86impl<A: Asset> Copy for AssetId<A> {}
87
88impl<A: Asset> Display for AssetId<A> {
89 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
90 Debug::fmt(self, f)
91 }
92}
93
94impl<A: Asset> Debug for AssetId<A> {
95 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
96 match self {
97 AssetId::Index { index, .. } => {
98 write!(
99 f,
100 "AssetId<{}>{{ index: {}, generation: {}}}",
101 core::any::type_name::<A>(),
102 index.index,
103 index.generation
104 )
105 }
106 AssetId::Uuid { uuid } => {
107 write!(
108 f,
109 "AssetId<{}>{{uuid: {}}}",
110 core::any::type_name::<A>(),
111 uuid
112 )
113 }
114 }
115 }
116}
117
118impl<A: Asset> Hash for AssetId<A> {
119 #[inline]
120 fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
121 self.internal().hash(state);
122 TypeId::of::<A>().hash(state);
123 }
124}
125
126impl<A: Asset> PartialEq for AssetId<A> {
127 #[inline]
128 fn eq(&self, other: &Self) -> bool {
129 self.internal().eq(&other.internal())
130 }
131}
132
133impl<A: Asset> Eq for AssetId<A> {}
134
135impl<A: Asset> PartialOrd for AssetId<A> {
136 fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
137 Some(self.cmp(other))
138 }
139}
140
141impl<A: Asset> Ord for AssetId<A> {
142 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
143 self.internal().cmp(&other.internal())
144 }
145}
146
147impl<A: Asset> From<AssetIndex> for AssetId<A> {
148 #[inline]
149 fn from(value: AssetIndex) -> Self {
150 Self::Index {
151 index: value,
152 marker: PhantomData,
153 }
154 }
155}
156
157#[derive(Debug, Copy, Clone)]
161pub enum UntypedAssetId {
162 Index { type_id: TypeId, index: AssetIndex },
168 Uuid { type_id: TypeId, uuid: Uuid },
173}
174
175impl UntypedAssetId {
176 #[inline]
180 pub fn typed_unchecked<A: Asset>(self) -> AssetId<A> {
181 match self {
182 UntypedAssetId::Index { index, .. } => AssetId::Index {
183 index,
184 marker: PhantomData,
185 },
186 UntypedAssetId::Uuid { uuid, .. } => AssetId::Uuid { uuid },
187 }
188 }
189
190 #[inline]
197 pub fn typed_debug_checked<A: Asset>(self) -> AssetId<A> {
198 debug_assert_eq!(
199 self.type_id(),
200 TypeId::of::<A>(),
201 "The target AssetId<{}>'s TypeId does not match the TypeId of this UntypedAssetId",
202 core::any::type_name::<A>()
203 );
204 self.typed_unchecked()
205 }
206
207 #[inline]
213 pub fn typed<A: Asset>(self) -> AssetId<A> {
214 let Ok(id) = self.try_typed() else {
215 panic!(
216 "The target AssetId<{}>'s TypeId does not match the TypeId of this UntypedAssetId",
217 core::any::type_name::<A>()
218 )
219 };
220
221 id
222 }
223
224 #[inline]
226 pub fn try_typed<A: Asset>(self) -> Result<AssetId<A>, UntypedAssetIdConversionError> {
227 AssetId::try_from(self)
228 }
229
230 #[inline]
232 pub fn type_id(&self) -> TypeId {
233 match self {
234 UntypedAssetId::Index { type_id, .. } | UntypedAssetId::Uuid { type_id, .. } => {
235 *type_id
236 }
237 }
238 }
239
240 #[inline]
241 pub(crate) fn internal(self) -> InternalAssetId {
242 match self {
243 UntypedAssetId::Index { index, .. } => InternalAssetId::Index(index),
244 UntypedAssetId::Uuid { uuid, .. } => InternalAssetId::Uuid(uuid),
245 }
246 }
247}
248
249impl Display for UntypedAssetId {
250 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
251 let mut writer = f.debug_struct("UntypedAssetId");
252 match self {
253 UntypedAssetId::Index { index, type_id } => {
254 writer
255 .field("type_id", type_id)
256 .field("index", &index.index)
257 .field("generation", &index.generation);
258 }
259 UntypedAssetId::Uuid { uuid, type_id } => {
260 writer.field("type_id", type_id).field("uuid", uuid);
261 }
262 }
263 writer.finish()
264 }
265}
266
267impl PartialEq for UntypedAssetId {
268 #[inline]
269 fn eq(&self, other: &Self) -> bool {
270 self.type_id() == other.type_id() && self.internal().eq(&other.internal())
271 }
272}
273
274impl Eq for UntypedAssetId {}
275
276impl Hash for UntypedAssetId {
277 #[inline]
278 fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
279 self.internal().hash(state);
280 self.type_id().hash(state);
281 }
282}
283
284impl Ord for UntypedAssetId {
285 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
286 self.type_id()
287 .cmp(&other.type_id())
288 .then_with(|| self.internal().cmp(&other.internal()))
289 }
290}
291
292impl PartialOrd for UntypedAssetId {
293 fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
294 Some(self.cmp(other))
295 }
296}
297
298#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, PartialOrd, Ord, From)]
307pub(crate) enum InternalAssetId {
308 Index(AssetIndex),
309 Uuid(Uuid),
310}
311
312impl InternalAssetId {
313 #[inline]
314 pub(crate) fn typed<A: Asset>(self) -> AssetId<A> {
315 match self {
316 InternalAssetId::Index(index) => AssetId::Index {
317 index,
318 marker: PhantomData,
319 },
320 InternalAssetId::Uuid(uuid) => AssetId::Uuid { uuid },
321 }
322 }
323
324 #[inline]
325 pub(crate) fn untyped(self, type_id: TypeId) -> UntypedAssetId {
326 match self {
327 InternalAssetId::Index(index) => UntypedAssetId::Index { index, type_id },
328 InternalAssetId::Uuid(uuid) => UntypedAssetId::Uuid { uuid, type_id },
329 }
330 }
331}
332
333impl<A: Asset> PartialEq<UntypedAssetId> for AssetId<A> {
336 #[inline]
337 fn eq(&self, other: &UntypedAssetId) -> bool {
338 TypeId::of::<A>() == other.type_id() && self.internal().eq(&other.internal())
339 }
340}
341
342impl<A: Asset> PartialEq<AssetId<A>> for UntypedAssetId {
343 #[inline]
344 fn eq(&self, other: &AssetId<A>) -> bool {
345 other.eq(self)
346 }
347}
348
349impl<A: Asset> PartialOrd<UntypedAssetId> for AssetId<A> {
350 #[inline]
351 fn partial_cmp(&self, other: &UntypedAssetId) -> Option<core::cmp::Ordering> {
352 if TypeId::of::<A>() != other.type_id() {
353 None
354 } else {
355 Some(self.internal().cmp(&other.internal()))
356 }
357 }
358}
359
360impl<A: Asset> PartialOrd<AssetId<A>> for UntypedAssetId {
361 #[inline]
362 fn partial_cmp(&self, other: &AssetId<A>) -> Option<core::cmp::Ordering> {
363 Some(other.partial_cmp(self)?.reverse())
364 }
365}
366
367impl<A: Asset> From<AssetId<A>> for UntypedAssetId {
368 #[inline]
369 fn from(value: AssetId<A>) -> Self {
370 let type_id = TypeId::of::<A>();
371
372 match value {
373 AssetId::Index { index, .. } => UntypedAssetId::Index { type_id, index },
374 AssetId::Uuid { uuid } => UntypedAssetId::Uuid { type_id, uuid },
375 }
376 }
377}
378
379impl<A: Asset> TryFrom<UntypedAssetId> for AssetId<A> {
380 type Error = UntypedAssetIdConversionError;
381
382 #[inline]
383 fn try_from(value: UntypedAssetId) -> Result<Self, Self::Error> {
384 let found = value.type_id();
385 let expected = TypeId::of::<A>();
386
387 match value {
388 UntypedAssetId::Index { index, type_id } if type_id == expected => Ok(AssetId::Index {
389 index,
390 marker: PhantomData,
391 }),
392 UntypedAssetId::Uuid { uuid, type_id } if type_id == expected => {
393 Ok(AssetId::Uuid { uuid })
394 }
395 _ => Err(UntypedAssetIdConversionError::TypeIdMismatch { expected, found }),
396 }
397 }
398}
399
400#[derive(Error, Display, Debug, PartialEq, Clone)]
402#[non_exhaustive]
403pub enum UntypedAssetIdConversionError {
404 #[display("This UntypedAssetId is for {found:?} and cannot be converted into an AssetId<{expected:?}>")]
406 TypeIdMismatch { expected: TypeId, found: TypeId },
407}
408
409#[cfg(test)]
410mod tests {
411 use super::*;
412
413 type TestAsset = ();
414
415 const UUID_1: Uuid = Uuid::from_u128(123);
416 const UUID_2: Uuid = Uuid::from_u128(456);
417
418 fn hash<T: Hash>(data: &T) -> u64 {
420 use core::hash::Hasher;
421
422 let mut hasher = bevy_utils::AHasher::default();
423 data.hash(&mut hasher);
424 hasher.finish()
425 }
426
427 #[test]
429 fn equality() {
430 let typed = AssetId::<TestAsset>::Uuid { uuid: UUID_1 };
431 let untyped = UntypedAssetId::Uuid {
432 type_id: TypeId::of::<TestAsset>(),
433 uuid: UUID_1,
434 };
435
436 assert_eq!(Ok(typed), AssetId::try_from(untyped));
437 assert_eq!(UntypedAssetId::from(typed), untyped);
438 assert_eq!(typed, untyped);
439 }
440
441 #[test]
443 fn ordering() {
444 assert!(UUID_1 < UUID_2);
445
446 let typed_1 = AssetId::<TestAsset>::Uuid { uuid: UUID_1 };
447 let typed_2 = AssetId::<TestAsset>::Uuid { uuid: UUID_2 };
448 let untyped_1 = UntypedAssetId::Uuid {
449 type_id: TypeId::of::<TestAsset>(),
450 uuid: UUID_1,
451 };
452 let untyped_2 = UntypedAssetId::Uuid {
453 type_id: TypeId::of::<TestAsset>(),
454 uuid: UUID_2,
455 };
456
457 assert!(typed_1 < typed_2);
458 assert!(untyped_1 < untyped_2);
459
460 assert!(UntypedAssetId::from(typed_1) < untyped_2);
461 assert!(untyped_1 < UntypedAssetId::from(typed_2));
462
463 assert!(AssetId::try_from(untyped_1).unwrap() < typed_2);
464 assert!(typed_1 < AssetId::try_from(untyped_2).unwrap());
465
466 assert!(typed_1 < untyped_2);
467 assert!(untyped_1 < typed_2);
468 }
469
470 #[test]
472 fn hashing() {
473 let typed = AssetId::<TestAsset>::Uuid { uuid: UUID_1 };
474 let untyped = UntypedAssetId::Uuid {
475 type_id: TypeId::of::<TestAsset>(),
476 uuid: UUID_1,
477 };
478
479 assert_eq!(
480 hash(&typed),
481 hash(&AssetId::<TestAsset>::try_from(untyped).unwrap())
482 );
483 assert_eq!(hash(&UntypedAssetId::from(typed)), hash(&untyped));
484 assert_eq!(hash(&typed), hash(&untyped));
485 }
486
487 #[test]
489 fn conversion() {
490 let typed = AssetId::<TestAsset>::Uuid { uuid: UUID_1 };
491 let untyped = UntypedAssetId::Uuid {
492 type_id: TypeId::of::<TestAsset>(),
493 uuid: UUID_1,
494 };
495
496 assert_eq!(Ok(typed), AssetId::try_from(untyped));
497 assert_eq!(UntypedAssetId::from(typed), untyped);
498 }
499}