Skip to main content

bevy_asset/
assets.rs

1use crate::asset_changed::AssetChanges;
2use crate::{Asset, AssetEvent, AssetHandleProvider, AssetId, AssetServer, Handle, UntypedHandle};
3use alloc::{sync::Arc, vec::Vec};
4use bevy_ecs::{
5    message::MessageWriter,
6    resource::Resource,
7    system::{Res, ResMut, SystemChangeTick},
8};
9use bevy_platform::collections::HashMap;
10use bevy_reflect::{Reflect, TypePath};
11use core::ops::{Deref, DerefMut};
12use core::{any::TypeId, iter::Enumerate, marker::PhantomData, sync::atomic::AtomicU32};
13use crossbeam_channel::{Receiver, Sender};
14use serde::{Deserialize, Serialize};
15use thiserror::Error;
16use 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)]
23pub struct AssetIndex {
24    pub(crate) generation: u32,
25    pub(crate) index: u32,
26}
27
28impl 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.
46pub(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
56impl 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
70impl 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)]
98pub 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)]
106enum 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`].
115struct DenseAssetStorage<A: Asset> {
116    storage: Vec<Entry<A>>,
117    len: u32,
118    allocator: Arc<AssetIndexAllocator>,
119}
120
121impl<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
131impl<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)]
288pub 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
298impl<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
313impl<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        // note that we must hold this lock for the entire duration of this function to ensure
575        // that `asset_server.load` calls that occur during it block, which ensures that
576        // re-loads are kicked off appropriately. This function must be "transactional" relative
577        // to other asset info operations
578        let mut infos = asset_server.write_infos();
579        while let Ok(drop_event) = assets.handle_provider.drop_receiver.try_recv() {
580            if drop_event.asset_server_managed {
581                // the process_handle_drop call checks whether new handles have been created since the drop event was fired, before removing the asset
582                if !infos.process_handle_drop(drop_event.index) {
583                    // a new handle has been created, or the asset doesn't exist
584                    continue;
585                }
586            }
587
588            assets.remove_dropped(drop_event.index.index);
589        }
590    }
591
592    /// A system that applies accumulated asset change events to the [`Messages`] resource.
593    ///
594    /// [`Messages`]: bevy_ecs::message::Messages
595    pub(crate) fn asset_events(
596        mut assets: ResMut<Self>,
597        mut messages: MessageWriter<AssetEvent<A>>,
598        asset_changes: Option<ResMut<AssetChanges<A>>>,
599        ticks: SystemChangeTick,
600    ) {
601        use AssetEvent::{Added, LoadedWithDependencies, Modified, Removed};
602
603        if let Some(mut asset_changes) = asset_changes {
604            for new_event in &assets.queued_events {
605                match new_event {
606                    Removed { id } | AssetEvent::Unused { id } => asset_changes.remove(id),
607                    Added { id } | Modified { id } | LoadedWithDependencies { id } => {
608                        asset_changes.insert(*id, ticks.this_run());
609                    }
610                };
611            }
612        }
613        messages.write_batch(assets.queued_events.drain(..));
614    }
615
616    /// A run condition for [`asset_events`]. The system will not run if there are no events to
617    /// flush.
618    ///
619    /// [`asset_events`]: Self::asset_events
620    pub(crate) fn asset_events_condition(assets: Res<Self>) -> bool {
621        !assets.queued_events.is_empty()
622    }
623}
624
625/// Unique mutable borrow of an asset.
626///
627/// [`AssetEvent::Modified`] events will be only triggered if an asset itself is mutably borrowed.
628///
629/// Just as an example, this allows checking if a material property has changed
630/// before modifying it to avoid unnecessary material extraction down the pipeline.
631pub struct AssetMut<'a, A: Asset> {
632    asset: &'a mut A,
633    guard: AssetMutChangeNotifier<'a, A>,
634}
635
636impl<'a, A: Asset> AssetMut<'a, A> {
637    /// Marks with inner asset as modified and returns reference to it.
638    pub fn into_inner(mut self) -> &'a mut A {
639        self.guard.changed = true;
640        self.asset
641    }
642
643    /// Returns reference to the inner asset but doesn't mark it as modified.
644    pub fn into_inner_untracked(self) -> &'a mut A {
645        self.asset
646    }
647
648    /// Manually bypasses change detection, allowing you to mutate the underlying value
649    /// without emitting [`AssetEvent::Modified`] event.
650    ///
651    /// # Warning
652    /// This is a risky operation, that can have unexpected consequences on any system relying on this code.
653    /// However, it can be an essential escape hatch when, for example,
654    /// you are trying to synchronize representations using change detection and need to avoid infinite recursion.
655    pub fn bypass_change_detection(&mut self) -> &mut A {
656        self.asset
657    }
658}
659
660impl<'a, A: Asset> Deref for AssetMut<'a, A> {
661    type Target = A;
662
663    fn deref(&self) -> &Self::Target {
664        self.asset
665    }
666}
667
668impl<'a, A: Asset> DerefMut for AssetMut<'a, A> {
669    fn deref_mut(&mut self) -> &mut Self::Target {
670        self.guard.changed = true;
671        self.asset
672    }
673}
674
675/// Helper struct to allow safe destructuring of the [`AssetMut::into_inner`]
676/// while also keeping strong change tracking guarantees.
677struct AssetMutChangeNotifier<'a, A: Asset> {
678    changed: bool,
679    asset_id: AssetId<A>,
680    queued_events: &'a mut Vec<AssetEvent<A>>,
681}
682
683impl<'a, A: Asset> Drop for AssetMutChangeNotifier<'a, A> {
684    fn drop(&mut self) {
685        if self.changed {
686            self.queued_events
687                .push(AssetEvent::Modified { id: self.asset_id });
688        }
689    }
690}
691
692/// A mutable iterator over [`Assets`].
693pub struct AssetsMutIterator<'a, A: Asset> {
694    queued_events: &'a mut Vec<AssetEvent<A>>,
695    dense_storage: Enumerate<core::slice::IterMut<'a, Entry<A>>>,
696    hash_map: bevy_platform::collections::hash_map::IterMut<'a, Uuid, A>,
697}
698
699impl<'a, A: Asset> Iterator for AssetsMutIterator<'a, A> {
700    type Item = (AssetId<A>, &'a mut A);
701
702    fn next(&mut self) -> Option<Self::Item> {
703        for (i, entry) in &mut self.dense_storage {
704            match entry {
705                Entry::None => {
706                    continue;
707                }
708                Entry::Some { value, generation } => {
709                    let id = AssetId::Index {
710                        index: AssetIndex {
711                            generation: *generation,
712                            index: i as u32,
713                        },
714                        marker: PhantomData,
715                    };
716                    self.queued_events.push(AssetEvent::Modified { id });
717                    if let Some(value) = value {
718                        return Some((id, value));
719                    }
720                }
721            }
722        }
723        if let Some((key, value)) = self.hash_map.next() {
724            let id = AssetId::Uuid { uuid: *key };
725            self.queued_events.push(AssetEvent::Modified { id });
726            Some((id, value))
727        } else {
728            None
729        }
730    }
731}
732
733/// An error returned when an [`AssetIndex`] has an invalid generation.
734#[derive(Error, Debug, PartialEq, Eq)]
735pub enum InvalidGenerationError {
736    #[error("AssetIndex {index:?} has an invalid generation. The current generation is: '{current_generation}'.")]
737    Occupied {
738        index: AssetIndex,
739        current_generation: u32,
740    },
741    #[error("AssetIndex {index:?} has been removed")]
742    Removed { index: AssetIndex },
743}
744
745#[cfg(test)]
746mod test {
747    use crate::tests::create_app;
748    use crate::{Asset, AssetApp, AssetEvent, AssetIndex, Assets};
749    use bevy_ecs::prelude::Messages;
750    use bevy_reflect::TypePath;
751
752    #[test]
753    fn asset_index_round_trip() {
754        let asset_index = AssetIndex {
755            generation: 42,
756            index: 1337,
757        };
758        let roundtripped = AssetIndex::from_bits(asset_index.to_bits());
759        assert_eq!(asset_index, roundtripped);
760    }
761
762    #[test]
763    fn assets_mut_change_detection() {
764        #[derive(Asset, TypePath, Default)]
765        struct TestAsset {
766            value: u32,
767        }
768
769        let mut app = create_app().0;
770        app.init_asset::<TestAsset>();
771
772        let mut assets = app.world_mut().resource_mut::<Assets<TestAsset>>();
773        let my_asset_handle = assets.add(TestAsset::default());
774        let my_asset_id = my_asset_handle.id();
775
776        // check a few times just in case there are some unexpected leftover events from previous runs
777        for _ in 0..3 {
778            // check that modifying the asset value triggers an event
779            {
780                let mut assets = app.world_mut().resource_mut::<Assets<TestAsset>>();
781                let mut asset = assets.get_mut(my_asset_id).unwrap();
782                asset.value += 1;
783            }
784
785            app.update();
786
787            let modified_count = app
788                .world_mut()
789                .resource_mut::<Messages<AssetEvent<TestAsset>>>()
790                .drain()
791                .filter(|event| event.is_modified(my_asset_id))
792                .count();
793
794            assert_eq!(
795                modified_count, 1,
796                "Asset value was changed but AssetEvent::Modified was not triggered",
797            );
798
799            // check that reading the asset value doesn't trigger an event
800            {
801                let mut assets = app.world_mut().resource_mut::<Assets<TestAsset>>();
802                let asset = assets.get_mut(my_asset_id).unwrap();
803                let _temp = asset.value;
804            }
805
806            app.update();
807
808            let modified_count = app
809                .world_mut()
810                .resource_mut::<Messages<AssetEvent<TestAsset>>>()
811                .drain()
812                .filter(|event| event.is_modified(my_asset_id))
813                .count();
814
815            assert_eq!(
816                modified_count, 0,
817                "Asset value was not changed but AssetEvent::Modified was triggered",
818            );
819        }
820    }
821}