Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_asset/src/id.rs
6598 views
1
use crate::{Asset, AssetIndex};
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
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
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
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
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 exist to support efficient type erased id drop tracking. We
318
/// could use [`UntypedAssetId`] for this, but the [`TypeId`] is unnecessary.
319
///
320
/// Do not _ever_ use this across asset types for comparison.
321
/// [`InternalAssetId`] contains no type information and will happily collide
322
/// with indices across types.
323
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, PartialOrd, Ord, From)]
324
pub(crate) enum InternalAssetId {
325
Index(AssetIndex),
326
Uuid(Uuid),
327
}
328
329
impl 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
350
// Cross Operations
351
352
impl<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
359
impl<A: Asset> PartialEq<AssetId<A>> for UntypedAssetId {
360
#[inline]
361
fn eq(&self, other: &AssetId<A>) -> bool {
362
other.eq(self)
363
}
364
}
365
366
impl<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
377
impl<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
384
impl<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
396
impl<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
/// Errors preventing the conversion of to/from an [`UntypedAssetId`] and an [`AssetId`].
418
#[derive(Error, Debug, PartialEq, Clone)]
419
#[non_exhaustive]
420
pub enum UntypedAssetIdConversionError {
421
/// Caused when trying to convert an [`UntypedAssetId`] into an [`AssetId`] of the wrong type.
422
#[error("This UntypedAssetId is for {found:?} and cannot be converted into an AssetId<{expected:?}>")]
423
TypeIdMismatch {
424
/// The [`TypeId`] of the asset that we are trying to convert to.
425
expected: TypeId,
426
/// The [`TypeId`] of the asset that we are trying to convert from.
427
found: TypeId,
428
},
429
}
430
431
#[cfg(test)]
432
mod 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
/// Simple utility to directly hash a value using a fixed hasher
441
fn hash<T: Hash>(data: &T) -> u64 {
442
use core::hash::BuildHasher;
443
444
bevy_platform::hash::FixedHasher.hash_one(data)
445
}
446
447
/// Typed and Untyped `AssetIds` should be equivalent to each other and themselves
448
#[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
/// Typed and Untyped `AssetIds` should be orderable amongst each other and themselves
462
#[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
/// Typed and Untyped `AssetIds` should be equivalently hashable to each other and themselves
491
#[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
/// Typed and Untyped `AssetIds` should be interchangeable
508
#[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
}
520
521