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::{any::TypeId, iter::Enumerate, marker::PhantomData, sync::atomic::AtomicU32};
12use crossbeam_channel::{Receiver, Sender};
13use serde::{Deserialize, Serialize};
14use thiserror::Error;
15use uuid::Uuid;
16
17/// A generational runtime-only identifier for a specific [`Asset`] stored in [`Assets`]. This is optimized for efficient runtime
18/// usage and is not suitable for identifying assets across app runs.
19#[derive(
20    Debug, Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd, Reflect, Serialize, Deserialize,
21)]
22pub struct AssetIndex {
23    pub(crate) generation: u32,
24    pub(crate) index: u32,
25}
26
27impl AssetIndex {
28    /// Convert the [`AssetIndex`] into an opaque blob of bits to transport it in circumstances where carrying a strongly typed index isn't possible.
29    ///
30    /// The result of this function should not be relied upon for anything except putting it back into [`AssetIndex::from_bits`] to recover the index.
31    pub fn to_bits(self) -> u64 {
32        let Self { generation, index } = self;
33        ((generation as u64) << 32) | index as u64
34    }
35    /// Convert an opaque `u64` acquired from [`AssetIndex::to_bits`] back into an [`AssetIndex`]. This should not be used with any inputs other than those
36    /// derived from [`AssetIndex::to_bits`], as there are no guarantees for what will happen with such inputs.
37    pub fn from_bits(bits: u64) -> Self {
38        let index = ((bits << 32) >> 32) as u32;
39        let generation = (bits >> 32) as u32;
40        Self { generation, index }
41    }
42}
43
44/// Allocates generational [`AssetIndex`] values and facilitates their reuse.
45pub(crate) struct AssetIndexAllocator {
46    /// A monotonically increasing index.
47    next_index: AtomicU32,
48    recycled_queue_sender: Sender<AssetIndex>,
49    /// This receives every recycled [`AssetIndex`]. It serves as a buffer/queue to store indices ready for reuse.
50    recycled_queue_receiver: Receiver<AssetIndex>,
51    recycled_sender: Sender<AssetIndex>,
52    recycled_receiver: Receiver<AssetIndex>,
53}
54
55impl Default for AssetIndexAllocator {
56    fn default() -> Self {
57        let (recycled_queue_sender, recycled_queue_receiver) = crossbeam_channel::unbounded();
58        let (recycled_sender, recycled_receiver) = crossbeam_channel::unbounded();
59        Self {
60            recycled_queue_sender,
61            recycled_queue_receiver,
62            recycled_sender,
63            recycled_receiver,
64            next_index: Default::default(),
65        }
66    }
67}
68
69impl AssetIndexAllocator {
70    /// Reserves a new [`AssetIndex`], either by reusing a recycled index (with an incremented generation), or by creating a new index
71    /// by incrementing the index counter for a given asset type `A`.
72    pub fn reserve(&self) -> AssetIndex {
73        if let Ok(mut recycled) = self.recycled_queue_receiver.try_recv() {
74            recycled.generation += 1;
75            self.recycled_sender.send(recycled).unwrap();
76            recycled
77        } else {
78            AssetIndex {
79                index: self
80                    .next_index
81                    .fetch_add(1, core::sync::atomic::Ordering::Relaxed),
82                generation: 0,
83            }
84        }
85    }
86
87    /// Queues the given `index` for reuse. This should only be done if the `index` is no longer being used.
88    pub fn recycle(&self, index: AssetIndex) {
89        self.recycled_queue_sender.send(index).unwrap();
90    }
91}
92
93/// A "loaded asset" containing the untyped handle for an asset stored in a given [`AssetPath`].
94///
95/// [`AssetPath`]: crate::AssetPath
96#[derive(Asset, TypePath)]
97pub struct LoadedUntypedAsset {
98    /// The handle to the loaded asset.
99    #[dependency]
100    pub handle: UntypedHandle,
101}
102
103// PERF: do we actually need this to be an enum? Can we just use an "invalid" generation instead
104#[derive(Default)]
105enum Entry<A: Asset> {
106    /// None is an indicator that this entry does not have live handles.
107    #[default]
108    None,
109    /// Some is an indicator that there is a live handle active for the entry at this [`AssetIndex`]
110    Some { value: Option<A>, generation: u32 },
111}
112
113/// Stores [`Asset`] values in a Vec-like storage identified by [`AssetIndex`].
114struct DenseAssetStorage<A: Asset> {
115    storage: Vec<Entry<A>>,
116    len: u32,
117    allocator: Arc<AssetIndexAllocator>,
118}
119
120impl<A: Asset> Default for DenseAssetStorage<A> {
121    fn default() -> Self {
122        Self {
123            len: 0,
124            storage: Default::default(),
125            allocator: Default::default(),
126        }
127    }
128}
129
130impl<A: Asset> DenseAssetStorage<A> {
131    // Returns the number of assets stored.
132    pub(crate) fn len(&self) -> usize {
133        self.len as usize
134    }
135
136    // Returns `true` if there are no assets stored.
137    pub(crate) fn is_empty(&self) -> bool {
138        self.len == 0
139    }
140
141    /// Insert the value at the given index. Returns true if a value already exists (and was replaced)
142    pub(crate) fn insert(
143        &mut self,
144        index: AssetIndex,
145        asset: A,
146    ) -> Result<bool, InvalidGenerationError> {
147        self.flush();
148        let entry = &mut self.storage[index.index as usize];
149        if let Entry::Some { value, generation } = entry {
150            if *generation == index.generation {
151                let exists = value.is_some();
152                if !exists {
153                    self.len += 1;
154                }
155                *value = Some(asset);
156                Ok(exists)
157            } else {
158                Err(InvalidGenerationError::Occupied {
159                    index,
160                    current_generation: *generation,
161                })
162            }
163        } else {
164            Err(InvalidGenerationError::Removed { index })
165        }
166    }
167
168    /// Removes the asset stored at the given `index` and returns it as [`Some`] (if the asset exists).
169    /// This will recycle the id and allow new entries to be inserted.
170    pub(crate) fn remove_dropped(&mut self, index: AssetIndex) -> Option<A> {
171        self.remove_internal(index, |dense_storage| {
172            dense_storage.storage[index.index as usize] = Entry::None;
173            dense_storage.allocator.recycle(index);
174        })
175    }
176
177    /// Removes the asset stored at the given `index` and returns it as [`Some`] (if the asset exists).
178    /// This will _not_ recycle the id. New values with the current ID can still be inserted. The ID will
179    /// not be reused until [`DenseAssetStorage::remove_dropped`] is called.
180    pub(crate) fn remove_still_alive(&mut self, index: AssetIndex) -> Option<A> {
181        self.remove_internal(index, |_| {})
182    }
183
184    fn remove_internal(
185        &mut self,
186        index: AssetIndex,
187        removed_action: impl FnOnce(&mut Self),
188    ) -> Option<A> {
189        self.flush();
190        let value = match &mut self.storage[index.index as usize] {
191            Entry::None => return None,
192            Entry::Some { value, generation } => {
193                if *generation == index.generation {
194                    value.take().inspect(|_| self.len -= 1)
195                } else {
196                    return None;
197                }
198            }
199        };
200        removed_action(self);
201        value
202    }
203
204    pub(crate) fn get(&self, index: AssetIndex) -> Option<&A> {
205        let entry = self.storage.get(index.index as usize)?;
206        match entry {
207            Entry::None => None,
208            Entry::Some { value, generation } => {
209                if *generation == index.generation {
210                    value.as_ref()
211                } else {
212                    None
213                }
214            }
215        }
216    }
217
218    pub(crate) fn get_mut(&mut self, index: AssetIndex) -> Option<&mut A> {
219        let entry = self.storage.get_mut(index.index as usize)?;
220        match entry {
221            Entry::None => None,
222            Entry::Some { value, generation } => {
223                if *generation == index.generation {
224                    value.as_mut()
225                } else {
226                    None
227                }
228            }
229        }
230    }
231
232    pub(crate) fn flush(&mut self) {
233        // NOTE: this assumes the allocator index is monotonically increasing.
234        let new_len = self
235            .allocator
236            .next_index
237            .load(core::sync::atomic::Ordering::Relaxed);
238        self.storage.resize_with(new_len as usize, || Entry::Some {
239            value: None,
240            generation: 0,
241        });
242        while let Ok(recycled) = self.allocator.recycled_receiver.try_recv() {
243            let entry = &mut self.storage[recycled.index as usize];
244            *entry = Entry::Some {
245                value: None,
246                generation: recycled.generation,
247            };
248        }
249    }
250
251    pub(crate) fn get_index_allocator(&self) -> Arc<AssetIndexAllocator> {
252        self.allocator.clone()
253    }
254
255    pub(crate) fn ids(&self) -> impl Iterator<Item = AssetId<A>> + '_ {
256        self.storage
257            .iter()
258            .enumerate()
259            .filter_map(|(i, v)| match v {
260                Entry::None => None,
261                Entry::Some { value, generation } => {
262                    if value.is_some() {
263                        Some(AssetId::from(AssetIndex {
264                            index: i as u32,
265                            generation: *generation,
266                        }))
267                    } else {
268                        None
269                    }
270                }
271            })
272    }
273}
274
275/// Stores [`Asset`] values identified by their [`AssetId`].
276///
277/// Assets identified by [`AssetId::Index`] will be stored in a "dense" vec-like storage. This is more efficient, but it means that
278/// the assets can only be identified at runtime. This is the default behavior.
279///
280/// Assets identified by [`AssetId::Uuid`] will be stored in a hashmap. This is less efficient, but it means that the assets can be referenced
281/// at compile time.
282///
283/// This tracks (and queues) [`AssetEvent`] events whenever changes to the collection occur.
284/// To check whether the asset used by a given component has changed (due to a change in the handle or the underlying asset)
285/// use the [`AssetChanged`](crate::asset_changed::AssetChanged) query filter.
286#[derive(Resource)]
287pub struct Assets<A: Asset> {
288    dense_storage: DenseAssetStorage<A>,
289    hash_map: HashMap<Uuid, A>,
290    handle_provider: AssetHandleProvider,
291    queued_events: Vec<AssetEvent<A>>,
292    /// Assets managed by the `Assets` struct with live strong `Handle`s
293    /// originating from `get_strong_handle`.
294    duplicate_handles: HashMap<AssetId<A>, u16>,
295}
296
297impl<A: Asset> Default for Assets<A> {
298    fn default() -> Self {
299        let dense_storage = DenseAssetStorage::default();
300        let handle_provider =
301            AssetHandleProvider::new(TypeId::of::<A>(), dense_storage.get_index_allocator());
302        Self {
303            dense_storage,
304            handle_provider,
305            hash_map: Default::default(),
306            queued_events: Default::default(),
307            duplicate_handles: Default::default(),
308        }
309    }
310}
311
312impl<A: Asset> Assets<A> {
313    /// Retrieves an [`AssetHandleProvider`] capable of reserving new [`Handle`] values for assets that will be stored in this
314    /// collection.
315    pub fn get_handle_provider(&self) -> AssetHandleProvider {
316        self.handle_provider.clone()
317    }
318
319    /// Reserves a new [`Handle`] for an asset that will be stored in this collection.
320    pub fn reserve_handle(&self) -> Handle<A> {
321        self.handle_provider.reserve_handle().typed::<A>()
322    }
323
324    /// Inserts the given `asset`, identified by the given `id`. If an asset already exists for
325    /// `id`, it will be replaced.
326    ///
327    /// Note: This will never return an error for UUID asset IDs.
328    pub fn insert(
329        &mut self,
330        id: impl Into<AssetId<A>>,
331        asset: A,
332    ) -> Result<(), InvalidGenerationError> {
333        match id.into() {
334            AssetId::Index { index, .. } => self.insert_with_index(index, asset).map(|_| ()),
335            AssetId::Uuid { uuid } => {
336                self.insert_with_uuid(uuid, asset);
337                Ok(())
338            }
339        }
340    }
341
342    /// Retrieves an [`Asset`] stored for the given `id` if it exists. If it does not exist, it will
343    /// be inserted using `insert_fn`.
344    ///
345    /// Note: This will never return an error for UUID asset IDs.
346    // PERF: Optimize this or remove it
347    pub fn get_or_insert_with(
348        &mut self,
349        id: impl Into<AssetId<A>>,
350        insert_fn: impl FnOnce() -> A,
351    ) -> Result<&mut A, InvalidGenerationError> {
352        let id: AssetId<A> = id.into();
353        if self.get(id).is_none() {
354            self.insert(id, insert_fn())?;
355        }
356        // This should be impossible since either, `self.get` was Some, in which case this succeeds,
357        // or `self.get` was None and we inserted it (and bailed out if there was an error).
358        Ok(self
359            .get_mut(id)
360            .expect("the Asset was none even though we checked or inserted"))
361    }
362
363    /// Returns `true` if the `id` exists in this collection. Otherwise it returns `false`.
364    pub fn contains(&self, id: impl Into<AssetId<A>>) -> bool {
365        match id.into() {
366            AssetId::Index { index, .. } => self.dense_storage.get(index).is_some(),
367            AssetId::Uuid { uuid } => self.hash_map.contains_key(&uuid),
368        }
369    }
370
371    pub(crate) fn insert_with_uuid(&mut self, uuid: Uuid, asset: A) -> Option<A> {
372        let result = self.hash_map.insert(uuid, asset);
373        if result.is_some() {
374            self.queued_events
375                .push(AssetEvent::Modified { id: uuid.into() });
376        } else {
377            self.queued_events
378                .push(AssetEvent::Added { id: uuid.into() });
379        }
380        result
381    }
382    pub(crate) fn insert_with_index(
383        &mut self,
384        index: AssetIndex,
385        asset: A,
386    ) -> Result<bool, InvalidGenerationError> {
387        let replaced = self.dense_storage.insert(index, asset)?;
388        if replaced {
389            self.queued_events
390                .push(AssetEvent::Modified { id: index.into() });
391        } else {
392            self.queued_events
393                .push(AssetEvent::Added { id: index.into() });
394        }
395        Ok(replaced)
396    }
397
398    /// Adds the given `asset` and allocates a new strong [`Handle`] for it.
399    #[inline]
400    pub fn add(&mut self, asset: impl Into<A>) -> Handle<A> {
401        let index = self.dense_storage.allocator.reserve();
402        self.insert_with_index(index, asset.into()).unwrap();
403        Handle::Strong(
404            self.handle_provider
405                .get_handle(index.into(), false, None, None),
406        )
407    }
408
409    /// Upgrade an `AssetId` into a strong `Handle` that will prevent asset drop.
410    ///
411    /// Returns `None` if the provided `id` is not part of this `Assets` collection.
412    /// For example, it may have been dropped earlier.
413    #[inline]
414    pub fn get_strong_handle(&mut self, id: AssetId<A>) -> Option<Handle<A>> {
415        if !self.contains(id) {
416            return None;
417        }
418        *self.duplicate_handles.entry(id).or_insert(0) += 1;
419        let index = match id {
420            AssetId::Index { index, .. } => index.into(),
421            AssetId::Uuid { uuid } => uuid.into(),
422        };
423        Some(Handle::Strong(
424            self.handle_provider.get_handle(index, false, None, None),
425        ))
426    }
427
428    /// Retrieves a reference to the [`Asset`] with the given `id`, if it exists.
429    /// Note that this supports anything that implements `Into<AssetId<A>>`, which includes [`Handle`] and [`AssetId`].
430    #[inline]
431    pub fn get(&self, id: impl Into<AssetId<A>>) -> Option<&A> {
432        match id.into() {
433            AssetId::Index { index, .. } => self.dense_storage.get(index),
434            AssetId::Uuid { uuid } => self.hash_map.get(&uuid),
435        }
436    }
437
438    /// Retrieves a mutable reference to the [`Asset`] with the given `id`, if it exists.
439    /// Note that this supports anything that implements `Into<AssetId<A>>`, which includes [`Handle`] and [`AssetId`].
440    #[inline]
441    pub fn get_mut(&mut self, id: impl Into<AssetId<A>>) -> Option<&mut A> {
442        let id: AssetId<A> = id.into();
443        let result = match id {
444            AssetId::Index { index, .. } => self.dense_storage.get_mut(index),
445            AssetId::Uuid { uuid } => self.hash_map.get_mut(&uuid),
446        };
447        if result.is_some() {
448            self.queued_events.push(AssetEvent::Modified { id });
449        }
450        result
451    }
452
453    /// Retrieves a mutable reference to the [`Asset`] with the given `id`, if it exists.
454    ///
455    /// This is the same as [`Assets::get_mut`] except it doesn't emit [`AssetEvent::Modified`].
456    #[inline]
457    pub fn get_mut_untracked(&mut self, id: impl Into<AssetId<A>>) -> Option<&mut A> {
458        let id: AssetId<A> = id.into();
459        match id {
460            AssetId::Index { index, .. } => self.dense_storage.get_mut(index),
461            AssetId::Uuid { uuid } => self.hash_map.get_mut(&uuid),
462        }
463    }
464
465    /// Removes (and returns) the [`Asset`] with the given `id`, if it exists.
466    /// Note that this supports anything that implements `Into<AssetId<A>>`, which includes [`Handle`] and [`AssetId`].
467    pub fn remove(&mut self, id: impl Into<AssetId<A>>) -> Option<A> {
468        let id: AssetId<A> = id.into();
469        let result = self.remove_untracked(id);
470        if result.is_some() {
471            self.queued_events.push(AssetEvent::Removed { id });
472        }
473        result
474    }
475
476    /// Removes (and returns) the [`Asset`] with the given `id`, if it exists. This skips emitting [`AssetEvent::Removed`].
477    /// Note that this supports anything that implements `Into<AssetId<A>>`, which includes [`Handle`] and [`AssetId`].
478    ///
479    /// This is the same as [`Assets::remove`] except it doesn't emit [`AssetEvent::Removed`].
480    pub fn remove_untracked(&mut self, id: impl Into<AssetId<A>>) -> Option<A> {
481        let id: AssetId<A> = id.into();
482        self.duplicate_handles.remove(&id);
483        match id {
484            AssetId::Index { index, .. } => self.dense_storage.remove_still_alive(index),
485            AssetId::Uuid { uuid } => self.hash_map.remove(&uuid),
486        }
487    }
488
489    /// Removes the [`Asset`] with the given `id`.
490    pub(crate) fn remove_dropped(&mut self, id: AssetId<A>) {
491        match self.duplicate_handles.get_mut(&id) {
492            None => {}
493            Some(0) => {
494                self.duplicate_handles.remove(&id);
495            }
496            Some(value) => {
497                *value -= 1;
498                return;
499            }
500        }
501
502        let existed = match id {
503            AssetId::Index { index, .. } => self.dense_storage.remove_dropped(index).is_some(),
504            AssetId::Uuid { uuid } => self.hash_map.remove(&uuid).is_some(),
505        };
506
507        self.queued_events.push(AssetEvent::Unused { id });
508        if existed {
509            self.queued_events.push(AssetEvent::Removed { id });
510        }
511    }
512
513    /// Returns `true` if there are no assets in this collection.
514    pub fn is_empty(&self) -> bool {
515        self.dense_storage.is_empty() && self.hash_map.is_empty()
516    }
517
518    /// Returns the number of assets currently stored in the collection.
519    pub fn len(&self) -> usize {
520        self.dense_storage.len() + self.hash_map.len()
521    }
522
523    /// Returns an iterator over the [`AssetId`] of every [`Asset`] stored in this collection.
524    pub fn ids(&self) -> impl Iterator<Item = AssetId<A>> + '_ {
525        self.dense_storage
526            .ids()
527            .chain(self.hash_map.keys().map(|uuid| AssetId::from(*uuid)))
528    }
529
530    /// Returns an iterator over the [`AssetId`] and [`Asset`] ref of every asset in this collection.
531    // PERF: this could be accelerated if we implement a skip list. Consider the cost/benefits
532    pub fn iter(&self) -> impl Iterator<Item = (AssetId<A>, &A)> {
533        self.dense_storage
534            .storage
535            .iter()
536            .enumerate()
537            .filter_map(|(i, v)| match v {
538                Entry::None => None,
539                Entry::Some { value, generation } => value.as_ref().map(|v| {
540                    let id = AssetId::Index {
541                        index: AssetIndex {
542                            generation: *generation,
543                            index: i as u32,
544                        },
545                        marker: PhantomData,
546                    };
547                    (id, v)
548                }),
549            })
550            .chain(
551                self.hash_map
552                    .iter()
553                    .map(|(i, v)| (AssetId::Uuid { uuid: *i }, v)),
554            )
555    }
556
557    /// Returns an iterator over the [`AssetId`] and mutable [`Asset`] ref of every asset in this collection.
558    // PERF: this could be accelerated if we implement a skip list. Consider the cost/benefits
559    pub fn iter_mut(&mut self) -> AssetsMutIterator<'_, A> {
560        AssetsMutIterator {
561            dense_storage: self.dense_storage.storage.iter_mut().enumerate(),
562            hash_map: self.hash_map.iter_mut(),
563            queued_events: &mut self.queued_events,
564        }
565    }
566
567    /// A system that synchronizes the state of assets in this collection with the [`AssetServer`]. This manages
568    /// [`Handle`] drop events.
569    pub fn track_assets(mut assets: ResMut<Self>, asset_server: Res<AssetServer>) {
570        let assets = &mut *assets;
571        // note that we must hold this lock for the entire duration of this function to ensure
572        // that `asset_server.load` calls that occur during it block, which ensures that
573        // re-loads are kicked off appropriately. This function must be "transactional" relative
574        // to other asset info operations
575        let mut infos = asset_server.data.infos.write();
576        while let Ok(drop_event) = assets.handle_provider.drop_receiver.try_recv() {
577            let id = drop_event.id.typed();
578
579            if drop_event.asset_server_managed {
580                let untyped_id = id.untyped();
581
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(untyped_id) {
584                    // a new handle has been created, or the asset doesn't exist
585                    continue;
586                }
587            }
588
589            assets.remove_dropped(id);
590        }
591    }
592
593    /// A system that applies accumulated asset change events to the [`Messages`] resource.
594    ///
595    /// [`Messages`]: bevy_ecs::event::Events
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/// A mutable iterator over [`Assets`].
627pub struct AssetsMutIterator<'a, A: Asset> {
628    queued_events: &'a mut Vec<AssetEvent<A>>,
629    dense_storage: Enumerate<core::slice::IterMut<'a, Entry<A>>>,
630    hash_map: bevy_platform::collections::hash_map::IterMut<'a, Uuid, A>,
631}
632
633impl<'a, A: Asset> Iterator for AssetsMutIterator<'a, A> {
634    type Item = (AssetId<A>, &'a mut A);
635
636    fn next(&mut self) -> Option<Self::Item> {
637        for (i, entry) in &mut self.dense_storage {
638            match entry {
639                Entry::None => {
640                    continue;
641                }
642                Entry::Some { value, generation } => {
643                    let id = AssetId::Index {
644                        index: AssetIndex {
645                            generation: *generation,
646                            index: i as u32,
647                        },
648                        marker: PhantomData,
649                    };
650                    self.queued_events.push(AssetEvent::Modified { id });
651                    if let Some(value) = value {
652                        return Some((id, value));
653                    }
654                }
655            }
656        }
657        if let Some((key, value)) = self.hash_map.next() {
658            let id = AssetId::Uuid { uuid: *key };
659            self.queued_events.push(AssetEvent::Modified { id });
660            Some((id, value))
661        } else {
662            None
663        }
664    }
665}
666
667/// An error returned when an [`AssetIndex`] has an invalid generation.
668#[derive(Error, Debug, PartialEq, Eq)]
669pub enum InvalidGenerationError {
670    #[error("AssetIndex {index:?} has an invalid generation. The current generation is: '{current_generation}'.")]
671    Occupied {
672        index: AssetIndex,
673        current_generation: u32,
674    },
675    #[error("AssetIndex {index:?} has been removed")]
676    Removed { index: AssetIndex },
677}
678
679#[cfg(test)]
680mod test {
681    use crate::AssetIndex;
682
683    #[test]
684    fn asset_index_round_trip() {
685        let asset_index = AssetIndex {
686            generation: 42,
687            index: 1337,
688        };
689        let roundtripped = AssetIndex::from_bits(asset_index.to_bits());
690        assert_eq!(asset_index, roundtripped);
691    }
692}