1use crate::{Asset, AssetIndex};
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 pub(crate) 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)]
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 pub(crate) 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)]
324pub(crate) enum InternalAssetId {
325 Index(AssetIndex),
326 Uuid(Uuid),
327}
328
329impl InternalAssetId {
330 #[inline]
331 pub(crate) fn typed<A: Asset>(self) -> AssetId<A> {
332 match self {
333 InternalAssetId::Index(index) => AssetId::Index {
334 index,
335 marker: PhantomData,
336 },
337 InternalAssetId::Uuid(uuid) => AssetId::Uuid { uuid },
338 }
339 }
340
341 #[inline]
342 pub(crate) fn untyped(self, type_id: TypeId) -> UntypedAssetId {
343 match self {
344 InternalAssetId::Index(index) => UntypedAssetId::Index { index, type_id },
345 InternalAssetId::Uuid(uuid) => UntypedAssetId::Uuid { uuid, type_id },
346 }
347 }
348}
349
350impl<A: Asset> PartialEq<UntypedAssetId> for AssetId<A> {
353 #[inline]
354 fn eq(&self, other: &UntypedAssetId) -> bool {
355 TypeId::of::<A>() == other.type_id() && self.internal().eq(&other.internal())
356 }
357}
358
359impl<A: Asset> PartialEq<AssetId<A>> for UntypedAssetId {
360 #[inline]
361 fn eq(&self, other: &AssetId<A>) -> bool {
362 other.eq(self)
363 }
364}
365
366impl<A: Asset> PartialOrd<UntypedAssetId> for AssetId<A> {
367 #[inline]
368 fn partial_cmp(&self, other: &UntypedAssetId) -> Option<core::cmp::Ordering> {
369 if TypeId::of::<A>() != other.type_id() {
370 None
371 } else {
372 Some(self.internal().cmp(&other.internal()))
373 }
374 }
375}
376
377impl<A: Asset> PartialOrd<AssetId<A>> for UntypedAssetId {
378 #[inline]
379 fn partial_cmp(&self, other: &AssetId<A>) -> Option<core::cmp::Ordering> {
380 Some(other.partial_cmp(self)?.reverse())
381 }
382}
383
384impl<A: Asset> From<AssetId<A>> for UntypedAssetId {
385 #[inline]
386 fn from(value: AssetId<A>) -> Self {
387 let type_id = TypeId::of::<A>();
388
389 match value {
390 AssetId::Index { index, .. } => UntypedAssetId::Index { type_id, index },
391 AssetId::Uuid { uuid } => UntypedAssetId::Uuid { type_id, uuid },
392 }
393 }
394}
395
396impl<A: Asset> TryFrom<UntypedAssetId> for AssetId<A> {
397 type Error = UntypedAssetIdConversionError;
398
399 #[inline]
400 fn try_from(value: UntypedAssetId) -> Result<Self, Self::Error> {
401 let found = value.type_id();
402 let expected = TypeId::of::<A>();
403
404 match value {
405 UntypedAssetId::Index { index, type_id } if type_id == expected => Ok(AssetId::Index {
406 index,
407 marker: PhantomData,
408 }),
409 UntypedAssetId::Uuid { uuid, type_id } if type_id == expected => {
410 Ok(AssetId::Uuid { uuid })
411 }
412 _ => Err(UntypedAssetIdConversionError::TypeIdMismatch { expected, found }),
413 }
414 }
415}
416
417#[derive(Error, Debug, PartialEq, Clone)]
419#[non_exhaustive]
420pub enum UntypedAssetIdConversionError {
421 #[error("This UntypedAssetId is for {found:?} and cannot be converted into an AssetId<{expected:?}>")]
423 TypeIdMismatch {
424 expected: TypeId,
426 found: TypeId,
428 },
429}
430
431#[cfg(test)]
432mod tests {
433 use super::*;
434
435 type TestAsset = ();
436
437 const UUID_1: Uuid = Uuid::from_u128(123);
438 const UUID_2: Uuid = Uuid::from_u128(456);
439
440 fn hash<T: Hash>(data: &T) -> u64 {
442 use core::hash::BuildHasher;
443
444 bevy_platform::hash::FixedHasher.hash_one(data)
445 }
446
447 #[test]
449 fn equality() {
450 let typed = AssetId::<TestAsset>::Uuid { uuid: UUID_1 };
451 let untyped = UntypedAssetId::Uuid {
452 type_id: TypeId::of::<TestAsset>(),
453 uuid: UUID_1,
454 };
455
456 assert_eq!(Ok(typed), AssetId::try_from(untyped));
457 assert_eq!(UntypedAssetId::from(typed), untyped);
458 assert_eq!(typed, untyped);
459 }
460
461 #[test]
463 fn ordering() {
464 assert!(UUID_1 < UUID_2);
465
466 let typed_1 = AssetId::<TestAsset>::Uuid { uuid: UUID_1 };
467 let typed_2 = AssetId::<TestAsset>::Uuid { uuid: UUID_2 };
468 let untyped_1 = UntypedAssetId::Uuid {
469 type_id: TypeId::of::<TestAsset>(),
470 uuid: UUID_1,
471 };
472 let untyped_2 = UntypedAssetId::Uuid {
473 type_id: TypeId::of::<TestAsset>(),
474 uuid: UUID_2,
475 };
476
477 assert!(typed_1 < typed_2);
478 assert!(untyped_1 < untyped_2);
479
480 assert!(UntypedAssetId::from(typed_1) < untyped_2);
481 assert!(untyped_1 < UntypedAssetId::from(typed_2));
482
483 assert!(AssetId::try_from(untyped_1).unwrap() < typed_2);
484 assert!(typed_1 < AssetId::try_from(untyped_2).unwrap());
485
486 assert!(typed_1 < untyped_2);
487 assert!(untyped_1 < typed_2);
488 }
489
490 #[test]
492 fn hashing() {
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!(
500 hash(&typed),
501 hash(&AssetId::<TestAsset>::try_from(untyped).unwrap())
502 );
503 assert_eq!(hash(&UntypedAssetId::from(typed)), hash(&untyped));
504 assert_eq!(hash(&typed), hash(&untyped));
505 }
506
507 #[test]
509 fn conversion() {
510 let typed = AssetId::<TestAsset>::Uuid { uuid: UUID_1 };
511 let untyped = UntypedAssetId::Uuid {
512 type_id: TypeId::of::<TestAsset>(),
513 uuid: UUID_1,
514 };
515
516 assert_eq!(Ok(typed), AssetId::try_from(untyped));
517 assert_eq!(UntypedAssetId::from(typed), untyped);
518 }
519}