Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_asset/src/assets.rs
9395 views
1
use crate::asset_changed::AssetChanges;
2
use crate::{Asset, AssetEvent, AssetHandleProvider, AssetId, AssetServer, Handle, UntypedHandle};
3
use alloc::{sync::Arc, vec::Vec};
4
use bevy_ecs::{
5
message::MessageWriter,
6
resource::Resource,
7
system::{Res, ResMut, SystemChangeTick},
8
};
9
use bevy_platform::collections::HashMap;
10
use bevy_reflect::{Reflect, TypePath};
11
use core::ops::{Deref, DerefMut};
12
use core::{any::TypeId, iter::Enumerate, marker::PhantomData, sync::atomic::AtomicU32};
13
use crossbeam_channel::{Receiver, Sender};
14
use serde::{Deserialize, Serialize};
15
use thiserror::Error;
16
use uuid::Uuid;
17
18
/// A generational runtime-only identifier for a specific [`Asset`] stored in [`Assets`]. This is optimized for efficient runtime
19
/// usage and is not suitable for identifying assets across app runs.
20
#[derive(
21
Debug, Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd, Reflect, Serialize, Deserialize,
22
)]
23
pub struct AssetIndex {
24
pub(crate) generation: u32,
25
pub(crate) index: u32,
26
}
27
28
impl AssetIndex {
29
/// Convert the [`AssetIndex`] into an opaque blob of bits to transport it in circumstances where carrying a strongly typed index isn't possible.
30
///
31
/// The result of this function should not be relied upon for anything except putting it back into [`AssetIndex::from_bits`] to recover the index.
32
pub fn to_bits(self) -> u64 {
33
let Self { generation, index } = self;
34
((generation as u64) << 32) | index as u64
35
}
36
/// Convert an opaque `u64` acquired from [`AssetIndex::to_bits`] back into an [`AssetIndex`]. This should not be used with any inputs other than those
37
/// derived from [`AssetIndex::to_bits`], as there are no guarantees for what will happen with such inputs.
38
pub fn from_bits(bits: u64) -> Self {
39
let index = ((bits << 32) >> 32) as u32;
40
let generation = (bits >> 32) as u32;
41
Self { generation, index }
42
}
43
}
44
45
/// Allocates generational [`AssetIndex`] values and facilitates their reuse.
46
pub(crate) struct AssetIndexAllocator {
47
/// A monotonically increasing index.
48
next_index: AtomicU32,
49
recycled_queue_sender: Sender<AssetIndex>,
50
/// This receives every recycled [`AssetIndex`]. It serves as a buffer/queue to store indices ready for reuse.
51
recycled_queue_receiver: Receiver<AssetIndex>,
52
recycled_sender: Sender<AssetIndex>,
53
recycled_receiver: Receiver<AssetIndex>,
54
}
55
56
impl Default for AssetIndexAllocator {
57
fn default() -> Self {
58
let (recycled_queue_sender, recycled_queue_receiver) = crossbeam_channel::unbounded();
59
let (recycled_sender, recycled_receiver) = crossbeam_channel::unbounded();
60
Self {
61
recycled_queue_sender,
62
recycled_queue_receiver,
63
recycled_sender,
64
recycled_receiver,
65
next_index: Default::default(),
66
}
67
}
68
}
69
70
impl AssetIndexAllocator {
71
/// Reserves a new [`AssetIndex`], either by reusing a recycled index (with an incremented generation), or by creating a new index
72
/// by incrementing the index counter for a given asset type `A`.
73
pub fn reserve(&self) -> AssetIndex {
74
if let Ok(mut recycled) = self.recycled_queue_receiver.try_recv() {
75
recycled.generation += 1;
76
self.recycled_sender.send(recycled).unwrap();
77
recycled
78
} else {
79
AssetIndex {
80
index: self
81
.next_index
82
.fetch_add(1, core::sync::atomic::Ordering::Relaxed),
83
generation: 0,
84
}
85
}
86
}
87
88
/// Queues the given `index` for reuse. This should only be done if the `index` is no longer being used.
89
pub fn recycle(&self, index: AssetIndex) {
90
self.recycled_queue_sender.send(index).unwrap();
91
}
92
}
93
94
/// A "loaded asset" containing the untyped handle for an asset stored in a given [`AssetPath`].
95
///
96
/// [`AssetPath`]: crate::AssetPath
97
#[derive(Asset, TypePath)]
98
pub struct LoadedUntypedAsset {
99
/// The handle to the loaded asset.
100
#[dependency]
101
pub handle: UntypedHandle,
102
}
103
104
// PERF: do we actually need this to be an enum? Can we just use an "invalid" generation instead
105
#[derive(Default)]
106
enum Entry<A: Asset> {
107
/// None is an indicator that this entry does not have live handles.
108
#[default]
109
None,
110
/// Some is an indicator that there is a live handle active for the entry at this [`AssetIndex`]
111
Some { value: Option<A>, generation: u32 },
112
}
113
114
/// Stores [`Asset`] values in a Vec-like storage identified by [`AssetIndex`].
115
struct DenseAssetStorage<A: Asset> {
116
storage: Vec<Entry<A>>,
117
len: u32,
118
allocator: Arc<AssetIndexAllocator>,
119
}
120
121
impl<A: Asset> Default for DenseAssetStorage<A> {
122
fn default() -> Self {
123
Self {
124
len: 0,
125
storage: Default::default(),
126
allocator: Default::default(),
127
}
128
}
129
}
130
131
impl<A: Asset> DenseAssetStorage<A> {
132
// Returns the number of assets stored.
133
pub(crate) fn len(&self) -> usize {
134
self.len as usize
135
}
136
137
// Returns `true` if there are no assets stored.
138
pub(crate) fn is_empty(&self) -> bool {
139
self.len == 0
140
}
141
142
/// Insert the value at the given index. Returns true if a value already exists (and was replaced)
143
pub(crate) fn insert(
144
&mut self,
145
index: AssetIndex,
146
asset: A,
147
) -> Result<bool, InvalidGenerationError> {
148
self.flush();
149
let entry = &mut self.storage[index.index as usize];
150
if let Entry::Some { value, generation } = entry {
151
if *generation == index.generation {
152
let exists = value.is_some();
153
if !exists {
154
self.len += 1;
155
}
156
*value = Some(asset);
157
Ok(exists)
158
} else {
159
Err(InvalidGenerationError::Occupied {
160
index,
161
current_generation: *generation,
162
})
163
}
164
} else {
165
Err(InvalidGenerationError::Removed { index })
166
}
167
}
168
169
/// Removes the asset stored at the given `index` and returns it as [`Some`] (if the asset exists).
170
/// This will recycle the id and allow new entries to be inserted.
171
pub(crate) fn remove_dropped(&mut self, index: AssetIndex) -> Option<A> {
172
self.remove_internal(index, |dense_storage| {
173
dense_storage.storage[index.index as usize] = Entry::None;
174
dense_storage.allocator.recycle(index);
175
})
176
}
177
178
/// Removes the asset stored at the given `index` and returns it as [`Some`] (if the asset exists).
179
/// This will _not_ recycle the id. New values with the current ID can still be inserted. The ID will
180
/// not be reused until [`DenseAssetStorage::remove_dropped`] is called.
181
pub(crate) fn remove_still_alive(&mut self, index: AssetIndex) -> Option<A> {
182
self.remove_internal(index, |_| {})
183
}
184
185
fn remove_internal(
186
&mut self,
187
index: AssetIndex,
188
removed_action: impl FnOnce(&mut Self),
189
) -> Option<A> {
190
self.flush();
191
let value = match &mut self.storage[index.index as usize] {
192
Entry::None => return None,
193
Entry::Some { value, generation } => {
194
if *generation == index.generation {
195
value.take().inspect(|_| self.len -= 1)
196
} else {
197
return None;
198
}
199
}
200
};
201
removed_action(self);
202
value
203
}
204
205
pub(crate) fn get(&self, index: AssetIndex) -> Option<&A> {
206
let entry = self.storage.get(index.index as usize)?;
207
match entry {
208
Entry::None => None,
209
Entry::Some { value, generation } => {
210
if *generation == index.generation {
211
value.as_ref()
212
} else {
213
None
214
}
215
}
216
}
217
}
218
219
pub(crate) fn get_mut(&mut self, index: AssetIndex) -> Option<&mut A> {
220
let entry = self.storage.get_mut(index.index as usize)?;
221
match entry {
222
Entry::None => None,
223
Entry::Some { value, generation } => {
224
if *generation == index.generation {
225
value.as_mut()
226
} else {
227
None
228
}
229
}
230
}
231
}
232
233
pub(crate) fn flush(&mut self) {
234
// NOTE: this assumes the allocator index is monotonically increasing.
235
let new_len = self
236
.allocator
237
.next_index
238
.load(core::sync::atomic::Ordering::Relaxed);
239
self.storage.resize_with(new_len as usize, || Entry::Some {
240
value: None,
241
generation: 0,
242
});
243
while let Ok(recycled) = self.allocator.recycled_receiver.try_recv() {
244
let entry = &mut self.storage[recycled.index as usize];
245
*entry = Entry::Some {
246
value: None,
247
generation: recycled.generation,
248
};
249
}
250
}
251
252
pub(crate) fn get_index_allocator(&self) -> Arc<AssetIndexAllocator> {
253
self.allocator.clone()
254
}
255
256
pub(crate) fn ids(&self) -> impl Iterator<Item = AssetId<A>> + '_ {
257
self.storage
258
.iter()
259
.enumerate()
260
.filter_map(|(i, v)| match v {
261
Entry::None => None,
262
Entry::Some { value, generation } => {
263
if value.is_some() {
264
Some(AssetId::from(AssetIndex {
265
index: i as u32,
266
generation: *generation,
267
}))
268
} else {
269
None
270
}
271
}
272
})
273
}
274
}
275
276
/// Stores [`Asset`] values identified by their [`AssetId`].
277
///
278
/// Assets identified by [`AssetId::Index`] will be stored in a "dense" vec-like storage. This is more efficient, but it means that
279
/// the assets can only be identified at runtime. This is the default behavior.
280
///
281
/// Assets identified by [`AssetId::Uuid`] will be stored in a hashmap. This is less efficient, but it means that the assets can be referenced
282
/// at compile time.
283
///
284
/// This tracks (and queues) [`AssetEvent`] events whenever changes to the collection occur.
285
/// To check whether the asset used by a given component has changed (due to a change in the handle or the underlying asset)
286
/// use the [`AssetChanged`](crate::asset_changed::AssetChanged) query filter.
287
#[derive(Resource)]
288
pub struct Assets<A: Asset> {
289
dense_storage: DenseAssetStorage<A>,
290
hash_map: HashMap<Uuid, A>,
291
handle_provider: AssetHandleProvider,
292
queued_events: Vec<AssetEvent<A>>,
293
/// Assets managed by the `Assets` struct with live strong `Handle`s
294
/// originating from `get_strong_handle`.
295
duplicate_handles: HashMap<AssetIndex, u16>,
296
}
297
298
impl<A: Asset> Default for Assets<A> {
299
fn default() -> Self {
300
let dense_storage = DenseAssetStorage::default();
301
let handle_provider =
302
AssetHandleProvider::new(TypeId::of::<A>(), dense_storage.get_index_allocator());
303
Self {
304
dense_storage,
305
handle_provider,
306
hash_map: Default::default(),
307
queued_events: Default::default(),
308
duplicate_handles: Default::default(),
309
}
310
}
311
}
312
313
impl<A: Asset> Assets<A> {
314
/// Retrieves an [`AssetHandleProvider`] capable of reserving new [`Handle`] values for assets that will be stored in this
315
/// collection.
316
pub fn get_handle_provider(&self) -> AssetHandleProvider {
317
self.handle_provider.clone()
318
}
319
320
/// Reserves a new [`Handle`] for an asset that will be stored in this collection.
321
pub fn reserve_handle(&self) -> Handle<A> {
322
self.handle_provider.reserve_handle().typed::<A>()
323
}
324
325
/// Inserts the given `asset`, identified by the given `id`. If an asset already exists for
326
/// `id`, it will be replaced.
327
///
328
/// Note: This will never return an error for UUID asset IDs.
329
pub fn insert(
330
&mut self,
331
id: impl Into<AssetId<A>>,
332
asset: A,
333
) -> Result<(), InvalidGenerationError> {
334
match id.into() {
335
AssetId::Index { index, .. } => self.insert_with_index(index, asset).map(|_| ()),
336
AssetId::Uuid { uuid } => {
337
self.insert_with_uuid(uuid, asset);
338
Ok(())
339
}
340
}
341
}
342
343
/// Retrieves an [`Asset`] stored for the given `id` if it exists. If it does not exist, it will
344
/// be inserted using `insert_fn`.
345
///
346
/// Note: This will never return an error for UUID asset IDs.
347
// PERF: Optimize this or remove it
348
pub fn get_or_insert_with(
349
&mut self,
350
id: impl Into<AssetId<A>>,
351
insert_fn: impl FnOnce() -> A,
352
) -> Result<AssetMut<'_, A>, InvalidGenerationError> {
353
let id: AssetId<A> = id.into();
354
if self.get(id).is_none() {
355
self.insert(id, insert_fn())?;
356
}
357
// This should be impossible since either, `self.get` was Some, in which case this succeeds,
358
// or `self.get` was None and we inserted it (and bailed out if there was an error).
359
Ok(self
360
.get_mut(id)
361
.expect("the Asset was none even though we checked or inserted"))
362
}
363
364
/// Returns `true` if the `id` exists in this collection. Otherwise it returns `false`.
365
pub fn contains(&self, id: impl Into<AssetId<A>>) -> bool {
366
match id.into() {
367
AssetId::Index { index, .. } => self.dense_storage.get(index).is_some(),
368
AssetId::Uuid { uuid } => self.hash_map.contains_key(&uuid),
369
}
370
}
371
372
pub(crate) fn insert_with_uuid(&mut self, uuid: Uuid, asset: A) -> Option<A> {
373
let result = self.hash_map.insert(uuid, asset);
374
if result.is_some() {
375
self.queued_events
376
.push(AssetEvent::Modified { id: uuid.into() });
377
} else {
378
self.queued_events
379
.push(AssetEvent::Added { id: uuid.into() });
380
}
381
result
382
}
383
pub(crate) fn insert_with_index(
384
&mut self,
385
index: AssetIndex,
386
asset: A,
387
) -> Result<bool, InvalidGenerationError> {
388
let replaced = self.dense_storage.insert(index, asset)?;
389
if replaced {
390
self.queued_events
391
.push(AssetEvent::Modified { id: index.into() });
392
} else {
393
self.queued_events
394
.push(AssetEvent::Added { id: index.into() });
395
}
396
Ok(replaced)
397
}
398
399
/// Adds the given `asset` and allocates a new strong [`Handle`] for it.
400
#[inline]
401
pub fn add(&mut self, asset: impl Into<A>) -> Handle<A> {
402
let index = self.dense_storage.allocator.reserve();
403
self.insert_with_index(index, asset.into()).unwrap();
404
Handle::Strong(self.handle_provider.get_handle(index, false, None, None))
405
}
406
407
/// Upgrade an `AssetId` into a strong `Handle` that will prevent asset drop.
408
///
409
/// Returns `None` if the provided `id` is not part of this `Assets` collection.
410
/// For example, it may have been dropped earlier.
411
#[inline]
412
pub fn get_strong_handle(&mut self, id: AssetId<A>) -> Option<Handle<A>> {
413
if !self.contains(id) {
414
return None;
415
}
416
let index = match id {
417
AssetId::Index { index, .. } => index,
418
// We don't support strong handles for Uuid assets.
419
AssetId::Uuid { .. } => return None,
420
};
421
*self.duplicate_handles.entry(index).or_insert(0) += 1;
422
Some(Handle::Strong(
423
self.handle_provider.get_handle(index, false, None, None),
424
))
425
}
426
427
/// Retrieves a reference to the [`Asset`] with the given `id`, if it exists.
428
/// Note that this supports anything that implements `Into<AssetId<A>>`, which includes [`Handle`] and [`AssetId`].
429
#[inline]
430
pub fn get(&self, id: impl Into<AssetId<A>>) -> Option<&A> {
431
match id.into() {
432
AssetId::Index { index, .. } => self.dense_storage.get(index),
433
AssetId::Uuid { uuid } => self.hash_map.get(&uuid),
434
}
435
}
436
437
/// Retrieves a mutable reference to the [`Asset`] with the given `id`, if it exists.
438
/// Note that this supports anything that implements `Into<AssetId<A>>`, which includes [`Handle`] and [`AssetId`].
439
#[inline]
440
pub fn get_mut(&mut self, id: impl Into<AssetId<A>>) -> Option<AssetMut<'_, A>> {
441
let id: AssetId<A> = id.into();
442
let result = match id {
443
AssetId::Index { index, .. } => self.dense_storage.get_mut(index),
444
AssetId::Uuid { uuid } => self.hash_map.get_mut(&uuid),
445
};
446
Some(AssetMut {
447
asset: result?,
448
guard: AssetMutChangeNotifier {
449
changed: false,
450
asset_id: id,
451
queued_events: &mut self.queued_events,
452
},
453
})
454
}
455
456
/// Retrieves a mutable reference to the [`Asset`] with the given `id`, if it exists.
457
///
458
/// This is the same as [`Assets::get_mut`] except it doesn't emit [`AssetEvent::Modified`].
459
#[inline]
460
pub fn get_mut_untracked(&mut self, id: impl Into<AssetId<A>>) -> Option<&mut A> {
461
let id: AssetId<A> = id.into();
462
match id {
463
AssetId::Index { index, .. } => self.dense_storage.get_mut(index),
464
AssetId::Uuid { uuid } => self.hash_map.get_mut(&uuid),
465
}
466
}
467
468
/// Removes (and returns) the [`Asset`] with the given `id`, if it exists.
469
/// Note that this supports anything that implements `Into<AssetId<A>>`, which includes [`Handle`] and [`AssetId`].
470
pub fn remove(&mut self, id: impl Into<AssetId<A>>) -> Option<A> {
471
let id: AssetId<A> = id.into();
472
let result = self.remove_untracked(id);
473
if result.is_some() {
474
self.queued_events.push(AssetEvent::Removed { id });
475
}
476
result
477
}
478
479
/// Removes (and returns) the [`Asset`] with the given `id`, if it exists. This skips emitting [`AssetEvent::Removed`].
480
/// Note that this supports anything that implements `Into<AssetId<A>>`, which includes [`Handle`] and [`AssetId`].
481
///
482
/// This is the same as [`Assets::remove`] except it doesn't emit [`AssetEvent::Removed`].
483
pub fn remove_untracked(&mut self, id: impl Into<AssetId<A>>) -> Option<A> {
484
let id: AssetId<A> = id.into();
485
match id {
486
AssetId::Index { index, .. } => {
487
self.duplicate_handles.remove(&index);
488
self.dense_storage.remove_still_alive(index)
489
}
490
AssetId::Uuid { uuid } => self.hash_map.remove(&uuid),
491
}
492
}
493
494
/// Removes the [`Asset`] with the given `id`.
495
pub(crate) fn remove_dropped(&mut self, index: AssetIndex) {
496
match self.duplicate_handles.get_mut(&index) {
497
None => {}
498
Some(0) => {
499
self.duplicate_handles.remove(&index);
500
}
501
Some(value) => {
502
*value -= 1;
503
return;
504
}
505
}
506
507
let existed = self.dense_storage.remove_dropped(index).is_some();
508
509
self.queued_events
510
.push(AssetEvent::Unused { id: index.into() });
511
if existed {
512
self.queued_events
513
.push(AssetEvent::Removed { id: index.into() });
514
}
515
}
516
517
/// Returns `true` if there are no assets in this collection.
518
pub fn is_empty(&self) -> bool {
519
self.dense_storage.is_empty() && self.hash_map.is_empty()
520
}
521
522
/// Returns the number of assets currently stored in the collection.
523
pub fn len(&self) -> usize {
524
self.dense_storage.len() + self.hash_map.len()
525
}
526
527
/// Returns an iterator over the [`AssetId`] of every [`Asset`] stored in this collection.
528
pub fn ids(&self) -> impl Iterator<Item = AssetId<A>> + '_ {
529
self.dense_storage
530
.ids()
531
.chain(self.hash_map.keys().map(|uuid| AssetId::from(*uuid)))
532
}
533
534
/// Returns an iterator over the [`AssetId`] and [`Asset`] ref of every asset in this collection.
535
// PERF: this could be accelerated if we implement a skip list. Consider the cost/benefits
536
pub fn iter(&self) -> impl Iterator<Item = (AssetId<A>, &A)> {
537
self.dense_storage
538
.storage
539
.iter()
540
.enumerate()
541
.filter_map(|(i, v)| match v {
542
Entry::None => None,
543
Entry::Some { value, generation } => value.as_ref().map(|v| {
544
let id = AssetId::Index {
545
index: AssetIndex {
546
generation: *generation,
547
index: i as u32,
548
},
549
marker: PhantomData,
550
};
551
(id, v)
552
}),
553
})
554
.chain(
555
self.hash_map
556
.iter()
557
.map(|(i, v)| (AssetId::Uuid { uuid: *i }, v)),
558
)
559
}
560
561
/// Returns an iterator over the [`AssetId`] and mutable [`Asset`] ref of every asset in this collection.
562
// PERF: this could be accelerated if we implement a skip list. Consider the cost/benefits
563
pub fn iter_mut(&mut self) -> AssetsMutIterator<'_, A> {
564
AssetsMutIterator {
565
dense_storage: self.dense_storage.storage.iter_mut().enumerate(),
566
hash_map: self.hash_map.iter_mut(),
567
queued_events: &mut self.queued_events,
568
}
569
}
570
571
/// A system that synchronizes the state of assets in this collection with the [`AssetServer`]. This manages
572
/// [`Handle`] drop events.
573
pub fn track_assets(mut assets: ResMut<Self>, asset_server: Res<AssetServer>) {
574
let assets = &mut *assets;
575
// note that we must hold this lock for the entire duration of this function to ensure
576
// that `asset_server.load` calls that occur during it block, which ensures that
577
// re-loads are kicked off appropriately. This function must be "transactional" relative
578
// to other asset info operations
579
let mut infos = asset_server.write_infos();
580
while let Ok(drop_event) = assets.handle_provider.drop_receiver.try_recv() {
581
if drop_event.asset_server_managed {
582
// the process_handle_drop call checks whether new handles have been created since the drop event was fired, before removing the asset
583
if !infos.process_handle_drop(drop_event.index) {
584
// a new handle has been created, or the asset doesn't exist
585
continue;
586
}
587
}
588
589
assets.remove_dropped(drop_event.index.index);
590
}
591
}
592
593
/// A system that applies accumulated asset change events to the [`Messages`] resource.
594
///
595
/// [`Messages`]: bevy_ecs::message::Messages
596
pub(crate) fn asset_events(
597
mut assets: ResMut<Self>,
598
mut messages: MessageWriter<AssetEvent<A>>,
599
asset_changes: Option<ResMut<AssetChanges<A>>>,
600
ticks: SystemChangeTick,
601
) {
602
use AssetEvent::{Added, LoadedWithDependencies, Modified, Removed};
603
604
if let Some(mut asset_changes) = asset_changes {
605
for new_event in &assets.queued_events {
606
match new_event {
607
Removed { id } | AssetEvent::Unused { id } => asset_changes.remove(id),
608
Added { id } | Modified { id } | LoadedWithDependencies { id } => {
609
asset_changes.insert(*id, ticks.this_run());
610
}
611
};
612
}
613
}
614
messages.write_batch(assets.queued_events.drain(..));
615
}
616
617
/// A run condition for [`asset_events`]. The system will not run if there are no events to
618
/// flush.
619
///
620
/// [`asset_events`]: Self::asset_events
621
pub(crate) fn asset_events_condition(assets: Res<Self>) -> bool {
622
!assets.queued_events.is_empty()
623
}
624
}
625
626
/// Unique mutable borrow of an asset.
627
///
628
/// [`AssetEvent::Modified`] events will be only triggered if an asset itself is mutably borrowed.
629
///
630
/// Just as an example, this allows checking if a material property has changed
631
/// before modifying it to avoid unnecessary material extraction down the pipeline.
632
pub struct AssetMut<'a, A: Asset> {
633
asset: &'a mut A,
634
guard: AssetMutChangeNotifier<'a, A>,
635
}
636
637
impl<'a, A: Asset> AssetMut<'a, A> {
638
/// Marks with inner asset as modified and returns reference to it.
639
pub fn into_inner(mut self) -> &'a mut A {
640
self.guard.changed = true;
641
self.asset
642
}
643
644
/// Returns reference to the inner asset but doesn't mark it as modified.
645
pub fn into_inner_untracked(self) -> &'a mut A {
646
self.asset
647
}
648
649
/// Manually bypasses change detection, allowing you to mutate the underlying value
650
/// without emitting [`AssetEvent::Modified`] event.
651
///
652
/// # Warning
653
/// This is a risky operation, that can have unexpected consequences on any system relying on this code.
654
/// However, it can be an essential escape hatch when, for example,
655
/// you are trying to synchronize representations using change detection and need to avoid infinite recursion.
656
pub fn bypass_change_detection(&mut self) -> &mut A {
657
self.asset
658
}
659
}
660
661
impl<'a, A: Asset> Deref for AssetMut<'a, A> {
662
type Target = A;
663
664
fn deref(&self) -> &Self::Target {
665
self.asset
666
}
667
}
668
669
impl<'a, A: Asset> DerefMut for AssetMut<'a, A> {
670
fn deref_mut(&mut self) -> &mut Self::Target {
671
self.guard.changed = true;
672
self.asset
673
}
674
}
675
676
/// Helper struct to allow safe destructuring of the [`AssetMut::into_inner`]
677
/// while also keeping strong change tracking guarantees.
678
struct AssetMutChangeNotifier<'a, A: Asset> {
679
changed: bool,
680
asset_id: AssetId<A>,
681
queued_events: &'a mut Vec<AssetEvent<A>>,
682
}
683
684
impl<'a, A: Asset> Drop for AssetMutChangeNotifier<'a, A> {
685
fn drop(&mut self) {
686
if self.changed {
687
self.queued_events
688
.push(AssetEvent::Modified { id: self.asset_id });
689
}
690
}
691
}
692
693
/// A mutable iterator over [`Assets`].
694
pub struct AssetsMutIterator<'a, A: Asset> {
695
queued_events: &'a mut Vec<AssetEvent<A>>,
696
dense_storage: Enumerate<core::slice::IterMut<'a, Entry<A>>>,
697
hash_map: bevy_platform::collections::hash_map::IterMut<'a, Uuid, A>,
698
}
699
700
impl<'a, A: Asset> Iterator for AssetsMutIterator<'a, A> {
701
type Item = (AssetId<A>, &'a mut A);
702
703
fn next(&mut self) -> Option<Self::Item> {
704
for (i, entry) in &mut self.dense_storage {
705
match entry {
706
Entry::None => {
707
continue;
708
}
709
Entry::Some { value, generation } => {
710
let id = AssetId::Index {
711
index: AssetIndex {
712
generation: *generation,
713
index: i as u32,
714
},
715
marker: PhantomData,
716
};
717
self.queued_events.push(AssetEvent::Modified { id });
718
if let Some(value) = value {
719
return Some((id, value));
720
}
721
}
722
}
723
}
724
if let Some((key, value)) = self.hash_map.next() {
725
let id = AssetId::Uuid { uuid: *key };
726
self.queued_events.push(AssetEvent::Modified { id });
727
Some((id, value))
728
} else {
729
None
730
}
731
}
732
}
733
734
/// An error returned when an [`AssetIndex`] has an invalid generation.
735
#[derive(Error, Debug, PartialEq, Eq)]
736
pub enum InvalidGenerationError {
737
#[error("AssetIndex {index:?} has an invalid generation. The current generation is: '{current_generation}'.")]
738
Occupied {
739
index: AssetIndex,
740
current_generation: u32,
741
},
742
#[error("AssetIndex {index:?} has been removed")]
743
Removed { index: AssetIndex },
744
}
745
746
#[cfg(test)]
747
mod test {
748
use crate::tests::create_app;
749
use crate::{Asset, AssetApp, AssetEvent, AssetIndex, Assets};
750
use bevy_ecs::prelude::Messages;
751
use bevy_reflect::TypePath;
752
753
#[test]
754
fn asset_index_round_trip() {
755
let asset_index = AssetIndex {
756
generation: 42,
757
index: 1337,
758
};
759
let roundtripped = AssetIndex::from_bits(asset_index.to_bits());
760
assert_eq!(asset_index, roundtripped);
761
}
762
763
#[test]
764
fn assets_mut_change_detection() {
765
#[derive(Asset, TypePath, Default)]
766
struct TestAsset {
767
value: u32,
768
}
769
770
let mut app = create_app().0;
771
app.init_asset::<TestAsset>();
772
773
let mut assets = app.world_mut().resource_mut::<Assets<TestAsset>>();
774
let my_asset_handle = assets.add(TestAsset::default());
775
let my_asset_id = my_asset_handle.id();
776
777
// check a few times just in case there are some unexpected leftover events from previous runs
778
for _ in 0..3 {
779
// check that modifying the asset value triggers an event
780
{
781
let mut assets = app.world_mut().resource_mut::<Assets<TestAsset>>();
782
let mut asset = assets.get_mut(my_asset_id).unwrap();
783
asset.value += 1;
784
}
785
786
app.update();
787
788
let modified_count = app
789
.world_mut()
790
.resource_mut::<Messages<AssetEvent<TestAsset>>>()
791
.drain()
792
.filter(|event| event.is_modified(my_asset_id))
793
.count();
794
795
assert_eq!(
796
modified_count, 1,
797
"Asset value was changed but AssetEvent::Modified was not triggered",
798
);
799
800
// check that reading the asset value doesn't trigger an event
801
{
802
let mut assets = app.world_mut().resource_mut::<Assets<TestAsset>>();
803
let asset = assets.get_mut(my_asset_id).unwrap();
804
let _temp = asset.value;
805
}
806
807
app.update();
808
809
let modified_count = app
810
.world_mut()
811
.resource_mut::<Messages<AssetEvent<TestAsset>>>()
812
.drain()
813
.filter(|event| event.is_modified(my_asset_id))
814
.count();
815
816
assert_eq!(
817
modified_count, 0,
818
"Asset value was not changed but AssetEvent::Modified was triggered",
819
);
820
}
821
}
822
}
823
824