
# Objective The clippy lint `type_complexity` is known not to play well with bevy. It frequently triggers when writing complex queries, and taking the lint's advice of using a type alias almost always just obfuscates the code with no benefit. Because of this, this lint is currently ignored in CI, but unfortunately it still shows up when viewing bevy code in an IDE. As someone who's made a fair amount of pull requests to this repo, I will say that this issue has been a consistent thorn in my side. Since bevy code is filled with spurious, ignorable warnings, it can be very difficult to spot the *real* warnings that must be fixed -- most of the time I just ignore all warnings, only to later find out that one of them was real after I'm done when CI runs. ## Solution Suppress this lint in all bevy crates. This was previously attempted in #7050, but the review process ended up making it more complicated than it needs to be and landed on a subpar solution. The discussion in https://github.com/rust-lang/rust-clippy/pull/10571 explores some better long-term solutions to this problem. Since there is no timeline on when these solutions may land, we should resolve this issue in the meantime by locally suppressing these lints. ### Unresolved issues Currently, these lints are not suppressed in our examples, since that would require suppressing the lint in every single source file. They are still ignored in CI.
307 lines
9.2 KiB
Rust
307 lines
9.2 KiB
Rust
//! General utilities for first-party [Bevy] engine crates.
|
|
//!
|
|
//! [Bevy]: https://bevyengine.org/
|
|
//!
|
|
#![allow(clippy::type_complexity)]
|
|
#![warn(missing_docs)]
|
|
#![warn(clippy::undocumented_unsafe_blocks)]
|
|
|
|
#[allow(missing_docs)]
|
|
pub mod prelude {
|
|
pub use crate::default;
|
|
}
|
|
|
|
pub mod futures;
|
|
pub mod label;
|
|
mod short_names;
|
|
pub use short_names::get_short_name;
|
|
pub mod synccell;
|
|
pub mod syncunsafecell;
|
|
|
|
mod default;
|
|
mod float_ord;
|
|
|
|
pub use ahash::AHasher;
|
|
pub use bevy_utils_proc_macros::*;
|
|
pub use default::default;
|
|
pub use float_ord::*;
|
|
pub use hashbrown;
|
|
pub use instant::{Duration, Instant};
|
|
pub use petgraph;
|
|
pub use thiserror;
|
|
pub use tracing;
|
|
pub use uuid::Uuid;
|
|
|
|
use ahash::RandomState;
|
|
use hashbrown::hash_map::RawEntryMut;
|
|
use std::{
|
|
fmt::Debug,
|
|
future::Future,
|
|
hash::{BuildHasher, Hash, Hasher},
|
|
marker::PhantomData,
|
|
mem::ManuallyDrop,
|
|
ops::Deref,
|
|
pin::Pin,
|
|
};
|
|
|
|
/// An owned and dynamically typed Future used when you can't statically type your result or need to add some indirection.
|
|
#[cfg(not(target_arch = "wasm32"))]
|
|
pub type BoxedFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
|
|
|
|
#[cfg(target_arch = "wasm32")]
|
|
pub type BoxedFuture<'a, T> = Pin<Box<dyn Future<Output = T> + 'a>>;
|
|
|
|
/// A shortcut alias for [`hashbrown::hash_map::Entry`].
|
|
pub type Entry<'a, K, V> = hashbrown::hash_map::Entry<'a, K, V, RandomState>;
|
|
|
|
/// A hasher builder that will create a fixed hasher.
|
|
#[derive(Debug, Clone, Default)]
|
|
pub struct FixedState;
|
|
|
|
impl std::hash::BuildHasher for FixedState {
|
|
type Hasher = AHasher;
|
|
|
|
#[inline]
|
|
fn build_hasher(&self) -> AHasher {
|
|
AHasher::new_with_keys(
|
|
0b1001010111101110000001001100010000000011001001101011001001111000,
|
|
0b1100111101101011011110001011010100000100001111100011010011010101,
|
|
)
|
|
}
|
|
}
|
|
|
|
/// A [`HashMap`][hashbrown::HashMap] implementing aHash, a high
|
|
/// speed keyed hashing algorithm intended for use in in-memory hashmaps.
|
|
///
|
|
/// aHash is designed for performance and is NOT cryptographically secure.
|
|
pub type HashMap<K, V> = hashbrown::HashMap<K, V, RandomState>;
|
|
|
|
/// A stable hash map implementing aHash, a high speed keyed hashing algorithm
|
|
/// intended for use in in-memory hashmaps.
|
|
///
|
|
/// Unlike [`HashMap`] this has an iteration order that only depends on the order
|
|
/// of insertions and deletions and not a random source.
|
|
///
|
|
/// aHash is designed for performance and is NOT cryptographically secure.
|
|
pub type StableHashMap<K, V> = hashbrown::HashMap<K, V, FixedState>;
|
|
|
|
/// A [`HashSet`][hashbrown::HashSet] implementing aHash, a high
|
|
/// speed keyed hashing algorithm intended for use in in-memory hashmaps.
|
|
///
|
|
/// aHash is designed for performance and is NOT cryptographically secure.
|
|
pub type HashSet<K> = hashbrown::HashSet<K, RandomState>;
|
|
|
|
/// A stable hash set implementing aHash, a high speed keyed hashing algorithm
|
|
/// intended for use in in-memory hashmaps.
|
|
///
|
|
/// Unlike [`HashSet`] this has an iteration order that only depends on the order
|
|
/// of insertions and deletions and not a random source.
|
|
///
|
|
/// aHash is designed for performance and is NOT cryptographically secure.
|
|
pub type StableHashSet<K> = hashbrown::HashSet<K, FixedState>;
|
|
|
|
/// A pre-hashed value of a specific type. Pre-hashing enables memoization of hashes that are expensive to compute.
|
|
/// It also enables faster [`PartialEq`] comparisons by short circuiting on hash equality.
|
|
/// See [`PassHash`] and [`PassHasher`] for a "pass through" [`BuildHasher`] and [`Hasher`] implementation
|
|
/// designed to work with [`Hashed`]
|
|
/// See [`PreHashMap`] for a hashmap pre-configured to use [`Hashed`] keys.
|
|
pub struct Hashed<V, H = FixedState> {
|
|
hash: u64,
|
|
value: V,
|
|
marker: PhantomData<H>,
|
|
}
|
|
|
|
impl<V: Hash, H: BuildHasher + Default> Hashed<V, H> {
|
|
/// Pre-hashes the given value using the [`BuildHasher`] configured in the [`Hashed`] type.
|
|
pub fn new(value: V) -> Self {
|
|
let builder = H::default();
|
|
let mut hasher = builder.build_hasher();
|
|
value.hash(&mut hasher);
|
|
Self {
|
|
hash: hasher.finish(),
|
|
value,
|
|
marker: PhantomData,
|
|
}
|
|
}
|
|
|
|
/// The pre-computed hash.
|
|
#[inline]
|
|
pub fn hash(&self) -> u64 {
|
|
self.hash
|
|
}
|
|
}
|
|
|
|
impl<V, H> Hash for Hashed<V, H> {
|
|
#[inline]
|
|
fn hash<R: Hasher>(&self, state: &mut R) {
|
|
state.write_u64(self.hash);
|
|
}
|
|
}
|
|
|
|
impl<V, H> Deref for Hashed<V, H> {
|
|
type Target = V;
|
|
|
|
#[inline]
|
|
fn deref(&self) -> &Self::Target {
|
|
&self.value
|
|
}
|
|
}
|
|
|
|
impl<V: PartialEq, H> PartialEq for Hashed<V, H> {
|
|
/// A fast impl of [`PartialEq`] that first checks that `other`'s pre-computed hash
|
|
/// matches this value's pre-computed hash.
|
|
#[inline]
|
|
fn eq(&self, other: &Self) -> bool {
|
|
self.hash == other.hash && self.value.eq(&other.value)
|
|
}
|
|
}
|
|
|
|
impl<V: Debug, H> Debug for Hashed<V, H> {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
f.debug_struct("Hashed")
|
|
.field("hash", &self.hash)
|
|
.field("value", &self.value)
|
|
.finish()
|
|
}
|
|
}
|
|
|
|
impl<V: Clone, H> Clone for Hashed<V, H> {
|
|
#[inline]
|
|
fn clone(&self) -> Self {
|
|
Self {
|
|
hash: self.hash,
|
|
value: self.value.clone(),
|
|
marker: PhantomData,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<V: Eq, H> Eq for Hashed<V, H> {}
|
|
|
|
/// A [`BuildHasher`] that results in a [`PassHasher`].
|
|
#[derive(Default)]
|
|
pub struct PassHash;
|
|
|
|
impl BuildHasher for PassHash {
|
|
type Hasher = PassHasher;
|
|
|
|
fn build_hasher(&self) -> Self::Hasher {
|
|
PassHasher::default()
|
|
}
|
|
}
|
|
|
|
/// A no-op hash that only works on `u64`s. Will panic if attempting to
|
|
/// hash a type containing non-u64 fields.
|
|
#[derive(Debug, Default)]
|
|
pub struct PassHasher {
|
|
hash: u64,
|
|
}
|
|
|
|
impl Hasher for PassHasher {
|
|
fn write(&mut self, _bytes: &[u8]) {
|
|
panic!("can only hash u64 using PassHasher");
|
|
}
|
|
|
|
#[inline]
|
|
fn write_u64(&mut self, i: u64) {
|
|
self.hash = i;
|
|
}
|
|
|
|
#[inline]
|
|
fn finish(&self) -> u64 {
|
|
self.hash
|
|
}
|
|
}
|
|
|
|
/// A [`HashMap`] pre-configured to use [`Hashed`] keys and [`PassHash`] passthrough hashing.
|
|
pub type PreHashMap<K, V> = hashbrown::HashMap<Hashed<K>, V, PassHash>;
|
|
|
|
/// Extension methods intended to add functionality to [`PreHashMap`].
|
|
pub trait PreHashMapExt<K, V> {
|
|
/// Tries to get or insert the value for the given `key` using the pre-computed hash first.
|
|
/// If the [`PreHashMap`] does not already contain the `key`, it will clone it and insert
|
|
/// the value returned by `func`.
|
|
fn get_or_insert_with<F: FnOnce() -> V>(&mut self, key: &Hashed<K>, func: F) -> &mut V;
|
|
}
|
|
|
|
impl<K: Hash + Eq + PartialEq + Clone, V> PreHashMapExt<K, V> for PreHashMap<K, V> {
|
|
#[inline]
|
|
fn get_or_insert_with<F: FnOnce() -> V>(&mut self, key: &Hashed<K>, func: F) -> &mut V {
|
|
let entry = self
|
|
.raw_entry_mut()
|
|
.from_key_hashed_nocheck(key.hash(), key);
|
|
match entry {
|
|
RawEntryMut::Occupied(entry) => entry.into_mut(),
|
|
RawEntryMut::Vacant(entry) => {
|
|
let (_, value) = entry.insert_hashed_nocheck(key.hash(), key.clone(), func());
|
|
value
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// A type which calls a function when dropped.
|
|
/// This can be used to ensure that cleanup code is run even in case of a panic.
|
|
///
|
|
/// Note that this only works for panics that [unwind](https://doc.rust-lang.org/nomicon/unwinding.html)
|
|
/// -- any code within `OnDrop` will be skipped if a panic does not unwind.
|
|
/// In most cases, this will just work.
|
|
///
|
|
/// # Examples
|
|
///
|
|
/// ```
|
|
/// # use bevy_utils::OnDrop;
|
|
/// # fn test_panic(do_panic: bool, log: impl FnOnce(&str)) {
|
|
/// // This will print a message when the variable `_catch` gets dropped,
|
|
/// // even if a panic occurs before we reach the end of this scope.
|
|
/// // This is similar to a `try ... catch` block in languages such as C++.
|
|
/// let _catch = OnDrop::new(|| log("Oops, a panic occurred and this function didn't complete!"));
|
|
///
|
|
/// // Some code that may panic...
|
|
/// // ...
|
|
/// # if do_panic { panic!() }
|
|
///
|
|
/// // Make sure the message only gets printed if a panic occurs.
|
|
/// // If we remove this line, then the message will be printed regardless of whether a panic occurs
|
|
/// // -- similar to a `try ... finally` block.
|
|
/// std::mem::forget(_catch);
|
|
/// # }
|
|
/// #
|
|
/// # test_panic(false, |_| unreachable!());
|
|
/// # let mut did_log = false;
|
|
/// # std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
|
|
/// # test_panic(true, |_| did_log = true);
|
|
/// # }));
|
|
/// # assert!(did_log);
|
|
/// ```
|
|
pub struct OnDrop<F: FnOnce()> {
|
|
callback: ManuallyDrop<F>,
|
|
}
|
|
|
|
impl<F: FnOnce()> OnDrop<F> {
|
|
/// Returns an object that will invoke the specified callback when dropped.
|
|
pub fn new(callback: F) -> Self {
|
|
Self {
|
|
callback: ManuallyDrop::new(callback),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<F: FnOnce()> Drop for OnDrop<F> {
|
|
fn drop(&mut self) {
|
|
// SAFETY: We may move out of `self`, since this instance can never be observed after it's dropped.
|
|
let callback = unsafe { ManuallyDrop::take(&mut self.callback) };
|
|
callback();
|
|
}
|
|
}
|
|
|
|
/// Like [`tracing::trace`], but conditional on cargo feature `detailed_trace`.
|
|
#[macro_export]
|
|
macro_rules! detailed_trace {
|
|
($($tts:tt)*) => {
|
|
if cfg!(detailed_trace) {
|
|
bevy_utils::tracing::trace!($($tts)*);
|
|
}
|
|
}
|
|
}
|