Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_asset/src/id.rs
9394 views
1
use crate::{Asset, AssetIndex, Handle, UntypedHandle};
2
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
3
use serde::{Deserialize, Serialize};
4
use uuid::Uuid;
5
6
use core::{
7
any::TypeId,
8
fmt::{Debug, Display},
9
hash::Hash,
10
marker::PhantomData,
11
};
12
use derive_more::derive::From;
13
use thiserror::Error;
14
15
/// A unique runtime-only identifier for an [`Asset`]. This is cheap to [`Copy`]/[`Clone`] and is not directly tied to the
16
/// lifetime of the Asset. This means it _can_ point to an [`Asset`] that no longer exists.
17
///
18
/// For an identifier tied to the lifetime of an asset, see [`Handle`](`crate::Handle`).
19
///
20
/// For an "untyped" / "generic-less" id, see [`UntypedAssetId`].
21
#[derive(Reflect, Serialize, Deserialize, From)]
22
#[reflect(Clone, Default, Debug, PartialEq, Hash)]
23
pub enum AssetId<A: Asset> {
24
/// A small / efficient runtime identifier that can be used to efficiently look up an asset stored in [`Assets`]. This is
25
/// the "default" identifier used for assets. The alternative(s) (ex: [`AssetId::Uuid`]) will only be used if assets are
26
/// explicitly registered that way.
27
///
28
/// [`Assets`]: crate::Assets
29
Index {
30
/// The unstable, opaque index of the asset.
31
index: AssetIndex,
32
/// A marker to store the type information of the asset.
33
#[reflect(ignore, clone)]
34
marker: PhantomData<fn() -> A>,
35
},
36
/// A stable-across-runs / const asset identifier. This will only be used if an asset is explicitly registered in [`Assets`]
37
/// with one.
38
///
39
/// [`Assets`]: crate::Assets
40
Uuid {
41
/// The UUID provided during asset registration.
42
uuid: Uuid,
43
},
44
}
45
46
impl<A: Asset> AssetId<A> {
47
/// The uuid for the default [`AssetId`]. It is valid to assign a value to this in [`Assets`](crate::Assets)
48
/// and by convention (where appropriate) assets should support this pattern.
49
pub const DEFAULT_UUID: Uuid = Uuid::from_u128(200809721996911295814598172825939264631);
50
51
/// This asset id _should_ never be valid. Assigning a value to this in [`Assets`](crate::Assets) will
52
/// produce undefined behavior, so don't do it!
53
pub const INVALID_UUID: Uuid = Uuid::from_u128(108428345662029828789348721013522787528);
54
55
/// Returns an [`AssetId`] with [`Self::INVALID_UUID`], which _should_ never be assigned to.
56
#[inline]
57
pub const fn invalid() -> Self {
58
Self::Uuid {
59
uuid: Self::INVALID_UUID,
60
}
61
}
62
63
/// Converts this to an "untyped" / "generic-less" [`Asset`] identifier that stores the type information
64
/// _inside_ the [`UntypedAssetId`].
65
#[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
79
impl<A: Asset> Default for AssetId<A> {
80
fn default() -> Self {
81
AssetId::Uuid {
82
uuid: Self::DEFAULT_UUID,
83
}
84
}
85
}
86
87
impl<A: Asset> Clone for AssetId<A> {
88
fn clone(&self) -> Self {
89
*self
90
}
91
}
92
93
impl<A: Asset> Copy for AssetId<A> {}
94
95
impl<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
101
impl<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
125
impl<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
133
impl<A: Asset> PartialEq for AssetId<A> {
134
#[inline]
135
fn eq(&self, other: &Self) -> bool {
136
self.internal().eq(&other.internal())
137
}
138
}
139
140
impl<A: Asset> Eq for AssetId<A> {}
141
142
impl<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
148
impl<A: Asset> Ord for AssetId<A> {
149
fn cmp(&self, other: &Self) -> core::cmp::Ordering {
150
self.internal().cmp(&other.internal())
151
}
152
}
153
154
impl<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
/// An "untyped" / "generic-less" [`Asset`] identifier that behaves much like [`AssetId`], but stores the [`Asset`] type
165
/// information at runtime instead of compile-time. This increases the size of the type, but it enables storing asset ids
166
/// across asset types together and enables comparisons between them.
167
#[derive(Debug, Copy, Clone, Reflect)]
168
pub enum UntypedAssetId {
169
/// A small / efficient runtime identifier that can be used to efficiently look up an asset stored in [`Assets`]. This is
170
/// the "default" identifier used for assets. The alternative(s) (ex: [`UntypedAssetId::Uuid`]) will only be used if assets are
171
/// explicitly registered that way.
172
///
173
/// [`Assets`]: crate::Assets
174
Index {
175
/// An identifier that records the underlying asset type.
176
type_id: TypeId,
177
/// The unstable, opaque index of the asset.
178
index: AssetIndex,
179
},
180
/// A stable-across-runs / const asset identifier. This will only be used if an asset is explicitly registered in [`Assets`]
181
/// with one.
182
///
183
/// [`Assets`]: crate::Assets
184
Uuid {
185
/// An identifier that records the underlying asset type.
186
type_id: TypeId,
187
/// The UUID provided during asset registration.
188
uuid: Uuid,
189
},
190
}
191
192
impl UntypedAssetId {
193
/// Converts this to a "typed" [`AssetId`] without checking the stored type to see if it matches the target `A` [`Asset`] type.
194
/// This should only be called if you are _absolutely certain_ the asset type matches the stored type. And even then, you should
195
/// consider using [`UntypedAssetId::typed_debug_checked`] instead.
196
#[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
/// Converts this to a "typed" [`AssetId`]. When compiled in debug-mode it will check to see if the stored type
208
/// matches the target `A` [`Asset`] type. When compiled in release-mode, this check will be skipped.
209
///
210
/// # Panics
211
///
212
/// Panics if compiled in debug mode and the [`TypeId`] of `A` does not match the stored [`TypeId`].
213
#[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
/// Converts this to a "typed" [`AssetId`].
225
///
226
/// # Panics
227
///
228
/// Panics if the [`TypeId`] of `A` does not match the stored type id.
229
#[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
/// Try to convert this to a "typed" [`AssetId`].
242
#[inline]
243
pub fn try_typed<A: Asset>(self) -> Result<AssetId<A>, UntypedAssetIdConversionError> {
244
AssetId::try_from(self)
245
}
246
247
/// Returns the stored [`TypeId`] of the referenced [`Asset`].
248
#[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
266
impl 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
284
impl 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
291
impl Eq for UntypedAssetId {}
292
293
impl 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
301
impl 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
309
impl PartialOrd for UntypedAssetId {
310
fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
311
Some(self.cmp(other))
312
}
313
}
314
315
/// An asset id without static or dynamic types associated with it.
316
///
317
/// This is provided to make implementing traits easier for the many different asset ID types.
318
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, PartialOrd, Ord, From)]
319
enum InternalAssetId {
320
Index(AssetIndex),
321
Uuid(Uuid),
322
}
323
324
/// An asset index bundled with its (dynamic) type.
325
#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)]
326
pub(crate) struct ErasedAssetIndex {
327
pub(crate) index: AssetIndex,
328
pub(crate) type_id: TypeId,
329
}
330
331
impl ErasedAssetIndex {
332
pub(crate) fn new(index: AssetIndex, type_id: TypeId) -> Self {
333
Self { index, type_id }
334
}
335
}
336
337
impl 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
347
// Cross Operations
348
349
impl<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
356
impl<A: Asset> PartialEq<AssetId<A>> for UntypedAssetId {
357
#[inline]
358
fn eq(&self, other: &AssetId<A>) -> bool {
359
other.eq(self)
360
}
361
}
362
363
impl<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
374
impl<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
381
impl<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
393
impl<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
414
impl 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
425
impl<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
436
impl 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
447
impl 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")]
458
pub(crate) struct UuidNotSupportedError;
459
460
/// Errors preventing the conversion of to/from an [`UntypedAssetId`] and an [`AssetId`].
461
#[derive(Error, Debug, PartialEq, Clone)]
462
#[non_exhaustive]
463
pub enum UntypedAssetIdConversionError {
464
/// Caused when trying to convert an [`UntypedAssetId`] into an [`AssetId`] of the wrong type.
465
#[error("This UntypedAssetId is for {found:?} and cannot be converted into an AssetId<{expected:?}>")]
466
TypeIdMismatch {
467
/// The [`TypeId`] of the asset that we are trying to convert to.
468
expected: TypeId,
469
/// The [`TypeId`] of the asset that we are trying to convert from.
470
found: TypeId,
471
},
472
}
473
474
#[cfg(test)]
475
mod 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
/// Simple utility to directly hash a value using a fixed hasher
484
fn hash<T: Hash>(data: &T) -> u64 {
485
use core::hash::BuildHasher;
486
487
bevy_platform::hash::FixedHasher.hash_one(data)
488
}
489
490
/// Typed and Untyped `AssetIds` should be equivalent to each other and themselves
491
#[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
/// Typed and Untyped `AssetIds` should be orderable amongst each other and themselves
505
#[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
/// Typed and Untyped `AssetIds` should be equivalently hashable to each other and themselves
534
#[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
/// Typed and Untyped `AssetIds` should be interchangeable
551
#[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
}
563
564