bevy_utils/
lib.rs

1#![cfg_attr(docsrs, feature(doc_auto_cfg))]
2#![doc(
3    html_logo_url = "https://bevyengine.org/assets/icon.png",
4    html_favicon_url = "https://bevyengine.org/assets/icon.png"
5)]
6#![no_std]
7
8//! General utilities for first-party [Bevy] engine crates.
9//!
10//! [Bevy]: https://bevyengine.org/
11
12#[cfg(feature = "std")]
13extern crate std;
14
15#[cfg(feature = "alloc")]
16extern crate alloc;
17
18/// The utilities prelude.
19///
20/// This includes the most common types in this crate, re-exported for your convenience.
21pub mod prelude {
22    pub use crate::default;
23}
24
25pub mod synccell;
26pub mod syncunsafecell;
27
28mod default;
29mod once;
30#[cfg(feature = "std")]
31mod parallel_queue;
32
33#[doc(hidden)]
34pub use once::OnceFlag;
35
36pub use default::default;
37
38#[cfg(feature = "std")]
39pub use parallel_queue::*;
40
41use core::mem::ManuallyDrop;
42
43#[cfg(feature = "alloc")]
44use {
45    bevy_platform::{
46        collections::HashMap,
47        hash::{Hashed, NoOpHash, PassHash},
48    },
49    core::{any::TypeId, hash::Hash},
50};
51
52/// A [`HashMap`] pre-configured to use [`Hashed`] keys and [`PassHash`] passthrough hashing.
53/// Iteration order only depends on the order of insertions and deletions.
54#[cfg(feature = "alloc")]
55pub type PreHashMap<K, V> = HashMap<Hashed<K>, V, PassHash>;
56
57/// Extension methods intended to add functionality to [`PreHashMap`].
58#[cfg(feature = "alloc")]
59pub trait PreHashMapExt<K, V> {
60    /// Tries to get or insert the value for the given `key` using the pre-computed hash first.
61    /// If the [`PreHashMap`] does not already contain the `key`, it will clone it and insert
62    /// the value returned by `func`.
63    fn get_or_insert_with<F: FnOnce() -> V>(&mut self, key: &Hashed<K>, func: F) -> &mut V;
64}
65
66#[cfg(feature = "alloc")]
67impl<K: Hash + Eq + PartialEq + Clone, V> PreHashMapExt<K, V> for PreHashMap<K, V> {
68    #[inline]
69    fn get_or_insert_with<F: FnOnce() -> V>(&mut self, key: &Hashed<K>, func: F) -> &mut V {
70        use bevy_platform::collections::hash_map::RawEntryMut;
71        let entry = self
72            .raw_entry_mut()
73            .from_key_hashed_nocheck(key.hash(), key);
74        match entry {
75            RawEntryMut::Occupied(entry) => entry.into_mut(),
76            RawEntryMut::Vacant(entry) => {
77                let (_, value) = entry.insert_hashed_nocheck(key.hash(), key.clone(), func());
78                value
79            }
80        }
81    }
82}
83
84/// A specialized hashmap type with Key of [`TypeId`]
85/// Iteration order only depends on the order of insertions and deletions.
86#[cfg(feature = "alloc")]
87pub type TypeIdMap<V> = HashMap<TypeId, V, NoOpHash>;
88
89/// A type which calls a function when dropped.
90/// This can be used to ensure that cleanup code is run even in case of a panic.
91///
92/// Note that this only works for panics that [unwind](https://doc.rust-lang.org/nomicon/unwinding.html)
93/// -- any code within `OnDrop` will be skipped if a panic does not unwind.
94/// In most cases, this will just work.
95///
96/// # Examples
97///
98/// ```
99/// # use bevy_utils::OnDrop;
100/// # fn test_panic(do_panic: bool, log: impl FnOnce(&str)) {
101/// // This will print a message when the variable `_catch` gets dropped,
102/// // even if a panic occurs before we reach the end of this scope.
103/// // This is similar to a `try ... catch` block in languages such as C++.
104/// let _catch = OnDrop::new(|| log("Oops, a panic occurred and this function didn't complete!"));
105///
106/// // Some code that may panic...
107/// // ...
108/// # if do_panic { panic!() }
109///
110/// // Make sure the message only gets printed if a panic occurs.
111/// // If we remove this line, then the message will be printed regardless of whether a panic occurs
112/// // -- similar to a `try ... finally` block.
113/// core::mem::forget(_catch);
114/// # }
115/// #
116/// # test_panic(false, |_| unreachable!());
117/// # let mut did_log = false;
118/// # std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
119/// #   test_panic(true, |_| did_log = true);
120/// # }));
121/// # assert!(did_log);
122/// ```
123pub struct OnDrop<F: FnOnce()> {
124    callback: ManuallyDrop<F>,
125}
126
127impl<F: FnOnce()> OnDrop<F> {
128    /// Returns an object that will invoke the specified callback when dropped.
129    pub fn new(callback: F) -> Self {
130        Self {
131            callback: ManuallyDrop::new(callback),
132        }
133    }
134}
135
136impl<F: FnOnce()> Drop for OnDrop<F> {
137    fn drop(&mut self) {
138        #![expect(
139            unsafe_code,
140            reason = "Taking from a ManuallyDrop requires unsafe code."
141        )]
142        // SAFETY: We may move out of `self`, since this instance can never be observed after it's dropped.
143        let callback = unsafe { ManuallyDrop::take(&mut self.callback) };
144        callback();
145    }
146}
147
148#[cfg(test)]
149mod tests {
150    use super::*;
151    use static_assertions::assert_impl_all;
152
153    // Check that the HashMaps are Clone if the key/values are Clone
154    assert_impl_all!(PreHashMap::<u64, usize>: Clone);
155
156    #[test]
157    fn fast_typeid_hash() {
158        struct Hasher;
159
160        impl core::hash::Hasher for Hasher {
161            fn finish(&self) -> u64 {
162                0
163            }
164            fn write(&mut self, _: &[u8]) {
165                panic!("Hashing of core::any::TypeId changed");
166            }
167            fn write_u64(&mut self, _: u64) {}
168        }
169
170        Hash::hash(&TypeId::of::<()>(), &mut Hasher);
171    }
172
173    #[cfg(feature = "alloc")]
174    #[test]
175    fn stable_hash_within_same_program_execution() {
176        use alloc::vec::Vec;
177
178        let mut map_1 = <HashMap<_, _>>::default();
179        let mut map_2 = <HashMap<_, _>>::default();
180        for i in 1..10 {
181            map_1.insert(i, i);
182            map_2.insert(i, i);
183        }
184        assert_eq!(
185            map_1.iter().collect::<Vec<_>>(),
186            map_2.iter().collect::<Vec<_>>()
187        );
188    }
189}