1use crate::{Asset, AssetIndex};
2use bevy_reflect::Reflect;
3use serde::{Deserialize, Serialize};
4use uuid::Uuid;
5
6use std::{
7 any::TypeId,
8 fmt::{Debug, Display},
9 hash::Hash,
10 marker::PhantomData,
11};
12use thiserror::Error;
13
14#[derive(Reflect, Serialize, Deserialize)]
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 std::fmt::Formatter<'_>) -> std::fmt::Result {
90 Debug::fmt(self, f)
91 }
92}
93
94impl<A: Asset> Debug for AssetId<A> {
95 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
96 match self {
97 AssetId::Index { index, .. } => {
98 write!(
99 f,
100 "AssetId<{}>{{ index: {}, generation: {}}}",
101 std::any::type_name::<A>(),
102 index.index,
103 index.generation
104 )
105 }
106 AssetId::Uuid { uuid } => {
107 write!(
108 f,
109 "AssetId<{}>{{uuid: {}}}",
110 std::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: std::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<std::cmp::Ordering> {
137 Some(self.cmp(other))
138 }
139}
140
141impl<A: Asset> Ord for AssetId<A> {
142 fn cmp(&self, other: &Self) -> std::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
157impl<A: Asset> From<Uuid> for AssetId<A> {
158 #[inline]
159 fn from(value: Uuid) -> Self {
160 Self::Uuid { uuid: value }
161 }
162}
163
164#[derive(Debug, Copy, Clone)]
168pub enum UntypedAssetId {
169 Index { type_id: TypeId, index: AssetIndex },
175 Uuid { type_id: TypeId, uuid: Uuid },
180}
181
182impl UntypedAssetId {
183 #[inline]
187 pub fn typed_unchecked<A: Asset>(self) -> AssetId<A> {
188 match self {
189 UntypedAssetId::Index { index, .. } => AssetId::Index {
190 index,
191 marker: PhantomData,
192 },
193 UntypedAssetId::Uuid { uuid, .. } => AssetId::Uuid { uuid },
194 }
195 }
196
197 #[inline]
204 pub fn typed_debug_checked<A: Asset>(self) -> AssetId<A> {
205 debug_assert_eq!(
206 self.type_id(),
207 TypeId::of::<A>(),
208 "The target AssetId<{}>'s TypeId does not match the TypeId of this UntypedAssetId",
209 std::any::type_name::<A>()
210 );
211 self.typed_unchecked()
212 }
213
214 #[inline]
220 pub fn typed<A: Asset>(self) -> AssetId<A> {
221 let Ok(id) = self.try_typed() else {
222 panic!(
223 "The target AssetId<{}>'s TypeId does not match the TypeId of this UntypedAssetId",
224 std::any::type_name::<A>()
225 )
226 };
227
228 id
229 }
230
231 #[inline]
233 pub fn try_typed<A: Asset>(self) -> Result<AssetId<A>, UntypedAssetIdConversionError> {
234 AssetId::try_from(self)
235 }
236
237 #[inline]
239 pub fn type_id(&self) -> TypeId {
240 match self {
241 UntypedAssetId::Index { type_id, .. } | UntypedAssetId::Uuid { type_id, .. } => {
242 *type_id
243 }
244 }
245 }
246
247 #[inline]
248 pub(crate) fn internal(self) -> InternalAssetId {
249 match self {
250 UntypedAssetId::Index { index, .. } => InternalAssetId::Index(index),
251 UntypedAssetId::Uuid { uuid, .. } => InternalAssetId::Uuid(uuid),
252 }
253 }
254}
255
256impl Display for UntypedAssetId {
257 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
258 let mut writer = f.debug_struct("UntypedAssetId");
259 match self {
260 UntypedAssetId::Index { index, type_id } => {
261 writer
262 .field("type_id", type_id)
263 .field("index", &index.index)
264 .field("generation", &index.generation);
265 }
266 UntypedAssetId::Uuid { uuid, type_id } => {
267 writer.field("type_id", type_id).field("uuid", uuid);
268 }
269 }
270 writer.finish()
271 }
272}
273
274impl PartialEq for UntypedAssetId {
275 #[inline]
276 fn eq(&self, other: &Self) -> bool {
277 self.type_id() == other.type_id() && self.internal().eq(&other.internal())
278 }
279}
280
281impl Eq for UntypedAssetId {}
282
283impl Hash for UntypedAssetId {
284 #[inline]
285 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
286 self.internal().hash(state);
287 self.type_id().hash(state);
288 }
289}
290
291impl Ord for UntypedAssetId {
292 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
293 self.type_id()
294 .cmp(&other.type_id())
295 .then_with(|| self.internal().cmp(&other.internal()))
296 }
297}
298
299impl PartialOrd for UntypedAssetId {
300 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
301 Some(self.cmp(other))
302 }
303}
304
305#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, PartialOrd, Ord)]
313pub(crate) enum InternalAssetId {
314 Index(AssetIndex),
315 Uuid(Uuid),
316}
317
318impl InternalAssetId {
319 #[inline]
320 pub(crate) fn typed<A: Asset>(self) -> AssetId<A> {
321 match self {
322 InternalAssetId::Index(index) => AssetId::Index {
323 index,
324 marker: PhantomData,
325 },
326 InternalAssetId::Uuid(uuid) => AssetId::Uuid { uuid },
327 }
328 }
329
330 #[inline]
331 pub(crate) fn untyped(self, type_id: TypeId) -> UntypedAssetId {
332 match self {
333 InternalAssetId::Index(index) => UntypedAssetId::Index { index, type_id },
334 InternalAssetId::Uuid(uuid) => UntypedAssetId::Uuid { uuid, type_id },
335 }
336 }
337}
338
339impl From<AssetIndex> for InternalAssetId {
340 fn from(value: AssetIndex) -> Self {
341 Self::Index(value)
342 }
343}
344
345impl From<Uuid> for InternalAssetId {
346 fn from(value: Uuid) -> Self {
347 Self::Uuid(value)
348 }
349}
350
351impl<A: Asset> PartialEq<UntypedAssetId> for AssetId<A> {
354 #[inline]
355 fn eq(&self, other: &UntypedAssetId) -> bool {
356 TypeId::of::<A>() == other.type_id() && self.internal().eq(&other.internal())
357 }
358}
359
360impl<A: Asset> PartialEq<AssetId<A>> for UntypedAssetId {
361 #[inline]
362 fn eq(&self, other: &AssetId<A>) -> bool {
363 other.eq(self)
364 }
365}
366
367impl<A: Asset> PartialOrd<UntypedAssetId> for AssetId<A> {
368 #[inline]
369 fn partial_cmp(&self, other: &UntypedAssetId) -> Option<std::cmp::Ordering> {
370 if TypeId::of::<A>() != other.type_id() {
371 None
372 } else {
373 Some(self.internal().cmp(&other.internal()))
374 }
375 }
376}
377
378impl<A: Asset> PartialOrd<AssetId<A>> for UntypedAssetId {
379 #[inline]
380 fn partial_cmp(&self, other: &AssetId<A>) -> Option<std::cmp::Ordering> {
381 Some(other.partial_cmp(self)?.reverse())
382 }
383}
384
385impl<A: Asset> From<AssetId<A>> for UntypedAssetId {
386 #[inline]
387 fn from(value: AssetId<A>) -> Self {
388 let type_id = TypeId::of::<A>();
389
390 match value {
391 AssetId::Index { index, .. } => UntypedAssetId::Index { type_id, index },
392 AssetId::Uuid { uuid } => UntypedAssetId::Uuid { type_id, uuid },
393 }
394 }
395}
396
397impl<A: Asset> TryFrom<UntypedAssetId> for AssetId<A> {
398 type Error = UntypedAssetIdConversionError;
399
400 #[inline]
401 fn try_from(value: UntypedAssetId) -> Result<Self, Self::Error> {
402 let found = value.type_id();
403 let expected = TypeId::of::<A>();
404
405 match value {
406 UntypedAssetId::Index { index, type_id } if type_id == expected => Ok(AssetId::Index {
407 index,
408 marker: PhantomData,
409 }),
410 UntypedAssetId::Uuid { uuid, type_id } if type_id == expected => {
411 Ok(AssetId::Uuid { uuid })
412 }
413 _ => Err(UntypedAssetIdConversionError::TypeIdMismatch { expected, found }),
414 }
415 }
416}
417
418#[derive(Error, Debug, PartialEq, Clone)]
420#[non_exhaustive]
421pub enum UntypedAssetIdConversionError {
422 #[error("This UntypedAssetId is for {found:?} and cannot be converted into an AssetId<{expected:?}>")]
424 TypeIdMismatch { expected: TypeId, found: TypeId },
425}
426
427#[cfg(test)]
428mod tests {
429 use super::*;
430
431 type TestAsset = ();
432
433 const UUID_1: Uuid = Uuid::from_u128(123);
434 const UUID_2: Uuid = Uuid::from_u128(456);
435
436 fn hash<T: Hash>(data: &T) -> u64 {
438 use std::hash::Hasher;
439
440 let mut hasher = bevy_utils::AHasher::default();
441 data.hash(&mut hasher);
442 hasher.finish()
443 }
444
445 #[test]
447 fn equality() {
448 let typed = AssetId::<TestAsset>::Uuid { uuid: UUID_1 };
449 let untyped = UntypedAssetId::Uuid {
450 type_id: TypeId::of::<TestAsset>(),
451 uuid: UUID_1,
452 };
453
454 assert_eq!(Ok(typed), AssetId::try_from(untyped));
455 assert_eq!(UntypedAssetId::from(typed), untyped);
456 assert_eq!(typed, untyped);
457 }
458
459 #[test]
461 fn ordering() {
462 assert!(UUID_1 < UUID_2);
463
464 let typed_1 = AssetId::<TestAsset>::Uuid { uuid: UUID_1 };
465 let typed_2 = AssetId::<TestAsset>::Uuid { uuid: UUID_2 };
466 let untyped_1 = UntypedAssetId::Uuid {
467 type_id: TypeId::of::<TestAsset>(),
468 uuid: UUID_1,
469 };
470 let untyped_2 = UntypedAssetId::Uuid {
471 type_id: TypeId::of::<TestAsset>(),
472 uuid: UUID_2,
473 };
474
475 assert!(typed_1 < typed_2);
476 assert!(untyped_1 < untyped_2);
477
478 assert!(UntypedAssetId::from(typed_1) < untyped_2);
479 assert!(untyped_1 < UntypedAssetId::from(typed_2));
480
481 assert!(AssetId::try_from(untyped_1).unwrap() < typed_2);
482 assert!(typed_1 < AssetId::try_from(untyped_2).unwrap());
483
484 assert!(typed_1 < untyped_2);
485 assert!(untyped_1 < typed_2);
486 }
487
488 #[test]
490 fn hashing() {
491 let typed = AssetId::<TestAsset>::Uuid { uuid: UUID_1 };
492 let untyped = UntypedAssetId::Uuid {
493 type_id: TypeId::of::<TestAsset>(),
494 uuid: UUID_1,
495 };
496
497 assert_eq!(
498 hash(&typed),
499 hash(&AssetId::<TestAsset>::try_from(untyped).unwrap())
500 );
501 assert_eq!(hash(&UntypedAssetId::from(typed)), hash(&untyped));
502 assert_eq!(hash(&typed), hash(&untyped));
503 }
504
505 #[test]
507 fn conversion() {
508 let typed = AssetId::<TestAsset>::Uuid { uuid: UUID_1 };
509 let untyped = UntypedAssetId::Uuid {
510 type_id: TypeId::of::<TestAsset>(),
511 uuid: UUID_1,
512 };
513
514 assert_eq!(Ok(typed), AssetId::try_from(untyped));
515 assert_eq!(UntypedAssetId::from(typed), untyped);
516 }
517}