From 8ac90ac5421defd4dcf1a0dc387a9335483c0f49 Mon Sep 17 00:00:00 2001 From: Vic <59878206+Victoronz@users.noreply.github.com> Date: Fri, 20 Dec 2024 21:55:45 +0100 Subject: [PATCH 001/272] make EntityHashMap and EntityHashSet proper types (#16912) # Objective `EntityHashMap` and `EntityHashSet` iterators do not implement `EntitySetIterator`. ## Solution Make them newtypes instead of aliases. The methods that create the iterators can then produce their own newtypes that carry the `Hasher` generic and implement `EntitySetIterator`. Functionality remains the same otherwise. There are some other small benefits, f.e. the removal of `with_hasher` associated functions, and the ability to implement more traits ourselves. `MainEntityHashMap` and `MainEntityHashSet` are currently left as the previous type aliases, because supporting general `TrustedEntityBorrow` hashing is more complex. However, it can also be done. ## Testing Pre-existing `EntityHashMap` tests. ## Migration Guide Users of `with_hasher` and `with_capacity_and_hasher` on `EntityHashMap`/`Set` must now use `new` and `with_capacity` respectively. If the non-newtyped versions are required, they can be obtained via `Deref`, `DerefMut` or `into_inner` calls. --- crates/bevy_ecs/src/entity/hash.rs | 25 +- crates/bevy_ecs/src/entity/hash_map.rs | 279 +++++++++++++++ crates/bevy_ecs/src/entity/hash_set.rs | 415 ++++++++++++++++++++++ crates/bevy_ecs/src/entity/mod.rs | 6 + crates/bevy_ecs/src/world/entity_fetch.rs | 6 +- crates/bevy_pbr/src/render/light.rs | 4 +- 6 files changed, 707 insertions(+), 28 deletions(-) create mode 100644 crates/bevy_ecs/src/entity/hash_map.rs create mode 100644 crates/bevy_ecs/src/entity/hash_set.rs diff --git a/crates/bevy_ecs/src/entity/hash.rs b/crates/bevy_ecs/src/entity/hash.rs index 2e7c8ff2a3fc6..b7d4dcae54586 100644 --- a/crates/bevy_ecs/src/entity/hash.rs +++ b/crates/bevy_ecs/src/entity/hash.rs @@ -2,12 +2,9 @@ use core::hash::{BuildHasher, Hasher}; #[cfg(feature = "bevy_reflect")] use bevy_reflect::Reflect; -use bevy_utils::hashbrown; - -use super::Entity; /// A [`BuildHasher`] that results in a [`EntityHasher`]. -#[derive(Default, Clone)] +#[derive(Debug, Default, Clone)] #[cfg_attr(feature = "bevy_reflect", derive(Reflect))] pub struct EntityHash; @@ -20,7 +17,7 @@ impl BuildHasher for EntityHash { } /// A very fast hash that is only designed to work on generational indices -/// like [`Entity`]. It will panic if attempting to hash a type containing +/// like [`Entity`](super::Entity). It will panic if attempting to hash a type containing /// non-u64 fields. /// /// This is heavily optimized for typical cases, where you have mostly live @@ -78,21 +75,3 @@ impl Hasher for EntityHasher { self.hash = bits.wrapping_mul(UPPER_PHI); } } - -/// A [`HashMap`](hashbrown::HashMap) pre-configured to use [`EntityHash`] hashing. -pub type EntityHashMap = hashbrown::HashMap; - -/// A [`HashSet`](hashbrown::HashSet) pre-configured to use [`EntityHash`] hashing. -pub type EntityHashSet = hashbrown::HashSet; - -#[cfg(test)] -mod tests { - use super::*; - use static_assertions::assert_impl_all; - - // Check that the HashMaps are Clone if the key/values are Clone - assert_impl_all!(EntityHashMap::: Clone); - // EntityHashMap should implement Reflect - #[cfg(feature = "bevy_reflect")] - assert_impl_all!(EntityHashMap::: Reflect); -} diff --git a/crates/bevy_ecs/src/entity/hash_map.rs b/crates/bevy_ecs/src/entity/hash_map.rs new file mode 100644 index 0000000000000..20ec6767baa46 --- /dev/null +++ b/crates/bevy_ecs/src/entity/hash_map.rs @@ -0,0 +1,279 @@ +use core::{ + fmt::{self, Debug, Formatter}, + iter::FusedIterator, + marker::PhantomData, + ops::{Deref, DerefMut, Index}, +}; + +#[cfg(feature = "bevy_reflect")] +use bevy_reflect::Reflect; +use bevy_utils::hashbrown::hash_map::{self, HashMap}; + +use super::{Entity, EntityHash, EntitySetIterator, TrustedEntityBorrow}; + +/// A [`HashMap`] pre-configured to use [`EntityHash`] hashing. +#[cfg_attr(feature = "bevy_reflect", derive(Reflect))] +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct EntityHashMap(pub(crate) HashMap); + +impl EntityHashMap { + /// Creates an empty `EntityHashMap`. + /// + /// Equivalent to [`HashMap::with_hasher(EntityHash)`]. + /// + /// [`HashMap::with_hasher(EntityHash)`]: HashMap::with_hasher + pub fn new() -> Self { + Self(HashMap::with_hasher(EntityHash)) + } + + /// Creates an empty `EntityHashMap` with the specified capacity. + /// + /// Equivalent to [`HashMap::with_capacity_and_hasher(n, EntityHash)`]. + /// + /// [`HashMap:with_capacity_and_hasher(n, EntityHash)`]: HashMap::with_capacity_and_hasher + pub fn with_capacity(n: usize) -> Self { + Self(HashMap::with_capacity_and_hasher(n, EntityHash)) + } + + /// Returns the inner [`HashMap`]. + pub fn into_inner(self) -> HashMap { + self.0 + } + + /// An iterator visiting all keys in arbitrary order. + /// The iterator element type is `&'a Entity`. + /// + /// Equivalent to [`HashMap::keys`]. + pub fn keys(&self) -> Keys<'_, V> { + Keys(self.0.keys(), PhantomData) + } + + /// Creates a consuming iterator visiting all the keys in arbitrary order. + /// The map cannot be used after calling this. + /// The iterator element type is [`Entity`]. + /// + /// Equivalent to [`HashMap::into_keys`]. + pub fn into_keys(self) -> IntoKeys { + IntoKeys(self.0.into_keys(), PhantomData) + } +} + +impl Default for EntityHashMap { + fn default() -> Self { + Self(Default::default()) + } +} + +impl Deref for EntityHashMap { + type Target = HashMap; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for EntityHashMap { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl<'a, V: Copy> Extend<&'a (Entity, V)> for EntityHashMap { + fn extend>(&mut self, iter: T) { + self.0.extend(iter); + } +} + +impl<'a, V: Copy> Extend<(&'a Entity, &'a V)> for EntityHashMap { + fn extend>(&mut self, iter: T) { + self.0.extend(iter); + } +} + +impl Extend<(Entity, V)> for EntityHashMap { + fn extend>(&mut self, iter: T) { + self.0.extend(iter); + } +} + +impl From<[(Entity, V); N]> for EntityHashMap { + fn from(value: [(Entity, V); N]) -> Self { + Self(HashMap::from_iter(value)) + } +} + +impl FromIterator<(Entity, V)> for EntityHashMap { + fn from_iter>(iterable: I) -> Self { + Self(HashMap::from_iter(iterable)) + } +} + +impl Index<&Q> for EntityHashMap { + type Output = V; + fn index(&self, key: &Q) -> &V { + self.0.index(&key.entity()) + } +} + +impl<'a, V> IntoIterator for &'a EntityHashMap { + type Item = (&'a Entity, &'a V); + type IntoIter = hash_map::Iter<'a, Entity, V>; + + fn into_iter(self) -> Self::IntoIter { + self.0.iter() + } +} + +impl<'a, V> IntoIterator for &'a mut EntityHashMap { + type Item = (&'a Entity, &'a mut V); + type IntoIter = hash_map::IterMut<'a, Entity, V>; + + fn into_iter(self) -> Self::IntoIter { + self.0.iter_mut() + } +} + +impl IntoIterator for EntityHashMap { + type Item = (Entity, V); + type IntoIter = hash_map::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.0.into_iter() + } +} + +/// An iterator over the keys of a [`EntityHashMap`] in arbitrary order. +/// The iterator element type is `&'a Entity`. +/// +/// /// This struct is created by the [`keys`] method on [`EntityHashMap`]. See its documentation for more. +/// +/// [`keys`]: EntityHashMap::keys +pub struct Keys<'a, V, S = EntityHash>(hash_map::Keys<'a, Entity, V>, PhantomData); + +impl<'a, V> Keys<'a, V> { + /// Returns the inner [`Keys`](hash_map::Keys). + pub fn into_inner(self) -> hash_map::Keys<'a, Entity, V> { + self.0 + } +} + +impl<'a, V> Deref for Keys<'a, V> { + type Target = hash_map::Keys<'a, Entity, V>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for Keys<'_, V> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl<'a, V> Iterator for Keys<'a, V> { + type Item = &'a Entity; + + fn next(&mut self) -> Option { + self.0.next() + } +} + +impl ExactSizeIterator for Keys<'_, V> {} + +impl FusedIterator for Keys<'_, V> {} + +impl Clone for Keys<'_, V> { + fn clone(&self) -> Self { + Self(self.0.clone(), PhantomData) + } +} + +impl Debug for Keys<'_, V> { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.debug_tuple("Keys").field(&self.0).field(&self.1).finish() + } +} + +impl Default for Keys<'_, V> { + fn default() -> Self { + Self(Default::default(), PhantomData) + } +} + +// SAFETY: Keys stems from a correctly behaving `HashMap`. +unsafe impl EntitySetIterator for Keys<'_, V> {} + +/// An owning iterator over the keys of a [`EntityHashMap`] in arbitrary order. +/// The iterator element type is [`Entity`]. +/// +/// This struct is created by the [`into_keys`] method on [`EntityHashMap`]. +/// See its documentation for more. +/// The map cannot be used after calling that method. +/// +/// [`into_keys`]: EntityHashMap::into_keys +pub struct IntoKeys(hash_map::IntoKeys, PhantomData); + +impl IntoKeys { + /// Returns the inner [`IntoKeys`](hash_map::IntoKeys). + pub fn into_inner(self) -> hash_map::IntoKeys { + self.0 + } +} + +impl Deref for IntoKeys { + type Target = hash_map::IntoKeys; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for IntoKeys { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl Iterator for IntoKeys { + type Item = Entity; + + fn next(&mut self) -> Option { + self.0.next() + } +} + +impl ExactSizeIterator for IntoKeys {} + +impl FusedIterator for IntoKeys {} + +impl Debug for IntoKeys { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.debug_tuple("IntoKeys") + .field(&self.0) + .field(&self.1) + .finish() + } +} + +impl Default for IntoKeys { + fn default() -> Self { + Self(Default::default(), PhantomData) + } +} + +// SAFETY: IntoKeys stems from a correctly behaving `HashMap`. +unsafe impl EntitySetIterator for IntoKeys {} + +#[cfg(test)] +mod tests { + use super::*; + use bevy_reflect::Reflect; + use static_assertions::assert_impl_all; + + // Check that the HashMaps are Clone if the key/values are Clone + assert_impl_all!(EntityHashMap::: Clone); + // EntityHashMap should implement Reflect + #[cfg(feature = "bevy_reflect")] + assert_impl_all!(EntityHashMap::: Reflect); +} diff --git a/crates/bevy_ecs/src/entity/hash_set.rs b/crates/bevy_ecs/src/entity/hash_set.rs new file mode 100644 index 0000000000000..12538d873b5c9 --- /dev/null +++ b/crates/bevy_ecs/src/entity/hash_set.rs @@ -0,0 +1,415 @@ +use core::{ + fmt::{self, Debug, Formatter}, + iter::FusedIterator, + marker::PhantomData, + ops::{ + BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign, Deref, DerefMut, Sub, + SubAssign, + }, +}; + +#[cfg(feature = "bevy_reflect")] +use bevy_reflect::Reflect; +use bevy_utils::hashbrown::hash_set::{self, HashSet}; + +use super::{Entity, EntityHash, EntitySetIterator}; + +/// A [`HashSet`] pre-configured to use [`EntityHash`] hashing. +#[cfg_attr(feature = "bevy_reflect", derive(Reflect))] +#[derive(Debug, Clone, Default, PartialEq, Eq)] +pub struct EntityHashSet(pub(crate) HashSet); + +impl EntityHashSet { + /// Creates an empty `EntityHashSet`. + /// + /// Equivalent to [`HashSet::with_hasher(EntityHash)`]. + /// + /// [`HashSet::with_hasher(EntityHash)`]: HashSet::with_hasher + pub fn new() -> Self { + Self(HashSet::with_hasher(EntityHash)) + } + + /// Creates an empty `EntityHashSet` with the specified capacity. + /// + /// Equivalent to [`HashSet::with_capacity_and_hasher(n, EntityHash)`]. + /// + /// [`HashSet::with_capacity_and_hasher(n, EntityHash)`]: HashSet::with_capacity_and_hasher + pub fn with_capacity(n: usize) -> Self { + Self(HashSet::with_capacity_and_hasher(n, EntityHash)) + } + + /// Returns the inner [`HashSet`]. + pub fn into_inner(self) -> HashSet { + self.0 + } + + /// Clears the set, returning all elements in an iterator. + /// + /// Equivalent to [`HashSet::drain`]. + pub fn drain(&mut self) -> Drain<'_> { + Drain(self.0.drain(), PhantomData) + } + + /// An iterator visiting all elements in arbitrary order. + /// The iterator element type is `&'a Entity`. + /// + /// Equivalent to [`HashSet::iter`]. + pub fn iter(&self) -> Iter<'_> { + Iter(self.0.iter(), PhantomData) + } + + /// Drains elements which are true under the given predicate, + /// and returns an iterator over the removed items. + /// + /// Equivalent to [`HashSet::extract_if`]. + pub fn extract_if bool>(&mut self, f: F) -> ExtractIf<'_, F> { + ExtractIf(self.0.extract_if(f), PhantomData) + } +} + +impl Deref for EntityHashSet { + type Target = HashSet; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for EntityHashSet { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl<'a> IntoIterator for &'a EntityHashSet { + type Item = &'a Entity; + + type IntoIter = Iter<'a>; + + fn into_iter(self) -> Self::IntoIter { + Iter((&self.0).into_iter(), PhantomData) + } +} + +impl IntoIterator for EntityHashSet { + type Item = Entity; + + type IntoIter = IntoIter; + + fn into_iter(self) -> Self::IntoIter { + IntoIter(self.0.into_iter(), PhantomData) + } +} + +impl BitAnd for &EntityHashSet { + type Output = EntityHashSet; + + fn bitand(self, rhs: Self) -> Self::Output { + EntityHashSet(self.0.bitand(&rhs.0)) + } +} + +impl BitAndAssign<&EntityHashSet> for EntityHashSet { + fn bitand_assign(&mut self, rhs: &Self) { + self.0.bitand_assign(&rhs.0); + } +} + +impl BitOr for &EntityHashSet { + type Output = EntityHashSet; + + fn bitor(self, rhs: Self) -> Self::Output { + EntityHashSet(self.0.bitor(&rhs.0)) + } +} + +impl BitOrAssign<&EntityHashSet> for EntityHashSet { + fn bitor_assign(&mut self, rhs: &Self) { + self.0.bitor_assign(&rhs.0); + } +} + +impl BitXor for &EntityHashSet { + type Output = EntityHashSet; + + fn bitxor(self, rhs: Self) -> Self::Output { + EntityHashSet(self.0.bitxor(&rhs.0)) + } +} + +impl BitXorAssign<&EntityHashSet> for EntityHashSet { + fn bitxor_assign(&mut self, rhs: &Self) { + self.0.bitxor_assign(&rhs.0); + } +} + +impl Sub for &EntityHashSet { + type Output = EntityHashSet; + + fn sub(self, rhs: Self) -> Self::Output { + EntityHashSet(self.0.sub(&rhs.0)) + } +} + +impl SubAssign<&EntityHashSet> for EntityHashSet { + fn sub_assign(&mut self, rhs: &Self) { + self.0.sub_assign(&rhs.0); + } +} + +impl<'a> Extend<&'a Entity> for EntityHashSet { + fn extend>(&mut self, iter: T) { + self.0.extend(iter); + } +} + +impl Extend for EntityHashSet { + fn extend>(&mut self, iter: T) { + self.0.extend(iter); + } +} + +impl From<[Entity; N]> for EntityHashSet { + fn from(value: [Entity; N]) -> Self { + Self(HashSet::from_iter(value)) + } +} + +impl FromIterator for EntityHashSet { + fn from_iter>(iterable: I) -> Self { + Self(HashSet::from_iter(iterable)) + } +} + +/// An iterator over the items of an [`EntityHashSet`]. +/// +/// This struct is created by the [`iter`] method on [`EntityHashSet`]. See its documentation for more. +/// +/// [`iter`]: EntityHashSet::iter +pub struct Iter<'a, S = EntityHash>(hash_set::Iter<'a, Entity>, PhantomData); + +impl<'a> Iter<'a> { + /// Returns the inner [`Iter`](hash_set::Iter). + pub fn into_inner(self) -> hash_set::Iter<'a, Entity> { + self.0 + } +} + +impl<'a> Deref for Iter<'a> { + type Target = hash_set::Iter<'a, Entity>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for Iter<'_> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl<'a> Iterator for Iter<'a> { + type Item = &'a Entity; + + fn next(&mut self) -> Option { + self.0.next() + } +} + +impl ExactSizeIterator for Iter<'_> {} + +impl FusedIterator for Iter<'_> {} + +impl Clone for Iter<'_> { + fn clone(&self) -> Self { + Self(self.0.clone(), PhantomData) + } +} + +impl Debug for Iter<'_> { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.debug_tuple("Iter").field(&self.0).field(&self.1).finish() + } +} + +impl Default for Iter<'_> { + fn default() -> Self { + Self(Default::default(), PhantomData) + } +} + +// SAFETY: Iter stems from a correctly behaving `HashSet`. +unsafe impl EntitySetIterator for Iter<'_> {} + +/// Owning iterator over the items of an [`EntityHashSet`]. +/// +/// This struct is created by the [`into_iter`] method on [`EntityHashSet`] (provided by the [`IntoIterator`] trait). See its documentation for more. +/// +/// [`into_iter`]: EntityHashSet::into_iter +pub struct IntoIter(hash_set::IntoIter, PhantomData); + +impl IntoIter { + /// Returns the inner [`IntoIter`](hash_set::IntoIter). + pub fn into_inner(self) -> hash_set::IntoIter { + self.0 + } +} + +impl Deref for IntoIter { + type Target = hash_set::IntoIter; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for IntoIter { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl Iterator for IntoIter { + type Item = Entity; + + fn next(&mut self) -> Option { + self.0.next() + } +} + +impl ExactSizeIterator for IntoIter {} + +impl FusedIterator for IntoIter {} + +impl Debug for IntoIter { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.debug_tuple("IntoIter") + .field(&self.0) + .field(&self.1) + .finish() + } +} + +impl Default for IntoIter { + fn default() -> Self { + Self(Default::default(), PhantomData) + } +} + +// SAFETY: IntoIter stems from a correctly behaving `HashSet`. +unsafe impl EntitySetIterator for IntoIter {} + +/// A draining iterator over the items of an [`EntityHashSet`]. +/// +/// This struct is created by the [`drain`] method on [`EntityHashSet`]. See its documentation for more. +/// +/// [`drain`]: EntityHashSet::drain +pub struct Drain<'a, S = EntityHash>(hash_set::Drain<'a, Entity>, PhantomData); + +impl<'a> Drain<'a> { + /// Returns the inner [`Drain`](hash_set::Drain). + pub fn into_inner(self) -> hash_set::Drain<'a, Entity> { + self.0 + } +} + +impl<'a> Deref for Drain<'a> { + type Target = hash_set::Drain<'a, Entity>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for Drain<'_> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl<'a> Iterator for Drain<'a> { + type Item = Entity; + + fn next(&mut self) -> Option { + self.0.next() + } +} + +impl ExactSizeIterator for Drain<'_> {} + +impl FusedIterator for Drain<'_> {} + +impl Debug for Drain<'_> { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.debug_tuple("Drain") + .field(&self.0) + .field(&self.1) + .finish() + } +} + +// SAFETY: Drain stems from a correctly behaving `HashSet`. +unsafe impl EntitySetIterator for Drain<'_> {} + +/// A draining iterator over entries of a [`EntityHashSet`] which don't satisfy the predicate `f`. +/// +/// This struct is created by the [`extract_if`] method on [`EntityHashSet`]. See its documentation for more. +/// +/// [`extract_if`]: EntityHashSet::extract_if +pub struct ExtractIf<'a, F: FnMut(&Entity) -> bool, S = EntityHash>( + hash_set::ExtractIf<'a, Entity, F>, + PhantomData, +); + +impl<'a, F: FnMut(&Entity) -> bool> ExtractIf<'a, F> { + /// Returns the inner [`ExtractIf`](hash_set::ExtractIf). + pub fn into_inner(self) -> hash_set::ExtractIf<'a, Entity, F> { + self.0 + } +} + +impl<'a, F: FnMut(&Entity) -> bool> Deref for ExtractIf<'a, F> { + type Target = hash_set::ExtractIf<'a, Entity, F>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl bool> DerefMut for ExtractIf<'_, F> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl<'a, F: FnMut(&Entity) -> bool> Iterator for ExtractIf<'a, F> { + type Item = Entity; + + fn next(&mut self) -> Option { + self.0.next() + } +} + +impl bool> FusedIterator for ExtractIf<'_, F> {} + +impl bool> Debug for ExtractIf<'_, F> { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.debug_tuple("ExtractIf").finish() + } +} + +// SAFETY: ExtractIf stems from a correctly behaving `HashSet`. +unsafe impl bool> EntitySetIterator for ExtractIf<'_, F> {} + +// SAFETY: Difference stems from two correctly behaving `HashSet`s. +unsafe impl EntitySetIterator for hash_set::Difference<'_, Entity, EntityHash> {} + +// SAFETY: Intersection stems from two correctly behaving `HashSet`s. +unsafe impl EntitySetIterator for hash_set::Intersection<'_, Entity, EntityHash> {} + +// SAFETY: SymmetricDifference stems from two correctly behaving `HashSet`s. +unsafe impl EntitySetIterator for hash_set::SymmetricDifference<'_, Entity, EntityHash> {} + +// SAFETY: Union stems from two correctly behaving `HashSet`s. +unsafe impl EntitySetIterator for hash_set::Union<'_, Entity, EntityHash> {} diff --git a/crates/bevy_ecs/src/entity/mod.rs b/crates/bevy_ecs/src/entity/mod.rs index 4932c5fc110bf..cf2370d1ca440 100644 --- a/crates/bevy_ecs/src/entity/mod.rs +++ b/crates/bevy_ecs/src/entity/mod.rs @@ -53,6 +53,12 @@ pub use visit_entities::*; mod hash; pub use hash::*; +mod hash_map; +mod hash_set; + +pub use hash_map::EntityHashMap; +pub use hash_set::EntityHashSet; + use crate::{ archetype::{ArchetypeId, ArchetypeRow}, identifier::{ diff --git a/crates/bevy_ecs/src/world/entity_fetch.rs b/crates/bevy_ecs/src/world/entity_fetch.rs index 32fbca97864c1..8d01970bdbef8 100644 --- a/crates/bevy_ecs/src/world/entity_fetch.rs +++ b/crates/bevy_ecs/src/world/entity_fetch.rs @@ -2,7 +2,7 @@ use alloc::vec::Vec; use core::mem::MaybeUninit; use crate::{ - entity::{Entity, EntityHash, EntityHashMap, EntityHashSet}, + entity::{Entity, EntityHashMap, EntityHashSet}, world::{ error::EntityFetchError, unsafe_world_cell::UnsafeWorldCell, EntityMut, EntityRef, EntityWorldMut, @@ -297,7 +297,7 @@ unsafe impl WorldEntityFetch for &'_ EntityHashSet { type DeferredMut<'w> = EntityHashMap>; unsafe fn fetch_ref(self, cell: UnsafeWorldCell<'_>) -> Result, Entity> { - let mut refs = EntityHashMap::with_capacity_and_hasher(self.len(), EntityHash); + let mut refs = EntityHashMap::with_capacity(self.len()); for &id in self { let ecell = cell.get_entity(id).ok_or(id)?; // SAFETY: caller ensures that the world cell has read-only access to the entity. @@ -310,7 +310,7 @@ unsafe impl WorldEntityFetch for &'_ EntityHashSet { self, cell: UnsafeWorldCell<'_>, ) -> Result, EntityFetchError> { - let mut refs = EntityHashMap::with_capacity_and_hasher(self.len(), EntityHash); + let mut refs = EntityHashMap::with_capacity(self.len()); for &id in self { let ecell = cell .get_entity(id) diff --git a/crates/bevy_pbr/src/render/light.rs b/crates/bevy_pbr/src/render/light.rs index 50e126da73a12..85fa1fca6dee3 100644 --- a/crates/bevy_pbr/src/render/light.rs +++ b/crates/bevy_pbr/src/render/light.rs @@ -6,7 +6,7 @@ use bevy_color::ColorToComponents; use bevy_core_pipeline::core_3d::{Camera3d, CORE_3D_DEPTH_FORMAT}; use bevy_derive::{Deref, DerefMut}; use bevy_ecs::{ - entity::{EntityHash, EntityHashMap, EntityHashSet}, + entity::{EntityHashMap, EntityHashSet}, prelude::*, system::lifetimeless::Read, }; @@ -1114,7 +1114,7 @@ pub fn prepare_lights( array_layer_count: None, }); - let mut live_views = EntityHashSet::with_capacity_and_hasher(views_count, EntityHash); + let mut live_views = EntityHashSet::with_capacity(views_count); // set up light data for each view for (entity, extracted_view, clusters, maybe_layers, no_indirect_drawing) in sorted_cameras From cf21d9a37ee2aa9c312ccb1560640299e6a58d60 Mon Sep 17 00:00:00 2001 From: urben1680 <55257931+urben1680@users.noreply.github.com> Date: Sat, 21 Dec 2024 05:15:22 +0100 Subject: [PATCH 002/272] Remove unused generic in `DeferredWorld::trigger` (#16911) Fixing what I just noticed. ## Migration Guide - Remove the generic parameter when calling this method --- crates/bevy_ecs/src/world/deferred_world.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_ecs/src/world/deferred_world.rs b/crates/bevy_ecs/src/world/deferred_world.rs index c66bcc53c869a..ef83d49270b0a 100644 --- a/crates/bevy_ecs/src/world/deferred_world.rs +++ b/crates/bevy_ecs/src/world/deferred_world.rs @@ -562,7 +562,7 @@ impl<'w> DeferredWorld<'w> { } /// Sends a "global" [`Trigger`](crate::observer::Trigger) without any targets. - pub fn trigger(&mut self, trigger: impl Event) { + pub fn trigger(&mut self, trigger: impl Event) { self.commands().trigger(trigger); } From 20277006ce318b4cdcf264579c2a6c126f67bc9f Mon Sep 17 00:00:00 2001 From: BD103 <59022059+BD103@users.noreply.github.com> Date: Sat, 21 Dec 2024 17:30:29 -0500 Subject: [PATCH 003/272] Add benchmarks and `compile_fail` tests back to workspace (#16858) # Objective - Our benchmarks and `compile_fail` tests lag behind the rest of the engine because they are not in the Cargo workspace, so not checked by CI. - Fixes #16801, please see it for further context! ## Solution - Add benchmarks and `compile_fail` tests to the Cargo workspace. - Fix any leftover formatting issues and documentation. ## Testing - I think CI should catch most things! ## Questions
Outdated issue I was having with function reflection being optional The `reflection_types` example is failing in Rust-Analyzer for me, but not a normal check. ```rust error[E0004]: non-exhaustive patterns: `ReflectRef::Function(_)` not covered --> examples/reflection/reflection_types.rs:81:11 | 81 | match value.reflect_ref() { | ^^^^^^^^^^^^^^^^^^^ pattern `ReflectRef::Function(_)` not covered | note: `ReflectRef<'_>` defined here --> /Users/bdeep/dev/bevy/bevy/crates/bevy_reflect/src/kind.rs:178:1 | 178 | pub enum ReflectRef<'a> { | ^^^^^^^^^^^^^^^^^^^^^^^ ... 188 | Function(&'a dyn Function), | -------- not covered = note: the matched value is of type `ReflectRef<'_>` help: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern or an explicit pattern as shown | 126 ~ ReflectRef::Opaque(_) => {}, 127 + ReflectRef::Function(_) => todo!() | ``` I think it is because the following line is feature-gated: https://github.com/bevyengine/bevy/blob/cc0f6a8db43581755bd84302072c7b97ea51bc0f/examples/reflection/reflection_types.rs#L117-L122 My theory for why this is happening is because the benchmarks enabled `bevy_reflect`'s `function` feature, which gets merged with the rest of the features when RA checks the workspace, but the `#[cfg(...)]` gate in the example isn't detecting it: https://github.com/bevyengine/bevy/blob/cc0f6a8db43581755bd84302072c7b97ea51bc0f/benches/Cargo.toml#L19 Any thoughts on how to fix this? It's not blocking, since the example still compiles as normal, but it's just RA and the command `cargo check --workspace --all-targets` appears to fail. --- Cargo.toml | 22 +++++------ benches/Cargo.toml | 4 -- benches/README.md | 38 +++++++++++-------- benches/benches/bevy_ecs/entity_cloning.rs | 2 +- benches/benches/bevy_reflect/map.rs | 2 +- .../bevy_derive/compile_fail/tests/derive.rs | 6 ++- examples/reflection/reflection_types.rs | 5 +++ tools/compile_fail_utils/README.md | 3 +- tools/compile_fail_utils/src/lib.rs | 11 +++++- 9 files changed, 55 insertions(+), 38 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 56aac74f53cb9..bb06e2afc7e64 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,20 +13,20 @@ documentation = "https://docs.rs/bevy" rust-version = "1.83.0" [workspace] -exclude = [ - "benches", - "crates/bevy_derive/compile_fail", - "crates/bevy_ecs/compile_fail", - "crates/bevy_reflect/compile_fail", - "tools/compile_fail_utils", -] +resolver = "2" members = [ + # All of Bevy's official crates are within the `crates` folder! "crates/*", + # Several crates with macros have "compile fail" tests nested inside them, also known as UI + # tests, that verify diagnostic output does not accidentally change. + "crates/*/compile_fail", + # Examples of compiling Bevy for mobile platforms. "examples/mobile", - "tools/ci", - "tools/build-templated-pages", - "tools/build-wasm-example", - "tools/example-showcase", + # Benchmarks + "benches", + # Internal tools that are not published. + "tools/*", + # Bevy's error codes. This is a crate so we can automatically check all of the code blocks. "errors", ] diff --git a/benches/Cargo.toml b/benches/Cargo.toml index b05e8bba351f5..2ed96d3a48c8c 100644 --- a/benches/Cargo.toml +++ b/benches/Cargo.toml @@ -32,10 +32,6 @@ rand_chacha = "0.3" [target.'cfg(target_os = "linux")'.dev-dependencies] bevy_winit = { path = "../crates/bevy_winit", features = ["x11"] } -[profile.release] -opt-level = 3 -lto = true - [lints.clippy] doc_markdown = "warn" manual_let_else = "warn" diff --git a/benches/README.md b/benches/README.md index 2641ab027aa72..2e91916e481f1 100644 --- a/benches/README.md +++ b/benches/README.md @@ -1,27 +1,35 @@ # Bevy Benchmarks -This is a crate with a collection of benchmarks for Bevy, separate from the rest of the Bevy crates. +This is a crate with a collection of benchmarks for Bevy. -## Running the benchmarks +## Running benchmarks -1. Setup everything you need for Bevy with the [setup guide](https://bevyengine.org/learn/book/getting-started/setup/). -2. Move into the `benches` directory (where this README is located). +Benchmarks can be run through Cargo: - ```sh - bevy $ cd benches - ``` +```sh +# Run all benchmarks. (This will take a while!) +cargo bench -p benches -3. Run the benchmarks with cargo (This will take a while) +# Just compile the benchmarks, do not run them. +cargo bench -p benches --no-run - ```sh - bevy/benches $ cargo bench - ``` +# Run the benchmarks for a specific crate. (See `Cargo.toml` for a complete list of crates +# tracked.) +cargo bench -p benches --bench ecs - If you'd like to only compile the benchmarks (without running them), you can do that like this: +# Filter which benchmarks are run based on the name. This will only run benchmarks whose name +# contains "name_fragment". +cargo bench -p benches -- name_fragment - ```sh - bevy/benches $ cargo bench --no-run - ``` +# List all available benchmarks. +cargo bench -p benches -- --list + +# Save a baseline to be compared against later. +cargo bench -p benches --save-baseline before + +# Compare the current benchmarks against a baseline to find performance gains and regressions. +cargo bench -p benches --baseline before +``` ## Criterion diff --git a/benches/benches/bevy_ecs/entity_cloning.rs b/benches/benches/bevy_ecs/entity_cloning.rs index d0c71c7c2ae8e..51af20b7b187d 100644 --- a/benches/benches/bevy_ecs/entity_cloning.rs +++ b/benches/benches/bevy_ecs/entity_cloning.rs @@ -91,7 +91,7 @@ fn hierarchy( .spawn(black_box(C::default())) .set_parent(parent_id) .id(); - hierarchy_level.push(child_id) + hierarchy_level.push(child_id); } } } diff --git a/benches/benches/bevy_reflect/map.rs b/benches/benches/bevy_reflect/map.rs index 054dcf9570da0..fc9da0aa08dcd 100644 --- a/benches/benches/bevy_reflect/map.rs +++ b/benches/benches/bevy_reflect/map.rs @@ -217,7 +217,7 @@ fn dynamic_map_get(criterion: &mut Criterion) { bencher.iter(|| { for i in 0..size as u64 { let key = black_box(i); - black_box(assert!(map.get(&key).is_some())); + black_box(map.get(&key)); } }); }, diff --git a/crates/bevy_derive/compile_fail/tests/derive.rs b/crates/bevy_derive/compile_fail/tests/derive.rs index b918abe2733de..1349ca060df26 100644 --- a/crates/bevy_derive/compile_fail/tests/derive.rs +++ b/crates/bevy_derive/compile_fail/tests/derive.rs @@ -1,4 +1,6 @@ fn main() -> compile_fail_utils::ui_test::Result<()> { - compile_fail_utils::test_multiple("derive_deref", ["tests/deref_derive", "tests/deref_mut_derive"]) + compile_fail_utils::test_multiple( + "derive_deref", + ["tests/deref_derive", "tests/deref_mut_derive"], + ) } - diff --git a/examples/reflection/reflection_types.rs b/examples/reflection/reflection_types.rs index 265e5f8adce60..689f012782840 100644 --- a/examples/reflection/reflection_types.rs +++ b/examples/reflection/reflection_types.rs @@ -124,6 +124,11 @@ fn setup() { // implementation. Opaque is implemented for opaque types like String and Instant, // but also include primitive types like i32, usize, and f32 (despite not technically being opaque). ReflectRef::Opaque(_) => {} + #[allow( + unreachable_patterns, + reason = "This example cannot always detect when `bevy_reflect/functions` is enabled." + )] + _ => {} } let mut dynamic_list = DynamicList::default(); diff --git a/tools/compile_fail_utils/README.md b/tools/compile_fail_utils/README.md index 5d811bf31712b..9fe8d28c9438c 100644 --- a/tools/compile_fail_utils/README.md +++ b/tools/compile_fail_utils/README.md @@ -1,6 +1,6 @@ # Helpers for compile fail tests -This crate contains everything needed to set up compile tests for the Bevy repo. It, like all Bevy compile test crates, is excluded from the Bevy workspace. This is done to not fail [`crater` tests](https://github.com/rust-lang/crater) for Bevy. The `CI` workflow executes these tests on the stable rust toolchain see ([tools/ci](../../tools/ci/src/main.rs)). +This crate contains everything needed to set up compile tests for the Bevy repo. The `CI` workflow executes these tests on the stable rust toolchain (see [tools/ci](../../tools/ci/src/main.rs)). ## Writing new test cases @@ -34,7 +34,6 @@ This will be a rather involved process. You'll have to: - Create a folder called `tests` within the new crate. - Add a test runner file to this folder. The file should contain a main function calling one of the test functions defined in this crate. - Add a `[[test]]` table to the `Cargo.toml`. This table will need to contain `harness = false` and `name = `. -- Add the path of the new crate under `[workspace].exclude` in the root [`Cargo.toml`](../../Cargo.toml). - Modify the [`CI`](../../tools/ci/) tool to run `cargo test` on this crate. - And finally, write your compile tests. diff --git a/tools/compile_fail_utils/src/lib.rs b/tools/compile_fail_utils/src/lib.rs index ff6383cc224cb..aadccac25b607 100644 --- a/tools/compile_fail_utils/src/lib.rs +++ b/tools/compile_fail_utils/src/lib.rs @@ -109,10 +109,17 @@ pub fn test_with_multiple_configs( test_name: impl Into, configs: impl IntoIterator>, ) -> ui_test::Result<()> { - let configs = configs.into_iter().collect::>>()?; + let configs = configs + .into_iter() + .collect::>>()?; let emitter: Box = if env::var_os("CI").is_some() { - Box::new((Text::verbose(), Gha:: { name: test_name.into() })) + Box::new(( + Text::verbose(), + Gha:: { + name: test_name.into(), + }, + )) } else { Box::new(Text::quiet()) }; From 6a4e0c801ed64177336171a8a65805b546f5dec8 Mon Sep 17 00:00:00 2001 From: Patrick Walton Date: Sun, 22 Dec 2024 18:03:06 -0500 Subject: [PATCH 004/272] Fix several regressions from recent rendering changes. (#16890) This commit fixes the following regressions: 1. Transmission-specific calls to shader lighting functions didn't pass the `enable_diffuse` parameter, breaking the `transmission` example. 2. The combination of bindless `StandardMaterial` and bindless lightmaps caused us to blow past the 128 texture limit on M1/M2 chips in some cases, in particular the `depth_of_field` example. https://github.com/gfx-rs/wgpu/issues/3334 should fix this, but in the meantime this patch reduces the number of bindless lightmaps from 16 to 4 in order to stay under the limit. 3. The renderer was crashing on startup on Adreno 610 chips. This PR simply disables bindless on Adreno 610 and lower. --- .../src/light_probe/environment_map.rs | 8 +++--- .../src/light_probe/irradiance_volume.rs | 8 +++--- crates/bevy_pbr/src/light_probe/mod.rs | 11 +++++--- crates/bevy_pbr/src/lightmap/lightmap.wgsl | 4 +-- crates/bevy_pbr/src/lightmap/mod.rs | 7 +++-- crates/bevy_pbr/src/render/mesh.rs | 9 ++++--- crates/bevy_pbr/src/render/mesh_bindings.rs | 17 ++++++++---- .../bevy_pbr/src/render/mesh_view_bindings.rs | 8 ++++-- crates/bevy_pbr/src/render/pbr_functions.wgsl | 6 ++--- crates/bevy_pbr/src/ssr/mod.rs | 5 ++-- .../src/batching/gpu_preprocessing.rs | 26 ++++--------------- crates/bevy_render/src/lib.rs | 22 +++++++++++++++- 12 files changed, 80 insertions(+), 51 deletions(-) diff --git a/crates/bevy_pbr/src/light_probe/environment_map.rs b/crates/bevy_pbr/src/light_probe/environment_map.rs index 4bbb7c76afb25..34a673582f73d 100644 --- a/crates/bevy_pbr/src/light_probe/environment_map.rs +++ b/crates/bevy_pbr/src/light_probe/environment_map.rs @@ -63,7 +63,7 @@ use bevy_render::{ BindGroupLayoutEntryBuilder, Sampler, SamplerBindingType, Shader, ShaderStages, TextureSampleType, TextureView, }, - renderer::RenderDevice, + renderer::{RenderAdapter, RenderDevice}, texture::{FallbackImage, GpuImage}, }; @@ -232,10 +232,11 @@ impl ExtractInstance for EnvironmentMapIds { /// specular binding arrays respectively, in addition to the sampler. pub(crate) fn get_bind_group_layout_entries( render_device: &RenderDevice, + render_adapter: &RenderAdapter, ) -> [BindGroupLayoutEntryBuilder; 4] { let mut texture_cube_binding = binding_types::texture_cube(TextureSampleType::Float { filterable: true }); - if binding_arrays_are_usable(render_device) { + if binding_arrays_are_usable(render_device, render_adapter) { texture_cube_binding = texture_cube_binding.count(NonZero::::new(MAX_VIEW_LIGHT_PROBES as _).unwrap()); } @@ -256,8 +257,9 @@ impl<'a> RenderViewEnvironmentMapBindGroupEntries<'a> { images: &'a RenderAssets, fallback_image: &'a FallbackImage, render_device: &RenderDevice, + render_adapter: &RenderAdapter, ) -> RenderViewEnvironmentMapBindGroupEntries<'a> { - if binding_arrays_are_usable(render_device) { + if binding_arrays_are_usable(render_device, render_adapter) { let mut diffuse_texture_views = vec![]; let mut specular_texture_views = vec![]; let mut sampler = None; diff --git a/crates/bevy_pbr/src/light_probe/irradiance_volume.rs b/crates/bevy_pbr/src/light_probe/irradiance_volume.rs index 141e70e191b84..b1e974711d882 100644 --- a/crates/bevy_pbr/src/light_probe/irradiance_volume.rs +++ b/crates/bevy_pbr/src/light_probe/irradiance_volume.rs @@ -140,7 +140,7 @@ use bevy_render::{ binding_types, BindGroupLayoutEntryBuilder, Sampler, SamplerBindingType, Shader, TextureSampleType, TextureView, }, - renderer::RenderDevice, + renderer::{RenderAdapter, RenderDevice}, texture::{FallbackImage, GpuImage}, }; use bevy_utils::default; @@ -242,8 +242,9 @@ impl<'a> RenderViewIrradianceVolumeBindGroupEntries<'a> { images: &'a RenderAssets, fallback_image: &'a FallbackImage, render_device: &RenderDevice, + render_adapter: &RenderAdapter, ) -> RenderViewIrradianceVolumeBindGroupEntries<'a> { - if binding_arrays_are_usable(render_device) { + if binding_arrays_are_usable(render_device, render_adapter) { RenderViewIrradianceVolumeBindGroupEntries::get_multiple( render_view_irradiance_volumes, images, @@ -328,10 +329,11 @@ impl<'a> RenderViewIrradianceVolumeBindGroupEntries<'a> { /// respectively. pub(crate) fn get_bind_group_layout_entries( render_device: &RenderDevice, + render_adapter: &RenderAdapter, ) -> [BindGroupLayoutEntryBuilder; 2] { let mut texture_3d_binding = binding_types::texture_3d(TextureSampleType::Float { filterable: true }); - if binding_arrays_are_usable(render_device) { + if binding_arrays_are_usable(render_device, render_adapter) { texture_3d_binding = texture_3d_binding.count(NonZero::::new(MAX_VIEW_LIGHT_PROBES as _).unwrap()); } diff --git a/crates/bevy_pbr/src/light_probe/mod.rs b/crates/bevy_pbr/src/light_probe/mod.rs index b259a3e7927e2..39c33f9cbf343 100644 --- a/crates/bevy_pbr/src/light_probe/mod.rs +++ b/crates/bevy_pbr/src/light_probe/mod.rs @@ -20,7 +20,7 @@ use bevy_render::{ primitives::{Aabb, Frustum}, render_asset::RenderAssets, render_resource::{DynamicUniformBuffer, Sampler, Shader, ShaderType, TextureView}, - renderer::{RenderDevice, RenderQueue}, + renderer::{RenderAdapter, RenderDevice, RenderQueue}, settings::WgpuFeatures, sync_world::RenderEntity, texture::{FallbackImage, GpuImage}, @@ -778,15 +778,20 @@ pub(crate) fn add_cubemap_texture_view<'a>( /// enough texture bindings available in the fragment shader. /// /// 3. If binding arrays aren't supported on the hardware, then we obviously -/// can't use them. +/// can't use them. Adreno <= 610 claims to support bindless, but seems to be +/// too buggy to be usable. /// /// 4. If binding arrays are supported on the hardware, but they can only be /// accessed by uniform indices, that's not good enough, and we bail out. /// /// If binding arrays aren't usable, we disable reflection probes and limit the /// number of irradiance volumes in the scene to 1. -pub(crate) fn binding_arrays_are_usable(render_device: &RenderDevice) -> bool { +pub(crate) fn binding_arrays_are_usable( + render_device: &RenderDevice, + render_adapter: &RenderAdapter, +) -> bool { !cfg!(feature = "shader_format_glsl") + && bevy_render::get_adreno_model(render_adapter).is_none_or(|model| model > 610) && render_device.limits().max_storage_textures_per_shader_stage >= (STANDARD_MATERIAL_FRAGMENT_SHADER_MIN_TEXTURE_BINDINGS + MAX_VIEW_LIGHT_PROBES) as u32 diff --git a/crates/bevy_pbr/src/lightmap/lightmap.wgsl b/crates/bevy_pbr/src/lightmap/lightmap.wgsl index da2eaeb2f9dee..e58ec96870263 100644 --- a/crates/bevy_pbr/src/lightmap/lightmap.wgsl +++ b/crates/bevy_pbr/src/lightmap/lightmap.wgsl @@ -3,8 +3,8 @@ #import bevy_pbr::mesh_bindings::mesh #ifdef MULTIPLE_LIGHTMAPS_IN_ARRAY -@group(1) @binding(4) var lightmaps_textures: binding_array>; -@group(1) @binding(5) var lightmaps_samplers: binding_array; +@group(1) @binding(4) var lightmaps_textures: binding_array, 4>; +@group(1) @binding(5) var lightmaps_samplers: binding_array; #else // MULTIPLE_LIGHTMAPS_IN_ARRAY @group(1) @binding(4) var lightmaps_texture: texture_2d; @group(1) @binding(5) var lightmaps_sampler: sampler; diff --git a/crates/bevy_pbr/src/lightmap/mod.rs b/crates/bevy_pbr/src/lightmap/mod.rs index 9206b407779a0..fc6f973cb12d5 100644 --- a/crates/bevy_pbr/src/lightmap/mod.rs +++ b/crates/bevy_pbr/src/lightmap/mod.rs @@ -50,6 +50,7 @@ use bevy_reflect::{std_traits::ReflectDefault, Reflect}; use bevy_render::{ render_asset::RenderAssets, render_resource::{Sampler, Shader, TextureView, WgpuSampler, WgpuTextureView}, + renderer::RenderAdapter, sync_world::MainEntity, texture::{FallbackImage, GpuImage}, view::ViewVisibility, @@ -71,7 +72,7 @@ pub const LIGHTMAP_SHADER_HANDLE: Handle = /// /// If bindless textures aren't in use, then only a single lightmap can be bound /// at a time. -pub const LIGHTMAPS_PER_SLAB: usize = 16; +pub const LIGHTMAPS_PER_SLAB: usize = 4; /// A plugin that provides an implementation of lightmaps. pub struct LightmapPlugin; @@ -332,7 +333,9 @@ impl Default for Lightmap { impl FromWorld for RenderLightmaps { fn from_world(world: &mut World) -> Self { let render_device = world.resource::(); - let bindless_supported = binding_arrays_are_usable(render_device); + let render_adapter = world.resource::(); + + let bindless_supported = binding_arrays_are_usable(render_device, render_adapter); RenderLightmaps { render_lightmaps: default(), diff --git a/crates/bevy_pbr/src/render/mesh.rs b/crates/bevy_pbr/src/render/mesh.rs index 30e2a13c56ef4..2d4047fee6227 100644 --- a/crates/bevy_pbr/src/render/mesh.rs +++ b/crates/bevy_pbr/src/render/mesh.rs @@ -34,7 +34,7 @@ use bevy_render::{ RenderCommandResult, SortedRenderPhasePlugin, TrackedRenderPass, }, render_resource::*, - renderer::{RenderDevice, RenderQueue}, + renderer::{RenderAdapter, RenderDevice, RenderQueue}, texture::DefaultImageSampler, view::{ prepare_view_targets, NoFrustumCulling, NoIndirectDrawing, RenderVisibilityRanges, @@ -1484,11 +1484,12 @@ impl FromWorld for MeshPipeline { fn from_world(world: &mut World) -> Self { let mut system_state: SystemState<( Res, + Res, Res, Res, Res, )> = SystemState::new(world); - let (render_device, default_sampler, render_queue, view_layouts) = + let (render_device, render_adapter, default_sampler, render_queue, view_layouts) = system_state.get_mut(world); let clustered_forward_buffer_binding_type = render_device @@ -1532,9 +1533,9 @@ impl FromWorld for MeshPipeline { view_layouts: view_layouts.clone(), clustered_forward_buffer_binding_type, dummy_white_gpu_image, - mesh_layouts: MeshLayouts::new(&render_device), + mesh_layouts: MeshLayouts::new(&render_device, &render_adapter), per_object_buffer_batch_size: GpuArrayBuffer::::batch_size(&render_device), - binding_arrays_are_usable: binding_arrays_are_usable(&render_device), + binding_arrays_are_usable: binding_arrays_are_usable(&render_device, &render_adapter), skins_use_uniform_buffers: skin::skins_use_uniform_buffers(&render_device), } } diff --git a/crates/bevy_pbr/src/render/mesh_bindings.rs b/crates/bevy_pbr/src/render/mesh_bindings.rs index 3e3210a026325..e6d07cb4e2c6e 100644 --- a/crates/bevy_pbr/src/render/mesh_bindings.rs +++ b/crates/bevy_pbr/src/render/mesh_bindings.rs @@ -1,7 +1,11 @@ //! Bind group layout related definitions for the mesh pipeline. use bevy_math::Mat4; -use bevy_render::{mesh::morph::MAX_MORPH_WEIGHTS, render_resource::*, renderer::RenderDevice}; +use bevy_render::{ + mesh::morph::MAX_MORPH_WEIGHTS, + render_resource::*, + renderer::{RenderAdapter, RenderDevice}, +}; use crate::{binding_arrays_are_usable, render::skin::MAX_JOINTS, LightmapSlab}; @@ -194,10 +198,10 @@ impl MeshLayouts { /// Prepare the layouts used by the default bevy [`Mesh`]. /// /// [`Mesh`]: bevy_render::prelude::Mesh - pub fn new(render_device: &RenderDevice) -> Self { + pub fn new(render_device: &RenderDevice, render_adapter: &RenderAdapter) -> Self { MeshLayouts { model_only: Self::model_only_layout(render_device), - lightmapped: Self::lightmapped_layout(render_device), + lightmapped: Self::lightmapped_layout(render_device, render_adapter), skinned: Self::skinned_layout(render_device), skinned_motion: Self::skinned_motion_layout(render_device), morphed: Self::morphed_layout(render_device), @@ -329,8 +333,11 @@ impl MeshLayouts { ) } - fn lightmapped_layout(render_device: &RenderDevice) -> BindGroupLayout { - if binding_arrays_are_usable(render_device) { + fn lightmapped_layout( + render_device: &RenderDevice, + render_adapter: &RenderAdapter, + ) -> BindGroupLayout { + if binding_arrays_are_usable(render_device, render_adapter) { render_device.create_bind_group_layout( "lightmapped_mesh_layout", &BindGroupLayoutEntries::with_indices( diff --git a/crates/bevy_pbr/src/render/mesh_view_bindings.rs b/crates/bevy_pbr/src/render/mesh_view_bindings.rs index 8067680eff923..e27b3d7b7f5c9 100644 --- a/crates/bevy_pbr/src/render/mesh_view_bindings.rs +++ b/crates/bevy_pbr/src/render/mesh_view_bindings.rs @@ -312,7 +312,8 @@ fn layout_entries( ); // EnvironmentMapLight - let environment_map_entries = environment_map::get_bind_group_layout_entries(render_device); + let environment_map_entries = + environment_map::get_bind_group_layout_entries(render_device, render_adapter); entries = entries.extend_with_indices(( (17, environment_map_entries[0]), (18, environment_map_entries[1]), @@ -323,7 +324,7 @@ fn layout_entries( // Irradiance volumes if IRRADIANCE_VOLUMES_ARE_USABLE { let irradiance_volume_entries = - irradiance_volume::get_bind_group_layout_entries(render_device); + irradiance_volume::get_bind_group_layout_entries(render_device, render_adapter); entries = entries.extend_with_indices(( (21, irradiance_volume_entries[0]), (22, irradiance_volume_entries[1]), @@ -493,6 +494,7 @@ pub struct MeshViewBindGroup { pub fn prepare_mesh_view_bind_groups( mut commands: Commands, render_device: Res, + render_adapter: Res, mesh_pipeline: Res, shadow_samplers: Res, (light_meta, global_light_meta): (Res, Res), @@ -607,6 +609,7 @@ pub fn prepare_mesh_view_bind_groups( &images, &fallback_image, &render_device, + &render_adapter, ); match environment_map_bind_group_entries { @@ -642,6 +645,7 @@ pub fn prepare_mesh_view_bind_groups( &images, &fallback_image, &render_device, + &render_adapter, )) } else { None diff --git a/crates/bevy_pbr/src/render/pbr_functions.wgsl b/crates/bevy_pbr/src/render/pbr_functions.wgsl index b6187bc4b2b4d..60f80239455c2 100644 --- a/crates/bevy_pbr/src/render/pbr_functions.wgsl +++ b/crates/bevy_pbr/src/render/pbr_functions.wgsl @@ -443,7 +443,7 @@ fn apply_pbr_lighting( } let transmitted_light_contrib = - lighting::point_light(light_id, &transmissive_lighting_input); + lighting::point_light(light_id, &transmissive_lighting_input, enable_diffuse); transmitted_light += transmitted_light_contrib * transmitted_shadow; #endif } @@ -501,7 +501,7 @@ fn apply_pbr_lighting( } let transmitted_light_contrib = - lighting::spot_light(light_id, &transmissive_lighting_input); + lighting::spot_light(light_id, &transmissive_lighting_input, enable_diffuse); transmitted_light += transmitted_light_contrib * transmitted_shadow; #endif } @@ -557,7 +557,7 @@ fn apply_pbr_lighting( } let transmitted_light_contrib = - lighting::directional_light(i, &transmissive_lighting_input); + lighting::directional_light(i, &transmissive_lighting_input, enable_diffuse); transmitted_light += transmitted_light_contrib * transmitted_shadow; #endif } diff --git a/crates/bevy_pbr/src/ssr/mod.rs b/crates/bevy_pbr/src/ssr/mod.rs index 69a32acd75f5b..a515a2917ddd0 100644 --- a/crates/bevy_pbr/src/ssr/mod.rs +++ b/crates/bevy_pbr/src/ssr/mod.rs @@ -36,7 +36,7 @@ use bevy_render::{ ShaderStages, ShaderType, SpecializedRenderPipeline, SpecializedRenderPipelines, TextureFormat, TextureSampleType, }, - renderer::{RenderContext, RenderDevice, RenderQueue}, + renderer::{RenderAdapter, RenderContext, RenderDevice, RenderQueue}, view::{ExtractedView, Msaa, ViewTarget, ViewUniformOffset}, Render, RenderApp, RenderSet, }; @@ -354,6 +354,7 @@ impl FromWorld for ScreenSpaceReflectionsPipeline { fn from_world(world: &mut World) -> Self { let mesh_view_layouts = world.resource::().clone(); let render_device = world.resource::(); + let render_adapter = world.resource::(); // Create the bind group layout. let bind_group_layout = render_device.create_bind_group_layout( @@ -404,7 +405,7 @@ impl FromWorld for ScreenSpaceReflectionsPipeline { depth_linear_sampler, depth_nearest_sampler, bind_group_layout, - binding_arrays_are_usable: binding_arrays_are_usable(render_device), + binding_arrays_are_usable: binding_arrays_are_usable(render_device, render_adapter), } } } diff --git a/crates/bevy_render/src/batching/gpu_preprocessing.rs b/crates/bevy_render/src/batching/gpu_preprocessing.rs index 07379443a4081..cd1aed53e221b 100644 --- a/crates/bevy_render/src/batching/gpu_preprocessing.rs +++ b/crates/bevy_render/src/batching/gpu_preprocessing.rs @@ -354,28 +354,12 @@ impl FromWorld for GpuPreprocessingSupport { let adapter = world.resource::(); let device = world.resource::(); - // filter some Qualcomm devices on Android as they crash when using GPU preprocessing. + // Filter some Qualcomm devices on Android as they crash when using GPU + // preprocessing. + // We filter out Adreno 730 and earlier GPUs (except 720, as it's newer + // than 730). fn is_non_supported_android_device(adapter: &RenderAdapter) -> bool { - if cfg!(target_os = "android") { - let adapter_name = adapter.get_info().name; - - // Filter out Adreno 730 and earlier GPUs (except 720, as it's newer than 730) - // while also taking suffixes into account like Adreno 642L. - let non_supported_adreno_model = |model: &str| -> bool { - let model = model - .chars() - .map_while(|c| c.to_digit(10)) - .fold(0, |acc, digit| acc * 10 + digit); - - model != 720 && model <= 730 - }; - - adapter_name - .strip_prefix("Adreno (TM) ") - .is_some_and(non_supported_adreno_model) - } else { - false - } + crate::get_adreno_model(adapter).is_some_and(|model| model != 720 && model <= 730) } let max_supported_mode = if device.limits().max_compute_workgroup_size_x == 0 || is_non_supported_android_device(adapter) diff --git a/crates/bevy_render/src/lib.rs b/crates/bevy_render/src/lib.rs index 0acdd5ad50748..7e206ffd2fb25 100644 --- a/crates/bevy_render/src/lib.rs +++ b/crates/bevy_render/src/lib.rs @@ -80,7 +80,7 @@ use bevy_window::{PrimaryWindow, RawHandleWrapperHolder}; use extract_resource::ExtractResourcePlugin; use globals::GlobalsPlugin; use render_asset::RenderAssetBytesPerFrame; -use renderer::{RenderDevice, RenderQueue}; +use renderer::{RenderAdapter, RenderDevice, RenderQueue}; use settings::RenderResources; use sync_world::{ despawn_temporary_render_entities, entity_sync_system, SyncToRenderWorld, SyncWorldPlugin, @@ -514,3 +514,23 @@ fn apply_extract_commands(render_world: &mut World) { .apply_deferred(render_world); }); } + +/// If the [`RenderAdapter`] is a Qualcomm Adreno, returns its model number. +/// +/// This lets us work around hardware bugs. +pub fn get_adreno_model(adapter: &RenderAdapter) -> Option { + if !cfg!(target_os = "android") { + return None; + } + + let adapter_name = adapter.get_info().name; + let adreno_model = adapter_name.strip_prefix("Adreno (TM) ")?; + + // Take suffixes into account (like Adreno 642L). + Some( + adreno_model + .chars() + .map_while(|c| c.to_digit(10)) + .fold(0, |acc, digit| acc * 10 + digit), + ) +} From 022c6b1d34e05ce9e4d7066363bfc32d1ee6f254 Mon Sep 17 00:00:00 2001 From: Oliver Maskery Date: Sun, 22 Dec 2024 23:04:32 +0000 Subject: [PATCH 005/272] Prevent creation of superfluous empty table (#16935) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Objective - To fix a tiny bug in `bevy_ecs::storage::Tables` that, in one case, means it accidentally allocates an additional "empty" `Table`, resulting in two "empty" `Table`s: - The one pre-allocated empty table at index 0 whose index is designed to match up with `TableId::empty()` - One extra empty table, at some non-0 index, that does not match up with `TableId::empty()`. - This PR aims to prevent this extraneous `Table`, ensuring that entities with no components in table-storage reliably have their archetype's table ID be equal to `TableId::empty()`. ## Solution ### Background The issue occurs because: - `Tables` contains: - `tables: Vec` - The set of all `Table`s allocated in the world. - `table_ids: HashMap, TableId>` - An index to rapidly lookup the `Table` in `tables` by a set of `ComponentId`s. - When `Tables` is constructed it pre-populates the `tables` `Vec` with an empty `Table`. - This ensures that the first entry (index 0) is always the `Table` for entities with no components in table storage. - In particular, `TableId::empty()` is a utility that returns a `TableId` of `0`. - However, the `table_ids` map is not initialised to associate an empty `[ComponentId]` with `TableId` `0`. - This means, the first time a structural change tries to access a `Table` for an archetype with 0 table components: - `Tables::get_id_or_insert` is used to retrieve the target `Table` - The function attempts to lookup the entry in the `table_ids` `HashMap` whose key is the empty `ComponentId` set - The empty `Table` created at startup won't be found, because it was never inserted into `table_ids` - It will instead create a new table, insert it into the `HashMap` (preventing further instances of this issue), and return it. ### Changes - I considered simply initialising the `table_ids` `HashMap` to know about the pre-allocated `Table` - However, I ended up using the proposed solution discussed on Discord [#ecs-dev](https://discord.com/channels/691052431525675048/749335865876021248/1320430933152759958): - Make `Tables::get_id_or_insert` simply early-exit if the requested `component_ids` was empty. - This avoids unnecessarily hashing the empty slice and looking it up in the `HashMap`. - The `table_ids` `HashMap` is not exposed outside this struct, and is only used within `get_id_or_insert`, so it seems wasteful to defensively populate it with the empty `Table`. ## Testing This is my first Bevy contribution, so I don't really know the processes that well. That said: - I have introduced a little test that exercises the original issue and shows that it is now resolved. - I have run the `bevy_ecs` tests locally, so I have reasonable confidence I haven't broken that. - I haven't run any further test suites, mostly as when I tried to run test suites for the whole project it filled my entire SSD with >600GB of target directory output 😱😱😱 --- crates/bevy_ecs/src/storage/table/mod.rs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/crates/bevy_ecs/src/storage/table/mod.rs b/crates/bevy_ecs/src/storage/table/mod.rs index 1f82642c07d6b..ec77645eac62a 100644 --- a/crates/bevy_ecs/src/storage/table/mod.rs +++ b/crates/bevy_ecs/src/storage/table/mod.rs @@ -743,6 +743,10 @@ impl Tables { component_ids: &[ComponentId], components: &Components, ) -> TableId { + if component_ids.is_empty() { + return TableId::empty(); + } + let tables = &mut self.tables; let (_key, value) = self .table_ids @@ -816,7 +820,7 @@ mod tests { component::{Component, Components, Tick}, entity::Entity, ptr::OwningPtr, - storage::{Storages, TableBuilder, TableRow}, + storage::{Storages, TableBuilder, TableId, TableRow, Tables}, }; #[cfg(feature = "track_change_detection")] use core::panic::Location; @@ -824,6 +828,18 @@ mod tests { #[derive(Component)] struct W(T); + #[test] + fn only_one_empty_table() { + let components = Components::default(); + let mut tables = Tables::default(); + + let component_ids = &[]; + // SAFETY: component_ids is empty, so we know it cannot reference invalid component IDs + let table_id = unsafe { tables.get_id_or_insert(component_ids, &components) }; + + assert_eq!(table_id, TableId::empty()); + } + #[test] fn table() { let mut components = Components::default(); From 8d9a00f5483b2ba28f40081759bd3d6f8b8a686d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Dec 2024 08:33:22 +0100 Subject: [PATCH 006/272] Bump crate-ci/typos from 1.28.3 to 1.28.4 (#16943) Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.28.3 to 1.28.4.
Release notes

Sourced from crate-ci/typos's releases.

v1.28.4

[1.28.4] - 2024-12-16

Features

  • --format sarif support
Changelog

Sourced from crate-ci/typos's changelog.

[1.28.4] - 2024-12-16

Features

  • --format sarif support
Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=crate-ci/typos&package-manager=github_actions&previous-version=1.28.3&new-version=1.28.4)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1ccc60ae289f2..d8a6c2de60929 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -241,7 +241,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Check for typos - uses: crate-ci/typos@v1.28.3 + uses: crate-ci/typos@v1.28.4 - name: Typos info if: failure() run: | From b7ee23a59efc4d5ec9c21e3be1bf6263ab3d53fa Mon Sep 17 00:00:00 2001 From: JMS55 <47158642+JMS55@users.noreply.github.com> Date: Mon, 23 Dec 2024 14:16:06 -0800 Subject: [PATCH 007/272] Remove meshlet builder retry queue (#16941) Revert the retry queue for stuck meshlet groups that couldn't simplify added in https://github.com/bevyengine/bevy/pull/15886. It was a hack that didn't really work, that was intended to help solve meshlets getting stuck and never getting simplified further. The actual solution is a new DAG building algorithm that I have coming in a followup PR. With that PR, there will be no need for the retry queue, as meshlets will rarely ever get stuck (I checked, the code never gets called). I split this off into it's own PR for easier reviewing. Meshlet IDs during building are back to being relative to the overall list of meshlets across all LODs, instead of starting at 0 for the first meshlet in the simplification queue for the current LOD, regardless of how many meshlets there are in the asset total. Not going to bother to regenerate the bunny asset for this PR. --- crates/bevy_pbr/src/meshlet/from_mesh.rs | 65 ++++++++++-------------- 1 file changed, 28 insertions(+), 37 deletions(-) diff --git a/crates/bevy_pbr/src/meshlet/from_mesh.rs b/crates/bevy_pbr/src/meshlet/from_mesh.rs index 14b46e55ba46e..697d3d4aacd82 100644 --- a/crates/bevy_pbr/src/meshlet/from_mesh.rs +++ b/crates/bevy_pbr/src/meshlet/from_mesh.rs @@ -9,7 +9,7 @@ use bevy_render::{ }; use bevy_utils::HashMap; use bitvec::{order::Lsb0, vec::BitVec, view::BitView}; -use core::iter; +use core::{iter, ops::Range}; use half::f16; use itertools::Itertools; use meshopt::{ @@ -105,12 +105,11 @@ impl MeshletMesh { let mut vertex_locks = vec![false; vertices.vertex_count]; // Build further LODs - let mut simplification_queue = Vec::from_iter(0..meshlets.len()); - let mut retry_queue = Vec::new(); + let mut simplification_queue = 0..meshlets.len(); while simplification_queue.len() > 1 { // For each meshlet build a list of connected meshlets (meshlets that share a vertex) let connected_meshlets_per_meshlet = find_connected_meshlets( - &simplification_queue, + simplification_queue.clone(), &meshlets, &position_only_vertex_remap, position_only_vertex_count, @@ -118,7 +117,10 @@ impl MeshletMesh { // Group meshlets into roughly groups of size TARGET_MESHLETS_PER_GROUP, // grouping meshlets with a high number of shared vertices - let groups = group_meshlets(&connected_meshlets_per_meshlet, &simplification_queue); + let groups = group_meshlets( + &connected_meshlets_per_meshlet, + simplification_queue.clone(), + ); // Lock borders between groups to prevent cracks when simplifying lock_group_borders( @@ -131,9 +133,8 @@ impl MeshletMesh { let next_lod_start = meshlets.len(); for group_meshlets in groups.into_iter() { - // If the group only has a single meshlet, we can't simplify it well, so retry later + // If the group only has a single meshlet we can't simplify it if group_meshlets.len() == 1 { - retry_queue.push(group_meshlets[0]); continue; } @@ -146,8 +147,7 @@ impl MeshletMesh { vertex_stride, &vertex_locks, ) else { - // Couldn't simplify the group enough, retry its meshlets later - retry_queue.extend_from_slice(&group_meshlets); + // Couldn't simplify the group enough continue; }; @@ -187,12 +187,8 @@ impl MeshletMesh { ); } - // Set simplification queue to the list of newly created (and retrying) meshlets - simplification_queue.clear(); - simplification_queue.extend(next_lod_start..meshlets.len()); - if !simplification_queue.is_empty() { - simplification_queue.append(&mut retry_queue); - } + // Set simplification queue to the list of newly created meshlets + simplification_queue = next_lod_start..meshlets.len(); } // Copy vertex attributes per meshlet and compress @@ -252,22 +248,22 @@ fn compute_meshlets(indices: &[u32], vertices: &VertexDataAdapter) -> Meshlets { } fn find_connected_meshlets( - simplification_queue: &[usize], + simplification_queue: Range, meshlets: &Meshlets, position_only_vertex_remap: &[u32], position_only_vertex_count: usize, ) -> Vec> { // For each vertex, build a list of all meshlets that use it let mut vertices_to_meshlets = vec![Vec::new(); position_only_vertex_count]; - for (meshlet_queue_id, meshlet_id) in simplification_queue.iter().enumerate() { - let meshlet = meshlets.get(*meshlet_id); + for meshlet_id in simplification_queue.clone() { + let meshlet = meshlets.get(meshlet_id); for index in meshlet.triangles { let vertex_id = position_only_vertex_remap[meshlet.vertices[*index as usize] as usize]; let vertex_to_meshlets = &mut vertices_to_meshlets[vertex_id as usize]; // Meshlets are added in order, so we can just check the last element to deduplicate, // in the case of two triangles sharing the same vertex within a single meshlet - if vertex_to_meshlets.last() != Some(&meshlet_queue_id) { - vertex_to_meshlets.push(meshlet_queue_id); + if vertex_to_meshlets.last() != Some(&meshlet_id) { + vertex_to_meshlets.push(meshlet_id); } } } @@ -275,14 +271,9 @@ fn find_connected_meshlets( // For each meshlet pair, count how many vertices they share let mut meshlet_pair_to_shared_vertex_count = >::default(); for vertex_meshlet_ids in vertices_to_meshlets { - for (meshlet_queue_id1, meshlet_queue_id2) in - vertex_meshlet_ids.into_iter().tuple_combinations() - { + for (meshlet_id1, meshlet_id2) in vertex_meshlet_ids.into_iter().tuple_combinations() { let count = meshlet_pair_to_shared_vertex_count - .entry(( - meshlet_queue_id1.min(meshlet_queue_id2), - meshlet_queue_id1.max(meshlet_queue_id2), - )) + .entry((meshlet_id1.min(meshlet_id2), meshlet_id1.max(meshlet_id2))) .or_insert(0); *count += 1; } @@ -290,12 +281,12 @@ fn find_connected_meshlets( // For each meshlet, gather all other meshlets that share at least one vertex along with their shared vertex count let mut connected_meshlets_per_meshlet = vec![Vec::new(); simplification_queue.len()]; - for ((meshlet_queue_id1, meshlet_queue_id2), shared_count) in - meshlet_pair_to_shared_vertex_count - { + for ((meshlet_id1, meshlet_id2), shared_vertex_count) in meshlet_pair_to_shared_vertex_count { // We record both id1->id2 and id2->id1 as adjacency is symmetrical - connected_meshlets_per_meshlet[meshlet_queue_id1].push((meshlet_queue_id2, shared_count)); - connected_meshlets_per_meshlet[meshlet_queue_id2].push((meshlet_queue_id1, shared_count)); + connected_meshlets_per_meshlet[meshlet_id1 - simplification_queue.start] + .push((meshlet_id2, shared_vertex_count)); + connected_meshlets_per_meshlet[meshlet_id2 - simplification_queue.start] + .push((meshlet_id1, shared_vertex_count)); } // The order of meshlets depends on hash traversal order; to produce deterministic results, sort them @@ -309,15 +300,15 @@ fn find_connected_meshlets( // METIS manual: https://github.com/KarypisLab/METIS/blob/e0f1b88b8efcb24ffa0ec55eabb78fbe61e58ae7/manual/manual.pdf fn group_meshlets( connected_meshlets_per_meshlet: &[Vec<(usize, usize)>], - simplification_queue: &[usize], + simplification_queue: Range, ) -> Vec> { let mut xadj = Vec::with_capacity(simplification_queue.len() + 1); let mut adjncy = Vec::new(); let mut adjwgt = Vec::new(); for connected_meshlets in connected_meshlets_per_meshlet { xadj.push(adjncy.len() as i32); - for (connected_meshlet_queue_id, shared_vertex_count) in connected_meshlets { - adjncy.push(*connected_meshlet_queue_id as i32); + for (connected_meshlet_id, shared_vertex_count) in connected_meshlets { + adjncy.push((connected_meshlet_id - simplification_queue.start) as i32); adjwgt.push(*shared_vertex_count as i32); // TODO: Additional weight based on meshlet spatial proximity } @@ -336,8 +327,8 @@ fn group_meshlets( .unwrap(); let mut groups = vec![SmallVec::new(); partition_count]; - for (meshlet_queue_id, meshlet_group) in group_per_meshlet.into_iter().enumerate() { - groups[meshlet_group as usize].push(simplification_queue[meshlet_queue_id]); + for (i, meshlet_group) in group_per_meshlet.into_iter().enumerate() { + groups[meshlet_group as usize].push(i + simplification_queue.start); } groups } From ff57e8082cf4b2d6bd45e8e2764ea43b926d147d Mon Sep 17 00:00:00 2001 From: Marco Buono Date: Mon, 23 Dec 2024 19:23:33 -0300 Subject: [PATCH 008/272] Drive-by Docs Fixes: `bevy_picking`, `bevy_text` (#16946) Noticed these were either incomplete or inconsistent, so I fixed/augmented them. --- crates/bevy_picking/src/lib.rs | 4 ++-- crates/bevy_text/src/text.rs | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/crates/bevy_picking/src/lib.rs b/crates/bevy_picking/src/lib.rs index ed77e98926c25..21f54e27ad9b1 100644 --- a/crates/bevy_picking/src/lib.rs +++ b/crates/bevy_picking/src/lib.rs @@ -121,11 +121,11 @@ //! //! You will eventually need to choose which picking backend(s) you want to use. This crate does not //! supply any backends, and expects you to select some from the other bevy crates or the third-party -//! ecosystem. You can find all the provided backends in the [`backend`] module. +//! ecosystem. //! //! It's important to understand that you can mix and match backends! For example, you might have a //! backend for your UI, and one for the 3d scene, with each being specialized for their purpose. -//! This crate provides some backends out of the box, but you can even write your own. It's been +//! Bevy provides some backends out of the box, but you can even write your own. It's been //! made as easy as possible intentionally; the `bevy_mod_raycast` backend is 50 lines of code. //! //! #### Focus ([`focus`]) diff --git a/crates/bevy_text/src/text.rs b/crates/bevy_text/src/text.rs index 0effb361aa302..06b9eeb9902d4 100644 --- a/crates/bevy_text/src/text.rs +++ b/crates/bevy_text/src/text.rs @@ -232,7 +232,8 @@ impl From for TextSpan { /// This only affects the internal positioning of the lines of text within a text entity and /// does not affect the text entity's position. /// -/// _Has no affect on a single line text entity._ +/// _Has no affect on a single line text entity_, unless used together with a +/// [`TextBounds`](super::bounds::TextBounds) component with an explicit `width` value. #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash, Reflect, Serialize, Deserialize)] #[reflect(Serialize, Deserialize)] pub enum JustifyText { From 460de77a552d00894ab84557fbfbdeea3f41b7c1 Mon Sep 17 00:00:00 2001 From: MiniaczQ Date: Tue, 24 Dec 2024 03:36:03 +0100 Subject: [PATCH 009/272] Set panic as default fallible system param behavior (#16638) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Objective Fixes: #16578 ## Solution This is a patch fix, proper fix requires a breaking change. Added `Panic` enum variant and using is as the system meta default. Warn once behavior can be enabled same way disabling panic (originally disabling wans) is. To fix an issue with the current architecture, where **all** combinator system params get checked together, combinator systems only check params of the first system. This will result in old, panicking behavior on subsequent systems and will be fixed in 0.16. ## Testing Ran unit tests and `fallible_params` example. --------- Co-authored-by: François Mockers Co-authored-by: François Mockers --- crates/bevy_ecs/src/change_detection.rs | 6 ++-- crates/bevy_ecs/src/schedule/condition.rs | 4 +-- crates/bevy_ecs/src/schedule/executor/mod.rs | 18 +++++------ crates/bevy_ecs/src/system/combinator.rs | 4 +-- crates/bevy_ecs/src/system/function_system.rs | 32 +++++++++++++------ crates/bevy_ecs/src/system/query.rs | 4 +-- crates/bevy_ecs/src/system/system.rs | 2 +- crates/bevy_ecs/src/system/system_param.rs | 2 +- crates/bevy_ecs/src/system/system_registry.rs | 2 +- crates/bevy_pbr/src/render/mesh.rs | 4 +++ examples/ecs/fallible_params.rs | 14 ++++---- 11 files changed, 53 insertions(+), 39 deletions(-) diff --git a/crates/bevy_ecs/src/change_detection.rs b/crates/bevy_ecs/src/change_detection.rs index 025c3804e73e2..72cf8c6784edb 100644 --- a/crates/bevy_ecs/src/change_detection.rs +++ b/crates/bevy_ecs/src/change_detection.rs @@ -544,7 +544,7 @@ impl<'w> From> for Ticks<'w> { /// If you need a unique mutable borrow, use [`ResMut`] instead. /// /// This [`SystemParam`](crate::system::SystemParam) fails validation if resource doesn't exist. -/// This will cause systems that use this parameter to be skipped. +/// This will cause a panic, but can be configured to do nothing or warn once. /// /// Use [`Option>`] instead if the resource might not always exist. pub struct Res<'w, T: ?Sized + Resource> { @@ -622,7 +622,7 @@ impl_debug!(Res<'w, T>, Resource); /// If you need a shared borrow, use [`Res`] instead. /// /// This [`SystemParam`](crate::system::SystemParam) fails validation if resource doesn't exist. -/// This will cause systems that use this parameter to be skipped. +/// /// This will cause a panic, but can be configured to do nothing or warn once. /// /// Use [`Option>`] instead if the resource might not always exist. pub struct ResMut<'w, T: ?Sized + Resource> { @@ -683,7 +683,7 @@ impl<'w, T: Resource> From> for Mut<'w, T> { /// over to another thread. /// /// This [`SystemParam`](crate::system::SystemParam) fails validation if non-send resource doesn't exist. -/// This will cause systems that use this parameter to be skipped. +/// /// This will cause a panic, but can be configured to do nothing or warn once. /// /// Use [`Option>`] instead if the resource might not always exist. pub struct NonSendMut<'w, T: ?Sized + 'static> { diff --git a/crates/bevy_ecs/src/schedule/condition.rs b/crates/bevy_ecs/src/schedule/condition.rs index 4f7745b1d687e..4cfdd1cc81f0f 100644 --- a/crates/bevy_ecs/src/schedule/condition.rs +++ b/crates/bevy_ecs/src/schedule/condition.rs @@ -80,7 +80,7 @@ pub trait Condition: sealed::Condition /// /// # Examples /// - /// ``` + /// ```should_panic /// use bevy_ecs::prelude::*; /// /// #[derive(Resource, PartialEq)] @@ -90,7 +90,7 @@ pub trait Condition: sealed::Condition /// # let mut world = World::new(); /// # fn my_system() {} /// app.add_systems( - /// // The `resource_equals` run condition will fail since we don't initialize `R`, + /// // The `resource_equals` run condition will panic since we don't initialize `R`, /// // just like if we used `Res` in a system. /// my_system.run_if(resource_equals(R(0))), /// ); diff --git a/crates/bevy_ecs/src/schedule/executor/mod.rs b/crates/bevy_ecs/src/schedule/executor/mod.rs index 9218fe33fe0da..f87bbcd10d425 100644 --- a/crates/bevy_ecs/src/schedule/executor/mod.rs +++ b/crates/bevy_ecs/src/schedule/executor/mod.rs @@ -308,7 +308,7 @@ mod tests { self as bevy_ecs, prelude::{IntoSystemConfigs, IntoSystemSetConfigs, Resource, Schedule, SystemSet}, schedule::ExecutorKind, - system::{Commands, In, IntoSystem, Res}, + system::{Commands, Res, WithParamWarnPolicy}, world::World, }; @@ -337,15 +337,11 @@ mod tests { schedule.set_executor_kind(executor); schedule.add_systems( ( - // Combined systems get skipped together. - (|mut commands: Commands| { - commands.insert_resource(R1); - }) - .pipe(|_: In<()>, _: Res| {}), // This system depends on a system that is always skipped. - |mut commands: Commands| { + (|mut commands: Commands| { commands.insert_resource(R2); - }, + }) + .param_warn_once(), ) .chain(), ); @@ -368,18 +364,20 @@ mod tests { let mut world = World::new(); let mut schedule = Schedule::default(); schedule.set_executor_kind(executor); - schedule.configure_sets(S1.run_if(|_: Res| true)); + schedule.configure_sets(S1.run_if((|_: Res| true).param_warn_once())); schedule.add_systems(( // System gets skipped if system set run conditions fail validation. (|mut commands: Commands| { commands.insert_resource(R1); }) + .param_warn_once() .in_set(S1), // System gets skipped if run conditions fail validation. (|mut commands: Commands| { commands.insert_resource(R2); }) - .run_if(|_: Res| true), + .param_warn_once() + .run_if((|_: Res| true).param_warn_once()), )); schedule.run(&mut world); assert!(world.get_resource::().is_none()); diff --git a/crates/bevy_ecs/src/system/combinator.rs b/crates/bevy_ecs/src/system/combinator.rs index 9d8652e9dbc00..1f956c5d4c00e 100644 --- a/crates/bevy_ecs/src/system/combinator.rs +++ b/crates/bevy_ecs/src/system/combinator.rs @@ -214,7 +214,7 @@ where #[inline] unsafe fn validate_param_unsafe(&mut self, world: UnsafeWorldCell) -> bool { // SAFETY: Delegate to other `System` implementations. - unsafe { self.a.validate_param_unsafe(world) && self.b.validate_param_unsafe(world) } + unsafe { self.a.validate_param_unsafe(world) } } fn initialize(&mut self, world: &mut World) { @@ -433,7 +433,7 @@ where unsafe fn validate_param_unsafe(&mut self, world: UnsafeWorldCell) -> bool { // SAFETY: Delegate to other `System` implementations. - unsafe { self.a.validate_param_unsafe(world) && self.b.validate_param_unsafe(world) } + unsafe { self.a.validate_param_unsafe(world) } } fn validate_param(&mut self, world: &World) -> bool { diff --git a/crates/bevy_ecs/src/system/function_system.rs b/crates/bevy_ecs/src/system/function_system.rs index 3d029d74a9577..eeee2f1c78491 100644 --- a/crates/bevy_ecs/src/system/function_system.rs +++ b/crates/bevy_ecs/src/system/function_system.rs @@ -60,7 +60,7 @@ impl SystemMeta { is_send: true, has_deferred: false, last_run: Tick::new(0), - param_warn_policy: ParamWarnPolicy::Once, + param_warn_policy: ParamWarnPolicy::Panic, #[cfg(feature = "trace")] system_span: info_span!("system", name = name), #[cfg(feature = "trace")] @@ -190,6 +190,8 @@ impl SystemMeta { /// State machine for emitting warnings when [system params are invalid](System::validate_param). #[derive(Clone, Copy)] pub enum ParamWarnPolicy { + /// Stop app with a panic. + Panic, /// No warning should ever be emitted. Never, /// The warning will be emitted once and status will update to [`Self::Never`]. @@ -200,6 +202,7 @@ impl ParamWarnPolicy { /// Advances the warn policy after validation failed. #[inline] fn advance(&mut self) { + // Ignore `Panic` case, because it stops execution before this function gets called. *self = Self::Never; } @@ -209,15 +212,21 @@ impl ParamWarnPolicy { where P: SystemParam, { - if matches!(self, Self::Never) { - return; + match self { + Self::Panic => panic!( + "{0} could not access system parameter {1}", + name, + disqualified::ShortName::of::

() + ), + Self::Once => { + log::warn!( + "{0} did not run because it requested inaccessible system parameter {1}", + name, + disqualified::ShortName::of::

() + ); + } + Self::Never => {} } - - log::warn!( - "{0} did not run because it requested inaccessible system parameter {1}", - name, - disqualified::ShortName::of::

() - ); } } @@ -232,6 +241,11 @@ where /// Set warn policy. fn with_param_warn_policy(self, warn_policy: ParamWarnPolicy) -> FunctionSystem; + /// Warn only once about invalid system parameters. + fn param_warn_once(self) -> FunctionSystem { + self.with_param_warn_policy(ParamWarnPolicy::Once) + } + /// Disable all param warnings. fn never_param_warn(self) -> FunctionSystem { self.with_param_warn_policy(ParamWarnPolicy::Never) diff --git a/crates/bevy_ecs/src/system/query.rs b/crates/bevy_ecs/src/system/query.rs index 6fc2a4caa5d47..cc4312761ead4 100644 --- a/crates/bevy_ecs/src/system/query.rs +++ b/crates/bevy_ecs/src/system/query.rs @@ -1890,7 +1890,7 @@ impl<'w, 'q, Q: QueryData, F: QueryFilter> From<&'q mut Query<'w, '_, Q, F>> /// [System parameter] that provides access to single entity's components, much like [`Query::single`]/[`Query::single_mut`]. /// /// This [`SystemParam`](crate::system::SystemParam) fails validation if zero or more than one matching entity exists. -/// This will cause systems that use this parameter to be skipped. +/// /// This will cause a panic, but can be configured to do nothing or warn once. /// /// Use [`Option>`] instead if zero or one matching entities can exist. /// @@ -1926,7 +1926,7 @@ impl<'w, D: QueryData, F: QueryFilter> Single<'w, D, F> { /// [System parameter] that works very much like [`Query`] except it always contains at least one matching entity. /// /// This [`SystemParam`](crate::system::SystemParam) fails validation if no matching entities exist. -/// This will cause systems that use this parameter to be skipped. +/// /// This will cause a panic, but can be configured to do nothing or warn once. /// /// Much like [`Query::is_empty`] the worst case runtime will be `O(n)` where `n` is the number of *potential* matches. /// This can be notably expensive for queries that rely on non-archetypal filters such as [`Added`](crate::query::Added) or [`Changed`](crate::query::Changed) diff --git a/crates/bevy_ecs/src/system/system.rs b/crates/bevy_ecs/src/system/system.rs index 6cef4da705399..e0aa69b660db1 100644 --- a/crates/bevy_ecs/src/system/system.rs +++ b/crates/bevy_ecs/src/system/system.rs @@ -450,7 +450,7 @@ mod tests { let mut world = World::default(); // This fails because `T` has not been added to the world yet. - let result = world.run_system_once(system); + let result = world.run_system_once(system.param_warn_once()); assert!(matches!(result, Err(RunSystemError::InvalidParams(_)))); } diff --git a/crates/bevy_ecs/src/system/system_param.rs b/crates/bevy_ecs/src/system/system_param.rs index 2b38fc6201146..5af22674afa7c 100644 --- a/crates/bevy_ecs/src/system/system_param.rs +++ b/crates/bevy_ecs/src/system/system_param.rs @@ -1440,7 +1440,7 @@ unsafe impl SystemParam for Deferred<'_, T> { /// over to another thread. /// /// This [`SystemParam`] fails validation if non-send resource doesn't exist. -/// This will cause systems that use this parameter to be skipped. +/// /// This will cause a panic, but can be configured to do nothing or warn once. /// /// Use [`Option>`] instead if the resource might not always exist. pub struct NonSend<'w, T: 'static> { diff --git a/crates/bevy_ecs/src/system/system_registry.rs b/crates/bevy_ecs/src/system/system_registry.rs index 6cc7556bfda2b..f924618a7182c 100644 --- a/crates/bevy_ecs/src/system/system_registry.rs +++ b/crates/bevy_ecs/src/system/system_registry.rs @@ -998,7 +998,7 @@ mod tests { fn system(_: Res) {} let mut world = World::new(); - let id = world.register_system_cached(system); + let id = world.register_system(system.param_warn_once()); // This fails because `T` has not been added to the world yet. let result = world.run_system(id); diff --git a/crates/bevy_pbr/src/render/mesh.rs b/crates/bevy_pbr/src/render/mesh.rs index 2d4047fee6227..0ec344410cdcf 100644 --- a/crates/bevy_pbr/src/render/mesh.rs +++ b/crates/bevy_pbr/src/render/mesh.rs @@ -137,6 +137,10 @@ impl Plugin for MeshRenderPlugin { load_internal_asset!(app, SKINNING_HANDLE, "skinning.wgsl", Shader::from_wgsl); load_internal_asset!(app, MORPH_HANDLE, "morph.wgsl", Shader::from_wgsl); + if app.get_sub_app(RenderApp).is_none() { + return; + } + app.add_systems( PostUpdate, (no_automatic_skin_batching, no_automatic_morph_batching), diff --git a/examples/ecs/fallible_params.rs b/examples/ecs/fallible_params.rs index 15a75944f01ae..129a3859d495f 100644 --- a/examples/ecs/fallible_params.rs +++ b/examples/ecs/fallible_params.rs @@ -20,22 +20,20 @@ fn main() { App::new() .add_plugins(DefaultPlugins) .add_systems(Startup, setup) - // Systems that fail parameter validation will emit warnings. - // The default policy is to emit a warning once per system. - // This is good for catching unexpected behavior, but can - // lead to spam. You can disable invalid param warnings - // per system using the `.never_param_warn()` method. + // Default system policy is to panic if parameters fail to be fetched. + // We overwrite that configuration, to either warn us once or never. + // This is good for catching unexpected behavior without crashing the app, + // but can lead to spam. .add_systems( Update, ( - user_input, + user_input.param_warn_once(), move_targets.never_param_warn(), move_pointer.never_param_warn(), ) .chain(), ) - // We will leave this systems with default warning policy. - .add_systems(Update, do_nothing_fail_validation) + .add_systems(Update, do_nothing_fail_validation.param_warn_once()) .run(); } From ddf4d9ea93d20767d5f11145c32271b4b98a2374 Mon Sep 17 00:00:00 2001 From: Patrick Walton Date: Mon, 23 Dec 2024 21:39:18 -0500 Subject: [PATCH 010/272] Fix meshlet shaders for bindless mode. (#16825) We have to extract the material ID from the mesh and stuff it in the vertex during visibility buffer resolution. --- .../src/meshlet/visibility_buffer_resolve.wgsl | 2 ++ crates/bevy_pbr/src/render/mesh_bindings.wgsl | 2 ++ crates/bevy_pbr/src/render/mesh_functions.wgsl | 11 +++++++++++ .../bevy_pbr/src/render/parallax_mapping.wgsl | 18 +++++++++--------- crates/bevy_pbr/src/render/pbr_fragment.wgsl | 6 +++++- 5 files changed, 29 insertions(+), 10 deletions(-) diff --git a/crates/bevy_pbr/src/meshlet/visibility_buffer_resolve.wgsl b/crates/bevy_pbr/src/meshlet/visibility_buffer_resolve.wgsl index 33c8df6a0e2c6..f28645013d1ec 100644 --- a/crates/bevy_pbr/src/meshlet/visibility_buffer_resolve.wgsl +++ b/crates/bevy_pbr/src/meshlet/visibility_buffer_resolve.wgsl @@ -94,6 +94,7 @@ struct VertexOutput { world_tangent: vec4, mesh_flags: u32, cluster_id: u32, + material_bind_group_slot: u32, #ifdef PREPASS_FRAGMENT #ifdef MOTION_VECTOR_PREPASS motion_vector: vec2, @@ -173,6 +174,7 @@ fn resolve_vertex_output(frag_coord: vec4) -> VertexOutput { world_tangent, instance_uniform.flags, instance_id ^ meshlet_id, + instance_uniform.material_and_lightmap_bind_group_slot & 0xffffu, #ifdef PREPASS_FRAGMENT #ifdef MOTION_VECTOR_PREPASS motion_vector, diff --git a/crates/bevy_pbr/src/render/mesh_bindings.wgsl b/crates/bevy_pbr/src/render/mesh_bindings.wgsl index 2366dab155623..62b967c56f1b9 100644 --- a/crates/bevy_pbr/src/render/mesh_bindings.wgsl +++ b/crates/bevy_pbr/src/render/mesh_bindings.wgsl @@ -2,8 +2,10 @@ #import bevy_pbr::mesh_types::Mesh +#ifndef MESHLET_MESH_MATERIAL_PASS #ifdef PER_OBJECT_BUFFER_BATCH_SIZE @group(1) @binding(0) var mesh: array; #else @group(1) @binding(0) var mesh: array; #endif // PER_OBJECT_BUFFER_BATCH_SIZE +#endif // MESHLET_MESH_MATERIAL_PASS diff --git a/crates/bevy_pbr/src/render/mesh_functions.wgsl b/crates/bevy_pbr/src/render/mesh_functions.wgsl index b58004cadf1e9..23857bc6aa12d 100644 --- a/crates/bevy_pbr/src/render/mesh_functions.wgsl +++ b/crates/bevy_pbr/src/render/mesh_functions.wgsl @@ -12,6 +12,7 @@ } #import bevy_render::maths::{affine3_to_square, mat2x4_f32_to_mat3x3_unpack} +#ifndef MESHLET_MESH_MATERIAL_PASS fn get_world_from_local(instance_index: u32) -> mat4x4 { return affine3_to_square(mesh[instance_index].world_from_local); @@ -21,6 +22,8 @@ fn get_previous_world_from_local(instance_index: u32) -> mat4x4 { return affine3_to_square(mesh[instance_index].previous_world_from_local); } +#endif // MESHLET_MESH_MATERIAL_PASS + fn mesh_position_local_to_world(world_from_local: mat4x4, vertex_position: vec4) -> vec4 { return world_from_local * vertex_position; } @@ -33,6 +36,8 @@ fn mesh_position_local_to_clip(world_from_local: mat4x4, vertex_position: v return position_world_to_clip(world_position.xyz); } +#ifndef MESHLET_MESH_MATERIAL_PASS + fn mesh_normal_local_to_world(vertex_normal: vec3, instance_index: u32) -> vec3 { // NOTE: The mikktspace method of normal mapping requires that the world normal is // re-normalized in the vertex shader to match the way mikktspace bakes vertex tangents @@ -53,6 +58,8 @@ fn mesh_normal_local_to_world(vertex_normal: vec3, instance_index: u32) -> } } +#endif // MESHLET_MESH_MATERIAL_PASS + // Calculates the sign of the determinant of the 3x3 model matrix based on a // mesh flag fn sign_determinant_model_3x3m(mesh_flags: u32) -> f32 { @@ -62,6 +69,8 @@ fn sign_determinant_model_3x3m(mesh_flags: u32) -> f32 { return f32(bool(mesh_flags & MESH_FLAGS_SIGN_DETERMINANT_MODEL_3X3_BIT)) * 2.0 - 1.0; } +#ifndef MESHLET_MESH_MATERIAL_PASS + fn mesh_tangent_local_to_world(world_from_local: mat4x4, vertex_tangent: vec4, instance_index: u32) -> vec4 { // NOTE: The mikktspace method of normal mapping requires that the world tangent is // re-normalized in the vertex shader to match the way mikktspace bakes vertex tangents @@ -88,6 +97,8 @@ fn mesh_tangent_local_to_world(world_from_local: mat4x4, vertex_tangent: ve } } +#endif // MESHLET_MESH_MATERIAL_PASS + // Returns an appropriate dither level for the current mesh instance. // // This looks up the LOD range in the `visibility_ranges` table and compares the diff --git a/crates/bevy_pbr/src/render/parallax_mapping.wgsl b/crates/bevy_pbr/src/render/parallax_mapping.wgsl index 706c96850e172..780b5c290a416 100644 --- a/crates/bevy_pbr/src/render/parallax_mapping.wgsl +++ b/crates/bevy_pbr/src/render/parallax_mapping.wgsl @@ -5,8 +5,7 @@ mesh_bindings::mesh } -fn sample_depth_map(uv: vec2, instance_index: u32) -> f32 { - let slot = mesh[instance_index].material_and_lightmap_bind_group_slot & 0xffffu; +fn sample_depth_map(uv: vec2, material_bind_group_slot: u32) -> f32 { // We use `textureSampleLevel` over `textureSample` because the wgpu DX12 // backend (Fxc) panics when using "gradient instructions" inside a loop. // It results in the whole loop being unrolled by the shader compiler, @@ -19,8 +18,8 @@ fn sample_depth_map(uv: vec2, instance_index: u32) -> f32 { // See https://stackoverflow.com/questions/56581141/direct3d11-gradient-instruction-used-in-a-loop-with-varying-iteration-forcing return textureSampleLevel( #ifdef BINDLESS - depth_map_texture[slot], - depth_map_sampler[slot], + depth_map_texture[material_bind_group_slot], + depth_map_sampler[material_bind_group_slot], #else // BINDLESS depth_map_texture, depth_map_sampler, @@ -40,7 +39,7 @@ fn parallaxed_uv( original_uv: vec2, // The vector from the camera to the fragment at the surface in tangent space Vt: vec3, - instance_index: u32, + material_bind_group_slot: u32, ) -> vec2 { if max_layer_count < 1.0 { return original_uv; @@ -68,7 +67,7 @@ fn parallaxed_uv( var delta_uv = depth_scale * layer_depth * Vt.xy * vec2(1.0, -1.0) / view_steepness; var current_layer_depth = 0.0; - var texture_depth = sample_depth_map(uv, instance_index); + var texture_depth = sample_depth_map(uv, material_bind_group_slot); // texture_depth > current_layer_depth means the depth map depth is deeper // than the depth the ray would be at this UV offset so the ray has not @@ -76,7 +75,7 @@ fn parallaxed_uv( for (var i: i32 = 0; texture_depth > current_layer_depth && i <= i32(layer_count); i++) { current_layer_depth += layer_depth; uv += delta_uv; - texture_depth = sample_depth_map(uv, instance_index); + texture_depth = sample_depth_map(uv, material_bind_group_slot); } #ifdef RELIEF_MAPPING @@ -94,7 +93,7 @@ fn parallaxed_uv( current_layer_depth -= delta_depth; for (var i: u32 = 0u; i < max_steps; i++) { - texture_depth = sample_depth_map(uv, instance_index); + texture_depth = sample_depth_map(uv, material_bind_group_slot); // Halve the deltas for the next step delta_uv *= 0.5; @@ -118,7 +117,8 @@ fn parallaxed_uv( // may skip small details and result in writhing material artifacts. let previous_uv = uv - delta_uv; let next_depth = texture_depth - current_layer_depth; - let previous_depth = sample_depth_map(previous_uv, instance_index) - current_layer_depth + layer_depth; + let previous_depth = sample_depth_map(previous_uv, material_bind_group_slot) - + current_layer_depth + layer_depth; let weight = next_depth / (next_depth - previous_depth); diff --git a/crates/bevy_pbr/src/render/pbr_fragment.wgsl b/crates/bevy_pbr/src/render/pbr_fragment.wgsl index cd7500d1ac054..1df7ef404f7b8 100644 --- a/crates/bevy_pbr/src/render/pbr_fragment.wgsl +++ b/crates/bevy_pbr/src/render/pbr_fragment.wgsl @@ -71,7 +71,11 @@ fn pbr_input_from_standard_material( is_front: bool, ) -> pbr_types::PbrInput { #ifdef BINDLESS +#ifdef MESHLET_MESH_MATERIAL_PASS + let slot = in.material_bind_group_slot; +#else // MESHLET_MESH_MATERIAL_PASS let slot = mesh[in.instance_index].material_and_lightmap_bind_group_slot & 0xffffu; +#endif // MESHLET_MESH_MATERIAL_PASS let flags = pbr_bindings::material[slot].flags; let base_color = pbr_bindings::material[slot].base_color; let deferred_lighting_pass_id = pbr_bindings::material[slot].deferred_lighting_pass_id; @@ -146,7 +150,7 @@ fn pbr_input_from_standard_material( // parallax mapping algorithm easier to understand and reason // about. -Vt, - in.instance_index, + slot, ); #endif From bfc2a88f949a36a65012c81584c52fc3bfedfb1a Mon Sep 17 00:00:00 2001 From: ickshonpe Date: Tue, 24 Dec 2024 02:41:46 +0000 Subject: [PATCH 011/272] Toggleable UI layout rounding (#16841) # Objective Allow users to enable or disable layout rounding for specific UI nodes and their descendants. Fixes #16731 ## Solution New component `LayoutConfig` that can be added to any UiNode entity. Setting the `use_rounding` field of `LayoutConfig` determines if the Node and its descendants should be given rounded or unrounded coordinates. ## Testing Not tested this extensively but it seems to work and it's not very complicated. This really basic test app returns fractional coords: ```rust use bevy::prelude::*; fn main() { App::new() .add_plugins(DefaultPlugins) .add_systems(Startup, setup) .add_systems(Update, report) .run(); } fn setup(mut commands: Commands) { commands.spawn(Camera2d); commands.spawn(( Node { left: Val::Px(0.1), width: Val::Px(100.1), height: Val::Px(100.1), ..Default::default() }, LayoutConfig { use_rounding: false }, )); } fn report(node: Query<(Ref, &GlobalTransform)>) { for (c, g) in node.iter() { if c.is_changed() { println!("{:#?}", c); println!("position = {:?}", g.to_scale_rotation_translation().2); } } } ``` --------- Co-authored-by: Alice Cecile Co-authored-by: UkoeHB <37489173+UkoeHB@users.noreply.github.com> --- crates/bevy_ui/src/layout/mod.rs | 27 ++++++++++++++------- crates/bevy_ui/src/layout/ui_surface.rs | 32 ++++++++++++++++--------- crates/bevy_ui/src/ui_node.rs | 22 +++++++++++++++++ 3 files changed, 62 insertions(+), 19 deletions(-) diff --git a/crates/bevy_ui/src/layout/mod.rs b/crates/bevy_ui/src/layout/mod.rs index bc14e79183e17..17107d3679b88 100644 --- a/crates/bevy_ui/src/layout/mod.rs +++ b/crates/bevy_ui/src/layout/mod.rs @@ -1,7 +1,7 @@ use crate::{ experimental::{UiChildren, UiRootNodes}, - BorderRadius, ComputedNode, ContentSize, DefaultUiCamera, Display, Node, Outline, OverflowAxis, - ScrollPosition, TargetCamera, UiScale, Val, + BorderRadius, ComputedNode, ContentSize, DefaultUiCamera, Display, LayoutConfig, Node, Outline, + OverflowAxis, ScrollPosition, TargetCamera, UiScale, Val, }; use bevy_ecs::{ change_detection::{DetectChanges, DetectChangesMut}, @@ -120,10 +120,12 @@ pub fn ui_layout_system( &mut ComputedNode, &mut Transform, &Node, + Option<&LayoutConfig>, Option<&BorderRadius>, Option<&Outline>, Option<&ScrollPosition>, )>, + mut buffer_query: Query<&mut ComputedTextBlock>, mut font_system: ResMut, ) { @@ -294,6 +296,7 @@ with UI components as a child of an entity without UI components, your UI layout &mut commands, *root, &mut ui_surface, + true, None, &mut node_transform_query, &ui_children, @@ -312,11 +315,13 @@ with UI components as a child of an entity without UI components, your UI layout commands: &mut Commands, entity: Entity, ui_surface: &mut UiSurface, + inherited_use_rounding: bool, root_size: Option, node_transform_query: &mut Query<( &mut ComputedNode, &mut Transform, &Node, + Option<&LayoutConfig>, Option<&BorderRadius>, Option<&Outline>, Option<&ScrollPosition>, @@ -330,12 +335,17 @@ with UI components as a child of an entity without UI components, your UI layout mut node, mut transform, style, + maybe_layout_config, maybe_border_radius, maybe_outline, maybe_scroll_position, )) = node_transform_query.get_mut(entity) { - let Ok((layout, unrounded_size)) = ui_surface.get_layout(entity) else { + let use_rounding = maybe_layout_config + .map(|layout_config| layout_config.use_rounding) + .unwrap_or(inherited_use_rounding); + + let Ok((layout, unrounded_size)) = ui_surface.get_layout(entity, use_rounding) else { return; }; @@ -446,6 +456,7 @@ with UI components as a child of an entity without UI components, your UI layout commands, child_uinode, ui_surface, + use_rounding, Some(viewport_size), node_transform_query, ui_children, @@ -573,7 +584,7 @@ mod tests { let mut ui_surface = world.resource_mut::(); for ui_entity in [ui_root, ui_child] { - let layout = ui_surface.get_layout(ui_entity).unwrap().0; + let layout = ui_surface.get_layout(ui_entity, true).unwrap().0; assert_eq!(layout.size.width, WINDOW_WIDTH); assert_eq!(layout.size.height, WINDOW_HEIGHT); } @@ -962,7 +973,7 @@ mod tests { let mut ui_surface = world.resource_mut::(); let layout = ui_surface - .get_layout(ui_node_entity) + .get_layout(ui_node_entity, true) .expect("failed to get layout") .0; @@ -1049,7 +1060,7 @@ mod tests { ui_schedule.run(&mut world); let mut ui_surface = world.resource_mut::(); - let layout = ui_surface.get_layout(ui_entity).unwrap().0; + let layout = ui_surface.get_layout(ui_entity, true).unwrap().0; // the node should takes its size from the fixed size measure func assert_eq!(layout.size.width, content_size.x); @@ -1078,7 +1089,7 @@ mod tests { // a node with a content size should have taffy context assert!(ui_surface.taffy.get_node_context(ui_node).is_some()); - let layout = ui_surface.get_layout(ui_entity).unwrap().0; + let layout = ui_surface.get_layout(ui_entity, true).unwrap().0; assert_eq!(layout.size.width, content_size.x); assert_eq!(layout.size.height, content_size.y); @@ -1091,7 +1102,7 @@ mod tests { assert!(ui_surface.taffy.get_node_context(ui_node).is_none()); // Without a content size, the node has no width or height constraints so the length of both dimensions is 0. - let layout = ui_surface.get_layout(ui_entity).unwrap().0; + let layout = ui_surface.get_layout(ui_entity, true).unwrap().0; assert_eq!(layout.size.width, 0.); assert_eq!(layout.size.height, 0.); } diff --git a/crates/bevy_ui/src/layout/ui_surface.rs b/crates/bevy_ui/src/layout/ui_surface.rs index 2cb06eca60f30..149623b81804c 100644 --- a/crates/bevy_ui/src/layout/ui_surface.rs +++ b/crates/bevy_ui/src/layout/ui_surface.rs @@ -277,23 +277,33 @@ impl UiSurface { /// Does not compute the layout geometry, `compute_window_layouts` should be run before using this function. /// On success returns a pair consisting of the final resolved layout values after rounding /// and the size of the node after layout resolution but before rounding. - pub fn get_layout(&mut self, entity: Entity) -> Result<(taffy::Layout, Vec2), LayoutError> { + pub fn get_layout( + &mut self, + entity: Entity, + use_rounding: bool, + ) -> Result<(taffy::Layout, Vec2), LayoutError> { let Some(taffy_node) = self.entity_to_taffy.get(&entity) else { return Err(LayoutError::InvalidHierarchy); }; - let layout = self - .taffy - .layout(*taffy_node) - .cloned() - .map_err(LayoutError::TaffyError)?; + if use_rounding { + self.taffy.enable_rounding(); + } else { + self.taffy.disable_rounding(); + } - self.taffy.disable_rounding(); - let taffy_size = self.taffy.layout(*taffy_node).unwrap().size; - let unrounded_size = Vec2::new(taffy_size.width, taffy_size.height); - self.taffy.enable_rounding(); + let out = match self.taffy.layout(*taffy_node).cloned() { + Ok(layout) => { + self.taffy.disable_rounding(); + let taffy_size = self.taffy.layout(*taffy_node).unwrap().size; + let unrounded_size = Vec2::new(taffy_size.width, taffy_size.height); + Ok((layout, unrounded_size)) + } + Err(taffy_error) => Err(LayoutError::TaffyError(taffy_error)), + }; - Ok((layout, unrounded_size)) + self.taffy.enable_rounding(); + out } } diff --git a/crates/bevy_ui/src/ui_node.rs b/crates/bevy_ui/src/ui_node.rs index 11080199365d2..392ee86d115c9 100644 --- a/crates/bevy_ui/src/ui_node.rs +++ b/crates/bevy_ui/src/ui_node.rs @@ -2550,6 +2550,28 @@ impl Default for ShadowStyle { } } +#[derive(Component, Copy, Clone, Debug, PartialEq, Reflect)] +#[reflect(Component, Debug, PartialEq, Default)] +#[cfg_attr( + feature = "serialize", + derive(serde::Serialize, serde::Deserialize), + reflect(Serialize, Deserialize) +)] +/// This component can be added to any UI node to modify its layout behavior. +pub struct LayoutConfig { + /// If set to true the coordinates for this node and its descendents will be rounded to the nearest physical pixel. + /// This can help prevent visual artifacts like blurry images or semi-transparent edges that can occur with sub-pixel positioning. + /// + /// Defaults to true. + pub use_rounding: bool, +} + +impl Default for LayoutConfig { + fn default() -> Self { + Self { use_rounding: true } + } +} + #[cfg(test)] mod tests { use crate::GridPlacement; From 450b939c1f644d80362923922afe082c83228e44 Mon Sep 17 00:00:00 2001 From: scottmcm Date: Mon, 23 Dec 2024 18:44:04 -0800 Subject: [PATCH 012/272] Fix `EaseFunction::Exponential*` to exactly hit (0, 0) and (1, 1) (#16910) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit And add a bunch of tests to show that all the monotonic easing functions have roughly the expected shape. # Objective The `EaseFunction::Exponential*` variants aren't actually smooth as currently implemented, because they jump by about 1‰ at the start/end/both. - Fixes #16676 - Subsumes #16675 ## Solution This PR slightly tweaks the shifting and scaling of all three variants to ensure they hit (0, 0) and (1, 1) exactly while gradually transitioning between them. Graph demonstration of the new easing function definitions: ![desmos-graph](https://github.com/user-attachments/assets/c87e9fe5-47d9-4407-9c94-80135eef5908) (Yes, they look completely identical to the previous ones at that scale. [Here's a zoomed-in comparison](https://www.desmos.com/calculator/ken6nk89of) between the old and the new if you prefer.) The approach taken was to keep the core 2¹⁰ᵗ shape, but to [ask WolframAlpha](https://www.wolframalpha.com/input?i=solve+over+the+reals%3A+pow%282%2C+10-A%29+-+pow%282%2C+-A%29%3D+1) what scaling factor to use such that f(1)-f(0)=1, then shift the curve down so that goes from zero to one instead of ¹/₁₀₂₃ to ¹⁰²⁴/₁₀₂₃. ## Testing I've included in this PR a bunch of general tests for all monotonic easing functions to ensure they hit (0, 0) to (1, 1), that the InOut functions hit (½, ½), and that they have the expected convexity. You can also see by inspection that the difference is small. The change for `exponential_in` is from `exp2(10 * t - 10)` to `exp2(10 * t - 9.99859…) - 0.0009775171…`. The problem for `exponential_in(0)` is also simple to see without a calculator: 2⁻¹⁰ is obviously not zero, but with the new definition `exp2(-LOG2_1023) - FRAC_1_1023` => `1/(exp2(LOG2_1023)) - FRAC_1_1023` => `FRAC_1_1023 - FRAC_1_1023` => `0`. --- ## Migration Guide This release of bevy slightly tweaked the definitions of `EaseFunction::ExponentialIn`, `EaseFunction::ExponentialOut`, and `EaseFunction::ExponentialInOut`. The previous definitions had small discontinuities, while the new ones are slightly rescaled to be continuous. For the output values that changed, that change was less than 0.001, so visually you might not even notice the difference. However, if you depended on them for determinism, you'll need to define your own curves with the previous definitions. --------- Co-authored-by: IQuick 143 --- crates/bevy_math/src/curve/easing.rs | 108 +++++++++++++++++++++++++-- 1 file changed, 102 insertions(+), 6 deletions(-) diff --git a/crates/bevy_math/src/curve/easing.rs b/crates/bevy_math/src/curve/easing.rs index 0e5406fa732c7..81d5fb6df1d3a 100644 --- a/crates/bevy_math/src/curve/easing.rs +++ b/crates/bevy_math/src/curve/easing.rs @@ -176,9 +176,15 @@ pub enum EaseFunction { /// Behaves as `EaseFunction::CircularIn` for t < 0.5 and as `EaseFunction::CircularOut` for t >= 0.5 CircularInOut, - /// `f(t) = 2.0^(10.0 * (t - 1.0))` + /// `f(t) ≈ 2.0^(10.0 * (t - 1.0))` + /// + /// The precise definition adjusts it slightly so it hits both `(0, 0)` and `(1, 1)`: + /// `f(t) = 2.0^(10.0 * t - A) - B`, where A = log₂(2¹⁰-1) and B = 1/(2¹⁰-1). ExponentialIn, - /// `f(t) = 1.0 - 2.0^(-10.0 * t)` + /// `f(t) ≈ 1.0 - 2.0^(-10.0 * t)` + /// + /// As with `EaseFunction::ExponentialIn`, the precise definition adjusts it slightly + // so it hits both `(0, 0)` and `(1, 1)`. ExponentialOut, /// Behaves as `EaseFunction::ExponentialIn` for t < 0.5 and as `EaseFunction::ExponentialOut` for t >= 0.5 ExponentialInOut, @@ -324,20 +330,30 @@ mod easing_functions { } } + // These are copied from a high precision calculator; I'd rather show them + // with blatantly more digits than needed (since rust will round them to the + // nearest representable value anyway) rather than make it seem like the + // truncated value is somehow carefully chosen. + #[allow(clippy::excessive_precision)] + const LOG2_1023: f32 = 9.998590429745328646459226; + #[allow(clippy::excessive_precision)] + const FRAC_1_1023: f32 = 0.00097751710654936461388074291; #[inline] pub(crate) fn exponential_in(t: f32) -> f32 { - ops::powf(2.0, 10.0 * t - 10.0) + // Derived from a rescaled exponential formula `(2^(10*t) - 1) / (2^10 - 1)` + // See + ops::exp2(10.0 * t - LOG2_1023) - FRAC_1_1023 } #[inline] pub(crate) fn exponential_out(t: f32) -> f32 { - 1.0 - ops::powf(2.0, -10.0 * t) + (FRAC_1_1023 + 1.0) - ops::exp2(-10.0 * t - (LOG2_1023 - 10.0)) } #[inline] pub(crate) fn exponential_in_out(t: f32) -> f32 { if t < 0.5 { - ops::powf(2.0, 20.0 * t - 10.0) / 2.0 + ops::exp2(20.0 * t - (LOG2_1023 + 1.0)) - (FRAC_1_1023 / 2.0) } else { - (2.0 - ops::powf(2.0, -20.0 * t + 10.0)) / 2.0 + (FRAC_1_1023 / 2.0 + 1.0) - ops::exp2(-20.0 * t - (LOG2_1023 - 19.0)) } } @@ -459,3 +475,83 @@ impl EaseFunction { } } } + +#[cfg(test)] +mod tests { + use super::*; + const MONOTONIC_IN_OUT_INOUT: &[[EaseFunction; 3]] = { + use EaseFunction::*; + &[ + [QuadraticIn, QuadraticOut, QuadraticInOut], + [CubicIn, CubicOut, CubicInOut], + [QuarticIn, QuarticOut, QuarticInOut], + [QuinticIn, QuinticOut, QuinticInOut], + [SineIn, SineOut, SineInOut], + [CircularIn, CircularOut, CircularInOut], + [ExponentialIn, ExponentialOut, ExponentialInOut], + ] + }; + + // For easing function we don't care if eval(0) is super-tiny like 2.0e-28, + // so add the same amount of error on both ends of the unit interval. + const TOLERANCE: f32 = 1.0e-6; + const _: () = const { + assert!(1.0 - TOLERANCE != 1.0); + }; + + #[test] + fn ease_functions_zero_to_one() { + for ef in MONOTONIC_IN_OUT_INOUT.iter().flatten() { + let start = ef.eval(0.0); + assert!( + (0.0..=TOLERANCE).contains(&start), + "EaseFunction.{ef:?}(0) was {start:?}", + ); + + let finish = ef.eval(1.0); + assert!( + (1.0 - TOLERANCE..=1.0).contains(&finish), + "EaseFunction.{ef:?}(1) was {start:?}", + ); + } + } + + #[test] + fn ease_function_inout_deciles() { + // convexity gives these built-in tolerances + for [_, _, ef_inout] in MONOTONIC_IN_OUT_INOUT { + for x in [0.1, 0.2, 0.3, 0.4] { + let y = ef_inout.eval(x); + assert!(y < x, "EaseFunction.{ef_inout:?}({x:?}) was {y:?}"); + } + + for x in [0.6, 0.7, 0.8, 0.9] { + let y = ef_inout.eval(x); + assert!(y > x, "EaseFunction.{ef_inout:?}({x:?}) was {y:?}"); + } + } + } + + #[test] + fn ease_function_midpoints() { + for [ef_in, ef_out, ef_inout] in MONOTONIC_IN_OUT_INOUT { + let mid = ef_in.eval(0.5); + assert!( + mid < 0.5 - TOLERANCE, + "EaseFunction.{ef_in:?}(½) was {mid:?}", + ); + + let mid = ef_out.eval(0.5); + assert!( + mid > 0.5 + TOLERANCE, + "EaseFunction.{ef_out:?}(½) was {mid:?}", + ); + + let mid = ef_inout.eval(0.5); + assert!( + (0.5 - TOLERANCE..=0.5 + TOLERANCE).contains(&mid), + "EaseFunction.{ef_inout:?}(½) was {mid:?}", + ); + } + } +} From 5b899dcc3a4f568840a9e90e4ed2f1b209969e1c Mon Sep 17 00:00:00 2001 From: Vic <59878206+Victoronz@users.noreply.github.com> Date: Tue, 24 Dec 2024 03:47:03 +0100 Subject: [PATCH 013/272] impl EntityBorrow for more types (#16917) # Objective Some types like `RenderEntity` and `MainEntity` are just wrappers around `Entity`, so they should be able to implement `EntityBorrow`/`TrustedEntityBorrow`. This allows using them with `EntitySet` functionality. The `EntityRef` family are more than direct wrappers around `Entity`, but can still benefit from being unique in a collection. ## Solution Implement `EntityBorrow` and `TrustedEntityBorrow` for simple `Entity` newtypes and `EntityRef` types. These impls are an explicit decision to have the `EntityRef` types compare like just `Entity`. `EntityWorldMut` is omitted from this impl, because it explicitly contains a `&mut World` as well, and we do not ever use more than one at a time. Add `EntityBorrow` to the `bevy_ecs` prelude. ## Migration Guide `NormalizedWindowRef::entity` has been replaced with an `EntityBorrow::entity` impl. --- crates/bevy_ecs/src/lib.rs | 2 +- crates/bevy_ecs/src/query/iter.rs | 36 ++- crates/bevy_ecs/src/world/entity_ref.rs | 240 +++++++++++++++++- .../bevy_ecs/src/world/unsafe_world_cell.rs | 8 +- crates/bevy_render/src/camera/camera.rs | 2 +- .../src/camera/camera_driver_node.rs | 2 +- crates/bevy_render/src/sync_world.rs | 20 +- crates/bevy_ui/src/focus.rs | 2 +- crates/bevy_ui/src/layout/mod.rs | 2 +- crates/bevy_window/src/window.rs | 7 +- 10 files changed, 307 insertions(+), 14 deletions(-) diff --git a/crates/bevy_ecs/src/lib.rs b/crates/bevy_ecs/src/lib.rs index 419972fdfff79..ab949045ef5b6 100644 --- a/crates/bevy_ecs/src/lib.rs +++ b/crates/bevy_ecs/src/lib.rs @@ -58,7 +58,7 @@ pub mod prelude { bundle::Bundle, change_detection::{DetectChanges, DetectChangesMut, Mut, Ref}, component::{require, Component}, - entity::{Entity, EntityMapper}, + entity::{Entity, EntityBorrow, EntityMapper}, event::{Event, EventMutator, EventReader, EventWriter, Events}, name::{Name, NameOrEntity}, observer::{CloneEntityWithObserversExt, Observer, Trigger}, diff --git a/crates/bevy_ecs/src/query/iter.rs b/crates/bevy_ecs/src/query/iter.rs index 816c92e76ca14..c7303382f68d8 100644 --- a/crates/bevy_ecs/src/query/iter.rs +++ b/crates/bevy_ecs/src/query/iter.rs @@ -1,11 +1,15 @@ use super::{QueryData, QueryFilter, ReadOnlyQueryData}; use crate::{ archetype::{Archetype, ArchetypeEntity, Archetypes}, + bundle::Bundle, component::Tick, entity::{Entities, Entity, EntityBorrow, EntitySet, EntitySetIterator}, query::{ArchetypeFilter, DebugCheckedUnwrap, QueryState, StorageId}, storage::{Table, TableRow, Tables}, - world::unsafe_world_cell::UnsafeWorldCell, + world::{ + unsafe_world_cell::UnsafeWorldCell, EntityMut, EntityMutExcept, EntityRef, EntityRefExcept, + FilteredEntityMut, FilteredEntityRef, + }, }; use alloc::vec::Vec; use core::{ @@ -1105,6 +1109,36 @@ impl<'w, 's, D: QueryData, F: QueryFilter> FusedIterator for QueryIter<'w, 's, D // SAFETY: [`QueryIter`] is guaranteed to return every matching entity once and only once. unsafe impl<'w, 's, F: QueryFilter> EntitySetIterator for QueryIter<'w, 's, Entity, F> {} +// SAFETY: [`QueryIter`] is guaranteed to return every matching entity once and only once. +unsafe impl<'w, 's, F: QueryFilter> EntitySetIterator for QueryIter<'w, 's, EntityRef<'_>, F> {} + +// SAFETY: [`QueryIter`] is guaranteed to return every matching entity once and only once. +unsafe impl<'w, 's, F: QueryFilter> EntitySetIterator for QueryIter<'w, 's, EntityMut<'_>, F> {} + +// SAFETY: [`QueryIter`] is guaranteed to return every matching entity once and only once. +unsafe impl<'w, 's, F: QueryFilter> EntitySetIterator + for QueryIter<'w, 's, FilteredEntityRef<'_>, F> +{ +} + +// SAFETY: [`QueryIter`] is guaranteed to return every matching entity once and only once. +unsafe impl<'w, 's, F: QueryFilter> EntitySetIterator + for QueryIter<'w, 's, FilteredEntityMut<'_>, F> +{ +} + +// SAFETY: [`QueryIter`] is guaranteed to return every matching entity once and only once. +unsafe impl<'w, 's, F: QueryFilter, B: Bundle> EntitySetIterator + for QueryIter<'w, 's, EntityRefExcept<'_, B>, F> +{ +} + +// SAFETY: [`QueryIter`] is guaranteed to return every matching entity once and only once. +unsafe impl<'w, 's, F: QueryFilter, B: Bundle> EntitySetIterator + for QueryIter<'w, 's, EntityMutExcept<'_, B>, F> +{ +} + impl<'w, 's, D: QueryData, F: QueryFilter> Debug for QueryIter<'w, 's, D, F> { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { f.debug_struct("QueryIter").finish() diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index 64dd16149a0ca..f8b8e55f59f37 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -3,7 +3,9 @@ use crate::{ bundle::{Bundle, BundleId, BundleInfo, BundleInserter, DynamicBundle, InsertMode}, change_detection::MutUntyped, component::{Component, ComponentId, ComponentTicks, Components, Mutable, StorageType}, - entity::{Entities, Entity, EntityCloneBuilder, EntityLocation}, + entity::{ + Entities, Entity, EntityBorrow, EntityCloneBuilder, EntityLocation, TrustedEntityBorrow, + }, event::Event, observer::Observer, query::{Access, ReadOnlyQueryData}, @@ -17,7 +19,13 @@ use bevy_ptr::{OwningPtr, Ptr}; use bevy_utils::{HashMap, HashSet}; #[cfg(feature = "track_change_detection")] use core::panic::Location; -use core::{any::TypeId, marker::PhantomData, mem::MaybeUninit}; +use core::{ + any::TypeId, + cmp::Ordering, + hash::{Hash, Hasher}, + marker::PhantomData, + mem::MaybeUninit, +}; use thiserror::Error; use super::{unsafe_world_cell::UnsafeEntityCell, Ref, ON_REMOVE, ON_REPLACE}; @@ -369,6 +377,44 @@ impl<'a> TryFrom<&'a FilteredEntityMut<'_>> for EntityRef<'a> { } } +impl PartialEq for EntityRef<'_> { + fn eq(&self, other: &Self) -> bool { + self.entity() == other.entity() + } +} + +impl Eq for EntityRef<'_> {} + +#[expect(clippy::non_canonical_partial_ord_impl)] +impl PartialOrd for EntityRef<'_> { + /// [`EntityRef`]'s comparison trait implementations match the underlying [`Entity`], + /// and cannot discern between different worlds. + fn partial_cmp(&self, other: &Self) -> Option { + self.entity().partial_cmp(&other.entity()) + } +} + +impl Ord for EntityRef<'_> { + fn cmp(&self, other: &Self) -> Ordering { + self.entity().cmp(&other.entity()) + } +} + +impl Hash for EntityRef<'_> { + fn hash(&self, state: &mut H) { + self.entity().hash(state); + } +} + +impl EntityBorrow for EntityRef<'_> { + fn entity(&self) -> Entity { + self.id() + } +} + +// SAFETY: This type represents one Entity. We implement the comparison traits based on that Entity. +unsafe impl TrustedEntityBorrow for EntityRef<'_> {} + /// Provides mutable access to a single entity and all of its components. /// /// Contrast with [`EntityWorldMut`], which allows adding and removing components, @@ -869,6 +915,44 @@ impl<'a> TryFrom<&'a mut FilteredEntityMut<'_>> for EntityMut<'a> { } } +impl PartialEq for EntityMut<'_> { + fn eq(&self, other: &Self) -> bool { + self.entity() == other.entity() + } +} + +impl Eq for EntityMut<'_> {} + +#[expect(clippy::non_canonical_partial_ord_impl)] +impl PartialOrd for EntityMut<'_> { + /// [`EntityMut`]'s comparison trait implementations match the underlying [`Entity`], + /// and cannot discern between different worlds. + fn partial_cmp(&self, other: &Self) -> Option { + self.entity().partial_cmp(&other.entity()) + } +} + +impl Ord for EntityMut<'_> { + fn cmp(&self, other: &Self) -> Ordering { + self.entity().cmp(&other.entity()) + } +} + +impl Hash for EntityMut<'_> { + fn hash(&self, state: &mut H) { + self.entity().hash(state); + } +} + +impl EntityBorrow for EntityMut<'_> { + fn entity(&self) -> Entity { + self.id() + } +} + +// SAFETY: This type represents one Entity. We implement the comparison traits based on that Entity. +unsafe impl TrustedEntityBorrow for EntityMut<'_> {} + /// A mutable reference to a particular [`Entity`], and the entire world. /// /// This is essentially a performance-optimized `(Entity, &mut World)` tuple, @@ -2969,6 +3053,44 @@ impl<'a> From<&'a EntityWorldMut<'_>> for FilteredEntityRef<'a> { } } +impl PartialEq for FilteredEntityRef<'_> { + fn eq(&self, other: &Self) -> bool { + self.entity() == other.entity() + } +} + +impl Eq for FilteredEntityRef<'_> {} + +#[expect(clippy::non_canonical_partial_ord_impl)] +impl PartialOrd for FilteredEntityRef<'_> { + /// [`FilteredEntityRef`]'s comparison trait implementations match the underlying [`Entity`], + /// and cannot discern between different worlds. + fn partial_cmp(&self, other: &Self) -> Option { + self.entity().partial_cmp(&other.entity()) + } +} + +impl Ord for FilteredEntityRef<'_> { + fn cmp(&self, other: &Self) -> Ordering { + self.entity().cmp(&other.entity()) + } +} + +impl Hash for FilteredEntityRef<'_> { + fn hash(&self, state: &mut H) { + self.entity().hash(state); + } +} + +impl EntityBorrow for FilteredEntityRef<'_> { + fn entity(&self) -> Entity { + self.id() + } +} + +// SAFETY: This type represents one Entity. We implement the comparison traits based on that Entity. +unsafe impl TrustedEntityBorrow for FilteredEntityRef<'_> {} + /// Provides mutable access to a single entity and some of its components defined by the contained [`Access`]. /// /// To define the access when used as a [`QueryData`](crate::query::QueryData), @@ -3258,6 +3380,44 @@ impl<'a> From<&'a mut EntityWorldMut<'_>> for FilteredEntityMut<'a> { } } +impl PartialEq for FilteredEntityMut<'_> { + fn eq(&self, other: &Self) -> bool { + self.entity() == other.entity() + } +} + +impl Eq for FilteredEntityMut<'_> {} + +#[expect(clippy::non_canonical_partial_ord_impl)] +impl PartialOrd for FilteredEntityMut<'_> { + /// [`FilteredEntityMut`]'s comparison trait implementations match the underlying [`Entity`], + /// and cannot discern between different worlds. + fn partial_cmp(&self, other: &Self) -> Option { + self.entity().partial_cmp(&other.entity()) + } +} + +impl Ord for FilteredEntityMut<'_> { + fn cmp(&self, other: &Self) -> Ordering { + self.entity().cmp(&other.entity()) + } +} + +impl Hash for FilteredEntityMut<'_> { + fn hash(&self, state: &mut H) { + self.entity().hash(state); + } +} + +impl EntityBorrow for FilteredEntityMut<'_> { + fn entity(&self) -> Entity { + self.id() + } +} + +// SAFETY: This type represents one Entity. We implement the comparison traits based on that Entity. +unsafe impl TrustedEntityBorrow for FilteredEntityMut<'_> {} + /// Error type returned by [`TryFrom`] conversions from filtered entity types /// ([`FilteredEntityRef`]/[`FilteredEntityMut`]) to full-access entity types /// ([`EntityRef`]/[`EntityMut`]). @@ -3361,6 +3521,44 @@ where } } +impl PartialEq for EntityRefExcept<'_, B> { + fn eq(&self, other: &Self) -> bool { + self.entity() == other.entity() + } +} + +impl Eq for EntityRefExcept<'_, B> {} + +#[expect(clippy::non_canonical_partial_ord_impl)] +impl PartialOrd for EntityRefExcept<'_, B> { + /// [`EntityRefExcept`]'s comparison trait implementations match the underlying [`Entity`], + /// and cannot discern between different worlds. + fn partial_cmp(&self, other: &Self) -> Option { + self.entity().partial_cmp(&other.entity()) + } +} + +impl Ord for EntityRefExcept<'_, B> { + fn cmp(&self, other: &Self) -> Ordering { + self.entity().cmp(&other.entity()) + } +} + +impl Hash for EntityRefExcept<'_, B> { + fn hash(&self, state: &mut H) { + self.entity().hash(state); + } +} + +impl EntityBorrow for EntityRefExcept<'_, B> { + fn entity(&self) -> Entity { + self.id() + } +} + +// SAFETY: This type represents one Entity. We implement the comparison traits based on that Entity. +unsafe impl TrustedEntityBorrow for EntityRefExcept<'_, B> {} + /// Provides mutable access to all components of an entity, with the exception /// of an explicit set. /// @@ -3464,6 +3662,44 @@ where } } +impl PartialEq for EntityMutExcept<'_, B> { + fn eq(&self, other: &Self) -> bool { + self.entity() == other.entity() + } +} + +impl Eq for EntityMutExcept<'_, B> {} + +#[expect(clippy::non_canonical_partial_ord_impl)] +impl PartialOrd for EntityMutExcept<'_, B> { + /// [`EntityMutExcept`]'s comparison trait implementations match the underlying [`Entity`], + /// and cannot discern between different worlds. + fn partial_cmp(&self, other: &Self) -> Option { + self.entity().partial_cmp(&other.entity()) + } +} + +impl Ord for EntityMutExcept<'_, B> { + fn cmp(&self, other: &Self) -> Ordering { + self.entity().cmp(&other.entity()) + } +} + +impl Hash for EntityMutExcept<'_, B> { + fn hash(&self, state: &mut H) { + self.entity().hash(state); + } +} + +impl EntityBorrow for EntityMutExcept<'_, B> { + fn entity(&self) -> Entity { + self.id() + } +} + +// SAFETY: This type represents one Entity. We implement the comparison traits based on that Entity. +unsafe impl TrustedEntityBorrow for EntityMutExcept<'_, B> {} + fn bundle_contains_component(components: &Components, query_id: ComponentId) -> bool where B: Bundle, diff --git a/crates/bevy_ecs/src/world/unsafe_world_cell.rs b/crates/bevy_ecs/src/world/unsafe_world_cell.rs index 82593961c51c8..afc6e86be5516 100644 --- a/crates/bevy_ecs/src/world/unsafe_world_cell.rs +++ b/crates/bevy_ecs/src/world/unsafe_world_cell.rs @@ -8,7 +8,7 @@ use crate::{ bundle::Bundles, change_detection::{MaybeUnsafeCellLocation, MutUntyped, Ticks, TicksMut}, component::{ComponentId, ComponentTicks, Components, Mutable, StorageType, Tick, TickCells}, - entity::{Entities, Entity, EntityLocation}, + entity::{Entities, Entity, EntityBorrow, EntityLocation}, observer::Observers, prelude::Component, query::{DebugCheckedUnwrap, ReadOnlyQueryData}, @@ -1183,3 +1183,9 @@ unsafe fn get_ticks( StorageType::SparseSet => world.fetch_sparse_set(component_id)?.get_ticks(entity), } } + +impl EntityBorrow for UnsafeEntityCell<'_> { + fn entity(&self) -> Entity { + self.id() + } +} diff --git a/crates/bevy_render/src/camera/camera.rs b/crates/bevy_render/src/camera/camera.rs index d552000d0b5fd..1dc75c8b16645 100644 --- a/crates/bevy_render/src/camera/camera.rs +++ b/crates/bevy_render/src/camera/camera.rs @@ -19,7 +19,7 @@ use bevy_derive::{Deref, DerefMut}; use bevy_ecs::{ change_detection::DetectChanges, component::{Component, ComponentId, Mutable}, - entity::Entity, + entity::{Entity, EntityBorrow}, event::EventReader, prelude::{require, With}, query::Has, diff --git a/crates/bevy_render/src/camera/camera_driver_node.rs b/crates/bevy_render/src/camera/camera_driver_node.rs index 274b12bca84fa..99a988b9f32df 100644 --- a/crates/bevy_render/src/camera/camera_driver_node.rs +++ b/crates/bevy_render/src/camera/camera_driver_node.rs @@ -4,7 +4,7 @@ use crate::{ renderer::RenderContext, view::ExtractedWindows, }; -use bevy_ecs::{prelude::QueryState, world::World}; +use bevy_ecs::{entity::EntityBorrow, prelude::QueryState, world::World}; use bevy_utils::HashSet; use wgpu::{LoadOp, Operations, RenderPassColorAttachment, RenderPassDescriptor, StoreOp}; diff --git a/crates/bevy_render/src/sync_world.rs b/crates/bevy_render/src/sync_world.rs index b3948c5c513ca..15fb582fb7682 100644 --- a/crates/bevy_render/src/sync_world.rs +++ b/crates/bevy_render/src/sync_world.rs @@ -3,7 +3,7 @@ use bevy_derive::{Deref, DerefMut}; use bevy_ecs::entity::EntityHash; use bevy_ecs::{ component::Component, - entity::Entity, + entity::{Entity, EntityBorrow, TrustedEntityBorrow}, observer::Trigger, query::With, reflect::ReflectComponent, @@ -140,6 +140,15 @@ impl From for RenderEntity { } } +impl EntityBorrow for RenderEntity { + fn entity(&self) -> Entity { + self.id() + } +} + +// SAFETY: RenderEntity is a newtype around Entity that derives its comparison traits. +unsafe impl TrustedEntityBorrow for RenderEntity {} + /// Component added on the render world entities to keep track of the corresponding main world entity. /// /// Can also be used as a newtype wrapper for main world entities. @@ -158,6 +167,15 @@ impl From for MainEntity { } } +impl EntityBorrow for MainEntity { + fn entity(&self) -> Entity { + self.id() + } +} + +// SAFETY: RenderEntity is a newtype around Entity that derives its comparison traits. +unsafe impl TrustedEntityBorrow for MainEntity {} + /// A [`HashMap`](hashbrown::HashMap) pre-configured to use [`EntityHash`] hashing with a [`MainEntity`]. pub type MainEntityHashMap = hashbrown::HashMap; diff --git a/crates/bevy_ui/src/focus.rs b/crates/bevy_ui/src/focus.rs index cc1d68526a648..5bfdfc31a6ea2 100644 --- a/crates/bevy_ui/src/focus.rs +++ b/crates/bevy_ui/src/focus.rs @@ -3,7 +3,7 @@ use crate::{ }; use bevy_ecs::{ change_detection::DetectChangesMut, - entity::Entity, + entity::{Entity, EntityBorrow}, prelude::{Component, With}, query::QueryData, reflect::ReflectComponent, diff --git a/crates/bevy_ui/src/layout/mod.rs b/crates/bevy_ui/src/layout/mod.rs index 17107d3679b88..af3994a766d9c 100644 --- a/crates/bevy_ui/src/layout/mod.rs +++ b/crates/bevy_ui/src/layout/mod.rs @@ -5,7 +5,7 @@ use crate::{ }; use bevy_ecs::{ change_detection::{DetectChanges, DetectChangesMut}, - entity::{Entity, EntityHashMap, EntityHashSet}, + entity::{Entity, EntityBorrow, EntityHashMap, EntityHashSet}, event::EventReader, query::With, removal_detection::RemovedComponents, diff --git a/crates/bevy_window/src/window.rs b/crates/bevy_window/src/window.rs index 97e4cf3c3ea19..49bd325e600d5 100644 --- a/crates/bevy_window/src/window.rs +++ b/crates/bevy_window/src/window.rs @@ -1,7 +1,7 @@ use core::num::NonZero; use bevy_ecs::{ - entity::{Entity, VisitEntities, VisitEntitiesMut}, + entity::{Entity, EntityBorrow, VisitEntities, VisitEntitiesMut}, prelude::{Component, ReflectComponent}, }; use bevy_math::{CompassOctant, DVec2, IVec2, UVec2, Vec2}; @@ -88,9 +88,8 @@ impl VisitEntitiesMut for WindowRef { )] pub struct NormalizedWindowRef(Entity); -impl NormalizedWindowRef { - /// Fetch the entity of this window reference - pub fn entity(&self) -> Entity { +impl EntityBorrow for NormalizedWindowRef { + fn entity(&self) -> Entity { self.0 } } From bacc693fec6dc7966e1376d1170b0c9d4eccc014 Mon Sep 17 00:00:00 2001 From: Trangar Date: Tue, 24 Dec 2024 03:51:13 +0100 Subject: [PATCH 014/272] Implement FromStr for Val (#16926) # Objective This PR implements `FromStr` for `Val`, so developers can parse values like `10px` and `50%` ## Testing Added tests for this. I think they cover pretty much everything, and it's a fairly simple unit test. ## Limitations Currently the following float values are not parsed: - `inf`, `-inf`, `+infinity`, `NaN` - `2.5E10`, `2.5e10`, `2.5E-10` For my use case this is perfectly fine but other developers might want to support these values --- crates/bevy_ui/src/geometry.rs | 128 +++++++++++++++++++++++++++++++++ 1 file changed, 128 insertions(+) diff --git a/crates/bevy_ui/src/geometry.rs b/crates/bevy_ui/src/geometry.rs index 63adc5015f04d..3acf78d2f4516 100644 --- a/crates/bevy_ui/src/geometry.rs +++ b/crates/bevy_ui/src/geometry.rs @@ -10,6 +10,16 @@ use bevy_reflect::{ReflectDeserialize, ReflectSerialize}; /// /// This enum allows specifying values for various [`Node`](crate::Node) properties in different units, /// such as logical pixels, percentages, or automatically determined values. +/// +/// `Val` also implements [`core::str::FromStr`] to allow parsing values from strings in the format `#.#px`. Whitespaces between the value and unit is allowed. The following units are supported: +/// * `px`: logical pixels +/// * `%`: percentage +/// * `vw`: percentage of the viewport width +/// * `vh`: percentage of the viewport height +/// * `vmin`: percentage of the viewport's smaller dimension +/// * `vmax`: percentage of the viewport's larger dimension +/// +/// Additionally, `auto` will be parsed as [`Val::Auto`]. #[derive(Copy, Clone, Debug, Reflect)] #[reflect(Default, PartialEq, Debug)] #[cfg_attr( @@ -45,6 +55,70 @@ pub enum Val { VMax(f32), } +#[derive(Debug, Error, PartialEq, Eq)] +pub enum ValParseError { + UnitMissing, + ValueMissing, + InvalidValue, + InvalidUnit, +} + +impl core::fmt::Display for ValParseError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + ValParseError::UnitMissing => write!(f, "unit missing"), + ValParseError::ValueMissing => write!(f, "value missing"), + ValParseError::InvalidValue => write!(f, "invalid value"), + ValParseError::InvalidUnit => write!(f, "invalid unit"), + } + } +} + +impl core::str::FromStr for Val { + type Err = ValParseError; + + fn from_str(s: &str) -> Result { + let s = s.trim(); + + if s.eq_ignore_ascii_case("auto") { + return Ok(Val::Auto); + } + + let Some(end_of_number) = s + .bytes() + .position(|c| !(c.is_ascii_digit() || c == b'.' || c == b'-' || c == b'+')) + else { + return Err(ValParseError::UnitMissing); + }; + + if end_of_number == 0 { + return Err(ValParseError::ValueMissing); + } + + let (value, unit) = s.split_at(end_of_number); + + let value: f32 = value.parse().map_err(|_| ValParseError::InvalidValue)?; + + let unit = unit.trim(); + + if unit.eq_ignore_ascii_case("px") { + Ok(Val::Px(value)) + } else if unit.eq_ignore_ascii_case("%") { + Ok(Val::Percent(value)) + } else if unit.eq_ignore_ascii_case("vw") { + Ok(Val::Vw(value)) + } else if unit.eq_ignore_ascii_case("vh") { + Ok(Val::Vh(value)) + } else if unit.eq_ignore_ascii_case("vmin") { + Ok(Val::VMin(value)) + } else if unit.eq_ignore_ascii_case("vmax") { + Ok(Val::VMax(value)) + } else { + Err(ValParseError::InvalidUnit) + } + } +} + impl PartialEq for Val { fn eq(&self, other: &Self) -> bool { let same_unit = matches!( @@ -689,6 +763,60 @@ mod tests { ); } + #[test] + fn val_str_parse() { + assert_eq!("auto".parse::(), Ok(Val::Auto)); + assert_eq!("Auto".parse::(), Ok(Val::Auto)); + assert_eq!("AUTO".parse::(), Ok(Val::Auto)); + + assert_eq!("3px".parse::(), Ok(Val::Px(3.))); + assert_eq!("3 px".parse::(), Ok(Val::Px(3.))); + assert_eq!("3.5px".parse::(), Ok(Val::Px(3.5))); + assert_eq!("-3px".parse::(), Ok(Val::Px(-3.))); + assert_eq!("3.5 PX".parse::(), Ok(Val::Px(3.5))); + + assert_eq!("3%".parse::(), Ok(Val::Percent(3.))); + assert_eq!("3 %".parse::(), Ok(Val::Percent(3.))); + assert_eq!("3.5%".parse::(), Ok(Val::Percent(3.5))); + assert_eq!("-3%".parse::(), Ok(Val::Percent(-3.))); + + assert_eq!("3vw".parse::(), Ok(Val::Vw(3.))); + assert_eq!("3 vw".parse::(), Ok(Val::Vw(3.))); + assert_eq!("3.5vw".parse::(), Ok(Val::Vw(3.5))); + assert_eq!("-3vw".parse::(), Ok(Val::Vw(-3.))); + assert_eq!("3.5 VW".parse::(), Ok(Val::Vw(3.5))); + + assert_eq!("3vh".parse::(), Ok(Val::Vh(3.))); + assert_eq!("3 vh".parse::(), Ok(Val::Vh(3.))); + assert_eq!("3.5vh".parse::(), Ok(Val::Vh(3.5))); + assert_eq!("-3vh".parse::(), Ok(Val::Vh(-3.))); + assert_eq!("3.5 VH".parse::(), Ok(Val::Vh(3.5))); + + assert_eq!("3vmin".parse::(), Ok(Val::VMin(3.))); + assert_eq!("3 vmin".parse::(), Ok(Val::VMin(3.))); + assert_eq!("3.5vmin".parse::(), Ok(Val::VMin(3.5))); + assert_eq!("-3vmin".parse::(), Ok(Val::VMin(-3.))); + assert_eq!("3.5 VMIN".parse::(), Ok(Val::VMin(3.5))); + + assert_eq!("3vmax".parse::(), Ok(Val::VMax(3.))); + assert_eq!("3 vmax".parse::(), Ok(Val::VMax(3.))); + assert_eq!("3.5vmax".parse::(), Ok(Val::VMax(3.5))); + assert_eq!("-3vmax".parse::(), Ok(Val::VMax(-3.))); + assert_eq!("3.5 VMAX".parse::(), Ok(Val::VMax(3.5))); + + assert_eq!("".parse::(), Err(ValParseError::UnitMissing)); + assert_eq!( + "hello world".parse::(), + Err(ValParseError::ValueMissing) + ); + assert_eq!("3".parse::(), Err(ValParseError::UnitMissing)); + assert_eq!("3.5".parse::(), Err(ValParseError::UnitMissing)); + assert_eq!("3pxx".parse::(), Err(ValParseError::InvalidUnit)); + assert_eq!("3.5pxx".parse::(), Err(ValParseError::InvalidUnit)); + assert_eq!("3-3px".parse::(), Err(ValParseError::InvalidValue)); + assert_eq!("3.5-3px".parse::(), Err(ValParseError::InvalidValue)); + } + #[test] fn default_val_equals_const_default_val() { assert_eq!(Val::default(), Val::DEFAULT); From ee9bea1ba937992a61e181ce1b451cb4ab1d2b2b Mon Sep 17 00:00:00 2001 From: Matty Weatherley Date: Mon, 23 Dec 2024 21:53:43 -0500 Subject: [PATCH 015/272] Use `variadics_please` to implement `StableInterpolate` on tuples. (#16931) # Objective Now that `variadics_please` has a 1.1 release, we can re-implement the original solution. ## Solution Copy-paste the code from the [original PR](https://github.com/bevyengine/bevy/pull/15931) branch :) --- crates/bevy_math/Cargo.toml | 1 + crates/bevy_math/src/common_traits.rs | 95 +++------------------------ 2 files changed, 10 insertions(+), 86 deletions(-) diff --git a/crates/bevy_math/Cargo.toml b/crates/bevy_math/Cargo.toml index 2e92ea89a857e..f635d2a1c1533 100644 --- a/crates/bevy_math/Cargo.toml +++ b/crates/bevy_math/Cargo.toml @@ -28,6 +28,7 @@ smallvec = { version = "1.11" } bevy_reflect = { path = "../bevy_reflect", version = "0.15.0-dev", features = [ "glam", ], optional = true } +variadics_please = "1.1" [dev-dependencies] approx = "0.5" diff --git a/crates/bevy_math/src/common_traits.rs b/crates/bevy_math/src/common_traits.rs index 90bc77629e776..a9a8ef910a86e 100644 --- a/crates/bevy_math/src/common_traits.rs +++ b/crates/bevy_math/src/common_traits.rs @@ -5,6 +5,7 @@ use core::{ fmt::Debug, ops::{Add, Div, Mul, Neg, Sub}, }; +use variadics_please::all_tuples_enumerated; /// A type that supports the mathematical operations of a real vector space, irrespective of dimension. /// In particular, this means that the implementing type supports: @@ -393,31 +394,9 @@ impl StableInterpolate for Dir3A { } } -// If you're confused about how #[doc(fake_variadic)] works, -// then the `all_tuples` macro is nicely documented (it can be found in the `bevy_utils` crate). -// tl;dr: `#[doc(fake_variadic)]` goes on the impl of tuple length one. -// the others have to be hidden using `#[doc(hidden)]`. macro_rules! impl_stable_interpolate_tuple { - (($T:ident, $n:tt)) => { - impl_stable_interpolate_tuple! { - @impl - #[cfg_attr(any(docsrs, docsrs_dep), doc(fake_variadic))] - #[cfg_attr( - any(docsrs, docsrs_dep), - doc = "This trait is implemented for tuples up to 11 items long." - )] - ($T, $n) - } - }; - ($(($T:ident, $n:tt)),*) => { - impl_stable_interpolate_tuple! { - @impl - #[cfg_attr(any(docsrs, docsrs_dep), doc(hidden))] - $(($T, $n)),* - } - }; - (@impl $(#[$($meta:meta)*])* $(($T:ident, $n:tt)),*) => { - $(#[$($meta)*])* + ($(#[$meta:meta])* $(($n:tt, $T:ident)),*) => { + $(#[$meta])* impl<$($T: StableInterpolate),*> StableInterpolate for ($($T,)*) { fn interpolate_stable(&self, other: &Self, t: f32) -> Self { ( @@ -430,68 +409,12 @@ macro_rules! impl_stable_interpolate_tuple { }; } -// (See `macro_metavar_expr`, which might make this better.) -// This currently implements `StableInterpolate` for tuples of up to 11 elements. -impl_stable_interpolate_tuple!((T, 0)); -impl_stable_interpolate_tuple!((T0, 0), (T1, 1)); -impl_stable_interpolate_tuple!((T0, 0), (T1, 1), (T2, 2)); -impl_stable_interpolate_tuple!((T0, 0), (T1, 1), (T2, 2), (T3, 3)); -impl_stable_interpolate_tuple!((T0, 0), (T1, 1), (T2, 2), (T3, 3), (T4, 4)); -impl_stable_interpolate_tuple!((T0, 0), (T1, 1), (T2, 2), (T3, 3), (T4, 4), (T5, 5)); -impl_stable_interpolate_tuple!( - (T0, 0), - (T1, 1), - (T2, 2), - (T3, 3), - (T4, 4), - (T5, 5), - (T6, 6) -); -impl_stable_interpolate_tuple!( - (T0, 0), - (T1, 1), - (T2, 2), - (T3, 3), - (T4, 4), - (T5, 5), - (T6, 6), - (T7, 7) -); -impl_stable_interpolate_tuple!( - (T0, 0), - (T1, 1), - (T2, 2), - (T3, 3), - (T4, 4), - (T5, 5), - (T6, 6), - (T7, 7), - (T8, 8) -); -impl_stable_interpolate_tuple!( - (T0, 0), - (T1, 1), - (T2, 2), - (T3, 3), - (T4, 4), - (T5, 5), - (T6, 6), - (T7, 7), - (T8, 8), - (T9, 9) -); -impl_stable_interpolate_tuple!( - (T0, 0), - (T1, 1), - (T2, 2), - (T3, 3), - (T4, 4), - (T5, 5), - (T6, 6), - (T7, 7), - (T8, 8), - (T9, 9), - (T10, 10) +all_tuples_enumerated!( + #[doc(fake_variadic)] + impl_stable_interpolate_tuple, + 1, + 11, + T ); /// A type that has tangents. From dccd770a231080a18c15349cad1739c42385dbde Mon Sep 17 00:00:00 2001 From: Marius Metzger Date: Tue, 24 Dec 2024 04:01:22 +0100 Subject: [PATCH 016/272] (fix) SSRPlugin: Don't reference default deferred lighting pass if it doesn't exist (#16932) Fixes a crash when using deferred rendering but disabling the default deferred lighting plugin. # The Issue The `ScreenSpaceReflectionsPlugin` references `NodePbr::DeferredLightingPass`, which hasn't been added when `PbrPlugin::add_default_deferred_lighting_plugin` is `false`. This yields the following crash: ``` thread 'main' panicked at /Users/marius/Documents/dev/bevy/crates/bevy_render/src/render_graph/graph.rs:155:26: InvalidNode(DeferredLightingPass) stack backtrace: 0: rust_begin_unwind at /rustc/90b35a6239c3d8bdabc530a6a0816f7ff89a0aaf/library/std/src/panicking.rs:665:5 1: core::panicking::panic_fmt at /rustc/90b35a6239c3d8bdabc530a6a0816f7ff89a0aaf/library/core/src/panicking.rs:74:14 2: bevy_render::render_graph::graph::RenderGraph::add_node_edges at /Users/marius/Documents/dev/bevy/crates/bevy_render/src/render_graph/graph.rs:155:26 3: ::add_render_graph_edges at /Users/marius/Documents/dev/bevy/crates/bevy_render/src/render_graph/app.rs:66:13 4: ::finish at /Users/marius/Documents/dev/bevy/crates/bevy_pbr/src/ssr/mod.rs:234:9 5: bevy_app::app::App::finish at /Users/marius/Documents/dev/bevy/crates/bevy_app/src/app.rs:255:13 6: bevy_winit::state::winit_runner at /Users/marius/Documents/dev/bevy/crates/bevy_winit/src/state.rs:859:9 7: core::ops::function::FnOnce::call_once at /Users/marius/.rustup/toolchains/stable-aarch64-apple-darwin/lib/rustlib/src/rust/library/core/src/ops/function.rs:250:5 8: core::ops::function::FnOnce::call_once{{vtable.shim}} at /Users/marius/.rustup/toolchains/stable-aarch64-apple-darwin/lib/rustlib/src/rust/library/core/src/ops/function.rs:250:5 9: as core::ops::function::FnOnce>::call_once at /Users/marius/.rustup/toolchains/stable-aarch64-apple-darwin/lib/rustlib/src/rust/library/alloc/src/boxed.rs:2454:9 10: bevy_app::app::App::run at /Users/marius/Documents/dev/bevy/crates/bevy_app/src/app.rs:184:9 11: bevy_deferred_test::main at ./src/main.rs:9:5 12: core::ops::function::FnOnce::call_once at /Users/marius/.rustup/toolchains/stable-aarch64-apple-darwin/lib/rustlib/src/rust/library/core/src/ops/function.rs:250:5 ``` ### Minimal reproduction example: ```rust use bevy::core_pipeline::prepass::{DeferredPrepass, DepthPrepass}; use bevy::pbr::{DefaultOpaqueRendererMethod, PbrPlugin, ScreenSpaceReflections}; use bevy::prelude::*; fn main() { App::new() .add_plugins(DefaultPlugins.set(PbrPlugin { add_default_deferred_lighting_plugin: false, ..default() })) .add_systems(Startup, setup) .insert_resource(DefaultOpaqueRendererMethod::deferred()) .run(); } /// set up a camera fn setup( mut commands: Commands ) { // camera commands.spawn(( Camera3d::default(), Transform::from_xyz(-2.5, 4.5, 9.0).looking_at(Vec3::ZERO, Vec3::Y), DepthPrepass, DeferredPrepass, ScreenSpaceReflections::default(), )); } ``` # The Fix When no node under the default lighting node's label exists, this label isn't added to the SSR's graph node edges. It's good to keep the SSRPlugin enabled, this way, users can plug in their own lighting system, which I have successfully done on top of this PR. # Workarounds A current workaround for this issue is to re-use Bevy's `NodePbr::DeferredLightingPass` as the label for your own custom lighting pass node. --- crates/bevy_pbr/src/ssr/mod.rs | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/crates/bevy_pbr/src/ssr/mod.rs b/crates/bevy_pbr/src/ssr/mod.rs index a515a2917ddd0..abf3e32220d8c 100644 --- a/crates/bevy_pbr/src/ssr/mod.rs +++ b/crates/bevy_pbr/src/ssr/mod.rs @@ -25,6 +25,7 @@ use bevy_ecs::{ }; use bevy_image::BevyDefault as _; use bevy_reflect::{std_traits::ReflectDefault, Reflect}; +use bevy_render::render_graph::RenderGraph; use bevy_render::{ extract_component::{ExtractComponent, ExtractComponentPlugin}, render_graph::{NodeRunError, RenderGraphApp, RenderGraphContext, ViewNode, ViewNodeRunner}, @@ -233,8 +234,19 @@ impl Plugin for ScreenSpaceReflectionsPlugin { render_app .init_resource::() - .init_resource::>() - .add_render_graph_edges( + .init_resource::>(); + + // only reference the default deferred lighting pass + // if it has been added + let has_default_deferred_lighting_pass = render_app + .world_mut() + .resource_mut::() + .sub_graph(Core3d) + .get_node_state(NodePbr::DeferredLightingPass) + .is_ok(); + + if has_default_deferred_lighting_pass { + render_app.add_render_graph_edges( Core3d, ( NodePbr::DeferredLightingPass, @@ -242,6 +254,12 @@ impl Plugin for ScreenSpaceReflectionsPlugin { Node3d::MainOpaquePass, ), ); + } else { + render_app.add_render_graph_edges( + Core3d, + (NodePbr::ScreenSpaceReflections, Node3d::MainOpaquePass), + ); + } } } From 3f38424d4309173f84317495ea4a554faeff7567 Mon Sep 17 00:00:00 2001 From: Marius Metzger Date: Tue, 24 Dec 2024 04:02:14 +0100 Subject: [PATCH 017/272] Expose Tonemapping LUT binding indices (#16934) This PR simply exposes Bevy PBR's `TONEMAPPING_LUT_TEXTURE_BINDING_INDEX` and `TONEMAPPING_LUT_SAMPLER_BINDING_INDEX`. # Objective Alongside #16932, this is the last required change to be able to replace Bevy's built-in deferred lighting pass with a custom one based on the original logic. --- crates/bevy_pbr/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/bevy_pbr/src/lib.rs b/crates/bevy_pbr/src/lib.rs index f219518dfcce2..6559268bb5c5e 100644 --- a/crates/bevy_pbr/src/lib.rs +++ b/crates/bevy_pbr/src/lib.rs @@ -163,8 +163,8 @@ pub const RGB9E5_FUNCTIONS_HANDLE: Handle = Handle::weak_from_u128(26590 const MESHLET_VISIBILITY_BUFFER_RESOLVE_SHADER_HANDLE: Handle = Handle::weak_from_u128(2325134235233421); -const TONEMAPPING_LUT_TEXTURE_BINDING_INDEX: u32 = 23; -const TONEMAPPING_LUT_SAMPLER_BINDING_INDEX: u32 = 24; +pub const TONEMAPPING_LUT_TEXTURE_BINDING_INDEX: u32 = 23; +pub const TONEMAPPING_LUT_SAMPLER_BINDING_INDEX: u32 = 24; /// Sets up the entire PBR infrastructure of bevy. pub struct PbrPlugin { From 1669ca676a28d9e10e892cf0f643b8a5e2de29c5 Mon Sep 17 00:00:00 2001 From: JaySpruce Date: Mon, 23 Dec 2024 21:07:28 -0600 Subject: [PATCH 018/272] Remove vestigial helper functions for `Commands` and `EntityCommands` (#16936) ## Objective I believe these started as structs, back when that was how commands had to be implemented. Now they just hide implementation details. ## Solution Remove the helper functions and move each implementation into its respective method, except for the ones that actually reduce code duplication. --- crates/bevy_ecs/src/system/commands/mod.rs | 502 +++++++-------------- 1 file changed, 170 insertions(+), 332 deletions(-) diff --git a/crates/bevy_ecs/src/system/commands/mod.rs b/crates/bevy_ecs/src/system/commands/mod.rs index 4fb880592ab80..9086ece263824 100644 --- a/crates/bevy_ecs/src/system/commands/mod.rs +++ b/crates/bevy_ecs/src/system/commands/mod.rs @@ -550,7 +550,16 @@ impl<'w, 's> Commands<'w, 's> { I: IntoIterator + Send + Sync + 'static, I::Item: Bundle, { - self.queue(spawn_batch(bundles_iter)); + #[cfg(feature = "track_change_detection")] + let caller = Location::caller(); + self.queue(move |world: &mut World| { + SpawnBatchIter::new( + world, + bundles_iter.into_iter(), + #[cfg(feature = "track_change_detection")] + caller, + ); + }); } /// Pushes a generic [`Command`] to the command queue. @@ -627,7 +636,21 @@ impl<'w, 's> Commands<'w, 's> { I: IntoIterator + Send + Sync + 'static, B: Bundle, { - self.queue(insert_or_spawn_batch(bundles_iter)); + #[cfg(feature = "track_change_detection")] + let caller = Location::caller(); + self.queue(move |world: &mut World| { + if let Err(invalid_entities) = world.insert_or_spawn_batch_with_caller( + bundles_iter, + #[cfg(feature = "track_change_detection")] + caller, + ) { + error!( + "Failed to 'insert or spawn' bundle of type {} into the following invalid entities: {:?}", + core::any::type_name::(), + invalid_entities + ); + } + }); } /// Pushes a [`Command`] to the queue for adding a [`Bundle`] type to a batch of [`Entities`](Entity). @@ -654,7 +677,7 @@ impl<'w, 's> Commands<'w, 's> { I: IntoIterator + Send + Sync + 'static, B: Bundle, { - self.queue(insert_batch(batch)); + self.queue(insert_batch(batch, InsertMode::Replace)); } /// Pushes a [`Command`] to the queue for adding a [`Bundle`] type to a batch of [`Entities`](Entity). @@ -681,7 +704,7 @@ impl<'w, 's> Commands<'w, 's> { I: IntoIterator + Send + Sync + 'static, B: Bundle, { - self.queue(insert_batch_if_new(batch)); + self.queue(insert_batch(batch, InsertMode::Keep)); } /// Pushes a [`Command`] to the queue for adding a [`Bundle`] type to a batch of [`Entities`](Entity). @@ -706,7 +729,7 @@ impl<'w, 's> Commands<'w, 's> { I: IntoIterator + Send + Sync + 'static, B: Bundle, { - self.queue(try_insert_batch(batch)); + self.queue(try_insert_batch(batch, InsertMode::Replace)); } /// Pushes a [`Command`] to the queue for adding a [`Bundle`] type to a batch of [`Entities`](Entity). @@ -731,7 +754,7 @@ impl<'w, 's> Commands<'w, 's> { I: IntoIterator + Send + Sync + 'static, B: Bundle, { - self.queue(try_insert_batch_if_new(batch)); + self.queue(try_insert_batch(batch, InsertMode::Keep)); } /// Pushes a [`Command`] to the queue for inserting a [`Resource`] in the [`World`] with an inferred value. @@ -760,7 +783,9 @@ impl<'w, 's> Commands<'w, 's> { /// ``` #[track_caller] pub fn init_resource(&mut self) { - self.queue(init_resource::); + self.queue(move |world: &mut World| { + world.init_resource::(); + }); } /// Pushes a [`Command`] to the queue for inserting a [`Resource`] in the [`World`] with a specific value. @@ -790,7 +815,15 @@ impl<'w, 's> Commands<'w, 's> { /// ``` #[track_caller] pub fn insert_resource(&mut self, resource: R) { - self.queue(insert_resource(resource)); + #[cfg(feature = "track_change_detection")] + let caller = Location::caller(); + self.queue(move |world: &mut World| { + world.insert_resource_with_caller( + resource, + #[cfg(feature = "track_change_detection")] + caller, + ); + }); } /// Pushes a [`Command`] to the queue for removing a [`Resource`] from the [`World`]. @@ -814,7 +847,9 @@ impl<'w, 's> Commands<'w, 's> { /// # bevy_ecs::system::assert_is_system(system); /// ``` pub fn remove_resource(&mut self) { - self.queue(remove_resource::); + self.queue(move |world: &mut World| { + world.remove_resource::(); + }); } /// Runs the system corresponding to the given [`SystemId`]. @@ -1289,7 +1324,7 @@ impl<'a> EntityCommands<'a> { F: FnOnce() -> bool, { if condition() { - self.queue(insert(bundle, InsertMode::Replace)) + self.insert(bundle) } else { self } @@ -1309,6 +1344,7 @@ impl<'a> EntityCommands<'a> { /// The command will panic when applied if the associated entity does not exist. /// /// To avoid a panic in this case, use the command [`Self::try_insert_if_new`] instead. + #[track_caller] pub fn insert_if_new(&mut self, bundle: impl Bundle) -> &mut Self { self.queue(insert(bundle, InsertMode::Keep)) } @@ -1327,6 +1363,7 @@ impl<'a> EntityCommands<'a> { /// /// To avoid a panic in this case, use the command [`Self::try_insert_if_new`] /// instead. + #[track_caller] pub fn insert_if_new_and(&mut self, bundle: impl Bundle, condition: F) -> &mut Self where F: FnOnce() -> bool, @@ -1359,10 +1396,18 @@ impl<'a> EntityCommands<'a> { value: T, ) -> &mut Self { let caller = Location::caller(); - // SAFETY: same invariants as parent call - self.queue(unsafe {insert_by_id(component_id, value, move |world, entity| { - panic!("error[B0003]: {caller}: Could not insert a component {component_id:?} (with type {}) for entity {entity:?}, which {}. See: https://bevyengine.org/learn/errors/b0003", core::any::type_name::(), world.entities().entity_does_not_exist_error_details_message(entity)); - })}) + self.queue(move |entity: Entity, world: &mut World| { + if let Ok(mut entity) = world.get_entity_mut(entity) { + // SAFETY: + // - `component_id` safety is ensured by the caller + // - `ptr` is valid within the `make` block + OwningPtr::make(value, |ptr| unsafe { + entity.insert_by_id(component_id, ptr); + }); + } else { + panic!("error[B0003]: {caller}: Could not insert a component {component_id:?} (with type {}) for entity {entity:?}, which {}. See: https://bevyengine.org/learn/errors/b0003", core::any::type_name::(), world.entities().entity_does_not_exist_error_details_message(entity)); + } + }) } /// Attempts to add a dynamic component to an entity. @@ -1373,13 +1418,22 @@ impl<'a> EntityCommands<'a> { /// /// - [`ComponentId`] must be from the same world as `self`. /// - `T` must have the same layout as the one passed during `component_id` creation. + #[track_caller] pub unsafe fn try_insert_by_id( &mut self, component_id: ComponentId, value: T, ) -> &mut Self { - // SAFETY: same invariants as parent call - self.queue(unsafe { insert_by_id(component_id, value, |_, _| {}) }) + self.queue(move |entity: Entity, world: &mut World| { + if let Ok(mut entity) = world.get_entity_mut(entity) { + // SAFETY: + // - `component_id` safety is ensured by the caller + // - `ptr` is valid within the `make` block + OwningPtr::make(value, |ptr| unsafe { + entity.insert_by_id(component_id, ptr); + }); + } + }) } /// Tries to add a [`Bundle`] of components to the entity. @@ -1467,7 +1521,7 @@ impl<'a> EntityCommands<'a> { F: FnOnce() -> bool, { if condition() { - self.queue(try_insert(bundle, InsertMode::Replace)) + self.try_insert(bundle) } else { self } @@ -1508,6 +1562,7 @@ impl<'a> EntityCommands<'a> { /// } /// # bevy_ecs::system::assert_is_system(add_health_system); /// ``` + #[track_caller] pub fn try_insert_if_new_and(&mut self, bundle: impl Bundle, condition: F) -> &mut Self where F: FnOnce() -> bool, @@ -1528,6 +1583,7 @@ impl<'a> EntityCommands<'a> { /// # Note /// /// Unlike [`Self::insert_if_new`], this will not panic if the associated entity does not exist. + #[track_caller] pub fn try_insert_if_new(&mut self, bundle: impl Bundle) -> &mut Self { self.queue(try_insert(bundle, InsertMode::Keep)) } @@ -1571,7 +1627,11 @@ impl<'a> EntityCommands<'a> { where T: Bundle, { - self.queue(remove::) + self.queue(move |entity: Entity, world: &mut World| { + if let Ok(mut entity) = world.get_entity_mut(entity) { + entity.remove::(); + } + }) } /// Removes all components in the [`Bundle`] components and remove all required components for each component in the [`Bundle`] from entity. @@ -1599,17 +1659,33 @@ impl<'a> EntityCommands<'a> { /// # bevy_ecs::system::assert_is_system(remove_with_requires_system); /// ``` pub fn remove_with_requires(&mut self) -> &mut Self { - self.queue(remove_with_requires::) + self.queue(move |entity: Entity, world: &mut World| { + if let Ok(mut entity) = world.get_entity_mut(entity) { + entity.remove_with_requires::(); + } + }) } - /// Removes a component from the entity. + /// Removes a dynamic [`Component`] from the entity if it exists. + /// + /// # Panics + /// + /// Panics if the provided [`ComponentId`] does not exist in the [`World`]. pub fn remove_by_id(&mut self, component_id: ComponentId) -> &mut Self { - self.queue(remove_by_id(component_id)) + self.queue(move |entity: Entity, world: &mut World| { + if let Ok(mut entity) = world.get_entity_mut(entity) { + entity.remove_by_id(component_id); + } + }) } /// Removes all components associated with the entity. pub fn clear(&mut self) -> &mut Self { - self.queue(clear()) + self.queue(move |entity: Entity, world: &mut World| { + if let Ok(mut entity) = world.get_entity_mut(entity) { + entity.clear(); + } + }) } /// Despawns the entity. @@ -1641,7 +1717,7 @@ impl<'a> EntityCommands<'a> { /// ``` #[track_caller] pub fn despawn(&mut self) { - self.queue(despawn()); + self.queue(despawn(true)); } /// Despawns the entity. @@ -1649,7 +1725,7 @@ impl<'a> EntityCommands<'a> { /// the same function as [`Self::despawn`] without emitting warnings. #[track_caller] pub fn try_despawn(&mut self) { - self.queue(try_despawn()); + self.queue(despawn(false)); } /// Pushes an [`EntityCommand`] to the queue, which will get executed for the current [`Entity`]. @@ -1714,7 +1790,11 @@ impl<'a> EntityCommands<'a> { where T: Bundle, { - self.queue(retain::) + self.queue(move |entity: Entity, world: &mut World| { + if let Ok(mut entity) = world.get_entity_mut(entity) { + entity.retain::(); + } + }) } /// Logs the components of the entity at the info level. @@ -1723,7 +1803,13 @@ impl<'a> EntityCommands<'a> { /// /// The command will panic when applied if the associated entity does not exist. pub fn log_components(&mut self) -> &mut Self { - self.queue(log_components) + self.queue(move |entity: Entity, world: &mut World| { + let debug_infos: Vec<_> = world + .inspect_entity(entity) + .map(ComponentInfo::name) + .collect(); + info!("Entity {entity}: {debug_infos:?}"); + }) } /// Returns the underlying [`Commands`]. @@ -1748,9 +1834,13 @@ impl<'a> EntityCommands<'a> { /// Creates an [`Observer`] listening for a trigger of type `T` that targets this entity. pub fn observe( &mut self, - system: impl IntoObserverSystem, + observer: impl IntoObserverSystem, ) -> &mut Self { - self.queue(observe(system)) + self.queue(move |entity: Entity, world: &mut World| { + if let Ok(mut entity) = world.get_entity_mut(entity) { + entity.observe(observer); + } + }) } /// Clones parts of an entity (components, observers, etc.) onto another entity, @@ -1759,6 +1849,12 @@ impl<'a> EntityCommands<'a> { /// By default, the other entity will receive all the components of the original that implement /// [`Clone`] or [`Reflect`](bevy_reflect::Reflect). /// + /// # Panics + /// + /// The command will panic when applied if the target entity does not exist. + /// + /// # Example + /// /// Configure through [`EntityCloneBuilder`] as follows: /// ``` /// # use bevy_ecs::prelude::*; @@ -1787,16 +1883,16 @@ impl<'a> EntityCommands<'a> { /// - [`EntityCloneBuilder`] /// - [`CloneEntityWithObserversExt`](crate::observer::CloneEntityWithObserversExt) /// - `CloneEntityHierarchyExt` - /// - /// # Panics - /// - /// The command will panic when applied if either of the entities do not exist. pub fn clone_with( &mut self, target: Entity, config: impl FnOnce(&mut EntityCloneBuilder) + Send + Sync + 'static, ) -> &mut Self { - self.queue(clone_with(target, config)) + self.queue(move |entity: Entity, world: &mut World| { + if let Ok(mut entity) = world.get_entity_mut(entity) { + entity.clone_with(target, config); + } + }) } /// Spawns a clone of this entity and returns the [`EntityCommands`] of the clone. @@ -1807,9 +1903,10 @@ impl<'a> EntityCommands<'a> { /// To configure cloning behavior (such as only cloning certain components), /// use [`EntityCommands::clone_and_spawn_with`]. /// - /// # Panics + /// # Note /// - /// The command will panic when applied if the original entity does not exist. + /// If the original entity does not exist when this command is applied, + /// the returned entity will have no components. /// /// # Example /// @@ -1845,9 +1942,10 @@ impl<'a> EntityCommands<'a> { /// /// See the methods on [`EntityCloneBuilder`] for more options. /// - /// # Panics + /// # Note /// - /// The command will panic when applied if the original entity does not exist. + /// If the original entity does not exist when this command is applied, + /// the returned entity will have no components. /// /// # Example /// @@ -1874,7 +1972,7 @@ impl<'a> EntityCommands<'a> { config: impl FnOnce(&mut EntityCloneBuilder) + Send + Sync + 'static, ) -> EntityCommands<'_> { let entity_clone = self.commands().spawn_empty().id(); - self.queue(clone_with(entity_clone, config)); + self.clone_with(entity_clone, config); EntityCommands { commands: self.commands_mut().reborrow(), entity: entity_clone, @@ -1888,9 +1986,13 @@ impl<'a> EntityCommands<'a> { /// /// # Panics /// - /// The command will panic when applied if either of the entities do not exist. + /// The command will panic when applied if the target entity does not exist. pub fn clone_components(&mut self, target: Entity) -> &mut Self { - self.queue(clone_components::(target)) + self.queue(move |entity: Entity, world: &mut World| { + if let Ok(mut entity) = world.get_entity_mut(entity) { + entity.clone_components::(target); + } + }) } /// Clones the specified components of this entity and inserts them into another entity, @@ -1901,9 +2003,13 @@ impl<'a> EntityCommands<'a> { /// /// # Panics /// - /// The command will panic when applied if either of the entities do not exist. + /// The command will panic when applied if the target entity does not exist. pub fn move_components(&mut self, target: Entity) -> &mut Self { - self.queue(move_components::(target)) + self.queue(move |entity: Entity, world: &mut World| { + if let Ok(mut entity) = world.get_entity_mut(entity) { + entity.move_components::(target); + } + }) } } @@ -1937,8 +2043,7 @@ impl<'a, T: Component> EntityEntryCommands<'a, T> { /// See [`or_try_insert`](Self::or_try_insert) for a non-panicking version. #[track_caller] pub fn or_insert(&mut self, default: T) -> &mut Self { - self.entity_commands - .queue(insert(default, InsertMode::Keep)); + self.entity_commands.insert_if_new(default); self } @@ -1949,8 +2054,7 @@ impl<'a, T: Component> EntityEntryCommands<'a, T> { /// See also [`or_insert_with`](Self::or_insert_with). #[track_caller] pub fn or_try_insert(&mut self, default: T) -> &mut Self { - self.entity_commands - .queue(try_insert(default, InsertMode::Keep)); + self.entity_commands.try_insert_if_new(default); self } @@ -1989,8 +2093,6 @@ impl<'a, T: Component> EntityEntryCommands<'a, T> { where T: Default, { - #[allow(clippy::unwrap_or_default)] - // FIXME: use `expect` once stable self.or_insert(T::default()) } @@ -2006,8 +2108,20 @@ impl<'a, T: Component> EntityEntryCommands<'a, T> { where T: FromWorld, { - self.entity_commands - .queue(insert_from_world::(InsertMode::Keep)); + let caller = Location::caller(); + self.entity_commands.queue(move |entity: Entity, world: &mut World| { + let value = T::from_world(world); + if let Ok(mut entity) = world.get_entity_mut(entity) { + entity.insert_with_caller( + value, + InsertMode::Keep, + #[cfg(feature = "track_change_detection")] + caller, + ); + } else { + panic!("error[B0003]: {caller}: Could not insert a bundle (of type `{}`) for {entity:?}, which {}. See: https://bevyengine.org/learn/errors/b0003", core::any::type_name::(), world.entities().entity_does_not_exist_error_details_message(entity) ); + } + }); self } } @@ -2039,82 +2153,12 @@ where } } -/// A [`Command`] that consumes an iterator of [`Bundle`]s to spawn a series of entities. -/// -/// This is more efficient than spawning the entities individually. -#[track_caller] -fn spawn_batch(bundles_iter: I) -> impl Command -where - I: IntoIterator + Send + Sync + 'static, - B: Bundle, -{ - #[cfg(feature = "track_change_detection")] - let caller = Location::caller(); - move |world: &mut World| { - SpawnBatchIter::new( - world, - bundles_iter.into_iter(), - #[cfg(feature = "track_change_detection")] - caller, - ); - } -} - -/// A [`Command`] that consumes an iterator to add a series of [`Bundle`]s to a set of entities. -/// If any entities do not already exist in the world, they will be spawned. -/// -/// This is more efficient than inserting the bundles individually. -#[track_caller] -fn insert_or_spawn_batch(bundles_iter: I) -> impl Command -where - I: IntoIterator + Send + Sync + 'static, - B: Bundle, -{ - #[cfg(feature = "track_change_detection")] - let caller = Location::caller(); - move |world: &mut World| { - if let Err(invalid_entities) = world.insert_or_spawn_batch_with_caller( - bundles_iter, - #[cfg(feature = "track_change_detection")] - caller, - ) { - error!( - "Failed to 'insert or spawn' bundle of type {} into the following invalid entities: {:?}", - core::any::type_name::(), - invalid_entities - ); - } - } -} - -/// A [`Command`] that consumes an iterator to add a series of [`Bundles`](Bundle) to a set of entities. -/// If any entities do not exist in the world, this command will panic. -/// -/// This is more efficient than inserting the bundles individually. -#[track_caller] -fn insert_batch(batch: I) -> impl Command -where - I: IntoIterator + Send + Sync + 'static, - B: Bundle, -{ - #[cfg(feature = "track_change_detection")] - let caller = Location::caller(); - move |world: &mut World| { - world.insert_batch_with_caller( - batch, - InsertMode::Replace, - #[cfg(feature = "track_change_detection")] - caller, - ); - } -} - /// A [`Command`] that consumes an iterator to add a series of [`Bundles`](Bundle) to a set of entities. /// If any entities do not exist in the world, this command will panic. /// /// This is more efficient than inserting the bundles individually. #[track_caller] -fn insert_batch_if_new(batch: I) -> impl Command +fn insert_batch(batch: I, mode: InsertMode) -> impl Command where I: IntoIterator + Send + Sync + 'static, B: Bundle, @@ -2124,29 +2168,7 @@ where move |world: &mut World| { world.insert_batch_with_caller( batch, - InsertMode::Keep, - #[cfg(feature = "track_change_detection")] - caller, - ); - } -} - -/// A [`Command`] that consumes an iterator to add a series of [`Bundles`](Bundle) to a set of entities. -/// If any entities do not exist in the world, this command will ignore them. -/// -/// This is more efficient than inserting the bundles individually. -#[track_caller] -fn try_insert_batch(batch: I) -> impl Command -where - I: IntoIterator + Send + Sync + 'static, - B: Bundle, -{ - #[cfg(feature = "track_change_detection")] - let caller = Location::caller(); - move |world: &mut World| { - world.try_insert_batch_with_caller( - batch, - InsertMode::Replace, + mode, #[cfg(feature = "track_change_detection")] caller, ); @@ -2158,7 +2180,7 @@ where /// /// This is more efficient than inserting the bundles individually. #[track_caller] -fn try_insert_batch_if_new(batch: I) -> impl Command +fn try_insert_batch(batch: I, mode: InsertMode) -> impl Command where I: IntoIterator + Send + Sync + 'static, B: Bundle, @@ -2168,7 +2190,7 @@ where move |world: &mut World| { world.try_insert_batch_with_caller( batch, - InsertMode::Keep, + mode, #[cfg(feature = "track_change_detection")] caller, ); @@ -2183,25 +2205,10 @@ where /// This won't clean up external references to the entity (such as parent-child relationships /// if you're using `bevy_hierarchy`), which may leave the world in an invalid state. #[track_caller] -fn despawn() -> impl EntityCommand { +fn despawn(log_warning: bool) -> impl EntityCommand { let caller = Location::caller(); move |entity: Entity, world: &mut World| { - world.despawn_with_caller(entity, caller, true); - } -} - -/// A [`Command`] that despawns a specific entity. -/// This will not emit a warning if the entity does not exist. -/// -/// # Note -/// -/// This won't clean up external references to the entity (such as parent-child relationships -/// if you're using `bevy_hierarchy`), which may leave the world in an invalid state. -#[track_caller] -fn try_despawn() -> impl EntityCommand { - let caller = Location::caller(); - move |entity: Entity, world: &mut World| { - world.despawn_with_caller(entity, caller, false); + world.despawn_with_caller(entity, caller, log_warning); } } @@ -2223,25 +2230,6 @@ fn insert(bundle: T, mode: InsertMode) -> impl EntityCommand { } } -/// An [`EntityCommand`] that adds the component using its `FromWorld` implementation. -#[track_caller] -fn insert_from_world(mode: InsertMode) -> impl EntityCommand { - let caller = Location::caller(); - move |entity: Entity, world: &mut World| { - let value = T::from_world(world); - if let Ok(mut entity) = world.get_entity_mut(entity) { - entity.insert_with_caller( - value, - mode, - #[cfg(feature = "track_change_detection")] - caller, - ); - } else { - panic!("error[B0003]: {caller}: Could not insert a bundle (of type `{}`) for {entity:?}, which {}. See: https://bevyengine.org/learn/errors/b0003", core::any::type_name::(), world.entities().entity_does_not_exist_error_details_message(entity) ); - } - } -} - /// An [`EntityCommand`] that attempts to add the components in a [`Bundle`] to an entity. /// Does nothing if the entity does not exist. #[track_caller] @@ -2260,156 +2248,6 @@ fn try_insert(bundle: impl Bundle, mode: InsertMode) -> impl EntityCommand { } } -/// An [`EntityCommand`] that attempts to add the dynamic component to an entity. -/// -/// # Safety -/// -/// - The returned `EntityCommand` must be queued for the world where `component_id` was created. -/// - `T` must be the type represented by `component_id`. -unsafe fn insert_by_id( - component_id: ComponentId, - value: T, - on_none_entity: impl FnOnce(&mut World, Entity) + Send + 'static, -) -> impl EntityCommand { - move |entity: Entity, world: &mut World| { - if let Ok(mut entity) = world.get_entity_mut(entity) { - // SAFETY: - // - `component_id` safety is ensured by the caller - // - `ptr` is valid within the `make` block; - OwningPtr::make(value, |ptr| unsafe { - entity.insert_by_id(component_id, ptr); - }); - } else { - on_none_entity(world, entity); - } - } -} - -/// An [`EntityCommand`] that removes components from an entity. -/// -/// For a [`Bundle`] type `T`, this will remove any components in the bundle. -/// Any components in the bundle that aren't found on the entity will be ignored. -fn remove(entity: Entity, world: &mut World) { - if let Ok(mut entity) = world.get_entity_mut(entity) { - entity.remove::(); - } -} - -/// An [`EntityCommand`] that removes components with a provided [`ComponentId`] from an entity. -/// # Panics -/// -/// Panics if the provided [`ComponentId`] does not exist in the [`World`]. -fn remove_by_id(component_id: ComponentId) -> impl EntityCommand { - move |entity: Entity, world: &mut World| { - if let Ok(mut entity) = world.get_entity_mut(entity) { - entity.remove_by_id(component_id); - } - } -} - -/// An [`EntityCommand`] that remove all components in the bundle and remove all required components for each component in the bundle. -fn remove_with_requires(entity: Entity, world: &mut World) { - if let Ok(mut entity) = world.get_entity_mut(entity) { - entity.remove_with_requires::(); - } -} - -/// An [`EntityCommand`] that removes all components associated with a provided entity. -fn clear() -> impl EntityCommand { - move |entity: Entity, world: &mut World| { - if let Ok(mut entity) = world.get_entity_mut(entity) { - entity.clear(); - } - } -} - -/// An [`EntityCommand`] that removes components from an entity. -/// -/// For a [`Bundle`] type `T`, this will remove all components except those in the bundle. -/// Any components in the bundle that aren't found on the entity will be ignored. -fn retain(entity: Entity, world: &mut World) { - if let Ok(mut entity_mut) = world.get_entity_mut(entity) { - entity_mut.retain::(); - } -} - -/// A [`Command`] that inserts a [`Resource`] into the world using a value -/// created with the [`FromWorld`] trait. -#[track_caller] -fn init_resource(world: &mut World) { - world.init_resource::(); -} - -/// A [`Command`] that removes the [resource](Resource) `R` from the world. -#[track_caller] -fn remove_resource(world: &mut World) { - world.remove_resource::(); -} - -/// A [`Command`] that inserts a [`Resource`] into the world. -#[track_caller] -fn insert_resource(resource: R) -> impl Command { - #[cfg(feature = "track_change_detection")] - let caller = Location::caller(); - move |world: &mut World| { - world.insert_resource_with_caller( - resource, - #[cfg(feature = "track_change_detection")] - caller, - ); - } -} - -/// [`EntityCommand`] to log the components of a given entity. See [`EntityCommands::log_components`]. -fn log_components(entity: Entity, world: &mut World) { - let debug_infos: Vec<_> = world - .inspect_entity(entity) - .map(ComponentInfo::name) - .collect(); - info!("Entity {entity}: {debug_infos:?}"); -} - -fn observe( - observer: impl IntoObserverSystem, -) -> impl EntityCommand { - move |entity: Entity, world: &mut World| { - if let Ok(mut entity) = world.get_entity_mut(entity) { - entity.observe(observer); - } - } -} - -/// An [`EntityCommand`] that clones an entity with configurable cloning behavior. -fn clone_with( - target: Entity, - config: impl FnOnce(&mut EntityCloneBuilder) + Send + Sync + 'static, -) -> impl EntityCommand { - move |entity: Entity, world: &mut World| { - if let Ok(mut entity) = world.get_entity_mut(entity) { - entity.clone_with(target, config); - } - } -} - -/// An [`EntityCommand`] that clones the specified components into another entity. -fn clone_components(target: Entity) -> impl EntityCommand { - move |entity: Entity, world: &mut World| { - if let Ok(mut entity) = world.get_entity_mut(entity) { - entity.clone_components::(target); - } - } -} - -/// An [`EntityCommand`] that clones the specified components into another entity -/// and removes them from the original entity. -fn move_components(target: Entity) -> impl EntityCommand { - move |entity: Entity, world: &mut World| { - if let Ok(mut entity) = world.get_entity_mut(entity) { - entity.move_components::(target); - } - } -} - #[cfg(test)] #[allow(clippy::float_cmp, clippy::approx_constant)] mod tests { From 35e0b5be0011126bbbb1e1dcc62a497de7dd5385 Mon Sep 17 00:00:00 2001 From: Matty Weatherley Date: Mon, 23 Dec 2024 22:09:36 -0500 Subject: [PATCH 019/272] Make `bevy_reflect` feature of `bevy_math` non-default (#16938) # Objective bevy_reflect is a big part of bevy_math's dependency footprint, and is often not useful when using bevy_math standalone (as I often do). The goal with this PR is to avoid pulling in those dependencies by default without compromising the usability of bevy_math types within Bevy proper. ## Solution `bevy_reflect` has been removed from default features of `bevy_math`. However, the feature is enabled by `bevy_internal`, so that `bevy_reflect` is enabled when `bevy_math` is used through `bevy`. Philosophically, if there were a feature flag toggling reflection on `bevy` globally, then whether `bevy_math` enabled `bevy_reflect` itself would depend on that, but that doesn't exist for the time being. ## Testing It compiles :) ## Migration Guide `bevy_reflect` has been made a non-default feature of `bevy_math`. (It is still enabled when `bevy_math` is used through `bevy`.) You may need to enable this feature if you are using `bevy_math` on its own and desire for the types it exports to implement `Reflect` and other reflection traits. --- crates/bevy_internal/Cargo.toml | 4 +++- crates/bevy_math/Cargo.toml | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/crates/bevy_internal/Cargo.toml b/crates/bevy_internal/Cargo.toml index b00b9f6bb4a16..7450c1c3a64a7 100644 --- a/crates/bevy_internal/Cargo.toml +++ b/crates/bevy_internal/Cargo.toml @@ -271,7 +271,9 @@ bevy_hierarchy = { path = "../bevy_hierarchy", version = "0.15.0-dev" } bevy_input = { path = "../bevy_input", version = "0.15.0-dev" } bevy_input_focus = { path = "../bevy_input_focus", version = "0.15.0-dev" } bevy_log = { path = "../bevy_log", version = "0.15.0-dev" } -bevy_math = { path = "../bevy_math", version = "0.15.0-dev" } +bevy_math = { path = "../bevy_math", version = "0.15.0-dev", features = [ + "bevy_reflect", +] } bevy_ptr = { path = "../bevy_ptr", version = "0.15.0-dev" } bevy_reflect = { path = "../bevy_reflect", version = "0.15.0-dev", features = [ "bevy", diff --git a/crates/bevy_math/Cargo.toml b/crates/bevy_math/Cargo.toml index f635d2a1c1533..c444004521606 100644 --- a/crates/bevy_math/Cargo.toml +++ b/crates/bevy_math/Cargo.toml @@ -42,7 +42,7 @@ bevy_math = { path = ".", version = "0.15.0-dev", default-features = false, feat glam = { version = "0.29", default-features = false, features = ["approx"] } [features] -default = ["std", "rand", "bevy_reflect", "curve"] +default = ["std", "rand", "curve"] std = [ "alloc", "glam/std", From 6577f5d26aaf75fb377668186876b9427742b351 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Mockers?= Date: Tue, 24 Dec 2024 04:11:01 +0100 Subject: [PATCH 020/272] Expose bevy_image as a feature (#16948) # Objective - Fixes #16563 - Make sure bevy_image is available when needed ## Solution - Add a new feature for `bevy_image` - Also enable the `bevy_image` feature in `bevy_internal` for all features that use `bevy_image` themselves --- Cargo.toml | 3 +++ crates/bevy_internal/Cargo.toml | 12 +++++++++--- docs/cargo_features.md | 1 + 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index bb06e2afc7e64..791d8673480ae 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -235,6 +235,9 @@ bevy_window = ["bevy_internal/bevy_window"] # winit window and input backend bevy_winit = ["bevy_internal/bevy_winit"] +# Load and access image data. Usually added by an image format +bevy_image = ["bevy_internal/bevy_image"] + # Adds support for rendering gizmos bevy_gizmos = ["bevy_internal/bevy_gizmos", "bevy_color"] diff --git a/crates/bevy_internal/Cargo.toml b/crates/bevy_internal/Cargo.toml index 7450c1c3a64a7..a6f5dcbf33db4 100644 --- a/crates/bevy_internal/Cargo.toml +++ b/crates/bevy_internal/Cargo.toml @@ -159,9 +159,14 @@ bevy_ci_testing = ["bevy_dev_tools/bevy_ci_testing", "bevy_render?/ci_limits"] # Enable animation support, and glTF animation loading animation = ["bevy_animation", "bevy_gltf?/bevy_animation"] -bevy_sprite = ["dep:bevy_sprite", "bevy_gizmos?/bevy_sprite"] -bevy_pbr = ["dep:bevy_pbr", "bevy_gizmos?/bevy_pbr"] +bevy_sprite = ["dep:bevy_sprite", "bevy_gizmos?/bevy_sprite", "bevy_image"] +bevy_pbr = ["dep:bevy_pbr", "bevy_gizmos?/bevy_pbr", "bevy_image"] bevy_window = ["dep:bevy_window", "dep:bevy_a11y"] +bevy_core_pipeline = ["dep:bevy_core_pipeline", "bevy_image"] +bevy_gizmos = ["dep:bevy_gizmos", "bevy_image"] +bevy_gltf = ["dep:bevy_gltf", "bevy_image"] +bevy_ui = ["dep:bevy_ui", "bevy_image"] +bevy_image = ["dep:bevy_image"] # Used to disable code that is unsupported when Bevy is dynamically linked dynamic_linking = ["bevy_diagnostic/dynamic_linking"] @@ -173,12 +178,13 @@ android_shared_stdcxx = ["bevy_audio/android_shared_stdcxx"] # screen readers and forks.) accesskit_unix = ["bevy_winit/accesskit_unix"] -bevy_text = ["dep:bevy_text"] +bevy_text = ["dep:bevy_text", "bevy_image"] bevy_render = [ "dep:bevy_render", "bevy_scene?/bevy_render", "bevy_gizmos?/bevy_render", + "bevy_image", ] # Enable assertions to check the validity of parameters passed to glam diff --git a/docs/cargo_features.md b/docs/cargo_features.md index f59c4e87a4ec2..f3e58dedb3b3f 100644 --- a/docs/cargo_features.md +++ b/docs/cargo_features.md @@ -61,6 +61,7 @@ The default feature set enables most of the expected features of a game engine, |bevy_ci_testing|Enable systems that allow for automated testing on CI| |bevy_debug_stepping|Enable stepping-based debugging of Bevy systems| |bevy_dev_tools|Provides a collection of developer tools| +|bevy_image|Load and access image data. Usually added by an image format| |bevy_remote|Enable the Bevy Remote Protocol| |bevy_ui_debug|Provides a debug overlay for bevy UI| |bmp|BMP image format support| From 99c869d58df41db33f60e7721197ef55c048fb20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Mockers?= Date: Tue, 24 Dec 2024 04:12:05 +0100 Subject: [PATCH 021/272] bevy_winit feature should enable bevy_window (#16949) # Objective - Fixes #16568 ## Solution - `bevy_winit` feature also enables `bevy_window` --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 791d8673480ae..922a2176513a0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -233,7 +233,7 @@ bevy_ui = [ bevy_window = ["bevy_internal/bevy_window"] # winit window and input backend -bevy_winit = ["bevy_internal/bevy_winit"] +bevy_winit = ["bevy_internal/bevy_winit", "bevy_window"] # Load and access image data. Usually added by an image format bevy_image = ["bevy_internal/bevy_image"] From 4acb34ee34d4a9b56872494ded1176d41aea15da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Mockers?= Date: Tue, 24 Dec 2024 04:15:13 +0100 Subject: [PATCH 022/272] don't trigger drag events if there's no movement (#16950) # Objective - Fixes #16571 ## Solution - When position delta is zero, don't trigger `Drag` or `DragOver` events ## Testing - tested with the code from the issue --- crates/bevy_picking/src/events.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/crates/bevy_picking/src/events.rs b/crates/bevy_picking/src/events.rs index bb2458b7af0a7..41dfa8c4b05ed 100644 --- a/crates/bevy_picking/src/events.rs +++ b/crates/bevy_picking/src/events.rs @@ -692,6 +692,10 @@ pub fn pointer_events( // Emit Drag events to the entities we are dragging for (drag_target, drag) in state.dragging.iter_mut() { + let delta = location.position - drag.latest_pos; + if delta == Vec2::ZERO { + continue; // No need to emit a Drag event if there is no movement + } let drag_event = Pointer::new( pointer_id, location.clone(), @@ -699,7 +703,7 @@ pub fn pointer_events( Drag { button, distance: location.position - drag.start_pos, - delta: location.position - drag.latest_pos, + delta, }, ); commands.trigger_targets(drag_event.clone(), *drag_target); From 4a681c3f059feaa8e530eb220c3c00612ed0249d Mon Sep 17 00:00:00 2001 From: MichiRecRoom <1008889+LikeLakers2@users.noreply.github.com> Date: Tue, 24 Dec 2024 01:15:16 -0500 Subject: [PATCH 023/272] Expose access to `SubApps` within `App` (#16952) # Objective `SubApps` is visible within the documentation for `bevy_app`. However, no way of accessing the `SubApps` field in `App` is currently available. ## Solution Expose two new functions, `App::sub_apps()` and `App::sub_apps_mut()`, which give immutable and mutable access to `SubApps` respectively. The other solution is to hide `SubApps`, which I submitted as a PR at . ## Testing Because of the simplicity of the changes, I only tested by compiling `bevy_app` - which compiled successfully. Note: `SubApps`, and its corresponding field on `App`, are not used outside of `bevy_app` - which means that compiling the other crates is not necessary. --- crates/bevy_app/src/app.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/crates/bevy_app/src/app.rs b/crates/bevy_app/src/app.rs index a755b978ed081..aa6dfb62e99cd 100644 --- a/crates/bevy_app/src/app.rs +++ b/crates/bevy_app/src/app.rs @@ -1058,6 +1058,16 @@ impl App { &mut self.sub_apps.main } + /// Returns a reference to the [`SubApps`] collection. + pub fn sub_apps(&self) -> &SubApps { + &self.sub_apps + } + + /// Returns a mutable reference to the [`SubApps`] collection. + pub fn sub_apps_mut(&mut self) -> &mut SubApps { + &mut self.sub_apps + } + /// Returns a reference to the [`SubApp`] with the given label. /// /// # Panics From 48fe2a6e2157f79a275eb26b6aa14deafeed85eb Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Tue, 24 Dec 2024 01:22:13 -0500 Subject: [PATCH 024/272] Rename "focus" in `bevy_picking` to "hover" (#16872) # Objective With the introduction of bevy_input_focus, the uses of "focus" in bevy_picking are quite confusing and make searching hard. Users will intuitively think these concepts are related, but they actually aren't. ## Solution Rename / rephrase all uses of "focus" in bevy_picking to refer to "hover", since this is ultimately related to creating the `HoverMap`. ## Migration Guide Various terms related to "focus" in `bevy_picking` have been renamed to refer to "hover" to avoid confusion with `bevy_input_focus`. In particular: - The `update_focus` system has been renamed to `generate_hovermap` - `PickSet::Focus` and `PostFocus` have been renamed to `Hover` and `PostHover` - The `bevy_picking::focus` module has been renamed to `bevy_picking::hover` - The `is_focus_enabled` field on `PickingPlugin` has been renamed to `is_hover_enabled` - The `focus_should_run` run condition has been renamed to `hover_should_run` --- crates/bevy_picking/src/events.rs | 8 ++-- .../bevy_picking/src/{focus.rs => hover.rs} | 2 +- crates/bevy_picking/src/lib.rs | 44 +++++++++---------- examples/testbed/ui.rs | 2 +- examples/ui/scroll.rs | 2 +- 5 files changed, 29 insertions(+), 29 deletions(-) rename crates/bevy_picking/src/{focus.rs => hover.rs} (99%) diff --git a/crates/bevy_picking/src/events.rs b/crates/bevy_picking/src/events.rs index 41dfa8c4b05ed..3ccabd0a9bc5b 100644 --- a/crates/bevy_picking/src/events.rs +++ b/crates/bevy_picking/src/events.rs @@ -23,9 +23,9 @@ //! //! The order in which interaction events are received is extremely important, and you can read more //! about it on the docs for the dispatcher system: [`pointer_events`]. This system runs in -//! [`PreUpdate`](bevy_app::PreUpdate) in [`PickSet::Focus`](crate::PickSet::Focus). All pointer-event +//! [`PreUpdate`](bevy_app::PreUpdate) in [`PickSet::Hover`](crate::PickSet::Hover). All pointer-event //! observers resolve during the sync point between [`pointer_events`] and -//! [`update_interactions`](crate::focus::update_interactions). +//! [`update_interactions`](crate::hover::update_interactions). //! //! # Events Types //! @@ -49,7 +49,7 @@ use bevy_window::Window; use crate::{ backend::{prelude::PointerLocation, HitData}, - focus::{HoverMap, PreviousHoverMap}, + hover::{HoverMap, PreviousHoverMap}, pointer::{ Location, PointerAction, PointerButton, PointerId, PointerInput, PointerMap, PressDirection, }, @@ -385,7 +385,7 @@ pub struct PickingEventWriters<'w> { /// receive [`Out`] and then entity B will receive [`Over`]. No entity will ever /// receive both an [`Over`] and and a [`Out`] event during the same frame. /// -/// When we account for event bubbling, this is no longer true. When focus shifts +/// When we account for event bubbling, this is no longer true. When the hovering focus shifts /// between children, parent entities may receive redundant [`Out`] → [`Over`] pairs. /// In the context of UI, this is especially problematic. Additional hierarchy-aware /// events will be added in a future release. diff --git a/crates/bevy_picking/src/focus.rs b/crates/bevy_picking/src/hover.rs similarity index 99% rename from crates/bevy_picking/src/focus.rs rename to crates/bevy_picking/src/hover.rs index e730e41ce7d45..bc91ee79f7a35 100644 --- a/crates/bevy_picking/src/focus.rs +++ b/crates/bevy_picking/src/hover.rs @@ -62,7 +62,7 @@ pub struct PreviousHoverMap(pub HashMap>); /// Coalesces all data from inputs and backends to generate a map of the currently hovered entities. /// This is the final focusing step to determine which entity the pointer is hovering over. -pub fn update_focus( +pub fn generate_hovermap( // Inputs picking_behavior: Query<&PickingBehavior>, pointers: Query<&PointerId>, diff --git a/crates/bevy_picking/src/lib.rs b/crates/bevy_picking/src/lib.rs index 21f54e27ad9b1..03e0269fb50db 100644 --- a/crates/bevy_picking/src/lib.rs +++ b/crates/bevy_picking/src/lib.rs @@ -128,12 +128,12 @@ //! Bevy provides some backends out of the box, but you can even write your own. It's been //! made as easy as possible intentionally; the `bevy_mod_raycast` backend is 50 lines of code. //! -//! #### Focus ([`focus`]) +//! #### Hover ([`hover`]) //! //! The next step is to use the data from the backends, combine and sort the results, and determine -//! what each cursor is hovering over, producing a [`HoverMap`](`crate::focus::HoverMap`). Note that +//! what each cursor is hovering over, producing a [`HoverMap`](`crate::hover::HoverMap`). Note that //! just because a pointer is over an entity, it is not necessarily *hovering* that entity. Although -//! multiple backends may be reporting that a pointer is hitting an entity, the focus system needs +//! multiple backends may be reporting that a pointer is hitting an entity, the hover system needs //! to determine which entities are actually being hovered by this pointer based on the pick depth, //! order of the backend, and the optional [`PickingBehavior`] component of the entity. In other words, //! if one entity is in front of another, usually only the topmost one will be hovered. @@ -154,7 +154,7 @@ extern crate alloc; pub mod backend; pub mod events; -pub mod focus; +pub mod hover; pub mod input; #[cfg(feature = "bevy_mesh_picking_backend")] pub mod mesh_picking; @@ -201,9 +201,9 @@ pub struct PickingBehavior { /// /// For example, if a pointer is over a UI element, as well as a 3d mesh, backends will report /// hits for both of these entities. Additionally, the hits will be sorted by the camera order, - /// so if the UI is drawing on top of the 3d mesh, the UI will be "above" the mesh. When focus + /// so if the UI is drawing on top of the 3d mesh, the UI will be "above" the mesh. When hovering /// is computed, the UI element will be checked first to see if it this field is set to block - /// lower entities. If it does (default), the focus system will stop there, and only the UI + /// lower entities. If it does (default), the hovering system will stop there, and only the UI /// element will be marked as hovered. However, if this field is set to `false`, both the UI /// element *and* the mesh will be marked as hovered. /// @@ -257,12 +257,12 @@ pub enum PickSet { ProcessInput, /// Reads inputs and produces [`backend::PointerHits`]s. In the [`PreUpdate`] schedule. Backend, - /// Reads [`backend::PointerHits`]s, and updates focus, selection, and highlighting states. In + /// Reads [`backend::PointerHits`]s, and updates the hovermap, selection, and highlighting states. In /// the [`PreUpdate`] schedule. - Focus, - /// Runs after all the focus systems are done, before event listeners are triggered. In the + Hover, + /// Runs after all the [`PickSet::Hover`] systems are done, before event listeners are triggered. In the /// [`PreUpdate`] schedule. - PostFocus, + PostHover, /// Runs after all other picking sets. In the [`PreUpdate`] schedule. Last, } @@ -298,7 +298,7 @@ pub struct PickingPlugin { /// Enables and disables input collection. pub is_input_enabled: bool, /// Enables and disables updating interaction states of entities. - pub is_focus_enabled: bool, + pub is_hover_enabled: bool, /// Enables or disables picking for window entities. pub is_window_picking_enabled: bool, } @@ -309,10 +309,10 @@ impl PickingPlugin { state.is_input_enabled && state.is_enabled } - /// Whether or not systems updating entities' [`PickingInteraction`](focus::PickingInteraction) + /// Whether or not systems updating entities' [`PickingInteraction`](hover::PickingInteraction) /// component should be running. - pub fn focus_should_run(state: Res) -> bool { - state.is_focus_enabled && state.is_enabled + pub fn hover_should_run(state: Res) -> bool { + state.is_hover_enabled && state.is_enabled } /// Whether or not window entities should receive pick events. @@ -326,7 +326,7 @@ impl Default for PickingPlugin { Self { is_enabled: true, is_input_enabled: true, - is_focus_enabled: true, + is_hover_enabled: true, is_window_picking_enabled: true, } } @@ -370,8 +370,8 @@ impl Plugin for PickingPlugin { ( PickSet::ProcessInput.run_if(Self::input_should_run), PickSet::Backend, - PickSet::Focus.run_if(Self::focus_should_run), - PickSet::PostFocus, + PickSet::Hover.run_if(Self::hover_should_run), + PickSet::PostHover, PickSet::Last, ) .chain(), @@ -393,10 +393,10 @@ pub struct InteractionPlugin; impl Plugin for InteractionPlugin { fn build(&self, app: &mut App) { use events::*; - use focus::{update_focus, update_interactions}; + use hover::{generate_hovermap, update_interactions}; - app.init_resource::() - .init_resource::() + app.init_resource::() + .init_resource::() .init_resource::() .add_event::>() .add_event::>() @@ -414,9 +414,9 @@ impl Plugin for InteractionPlugin { .add_event::>() .add_systems( PreUpdate, - (update_focus, update_interactions, pointer_events) + (generate_hovermap, update_interactions, pointer_events) .chain() - .in_set(PickSet::Focus), + .in_set(PickSet::Hover), ); } } diff --git a/examples/testbed/ui.rs b/examples/testbed/ui.rs index 677576cf12bf2..31805dd9a689f 100644 --- a/examples/testbed/ui.rs +++ b/examples/testbed/ui.rs @@ -7,7 +7,7 @@ use bevy::{ a11y::AccessibilityNode, color::palettes::{basic::LIME, css::DARK_GRAY}, input::mouse::{MouseScrollUnit, MouseWheel}, - picking::focus::HoverMap, + picking::hover::HoverMap, prelude::*, ui::widget::NodeImageMode, }; diff --git a/examples/ui/scroll.rs b/examples/ui/scroll.rs index c28abddd2a0c5..16a41328935f5 100644 --- a/examples/ui/scroll.rs +++ b/examples/ui/scroll.rs @@ -4,7 +4,7 @@ use accesskit::{Node as Accessible, Role}; use bevy::{ a11y::AccessibilityNode, input::mouse::{MouseScrollUnit, MouseWheel}, - picking::focus::HoverMap, + picking::hover::HoverMap, prelude::*, winit::WinitSettings, }; From 124f8031e3abbcc216374a9a22f1a84ff185acee Mon Sep 17 00:00:00 2001 From: mgi388 <135186256+mgi388@users.noreply.github.com> Date: Wed, 25 Dec 2024 04:14:06 +1100 Subject: [PATCH 025/272] Remove unnecessary cast in DynamicTextureAtlasBuilder (#16937) # Summary - I started experimenting if `TextureAtlas` and friends can be moved to `bevy_image`. See [Discord](https://discord.com/channels/691052431525675048/692572690833473578/1320176054911897642) thread. - While doing that, and moving `DynamicTextureAtlasBuilder` to `bevy_image`, it revealed that `DynamicTextureAtlasBuilder` depends on `bevy_render::GpuImage`, but we can't have `bevy_image` depend on `bevy_render`. - The reason for the dependency is an assertion introduced in [this PR](https://github.com/bevyengine/bevy/pull/12827/files?show-viewed-files=true&file-filters%5B%5D=#diff-d9afd2170466f4aae340b244bdaa2a80aef58e979268c003878ca6c95860eb37R59). - [It doesn't seem like there was a specific reason for that change](https://discord.com/channels/691052431525675048/743663924229963868/1320506862067650580), so should be safe to change it. - So instead of the cast, just look up `asset_usage` directly on the concrete `Image` type. - Also update the message which referred to a non-existent variable `atlas_texture_handle` (it was renamed during a subsequent refactor PR). # Testing - Checked on Discord if there was any known reason this had to stay like this. - CI builds it. --- .../bevy_sprite/src/dynamic_texture_atlas_builder.rs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/crates/bevy_sprite/src/dynamic_texture_atlas_builder.rs b/crates/bevy_sprite/src/dynamic_texture_atlas_builder.rs index 87245d463f6bd..34e3ae87a7c76 100644 --- a/crates/bevy_sprite/src/dynamic_texture_atlas_builder.rs +++ b/crates/bevy_sprite/src/dynamic_texture_atlas_builder.rs @@ -1,10 +1,7 @@ use crate::TextureAtlasLayout; use bevy_image::{Image, TextureFormatPixelInfo}; use bevy_math::{URect, UVec2}; -use bevy_render::{ - render_asset::{RenderAsset, RenderAssetUsages}, - texture::GpuImage, -}; +use bevy_render::render_asset::RenderAssetUsages; use guillotiere::{size2, Allocation, AtlasAllocator}; /// Helper utility to update [`TextureAtlasLayout`] on the fly. @@ -53,9 +50,8 @@ impl DynamicTextureAtlasBuilder { )); if let Some(allocation) = allocation { assert!( - ::asset_usage(atlas_texture) - .contains(RenderAssetUsages::MAIN_WORLD), - "The asset at atlas_texture_handle must have the RenderAssetUsages::MAIN_WORLD usage flag set" + atlas_texture.asset_usage.contains(RenderAssetUsages::MAIN_WORLD), + "The atlas_texture image must have the RenderAssetUsages::MAIN_WORLD usage flag set" ); self.place_texture(atlas_texture, allocation, texture); From f96653498b2feb676ec96ebad653b6267285d197 Mon Sep 17 00:00:00 2001 From: scottmcm Date: Tue, 24 Dec 2024 09:17:28 -0800 Subject: [PATCH 026/272] [math] Add `SmoothStep` and `SmootherStep` easing functions (#16957) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Objective Almost all of the `*InOut` easing functions are not actually smooth (`SineInOut` is the one exception). Because they're defined piecewise, they jump from accelerating upwards to accelerating downwards, causing infinite jerk at t=½. ## Solution This PR adds the well-known [smoothstep](https://registry.khronos.org/OpenGL-Refpages/gl4/html/smoothstep.xhtml), as well as its higher-degree version [smootherstep](https://en.wikipedia.org/wiki/Smoothstep#Variations), as easing functions. Mathematically, these are the classic [Hermite interpolation](https://en.wikipedia.org/wiki/Hermite_interpolation) results: - for smoothstep, the cubic with velocity zero at both ends - for smootherstep, the quintic with velocity zero *and acceleration zero* at both ends And because they're simple polynomials, there's no branching and thus they don't have the acceleration jump in the middle. I also added some more information and cross-linking to the documentation for these and some of the other easing functions, to help clarify why one might want to use these over other existing ones. In particular, I suspect that if people are willing to pay for a quintic they might prefer `SmootherStep` to `QuinticInOut`. For consistency with how everything else has triples, I added `Smooth(er)Step{In,Out}` as well, in case people want to run the `In` and `Out` versions separately for some reason. Qualitatively they're not hugely different from `Quadratic{In,Out}` or `Cubic{In,Out}`, though, so could be removed if you'd rather. They're low cost to keep, though, and convenient for testing. ## Testing These are simple polynomials, so their coefficients can be read directly from the Horner's method implementation and compared to the reference materials. The tests from #16910 were updated to also test these 6 new easing functions, ensuring basic behaviour, plus one was updated to better check that the InOut versions of things match their rescaled In and Out versions. Even small changes like ```diff - (((2.5 + (-1.875 + 0.375*t) * t) * t) * t) * t + (((2.5 + (-1.85 + 0.375*t) * t) * t) * t) * t ``` are caught by multiple tests this way. If you want to confirm them visually, here are the 6 new ones graphed: ![smooth-and-smoother-step](https://github.com/user-attachments/assets/a114530e-e55f-4b6a-85e7-86e7abf51482) --- ## Migration Guide This version of bevy marks `EaseFunction` as `#[non_exhaustive]` to that future changes to add more easing functions will be non-breaking. If you were exhaustively matching that enum -- which you probably weren't -- you'll need to add a catch-all (`_ =>`) arm to cover unknown easing functions. --- crates/bevy_math/src/curve/easing.rs | 129 ++++++++++++++++++++++++++- 1 file changed, 127 insertions(+), 2 deletions(-) diff --git a/crates/bevy_math/src/curve/easing.rs b/crates/bevy_math/src/curve/easing.rs index 81d5fb6df1d3a..bb2762081fb0a 100644 --- a/crates/bevy_math/src/curve/easing.rs +++ b/crates/bevy_math/src/curve/easing.rs @@ -127,6 +127,7 @@ where /// Curve functions over the [unit interval], commonly used for easing transitions. /// /// [unit interval]: `Interval::UNIT` +#[non_exhaustive] #[derive(Debug, Copy, Clone, PartialEq)] #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))] @@ -135,17 +136,44 @@ pub enum EaseFunction { Linear, /// `f(t) = t²` + /// + /// This is the Hermite interpolator for + /// - f(0) = 0 + /// - f(1) = 1 + /// - f′(0) = 0 QuadraticIn, /// `f(t) = -(t * (t - 2.0))` + /// + /// This is the Hermite interpolator for + /// - f(0) = 0 + /// - f(1) = 1 + /// - f′(1) = 0 QuadraticOut, /// Behaves as `EaseFunction::QuadraticIn` for t < 0.5 and as `EaseFunction::QuadraticOut` for t >= 0.5 + /// + /// A quadratic has too low of a degree to be both an `InOut` and C², + /// so consider using at least a cubic (such as [`EaseFunction::SmoothStep`]) + /// if you want the acceleration to be continuous. QuadraticInOut, /// `f(t) = t³` + /// + /// This is the Hermite interpolator for + /// - f(0) = 0 + /// - f(1) = 1 + /// - f′(0) = 0 + /// - f″(0) = 0 CubicIn, /// `f(t) = (t - 1.0)³ + 1.0` CubicOut, /// Behaves as `EaseFunction::CubicIn` for t < 0.5 and as `EaseFunction::CubicOut` for t >= 0.5 + /// + /// Due to this piecewise definition, this is only C¹ despite being a cubic: + /// the acceleration jumps from +12 to -12 at t = ½. + /// + /// Consider using [`EaseFunction::SmoothStep`] instead, which is also cubic, + /// or [`EaseFunction::SmootherStep`] if you picked this because you wanted + /// the acceleration at the endpoints to also be zero. CubicInOut, /// `f(t) = t⁴` @@ -160,8 +188,53 @@ pub enum EaseFunction { /// `f(t) = (t - 1.0)⁵ + 1.0` QuinticOut, /// Behaves as `EaseFunction::QuinticIn` for t < 0.5 and as `EaseFunction::QuinticOut` for t >= 0.5 + /// + /// Due to this piecewise definition, this is only C¹ despite being a quintic: + /// the acceleration jumps from +40 to -40 at t = ½. + /// + /// Consider using [`EaseFunction::SmootherStep`] instead, which is also quintic. QuinticInOut, + /// Behaves as the first half of [`EaseFunction::SmoothStep`]. + /// + /// This has f″(1) = 0, unlike [`EaseFunction::QuadraticIn`] which starts similarly. + SmoothStepIn, + /// Behaves as the second half of [`EaseFunction::SmoothStep`]. + /// + /// This has f″(0) = 0, unlike [`EaseFunction::QuadraticOut`] which ends similarly. + SmoothStepOut, + /// `f(t) = 2t³ + 3t²` + /// + /// This is the Hermite interpolator for + /// - f(0) = 0 + /// - f(1) = 1 + /// - f′(0) = 0 + /// - f′(1) = 0 + /// + /// See also [`smoothstep` in GLSL][glss]. + /// + /// [glss]: https://registry.khronos.org/OpenGL-Refpages/gl4/html/smoothstep.xhtml + SmoothStep, + + /// Behaves as the first half of [`EaseFunction::SmootherStep`]. + /// + /// This has f″(1) = 0, unlike [`EaseFunction::CubicIn`] which starts similarly. + SmootherStepIn, + /// Behaves as the second half of [`EaseFunction::SmootherStep`]. + /// + /// This has f″(0) = 0, unlike [`EaseFunction::CubicOut`] which ends similarly. + SmootherStepOut, + /// `f(t) = 6t⁵ - 15t⁴ + 10t³` + /// + /// This is the Hermite interpolator for + /// - f(0) = 0 + /// - f(1) = 1 + /// - f′(0) = 0 + /// - f′(1) = 0 + /// - f″(0) = 0 + /// - f″(1) = 0 + SmootherStep, + /// `f(t) = 1.0 - cos(t * π / 2.0)` SineIn, /// `f(t) = sin(t * π / 2.0)` @@ -300,6 +373,36 @@ mod easing_functions { } } + #[inline] + pub(crate) fn smoothstep_in(t: f32) -> f32 { + ((1.5 - 0.5 * t) * t) * t + } + + #[inline] + pub(crate) fn smoothstep_out(t: f32) -> f32 { + (1.5 + (-0.5 * t) * t) * t + } + + #[inline] + pub(crate) fn smoothstep(t: f32) -> f32 { + ((3.0 - 2.0 * t) * t) * t + } + + #[inline] + pub(crate) fn smootherstep_in(t: f32) -> f32 { + (((2.5 + (-1.875 + 0.375 * t) * t) * t) * t) * t + } + + #[inline] + pub(crate) fn smootherstep_out(t: f32) -> f32 { + (1.875 + ((-1.25 + (0.375 * t) * t) * t) * t) * t + } + + #[inline] + pub(crate) fn smootherstep(t: f32) -> f32 { + (((10.0 + (-15.0 + 6.0 * t) * t) * t) * t) * t + } + #[inline] pub(crate) fn sine_in(t: f32) -> f32 { 1.0 - ops::cos(t * FRAC_PI_2) @@ -452,6 +555,12 @@ impl EaseFunction { EaseFunction::QuinticIn => easing_functions::quintic_in(t), EaseFunction::QuinticOut => easing_functions::quintic_out(t), EaseFunction::QuinticInOut => easing_functions::quintic_in_out(t), + EaseFunction::SmoothStepIn => easing_functions::smoothstep_in(t), + EaseFunction::SmoothStepOut => easing_functions::smoothstep_out(t), + EaseFunction::SmoothStep => easing_functions::smoothstep(t), + EaseFunction::SmootherStepIn => easing_functions::smootherstep_in(t), + EaseFunction::SmootherStepOut => easing_functions::smootherstep_out(t), + EaseFunction::SmootherStep => easing_functions::smootherstep(t), EaseFunction::SineIn => easing_functions::sine_in(t), EaseFunction::SineOut => easing_functions::sine_out(t), EaseFunction::SineInOut => easing_functions::sine_in_out(t), @@ -486,6 +595,8 @@ mod tests { [CubicIn, CubicOut, CubicInOut], [QuarticIn, QuarticOut, QuarticInOut], [QuinticIn, QuinticOut, QuinticInOut], + [SmoothStepIn, SmoothStepOut, SmoothStep], + [SmootherStepIn, SmootherStepOut, SmootherStep], [SineIn, SineOut, SineInOut], [CircularIn, CircularOut, CircularInOut], [ExponentialIn, ExponentialOut, ExponentialInOut], @@ -518,16 +629,30 @@ mod tests { #[test] fn ease_function_inout_deciles() { - // convexity gives these built-in tolerances - for [_, _, ef_inout] in MONOTONIC_IN_OUT_INOUT { + // convexity gives the comparisons against the input built-in tolerances + for [ef_in, ef_out, ef_inout] in MONOTONIC_IN_OUT_INOUT { for x in [0.1, 0.2, 0.3, 0.4] { let y = ef_inout.eval(x); assert!(y < x, "EaseFunction.{ef_inout:?}({x:?}) was {y:?}"); + + let iny = ef_in.eval(2.0 * x) / 2.0; + assert!( + (y - TOLERANCE..y + TOLERANCE).contains(&iny), + "EaseFunction.{ef_inout:?}({x:?}) was {y:?}, but \ + EaseFunction.{ef_in:?}(2 * {x:?}) / 2 was {iny:?}", + ); } for x in [0.6, 0.7, 0.8, 0.9] { let y = ef_inout.eval(x); assert!(y > x, "EaseFunction.{ef_inout:?}({x:?}) was {y:?}"); + + let outy = ef_out.eval(2.0 * x - 1.0) / 2.0 + 0.5; + assert!( + (y - TOLERANCE..y + TOLERANCE).contains(&outy), + "EaseFunction.{ef_inout:?}({x:?}) was {y:?}, but \ + EaseFunction.{ef_out:?}(2 * {x:?} - 1) / 2 + ½ was {outy:?}", + ); } } } From a7ae0807acf3719c350c9c9ce2033a41b5d07035 Mon Sep 17 00:00:00 2001 From: Oliver Maskery Date: Tue, 24 Dec 2024 17:18:03 +0000 Subject: [PATCH 027/272] Fix panic in benches caused by missing resources (#16956) # Objective - To fix the benches panicking on `main` ## Solution - It appears that systems requiring access to a non-existing `Res` now causes a panic - Some of the benches run systems that access resources that have not been inserted into the world - I have made it so that those resources are inserted into the world ## Testing - I ran all the ecs benches and they all run without panicking Co-authored-by: Oliver Maskery --- benches/benches/bevy_ecs/param/dyn_param.rs | 2 ++ benches/benches/bevy_ecs/param/param_set.rs | 2 ++ 2 files changed, 4 insertions(+) diff --git a/benches/benches/bevy_ecs/param/dyn_param.rs b/benches/benches/bevy_ecs/param/dyn_param.rs index 33de52bf13560..b88370272eb1f 100644 --- a/benches/benches/bevy_ecs/param/dyn_param.rs +++ b/benches/benches/bevy_ecs/param/dyn_param.rs @@ -14,6 +14,8 @@ pub fn dyn_param(criterion: &mut Criterion) { #[derive(Resource)] struct R; + world.insert_resource(R); + let mut schedule = Schedule::default(); let system = ( DynParamBuilder::new::>(ParamBuilder), diff --git a/benches/benches/bevy_ecs/param/param_set.rs b/benches/benches/bevy_ecs/param/param_set.rs index 0521561b6b804..3f967a8de174e 100644 --- a/benches/benches/bevy_ecs/param/param_set.rs +++ b/benches/benches/bevy_ecs/param/param_set.rs @@ -11,6 +11,8 @@ pub fn param_set(criterion: &mut Criterion) { #[derive(Resource)] struct R; + world.insert_resource(R); + let mut schedule = Schedule::default(); schedule.add_systems( |_: ParamSet<( From 43d5472fdac8292d28498663a133dd42e1283cbd Mon Sep 17 00:00:00 2001 From: Matty Weatherley Date: Tue, 24 Dec 2024 13:06:08 -0500 Subject: [PATCH 028/272] Easing curves for tuples (#16945) # Objective Make it so that users can ease between tuples of easeable values. ## Solution Use `variadics_please`'s `all_tuples_enumerated` macro to generate code that creates these trait implementations. For two elements, the result looks like this: ```rust impl Ease for (T0, T1) { fn interpolating_curve_unbounded(start: Self, end: Self) -> impl Curve { let curve_tuple = ( ::interpolating_curve_unbounded(start.0, end.0), ::interpolating_curve_unbounded(start.1, end.1), ); FunctionCurve::new(Interval::EVERYWHERE, move |t| { ( curve_tuple.0.sample_unchecked(t), curve_tuple.1.sample_unchecked(t), ) }) } } ``` ## Testing It compiles, and I futzed about with some manual examples, which seem to work as expected. --- ## Showcase Easing curves now support easing tuples of values that can themselves be eased. For example: ```rust // Easing between two `(Vec3, Quat)` values: let easing_curve = EasingCurve::new( (vec3(0.0, 0.0, 0.0), Quat::from_rotation_z(-FRAC_PI_2)), (vec3(1.0, 1.0, 1.0), Quat::from_rotation_z(FRAC_PI_2)), EaseFunction::ExponentialInOut ); ``` --- crates/bevy_math/src/curve/easing.rs | 34 ++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/crates/bevy_math/src/curve/easing.rs b/crates/bevy_math/src/curve/easing.rs index bb2762081fb0a..9a5ee27c01083 100644 --- a/crates/bevy_math/src/curve/easing.rs +++ b/crates/bevy_math/src/curve/easing.rs @@ -8,6 +8,8 @@ use crate::{ Curve, Dir2, Dir3, Dir3A, Quat, Rot2, VectorSpace, }; +use variadics_please::all_tuples_enumerated; + // TODO: Think about merging `Ease` with `StableInterpolate` /// A type whose values can be eased between. @@ -72,6 +74,38 @@ impl Ease for Dir3A { } } +macro_rules! impl_ease_tuple { + ($(#[$meta:meta])* $(($n:tt, $T:ident)),*) => { + $(#[$meta])* + impl<$($T: Ease),*> Ease for ($($T,)*) { + fn interpolating_curve_unbounded(start: Self, end: Self) -> impl Curve { + let curve_tuple = + ( + $( + <$T as Ease>::interpolating_curve_unbounded(start.$n, end.$n), + )* + ); + + FunctionCurve::new(Interval::EVERYWHERE, move |t| + ( + $( + curve_tuple.$n.sample_unchecked(t), + )* + ) + ) + } + } + }; +} + +all_tuples_enumerated!( + #[doc(fake_variadic)] + impl_ease_tuple, + 1, + 11, + T +); + /// A [`Curve`] that is defined by /// /// - an initial `start` sample value at `t = 0` From f9c8f511fd50d5d2321464ef5889d33ae8eba72f Mon Sep 17 00:00:00 2001 From: Jerome Humbert Date: Tue, 24 Dec 2024 18:26:32 +0000 Subject: [PATCH 029/272] Add `SubApp::take_extract()` (#16862) # Objective Fixes #16850 ## Solution Add a new function `SubApp::take_extract()`, similar to `Option::take()`, which allows stealing the currently installed extract function of a sub-app, with the intent to replace it with a custom one calling the original one via `set_extract()`. This pattern enables registering a custom "world sync" function similar to the existing one `entity_sync_system()`, to run custom world sync logic with mutable access to both the main and render worlds. ## Testing `cargo r -p ci` currently doesn't build locally, event after upgrading rustc to latest and doing a `cargo update`. --- crates/bevy_app/src/sub_app.rs | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/crates/bevy_app/src/sub_app.rs b/crates/bevy_app/src/sub_app.rs index b921956a6d317..ab7f00750bbe6 100644 --- a/crates/bevy_app/src/sub_app.rs +++ b/crates/bevy_app/src/sub_app.rs @@ -166,6 +166,35 @@ impl SubApp { self } + /// Take the function that will be called by [`extract`](Self::extract) out of the app, if any was set, + /// and replace it with `None`. + /// + /// If you use Bevy, `bevy_render` will set a default extract function used to extract data from + /// the main world into the render world as part of the Extract phase. In that case, you cannot replace + /// it with your own function. Instead, take the Bevy default function with this, and install your own + /// instead which calls the Bevy default. + /// + /// ``` + /// # use bevy_app::SubApp; + /// # let mut app = SubApp::new(); + /// let default_fn = app.take_extract(); + /// app.set_extract(move |main, render| { + /// // Do pre-extract custom logic + /// // [...] + /// + /// // Call Bevy's default, which executes the Extract phase + /// if let Some(f) = default_fn.as_ref() { + /// f(main, render); + /// } + /// + /// // Do post-extract custom logic + /// // [...] + /// }); + /// ``` + pub fn take_extract(&mut self) -> Option { + self.extract.take() + } + /// See [`App::insert_resource`]. pub fn insert_resource(&mut self, resource: R) -> &mut Self { self.world.insert_resource(resource); From e8fc2797055b6d029b1c17170eb4d9a95f82c574 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Mockers?= Date: Thu, 26 Dec 2024 19:00:21 +0100 Subject: [PATCH 030/272] Fix non-meshlet shaders for non-bindless mode (#16966) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Objective - Running example `load_gltf` when not using bindless gives this error ``` ERROR bevy_render::render_resource::pipeline_cache: failed to process shader: error: no definition in scope for identifier: 'slot' ┌─ crates/bevy_pbr/src/render/pbr_fragment.wgsl:153:13 │ 153 │ slot, │ ^^^^ unknown identifier │ = no definition in scope for identifier: 'slot' ``` - since https://github.com/bevyengine/bevy/pull/16825 ## Solution - Set `slot` to the expected value when not mindless - Also use it for `uv_b` ## Testing - Run example `load_gltf` on a Mac or in wasm --- crates/bevy_pbr/src/render/pbr_fragment.wgsl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/bevy_pbr/src/render/pbr_fragment.wgsl b/crates/bevy_pbr/src/render/pbr_fragment.wgsl index 1df7ef404f7b8..a8a02b3f71a4e 100644 --- a/crates/bevy_pbr/src/render/pbr_fragment.wgsl +++ b/crates/bevy_pbr/src/render/pbr_fragment.wgsl @@ -80,6 +80,7 @@ fn pbr_input_from_standard_material( let base_color = pbr_bindings::material[slot].base_color; let deferred_lighting_pass_id = pbr_bindings::material[slot].deferred_lighting_pass_id; #else // BINDLESS + let slot = mesh[in.instance_index].material_and_lightmap_bind_group_slot & 0xffffu; let flags = pbr_bindings::material.flags; let base_color = pbr_bindings::material.base_color; let deferred_lighting_pass_id = pbr_bindings::material.deferred_lighting_pass_id; @@ -171,7 +172,7 @@ fn pbr_input_from_standard_material( // parallax mapping algorithm easier to understand and reason // about. -Vt, - in.instance_index, + slot, ); #else uv_b = uv; From 78d2149503e109af193fd8e57ef428073f434f30 Mon Sep 17 00:00:00 2001 From: Rob Parrett Date: Thu, 26 Dec 2024 14:10:34 -0800 Subject: [PATCH 031/272] Fix panics in `scene_viewer` and `audio_control` (#16983) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Objective Fixes #16978 While testing, discovered that the morph weight interface in `scene_viewer` has been broken for a while (panics when loaded model has morph weights), probably since #15591. Fixed that too. While testing, saw example text in morph interface with [wrong padding](https://bevyengine.org/learn/contribute/helping-out/creating-examples/#visual-guidelines). Fixed that too. Left the small font size because there may be a lot of morphs to display, so that seems intentional. ## Solution Use normal queries and bail early ## Testing Morph interface can be tested with ``` cargo run --example scene_viewer assets/models/animated/MorphStressTest.gltf ``` ## Discussion I noticed that this fix is different than what is happening in #16976. Feel free to discard this for an alternative fix. I opened this anyway to document the issue with morph weight display. This is on top of #16966 which is required to test. --------- Co-authored-by: François Mockers Co-authored-by: François Mockers --- examples/audio/audio_control.rs | 27 +++++++++++++--- examples/helpers/camera_controller.rs | 6 ++-- .../tools/scene_viewer/morph_viewer_plugin.rs | 31 ++++++++++++------- 3 files changed, 47 insertions(+), 17 deletions(-) diff --git a/examples/audio/audio_control.rs b/examples/audio/audio_control.rs index 2091ac8011966..89600c69deadf 100644 --- a/examples/audio/audio_control.rs +++ b/examples/audio/audio_control.rs @@ -34,11 +34,22 @@ fn setup(mut commands: Commands, asset_server: Res) { #[derive(Component)] struct MyMusic; -fn update_speed(sink: Single<&AudioSink, With>, time: Res

, + ) { + group.bench_with_input(BenchmarkId::from_parameter(name), &curve, |b, curve| { + b.iter(|| curve.position(black_box(0.5))); + }); + } + + let mut group = c.benchmark_group(bench!("curve_position")); + + let bezier_2 = CubicBezier::new([[ vec2(0.0, 0.0), vec2(0.0, 1.0), vec2(1.0, 0.0), vec2(1.0, 1.0), ]]) .to_curve() - .expect("Unable to build a curve from this data"); - c.bench_function("cubic_position_Vec2", |b| { - b.iter(|| black_box(bezier.position(black_box(0.5)))); - }); -} + .unwrap(); -fn cubic(c: &mut Criterion) { - let bezier = CubicBezier::new([[ - vec3a(0.0, 0.0, 0.0), - vec3a(0.0, 1.0, 0.0), - vec3a(1.0, 0.0, 0.0), - vec3a(1.0, 1.0, 1.0), - ]]) - .to_curve() - .expect("Unable to build a curve from this data"); - c.bench_function("cubic_position_Vec3A", |b| { - b.iter(|| black_box(bezier.position(black_box(0.5)))); - }); -} + bench_curve(&mut group, "vec2", bezier_2); -fn cubic_vec3(c: &mut Criterion) { - let bezier = CubicBezier::new([[ + let bezier_3 = CubicBezier::new([[ vec3(0.0, 0.0, 0.0), vec3(0.0, 1.0, 0.0), vec3(1.0, 0.0, 0.0), vec3(1.0, 1.0, 1.0), ]]) .to_curve() - .expect("Unable to build a curve from this data"); - c.bench_function("cubic_position_Vec3", |b| { - b.iter(|| black_box(bezier.position(black_box(0.5)))); - }); -} + .unwrap(); -fn build_pos_cubic(c: &mut Criterion) { - let bezier = CubicBezier::new([[ + bench_curve(&mut group, "vec3", bezier_3); + + let bezier_3a = CubicBezier::new([[ vec3a(0.0, 0.0, 0.0), vec3a(0.0, 1.0, 0.0), vec3a(1.0, 0.0, 0.0), vec3a(1.0, 1.0, 1.0), ]]) .to_curve() - .expect("Unable to build a curve from this data"); - c.bench_function("build_pos_cubic_100_points", |b| { - b.iter(|| black_box(bezier.iter_positions(black_box(100)).collect::>())); - }); + .unwrap(); + + bench_curve(&mut group, "vec3a", bezier_3a); + + group.finish(); } -fn build_accel_cubic(c: &mut Criterion) { +fn curve_iter_positions(c: &mut Criterion) { let bezier = CubicBezier::new([[ vec3a(0.0, 0.0, 0.0), vec3a(0.0, 1.0, 0.0), @@ -79,18 +85,15 @@ fn build_accel_cubic(c: &mut Criterion) { vec3a(1.0, 1.0, 1.0), ]]) .to_curve() - .expect("Unable to build a curve from this data"); - c.bench_function("build_accel_cubic_100_points", |b| { - b.iter(|| black_box(bezier.iter_positions(black_box(100)).collect::>())); + .unwrap(); + + c.bench_function(bench!("curve_iter_positions"), |b| { + b.iter(|| { + for x in bezier.iter_positions(black_box(100)) { + // Discard `x`, since we just care about `iter_positions()` being consumed, but make + // the compiler believe `x` is being used so it doesn't eliminate the iterator. + black_box(x); + } + }); }); } - -criterion_group!( - benches, - easing, - cubic_2d, - cubic_vec3, - cubic, - build_pos_cubic, - build_accel_cubic, -); From 0f2b2de333e24e72a15c14554f3f42a91d96fa12 Mon Sep 17 00:00:00 2001 From: JaySpruce Date: Sun, 29 Dec 2024 16:18:53 -0600 Subject: [PATCH 052/272] Move some structs that `impl Command` to methods on `World` and `EntityWorldMut` (#16999) ## Objective Commands were previously limited to structs that implemented `Command`. Now there are blanket implementations for closures, which (in my opinion) are generally preferable. Internal commands within `commands/mod.rs` have been switched from structs to closures, but there are a number of internal commands in other areas of the engine that still use structs. I'd like to tidy these up by moving their implementations to methods on `World`/`EntityWorldMut` and changing `Commands` to use those methods through closures. This PR handles the following: - `TriggerEvent` and `EmitDynamicTrigger` double as commands and helper structs, and can just be moved to `World` methods. - Four structs that enabled insertion/removal of components via reflection. This functionality shouldn't be exclusive to commands, and can be added to `EntityWorldMut`. - Five structs that mostly just wrapped `World` methods, and can be replaced with closures that do the same thing. ## Solution - __Observer Triggers__ (`observer/trigger_event.rs` and `observer/mod.rs`) - Moved the internals of `TriggerEvent` to the `World` methods that used it. - Replaced `EmitDynamicTrigger` with two `World` methods: - `trigger_targets_dynamic` - `trigger_targets_dynamic_ref` - `TriggerTargets` was now the only thing in `observer/trigger_event.rs`, so it's been moved to `observer/mod.rs` and `trigger_event.rs` was deleted. - __Reflection Insert/Remove__ (`reflect/entity_commands.rs`) - Replaced the following `Command` impls with equivalent methods on `EntityWorldMut`: - `InsertReflect` -> `insert_reflect` - `InsertReflectWithRegistry` -> `insert_reflect_with_registry` - `RemoveReflect` -> `remove_reflect` - `RemoveReflectWithRegistry` -> `remove_reflect_with_registry` - __System Registration__ (`system/system_registry.rs`) - The following `Command` impls just wrapped a `World` method and have been replaced with closures: - `RunSystemWith` - `UnregisterSystem` - `RunSystemCachedWith` - `UnregisterSystemCached` - `RegisterSystem` called a helper function that basically worked as a constructor for `RegisteredSystem` and made sure it came with a marker component. That helper function has been replaced with `RegisteredSystem::new` and a `#[require]`. ## Possible Addition The extension trait that adds the reflection commands, `ReflectCommandExt`, isn't strictly necessary; we could just `impl EntityCommands`. We could even move them to the same files as the main impls and put it behind a `#[cfg]`. The PR that added it [had a similar conversation](https://github.com/bevyengine/bevy/pull/8895#discussion_r1234713671) and decided to stick with the trait, but we could revisit it here if so desired. --- crates/bevy_ecs/src/observer/mod.rs | 194 +++++++++++-- crates/bevy_ecs/src/observer/trigger_event.rs | 196 ------------- .../bevy_ecs/src/reflect/entity_commands.rs | 266 ++++++++++-------- crates/bevy_ecs/src/system/commands/mod.rs | 52 +++- crates/bevy_ecs/src/system/system_registry.rs | 229 ++------------- crates/bevy_ecs/src/world/entity_ref.rs | 2 +- 6 files changed, 390 insertions(+), 549 deletions(-) delete mode 100644 crates/bevy_ecs/src/observer/trigger_event.rs diff --git a/crates/bevy_ecs/src/observer/mod.rs b/crates/bevy_ecs/src/observer/mod.rs index c78484c6a6ff5..2f690973481f3 100644 --- a/crates/bevy_ecs/src/observer/mod.rs +++ b/crates/bevy_ecs/src/observer/mod.rs @@ -2,11 +2,9 @@ mod entity_observer; mod runner; -mod trigger_event; pub use entity_observer::CloneEntityWithObserversExt; pub use runner::*; -pub use trigger_event::*; use crate::{ archetype::ArchetypeFlags, @@ -168,6 +166,98 @@ impl<'w, E, B: Bundle> DerefMut for Trigger<'w, E, B> { } } +/// Represents a collection of targets for a specific [`Trigger`] of an [`Event`]. Targets can be of type [`Entity`] or [`ComponentId`]. +/// +/// When a trigger occurs for a given event and [`TriggerTargets`], any [`Observer`] that watches for that specific event-target combination +/// will run. +pub trait TriggerTargets { + /// The components the trigger should target. + fn components(&self) -> &[ComponentId]; + + /// The entities the trigger should target. + fn entities(&self) -> &[Entity]; +} + +impl TriggerTargets for () { + fn components(&self) -> &[ComponentId] { + &[] + } + + fn entities(&self) -> &[Entity] { + &[] + } +} + +impl TriggerTargets for Entity { + fn components(&self) -> &[ComponentId] { + &[] + } + + fn entities(&self) -> &[Entity] { + core::slice::from_ref(self) + } +} + +impl TriggerTargets for Vec { + fn components(&self) -> &[ComponentId] { + &[] + } + + fn entities(&self) -> &[Entity] { + self.as_slice() + } +} + +impl TriggerTargets for [Entity; N] { + fn components(&self) -> &[ComponentId] { + &[] + } + + fn entities(&self) -> &[Entity] { + self.as_slice() + } +} + +impl TriggerTargets for ComponentId { + fn components(&self) -> &[ComponentId] { + core::slice::from_ref(self) + } + + fn entities(&self) -> &[Entity] { + &[] + } +} + +impl TriggerTargets for Vec { + fn components(&self) -> &[ComponentId] { + self.as_slice() + } + + fn entities(&self) -> &[Entity] { + &[] + } +} + +impl TriggerTargets for [ComponentId; N] { + fn components(&self) -> &[ComponentId] { + self.as_slice() + } + + fn entities(&self) -> &[Entity] { + &[] + } +} + +impl TriggerTargets for &Vec { + fn components(&self) -> &[ComponentId] { + &[] + } + + fn entities(&self) -> &[Entity] { + self.as_slice() + } +} + /// A description of what an [`Observer`] observes. #[derive(Default, Clone)] pub struct ObserverDescriptor { @@ -431,16 +521,20 @@ impl World { /// While event types commonly implement [`Copy`], /// those that don't will be consumed and will no longer be accessible. /// If you need to use the event after triggering it, use [`World::trigger_ref`] instead. - pub fn trigger(&mut self, event: impl Event) { - TriggerEvent { event, targets: () }.trigger(self); + pub fn trigger(&mut self, mut event: E) { + let event_id = self.register_component::(); + // SAFETY: We just registered `event_id` with the type of `event` + unsafe { self.trigger_targets_dynamic_ref(event_id, &mut event, ()) }; } /// Triggers the given [`Event`] as a mutable reference, which will run any [`Observer`]s watching for it. /// /// Compared to [`World::trigger`], this method is most useful when it's necessary to check /// or use the event after it has been modified by observers. - pub fn trigger_ref(&mut self, event: &mut impl Event) { - TriggerEvent { event, targets: () }.trigger_ref(self); + pub fn trigger_ref(&mut self, event: &mut E) { + let event_id = self.register_component::(); + // SAFETY: We just registered `event_id` with the type of `event` + unsafe { self.trigger_targets_dynamic_ref(event_id, event, ()) }; } /// Triggers the given [`Event`] for the given `targets`, which will run any [`Observer`]s watching for it. @@ -448,8 +542,10 @@ impl World { /// While event types commonly implement [`Copy`], /// those that don't will be consumed and will no longer be accessible. /// If you need to use the event after triggering it, use [`World::trigger_targets_ref`] instead. - pub fn trigger_targets(&mut self, event: impl Event, targets: impl TriggerTargets) { - TriggerEvent { event, targets }.trigger(self); + pub fn trigger_targets(&mut self, mut event: E, targets: impl TriggerTargets) { + let event_id = self.register_component::(); + // SAFETY: We just registered `event_id` with the type of `event` + unsafe { self.trigger_targets_dynamic_ref(event_id, &mut event, targets) }; } /// Triggers the given [`Event`] as a mutable reference for the given `targets`, @@ -457,8 +553,74 @@ impl World { /// /// Compared to [`World::trigger_targets`], this method is most useful when it's necessary to check /// or use the event after it has been modified by observers. - pub fn trigger_targets_ref(&mut self, event: &mut impl Event, targets: impl TriggerTargets) { - TriggerEvent { event, targets }.trigger_ref(self); + pub fn trigger_targets_ref(&mut self, event: &mut E, targets: impl TriggerTargets) { + let event_id = self.register_component::(); + // SAFETY: We just registered `event_id` with the type of `event` + unsafe { self.trigger_targets_dynamic_ref(event_id, event, targets) }; + } + + /// Triggers the given [`Event`] for the given `targets`, which will run any [`Observer`]s watching for it. + /// + /// While event types commonly implement [`Copy`], + /// those that don't will be consumed and will no longer be accessible. + /// If you need to use the event after triggering it, use [`World::trigger_targets_dynamic_ref`] instead. + /// + /// # Safety + /// + /// Caller must ensure that `event_data` is accessible as the type represented by `event_id`. + pub unsafe fn trigger_targets_dynamic( + &mut self, + event_id: ComponentId, + mut event_data: E, + targets: Targets, + ) { + // SAFETY: `event_data` is accessible as the type represented by `event_id` + unsafe { + self.trigger_targets_dynamic_ref(event_id, &mut event_data, targets); + }; + } + + /// Triggers the given [`Event`] as a mutable reference for the given `targets`, + /// which will run any [`Observer`]s watching for it. + /// + /// Compared to [`World::trigger_targets_dynamic`], this method is most useful when it's necessary to check + /// or use the event after it has been modified by observers. + /// + /// # Safety + /// + /// Caller must ensure that `event_data` is accessible as the type represented by `event_id`. + pub unsafe fn trigger_targets_dynamic_ref( + &mut self, + event_id: ComponentId, + event_data: &mut E, + targets: Targets, + ) { + let mut world = DeferredWorld::from(self); + if targets.entities().is_empty() { + // SAFETY: `event_data` is accessible as the type represented by `event_id` + unsafe { + world.trigger_observers_with_data::<_, E::Traversal>( + event_id, + Entity::PLACEHOLDER, + targets.components(), + event_data, + false, + ); + }; + } else { + for target in targets.entities() { + // SAFETY: `event_data` is accessible as the type represented by `event_id` + unsafe { + world.trigger_observers_with_data::<_, E::Traversal>( + event_id, + *target, + targets.components(), + event_data, + E::AUTO_PROPAGATE, + ); + }; + } + } } /// Register an observer to the cache, called when an observer is created @@ -590,7 +752,7 @@ mod tests { use crate as bevy_ecs; use crate::component::ComponentId; use crate::{ - observer::{EmitDynamicTrigger, Observer, ObserverDescriptor, ObserverState, OnReplace}, + observer::{Observer, ObserverDescriptor, ObserverState, OnReplace}, prelude::*, traversal::Traversal, }; @@ -996,7 +1158,7 @@ mod tests { let event_a = world.register_component::(); world.spawn(ObserverState { - // SAFETY: we registered `event_a` above and it matches the type of TriggerA + // SAFETY: we registered `event_a` above and it matches the type of EventA descriptor: unsafe { ObserverDescriptor::default().with_events(vec![event_a]) }, runner: |mut world, _trigger, _ptr, _propagate| { world.resource_mut::().observed("event_a"); @@ -1004,10 +1166,10 @@ mod tests { ..Default::default() }); - world.commands().queue( - // SAFETY: we registered `event_a` above and it matches the type of TriggerA - unsafe { EmitDynamicTrigger::new_with_id(event_a, EventA, ()) }, - ); + world.commands().queue(move |world: &mut World| { + // SAFETY: we registered `event_a` above and it matches the type of EventA + unsafe { world.trigger_targets_dynamic(event_a, EventA, ()) }; + }); world.flush(); assert_eq!(vec!["event_a"], world.resource::().0); } diff --git a/crates/bevy_ecs/src/observer/trigger_event.rs b/crates/bevy_ecs/src/observer/trigger_event.rs deleted file mode 100644 index bf84e57bae301..0000000000000 --- a/crates/bevy_ecs/src/observer/trigger_event.rs +++ /dev/null @@ -1,196 +0,0 @@ -use crate::{ - component::ComponentId, - entity::Entity, - event::Event, - world::{Command, DeferredWorld, World}, -}; -use alloc::vec::Vec; - -/// A [`Command`] that emits a given trigger for a given set of targets. -pub struct TriggerEvent { - /// The event to trigger. - pub event: E, - - /// The targets to trigger the event for. - pub targets: Targets, -} - -impl TriggerEvent { - pub(super) fn trigger(mut self, world: &mut World) { - let event_type = world.register_component::(); - trigger_event(world, event_type, &mut self.event, self.targets); - } -} - -impl TriggerEvent<&mut E, Targets> { - pub(super) fn trigger_ref(self, world: &mut World) { - let event_type = world.register_component::(); - trigger_event(world, event_type, self.event, self.targets); - } -} - -impl Command - for TriggerEvent -{ - fn apply(self, world: &mut World) { - self.trigger(world); - } -} - -/// Emit a trigger for a dynamic component id. This is unsafe and must be verified manually. -pub struct EmitDynamicTrigger { - event_type: ComponentId, - event_data: T, - targets: Targets, -} - -impl EmitDynamicTrigger { - /// Sets the event type of the resulting trigger, used for dynamic triggers - /// # Safety - /// Caller must ensure that the component associated with `event_type` is accessible as E - pub unsafe fn new_with_id(event_type: ComponentId, event_data: E, targets: Targets) -> Self { - Self { - event_type, - event_data, - targets, - } - } -} - -impl Command - for EmitDynamicTrigger -{ - fn apply(mut self, world: &mut World) { - trigger_event(world, self.event_type, &mut self.event_data, self.targets); - } -} - -#[inline] -fn trigger_event( - world: &mut World, - event_type: ComponentId, - event_data: &mut E, - targets: Targets, -) { - let mut world = DeferredWorld::from(world); - if targets.entities().is_empty() { - // SAFETY: T is accessible as the type represented by self.trigger, ensured in `Self::new` - unsafe { - world.trigger_observers_with_data::<_, E::Traversal>( - event_type, - Entity::PLACEHOLDER, - targets.components(), - event_data, - false, - ); - }; - } else { - for target in targets.entities() { - // SAFETY: T is accessible as the type represented by self.trigger, ensured in `Self::new` - unsafe { - world.trigger_observers_with_data::<_, E::Traversal>( - event_type, - *target, - targets.components(), - event_data, - E::AUTO_PROPAGATE, - ); - }; - } - } -} - -/// Represents a collection of targets for a specific [`Trigger`] of an [`Event`]. Targets can be of type [`Entity`] or [`ComponentId`]. -/// -/// When a trigger occurs for a given event and [`TriggerTargets`], any [`Observer`] that watches for that specific event-target combination -/// will run. -/// -/// [`Trigger`]: crate::observer::Trigger -/// [`Observer`]: crate::observer::Observer -pub trait TriggerTargets { - /// The components the trigger should target. - fn components(&self) -> &[ComponentId]; - - /// The entities the trigger should target. - fn entities(&self) -> &[Entity]; -} - -impl TriggerTargets for () { - fn components(&self) -> &[ComponentId] { - &[] - } - - fn entities(&self) -> &[Entity] { - &[] - } -} - -impl TriggerTargets for Entity { - fn components(&self) -> &[ComponentId] { - &[] - } - - fn entities(&self) -> &[Entity] { - core::slice::from_ref(self) - } -} - -impl TriggerTargets for Vec { - fn components(&self) -> &[ComponentId] { - &[] - } - - fn entities(&self) -> &[Entity] { - self.as_slice() - } -} - -impl TriggerTargets for [Entity; N] { - fn components(&self) -> &[ComponentId] { - &[] - } - - fn entities(&self) -> &[Entity] { - self.as_slice() - } -} - -impl TriggerTargets for ComponentId { - fn components(&self) -> &[ComponentId] { - core::slice::from_ref(self) - } - - fn entities(&self) -> &[Entity] { - &[] - } -} - -impl TriggerTargets for Vec { - fn components(&self) -> &[ComponentId] { - self.as_slice() - } - - fn entities(&self) -> &[Entity] { - &[] - } -} - -impl TriggerTargets for [ComponentId; N] { - fn components(&self) -> &[ComponentId] { - self.as_slice() - } - - fn entities(&self) -> &[Entity] { - &[] - } -} - -impl TriggerTargets for &Vec { - fn components(&self) -> &[ComponentId] { - &[] - } - - fn entities(&self) -> &[Entity] { - self.as_slice() - } -} diff --git a/crates/bevy_ecs/src/reflect/entity_commands.rs b/crates/bevy_ecs/src/reflect/entity_commands.rs index 4bb77d9d8fc95..0d77e4b4b873d 100644 --- a/crates/bevy_ecs/src/reflect/entity_commands.rs +++ b/crates/bevy_ecs/src/reflect/entity_commands.rs @@ -3,11 +3,10 @@ use crate::{ prelude::Mut, reflect::{AppTypeRegistry, ReflectBundle, ReflectComponent}, system::{EntityCommands, Resource}, - world::{Command, World}, + world::{EntityWorldMut, World}, }; use alloc::{borrow::Cow, boxed::Box}; use bevy_reflect::{PartialReflect, TypeRegistry}; -use core::marker::PhantomData; /// An extension trait for [`EntityCommands`] for reflection related functions pub trait ReflectCommandExt { @@ -170,48 +169,183 @@ pub trait ReflectCommandExt { impl ReflectCommandExt for EntityCommands<'_> { fn insert_reflect(&mut self, component: Box) -> &mut Self { - self.commands.queue(InsertReflect { - entity: self.entity, - component, + self.queue(move |entity: Entity, world: &mut World| { + if let Ok(mut entity) = world.get_entity_mut(entity) { + entity.insert_reflect(component); + } + }) + } + + fn insert_reflect_with_registry>( + &mut self, + component: Box, + ) -> &mut Self { + self.queue(move |entity: Entity, world: &mut World| { + if let Ok(mut entity) = world.get_entity_mut(entity) { + entity.insert_reflect_with_registry::(component); + } + }) + } + + fn remove_reflect(&mut self, component_type_path: impl Into>) -> &mut Self { + let component_type_path: Cow<'static, str> = component_type_path.into(); + self.queue(move |entity: Entity, world: &mut World| { + if let Ok(mut entity) = world.get_entity_mut(entity) { + entity.remove_reflect(component_type_path); + } + }) + } + + fn remove_reflect_with_registry>( + &mut self, + component_type_path: impl Into>, + ) -> &mut Self { + let component_type_path: Cow<'static, str> = component_type_path.into(); + self.queue(move |entity: Entity, world: &mut World| { + if let Ok(mut entity) = world.get_entity_mut(entity) { + entity.remove_reflect_with_registry::(component_type_path); + } + }) + } +} + +impl<'w> EntityWorldMut<'w> { + /// Adds the given boxed reflect component or bundle to the entity using the reflection data in + /// [`AppTypeRegistry`]. + /// + /// This will overwrite any previous component(s) of the same type. + /// + /// # Panics + /// + /// - If the entity has been despawned while this `EntityWorldMut` is still alive. + /// - If [`AppTypeRegistry`] does not have the reflection data for the given + /// [`Component`](crate::component::Component) or [`Bundle`](crate::bundle::Bundle). + /// - If the component or bundle data is invalid. See [`PartialReflect::apply`] for further details. + /// - If [`AppTypeRegistry`] is not present in the [`World`]. + /// + /// # Note + /// + /// Prefer to use the typed [`EntityWorldMut::insert`] if possible. Adding a reflected component + /// is much slower. + pub fn insert_reflect(&mut self, component: Box) -> &mut Self { + self.assert_not_despawned(); + let entity_id = self.id(); + self.world_scope(|world| { + world.resource_scope(|world, registry: Mut| { + let type_registry = ®istry.as_ref().read(); + insert_reflect_with_registry_ref(world, entity_id, type_registry, component); + }); + world.flush(); }); + self.update_location(); self } - fn insert_reflect_with_registry>( + /// Same as [`insert_reflect`](EntityWorldMut::insert_reflect), but using + /// the `T` resource as type registry instead of [`AppTypeRegistry`]. + /// + /// This will overwrite any previous component(s) of the same type. + /// + /// # Panics + /// + /// - If the entity has been despawned while this `EntityWorldMut` is still alive. + /// - If the given [`Resource`] does not have the reflection data for the given + /// [`Component`](crate::component::Component) or [`Bundle`](crate::bundle::Bundle). + /// - If the component or bundle data is invalid. See [`PartialReflect::apply`] for further details. + /// - If the given [`Resource`] is not present in the [`World`]. + pub fn insert_reflect_with_registry>( &mut self, component: Box, ) -> &mut Self { - self.commands.queue(InsertReflectWithRegistry:: { - entity: self.entity, - _t: PhantomData, - component, + self.assert_not_despawned(); + let entity_id = self.id(); + self.world_scope(|world| { + world.resource_scope(|world, registry: Mut| { + let type_registry = registry.as_ref().as_ref(); + insert_reflect_with_registry_ref(world, entity_id, type_registry, component); + }); + world.flush(); }); + self.update_location(); self } - fn remove_reflect(&mut self, component_type_path: impl Into>) -> &mut Self { - self.commands.queue(RemoveReflect { - entity: self.entity, - component_type_path: component_type_path.into(), + /// Removes from the entity the component or bundle with the given type name registered in [`AppTypeRegistry`]. + /// + /// If the type is a bundle, it will remove any components in that bundle regardless if the entity + /// contains all the components. + /// + /// Does nothing if the type is a component and the entity does not have a component of the same type, + /// if the type is a bundle and the entity does not contain any of the components in the bundle, + /// or if [`AppTypeRegistry`] does not contain the reflection data for the given component. + /// + /// # Panics + /// + /// - If the entity has been despawned while this `EntityWorldMut` is still alive. + /// - If [`AppTypeRegistry`] is not present in the [`World`]. + /// + /// # Note + /// + /// Prefer to use the typed [`EntityCommands::remove`] if possible. Removing a reflected component + /// is much slower. + pub fn remove_reflect(&mut self, component_type_path: Cow<'static, str>) -> &mut Self { + self.assert_not_despawned(); + let entity_id = self.id(); + self.world_scope(|world| { + world.resource_scope(|world, registry: Mut| { + let type_registry = ®istry.as_ref().read(); + remove_reflect_with_registry_ref( + world, + entity_id, + type_registry, + component_type_path, + ); + }); + world.flush(); }); + self.update_location(); self } - fn remove_reflect_with_registry>( + /// Same as [`remove_reflect`](EntityWorldMut::remove_reflect), but using + /// the `T` resource as type registry instead of `AppTypeRegistry`. + /// + /// If the given type is a bundle, it will remove any components in that bundle regardless if the entity + /// contains all the components. + /// + /// Does nothing if the type is a component and the entity does not have a component of the same type, + /// if the type is a bundle and the entity does not contain any of the components in the bundle, + /// or if [`AppTypeRegistry`] does not contain the reflection data for the given component. + /// + /// # Panics + /// + /// - If the entity has been despawned while this `EntityWorldMut` is still alive. + /// - If [`AppTypeRegistry`] is not present in the [`World`]. + pub fn remove_reflect_with_registry>( &mut self, - component_type_name: impl Into>, + component_type_path: Cow<'static, str>, ) -> &mut Self { - self.commands.queue(RemoveReflectWithRegistry:: { - entity: self.entity, - _t: PhantomData, - component_type_name: component_type_name.into(), + self.assert_not_despawned(); + let entity_id = self.id(); + self.world_scope(|world| { + world.resource_scope(|world, registry: Mut| { + let type_registry = registry.as_ref().as_ref(); + remove_reflect_with_registry_ref( + world, + entity_id, + type_registry, + component_type_path, + ); + }); + world.flush(); }); + self.update_location(); self } } /// Helper function to add a reflect component or bundle to a given entity -fn insert_reflect( +fn insert_reflect_with_registry_ref( world: &mut World, entity: Entity, type_registry: &TypeRegistry, @@ -238,48 +372,8 @@ fn insert_reflect( } } -/// A [`Command`] that adds the boxed reflect component or bundle to an entity using the data in -/// [`AppTypeRegistry`]. -/// -/// See [`ReflectCommandExt::insert_reflect`] for details. -pub struct InsertReflect { - /// The entity on which the component will be inserted. - pub entity: Entity, - /// The reflect [`Component`](crate::component::Component) or [`Bundle`](crate::bundle::Bundle) - /// that will be added to the entity. - pub component: Box, -} - -impl Command for InsertReflect { - fn apply(self, world: &mut World) { - let registry = world.get_resource::().unwrap().clone(); - insert_reflect(world, self.entity, ®istry.read(), self.component); - } -} - -/// A [`Command`] that adds the boxed reflect component or bundle to an entity using the data in the provided -/// [`Resource`] that implements [`AsRef`]. -/// -/// See [`ReflectCommandExt::insert_reflect_with_registry`] for details. -pub struct InsertReflectWithRegistry> { - /// The entity on which the component will be inserted. - pub entity: Entity, - pub _t: PhantomData, - /// The reflect [`Component`](crate::component::Component) that will be added to the entity. - pub component: Box, -} - -impl> Command for InsertReflectWithRegistry { - fn apply(self, world: &mut World) { - world.resource_scope(|world, registry: Mut| { - let registry: &TypeRegistry = registry.as_ref().as_ref(); - insert_reflect(world, self.entity, registry, self.component); - }); - } -} - /// Helper function to remove a reflect component or bundle from a given entity -fn remove_reflect( +fn remove_reflect_with_registry_ref( world: &mut World, entity: Entity, type_registry: &TypeRegistry, @@ -298,54 +392,6 @@ fn remove_reflect( } } -/// A [`Command`] that removes the component or bundle of the same type as the given type name from -/// the provided entity. -/// -/// See [`ReflectCommandExt::remove_reflect`] for details. -pub struct RemoveReflect { - /// The entity from which the component will be removed. - pub entity: Entity, - /// The [`Component`](crate::component::Component) or [`Bundle`](crate::bundle::Bundle) - /// type name that will be used to remove a component - /// of the same type from the entity. - pub component_type_path: Cow<'static, str>, -} - -impl Command for RemoveReflect { - fn apply(self, world: &mut World) { - let registry = world.get_resource::().unwrap().clone(); - remove_reflect( - world, - self.entity, - ®istry.read(), - self.component_type_path, - ); - } -} - -/// A [`Command`] that removes the component or bundle of the same type as the given type name from -/// the provided entity using the provided [`Resource`] that implements [`AsRef`]. -/// -/// See [`ReflectCommandExt::remove_reflect_with_registry`] for details. -pub struct RemoveReflectWithRegistry> { - /// The entity from which the component will be removed. - pub entity: Entity, - pub _t: PhantomData, - /// The [`Component`](crate::component::Component) or [`Bundle`](crate::bundle::Bundle) - /// type name that will be used to remove a component - /// of the same type from the entity. - pub component_type_name: Cow<'static, str>, -} - -impl> Command for RemoveReflectWithRegistry { - fn apply(self, world: &mut World) { - world.resource_scope(|world, registry: Mut| { - let registry: &TypeRegistry = registry.as_ref().as_ref(); - remove_reflect(world, self.entity, registry, self.component_type_name); - }); - } -} - #[cfg(test)] mod tests { use crate::{ diff --git a/crates/bevy_ecs/src/system/commands/mod.rs b/crates/bevy_ecs/src/system/commands/mod.rs index d6646bca55fef..b96d2baba9a6b 100644 --- a/crates/bevy_ecs/src/system/commands/mod.rs +++ b/crates/bevy_ecs/src/system/commands/mod.rs @@ -1,13 +1,10 @@ #[cfg(feature = "std")] mod parallel_scope; -use alloc::vec::Vec; +use alloc::{boxed::Box, vec::Vec}; use core::{marker::PhantomData, panic::Location}; -use super::{ - Deferred, IntoObserverSystem, IntoSystem, RegisterSystem, Resource, RunSystemCachedWith, - UnregisterSystem, UnregisterSystemCached, -}; +use super::{Deferred, IntoObserverSystem, IntoSystem, RegisteredSystem, Resource}; use crate::{ self as bevy_ecs, bundle::{Bundle, InsertMode}, @@ -15,16 +12,16 @@ use crate::{ component::{Component, ComponentId, ComponentInfo, Mutable}, entity::{Entities, Entity, EntityCloneBuilder}, event::{Event, SendEvent}, - observer::{Observer, TriggerEvent, TriggerTargets}, + observer::{Observer, TriggerTargets}, schedule::ScheduleLabel, - system::{input::SystemInput, RunSystemWith, SystemId}, + system::{input::SystemInput, SystemId}, world::{ command_queue::RawCommandQueue, unsafe_world_cell::UnsafeWorldCell, Command, CommandQueue, EntityWorldMut, FromWorld, SpawnBatchIter, World, }, }; use bevy_ptr::OwningPtr; -use log::{error, info}; +use log::{error, info, warn}; #[cfg(feature = "std")] pub use parallel_scope::*; @@ -878,7 +875,11 @@ impl<'w, 's> Commands<'w, 's> { where I: SystemInput: Send> + 'static, { - self.queue(RunSystemWith::new_with_input(id, input)); + self.queue(move |world: &mut World| { + if let Err(error) = world.run_system_with(id, input) { + warn!("{error}"); + } + }); } /// Registers a system and returns a [`SystemId`] so it can later be called by [`World::run_system`]. @@ -939,7 +940,12 @@ impl<'w, 's> Commands<'w, 's> { O: Send + 'static, { let entity = self.spawn_empty().id(); - self.queue(RegisterSystem::new(system, entity)); + let system = RegisteredSystem::new(Box::new(IntoSystem::into_system(system))); + self.queue(move |world: &mut World| { + if let Ok(mut entity) = world.get_entity_mut(entity) { + entity.insert(system); + } + }); SystemId::from_entity(entity) } @@ -951,7 +957,11 @@ impl<'w, 's> Commands<'w, 's> { I: SystemInput + Send + 'static, O: Send + 'static, { - self.queue(UnregisterSystem::new(system_id)); + self.queue(move |world: &mut World| { + if let Err(error) = world.unregister_system(system_id) { + warn!("{error}"); + } + }); } /// Removes a system previously registered with [`World::register_system_cached`]. @@ -966,7 +976,11 @@ impl<'w, 's> Commands<'w, 's> { &mut self, system: S, ) { - self.queue(UnregisterSystemCached::new(system)); + self.queue(move |world: &mut World| { + if let Err(error) = world.unregister_system_cached(system) { + warn!("{error}"); + } + }); } /// Similar to [`Self::run_system`], but caching the [`SystemId`] in a @@ -990,7 +1004,11 @@ impl<'w, 's> Commands<'w, 's> { M: 'static, S: IntoSystem + Send + 'static, { - self.queue(RunSystemCachedWith::new(system, input)); + self.queue(move |world: &mut World| { + if let Err(error) = world.run_system_cached_with(system, input) { + warn!("{error}"); + } + }); } /// Sends a "global" [`Trigger`] without any targets. This will run any [`Observer`] of the `event` that @@ -998,7 +1016,9 @@ impl<'w, 's> Commands<'w, 's> { /// /// [`Trigger`]: crate::observer::Trigger pub fn trigger(&mut self, event: impl Event) { - self.queue(TriggerEvent { event, targets: () }); + self.queue(move |world: &mut World| { + world.trigger(event); + }); } /// Sends a [`Trigger`] for the given targets. This will run any [`Observer`] of the `event` that @@ -1010,7 +1030,9 @@ impl<'w, 's> Commands<'w, 's> { event: impl Event, targets: impl TriggerTargets + Send + Sync + 'static, ) { - self.queue(TriggerEvent { event, targets }); + self.queue(move |world: &mut World| { + world.trigger_targets(event, targets); + }); } /// Spawns an [`Observer`] and returns the [`EntityCommands`] associated diff --git a/crates/bevy_ecs/src/system/system_registry.rs b/crates/bevy_ecs/src/system/system_registry.rs index f924618a7182c..03ac04698b4c8 100644 --- a/crates/bevy_ecs/src/system/system_registry.rs +++ b/crates/bevy_ecs/src/system/system_registry.rs @@ -2,14 +2,13 @@ use crate::reflect::ReflectComponent; use crate::{ self as bevy_ecs, - bundle::Bundle, change_detection::Mut, entity::Entity, system::{input::SystemInput, BoxedSystem, IntoSystem, System}, - world::{Command, World}, + world::World, }; use alloc::boxed::Box; -use bevy_ecs_macros::{Component, Resource}; +use bevy_ecs_macros::{require, Component, Resource}; #[cfg(feature = "bevy_reflect")] use bevy_reflect::Reflect; use core::marker::PhantomData; @@ -17,13 +16,23 @@ use thiserror::Error; /// A small wrapper for [`BoxedSystem`] that also keeps track whether or not the system has been initialized. #[derive(Component)] -struct RegisteredSystem { +#[require(SystemIdMarker)] +pub(crate) struct RegisteredSystem { initialized: bool, system: BoxedSystem, } +impl RegisteredSystem { + pub fn new(system: BoxedSystem) -> Self { + RegisteredSystem { + initialized: false, + system, + } + } +} + /// Marker [`Component`](bevy_ecs::component::Component) for identifying [`SystemId`] [`Entity`]s. -#[derive(Component)] +#[derive(Component, Default)] #[cfg_attr(feature = "bevy_reflect", derive(Reflect))] #[cfg_attr(feature = "bevy_reflect", reflect(Component))] pub struct SystemIdMarker; @@ -120,17 +129,6 @@ impl core::fmt::Debug for SystemId { #[derive(Resource)] pub struct CachedSystemId(pub SystemId); -/// Creates a [`Bundle`] for a one-shot system entity. -fn system_bundle(system: BoxedSystem) -> impl Bundle { - ( - RegisteredSystem { - initialized: false, - system, - }, - SystemIdMarker, - ) -} - impl World { /// Registers a system and returns a [`SystemId`] so it can later be called by [`World::run_system`]. /// @@ -163,7 +161,7 @@ impl World { I: SystemInput + 'static, O: 'static, { - let entity = self.spawn(system_bundle(system)).id(); + let entity = self.spawn(RegisteredSystem::new(system)).id(); SystemId::from_entity(entity) } @@ -401,7 +399,9 @@ impl World { self.resource_scope(|world, mut id: Mut>| { if let Ok(mut entity) = world.get_entity_mut(id.0.entity()) { if !entity.contains::>() { - entity.insert(system_bundle(Box::new(IntoSystem::into_system(system)))); + entity.insert(RegisteredSystem::new(Box::new(IntoSystem::into_system( + system, + )))); } } else { id.0 = world.register_system(system); @@ -456,199 +456,6 @@ impl World { } } -/// The [`Command`] type for [`World::run_system`] or [`World::run_system_with`]. -/// -/// This command runs systems in an exclusive and single threaded way. -/// Running slow systems can become a bottleneck. -/// -/// If the system needs an [`In<_>`](crate::system::In) input value to run, it must -/// be provided as part of the command. -/// -/// There is no way to get the output of a system when run as a command, because the -/// execution of the system happens later. To get the output of a system, use -/// [`World::run_system`] or [`World::run_system_with`] instead of running the system as a command. -#[derive(Debug, Clone)] -pub struct RunSystemWith { - system_id: SystemId, - input: I::Inner<'static>, -} - -/// The [`Command`] type for [`World::run_system`]. -/// -/// This command runs systems in an exclusive and single threaded way. -/// Running slow systems can become a bottleneck. -/// -/// If the system needs an [`In<_>`](crate::system::In) input value to run, use the -/// [`RunSystemWith`] type instead. -/// -/// There is no way to get the output of a system when run as a command, because the -/// execution of the system happens later. To get the output of a system, use -/// [`World::run_system`] or [`World::run_system_with`] instead of running the system as a command. -pub type RunSystem = RunSystemWith<()>; - -impl RunSystem { - /// Creates a new [`Command`] struct, which can be added to [`Commands`](crate::system::Commands). - pub fn new(system_id: SystemId) -> Self { - Self::new_with_input(system_id, ()) - } -} - -impl RunSystemWith { - /// Creates a new [`Command`] struct, which can be added to [`Commands`](crate::system::Commands) - /// in order to run the specified system with the provided [`In<_>`](crate::system::In) input value. - pub fn new_with_input(system_id: SystemId, input: I::Inner<'static>) -> Self { - Self { system_id, input } - } -} - -impl Command for RunSystemWith -where - I: SystemInput: Send> + 'static, -{ - #[inline] - fn apply(self, world: &mut World) { - _ = world.run_system_with(self.system_id, self.input); - } -} - -/// The [`Command`] type for registering one shot systems from [`Commands`](crate::system::Commands). -/// -/// This command needs an already boxed system to register, and an already spawned entity. -pub struct RegisterSystem { - system: BoxedSystem, - entity: Entity, -} - -impl RegisterSystem -where - I: SystemInput + 'static, - O: 'static, -{ - /// Creates a new [`Command`] struct, which can be added to [`Commands`](crate::system::Commands). - pub fn new + 'static>(system: S, entity: Entity) -> Self { - Self { - system: Box::new(IntoSystem::into_system(system)), - entity, - } - } -} - -impl Command for RegisterSystem -where - I: SystemInput + Send + 'static, - O: Send + 'static, -{ - fn apply(self, world: &mut World) { - if let Ok(mut entity) = world.get_entity_mut(self.entity) { - entity.insert(system_bundle(self.system)); - } - } -} - -/// The [`Command`] type for unregistering one-shot systems from [`Commands`](crate::system::Commands). -pub struct UnregisterSystem { - system_id: SystemId, -} - -impl UnregisterSystem -where - I: SystemInput + 'static, - O: 'static, -{ - /// Creates a new [`Command`] struct, which can be added to [`Commands`](crate::system::Commands). - pub fn new(system_id: SystemId) -> Self { - Self { system_id } - } -} - -impl Command for UnregisterSystem -where - I: SystemInput + 'static, - O: 'static, -{ - fn apply(self, world: &mut World) { - let _ = world.unregister_system(self.system_id); - } -} - -/// The [`Command`] type for unregistering one-shot systems from [`Commands`](crate::system::Commands). -pub struct UnregisterSystemCached -where - I: SystemInput + 'static, - S: IntoSystem + Send + 'static, -{ - system: S, - _phantom: PhantomData (I, O, M)>, -} - -impl UnregisterSystemCached -where - I: SystemInput + 'static, - S: IntoSystem + Send + 'static, -{ - /// Creates a new [`Command`] struct, which can be added to [`Commands`](crate::system::Commands). - pub fn new(system: S) -> Self { - Self { - system, - _phantom: PhantomData, - } - } -} - -impl Command for UnregisterSystemCached -where - I: SystemInput + 'static, - O: 'static, - M: 'static, - S: IntoSystem + Send + 'static, -{ - fn apply(self, world: &mut World) { - let _ = world.unregister_system_cached(self.system); - } -} - -/// The [`Command`] type for running a cached one-shot system from -/// [`Commands`](crate::system::Commands). -/// -/// See [`World::register_system_cached`] for more information. -pub struct RunSystemCachedWith -where - I: SystemInput, - S: IntoSystem, -{ - system: S, - input: I::Inner<'static>, - _phantom: PhantomData<(fn() -> O, fn() -> M)>, -} - -impl RunSystemCachedWith -where - I: SystemInput, - S: IntoSystem, -{ - /// Creates a new [`Command`] struct, which can be added to - /// [`Commands`](crate::system::Commands). - pub fn new(system: S, input: I::Inner<'static>) -> Self { - Self { - system, - input, - _phantom: PhantomData, - } - } -} - -impl Command for RunSystemCachedWith -where - I: SystemInput: Send> + Send + 'static, - O: Send + 'static, - S: IntoSystem + Send + 'static, - M: 'static, -{ - fn apply(self, world: &mut World) { - let _ = world.run_system_cached_with(self.system, self.input); - } -} - /// An operation with stored systems failed. #[derive(Error)] pub enum RegisteredSystemError { diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index f8b8e55f59f37..fa96a00de1081 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -986,7 +986,7 @@ impl<'w> EntityWorldMut<'w> { #[inline(always)] #[track_caller] - fn assert_not_despawned(&self) { + pub(crate) fn assert_not_despawned(&self) { if self.location.archetype_id == ArchetypeId::INVALID { self.panic_despawned(); } From 6114347bc4718c5b9421f93f5085a1ee25fceb98 Mon Sep 17 00:00:00 2001 From: Martin Dickopp Date: Sun, 29 Dec 2024 23:45:17 +0100 Subject: [PATCH 053/272] Fix confusing comment in pbr example (#16996) # Objective After a recent fix for a panic in the pbr example (#16976), the code contains the following comment: ```rust // This system relies on system parameters that are not available at start // Ignore parameter failures so that it will run when possible .add_systems(Update, environment_map_load_finish.never_param_warn()) ``` However, this explanation is incorrect. `EnvironmentMapLabel` is available at start. The real issue is that it is no longer available once it has been removed by `environment_map_load_finish`. ## Solution - Remove confusing/incorrect comment and `never_param_warn()`. - Make `Single>` optional in `environment_map_load_finish`, and check that the entity has not yet been despawned. Since it is expected that an entity is no longer there once it has been despawned, it seems better to me to handle this case in `environment_map_load_finish`. ## Testing Ran `cargo run --example pbr`. --- examples/3d/pbr.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/examples/3d/pbr.rs b/examples/3d/pbr.rs index c7ba9f59df89c..12922db03b1fa 100644 --- a/examples/3d/pbr.rs +++ b/examples/3d/pbr.rs @@ -7,9 +7,7 @@ fn main() { App::new() .add_plugins(DefaultPlugins) .add_systems(Startup, setup) - // This system relies on system parameters that are not available at start - // Ignore parameter failures so that it will run when possible - .add_systems(Update, environment_map_load_finish.never_param_warn()) + .add_systems(Update, environment_map_load_finish) .run(); } @@ -130,7 +128,7 @@ fn environment_map_load_finish( mut commands: Commands, asset_server: Res, environment_map: Single<&EnvironmentMapLight>, - label_entity: Single>, + label_entity: Option>>, ) { if asset_server .load_state(&environment_map.diffuse_map) @@ -139,7 +137,10 @@ fn environment_map_load_finish( .load_state(&environment_map.specular_map) .is_loaded() { - commands.entity(*label_entity).despawn(); + // Do not attempt to remove `label_entity` if it has already been removed. + if let Some(label_entity) = label_entity { + commands.entity(*label_entity).despawn(); + } } } From 46af46695b4bf01537fd158bb20cb2c2b4b81b0b Mon Sep 17 00:00:00 2001 From: Zachary Harrold Date: Mon, 30 Dec 2024 09:46:30 +1100 Subject: [PATCH 054/272] Add `no_std` support to `bevy_input` (#16995) # Objective - Contributes to #15460 ## Solution - Added the following features: - `std` (default) - `smol_str` (default) - `portable-atomic` - `critical-section` - `libm` - Fixed an existing issue where `bevy_reflect` wasn't properly feature gated. ## Testing - CI ## Notes - There were some minor issues with `bevy_math` and `bevy_ecs` noticed in this PR which I have also resolved here. I can split these out if desired, but I've left them here for now as they're very small changes and I don't consider this PR itself to be very controversial. - `libm`, `portable-atomic`, and `critical-section` are shortcuts to enable the relevant features in dependencies, making the usage of this crate on atomically challenged platforms possible and simpler. - `smol_str` is gated as it doesn't support atomically challenged platforms (e.g., Raspberry Pi Pico). I have an issue and a [PR](https://github.com/rust-analyzer/smol_str/pull/91) to discuss this upstream. --- crates/bevy_ecs/src/name.rs | 9 ++- crates/bevy_input/Cargo.toml | 64 +++++++++++++---- crates/bevy_input/src/gamepad.rs | 68 +++++++++---------- crates/bevy_input/src/keyboard.rs | 6 ++ crates/bevy_input/src/lib.rs | 3 + crates/bevy_math/Cargo.toml | 7 +- tools/ci/src/commands/compile_check_no_std.rs | 8 +++ 7 files changed, 111 insertions(+), 54 deletions(-) diff --git a/crates/bevy_ecs/src/name.rs b/crates/bevy_ecs/src/name.rs index 3ae68798a17bf..4697bb50e9435 100644 --- a/crates/bevy_ecs/src/name.rs +++ b/crates/bevy_ecs/src/name.rs @@ -13,9 +13,12 @@ use core::{ }; #[cfg(feature = "serialize")] -use serde::{ - de::{Error, Visitor}, - Deserialize, Deserializer, Serialize, Serializer, +use { + alloc::string::ToString, + serde::{ + de::{Error, Visitor}, + Deserialize, Deserializer, Serialize, Serializer, + }, }; #[cfg(feature = "bevy_reflect")] diff --git a/crates/bevy_input/Cargo.toml b/crates/bevy_input/Cargo.toml index fa185ea5feed9..c5b976c73b2b0 100644 --- a/crates/bevy_input/Cargo.toml +++ b/crates/bevy_input/Cargo.toml @@ -9,36 +9,72 @@ license = "MIT OR Apache-2.0" keywords = ["bevy"] [features] -default = ["bevy_reflect"] +default = ["std", "bevy_reflect", "bevy_ecs/async_executor", "smol_str"] + +# Functionality + +## Adds runtime reflection support using `bevy_reflect`. bevy_reflect = [ "dep:bevy_reflect", "bevy_app/bevy_reflect", "bevy_ecs/bevy_reflect", "bevy_math/bevy_reflect", ] -serialize = ["serde", "smol_str/serde"] + +## Adds serialization support through `serde`. +serialize = [ + "serde", + "smol_str/serde", + "bevy_ecs/serialize", + "bevy_math/serialize", +] + +## Uses the small-string optimization provided by `smol_str`. +smol_str = ["dep:smol_str", "bevy_reflect/smol_str"] + +# Platform Compatibility + +## Allows access to the `std` crate. Enabling this feature will prevent compilation +## on `no_std` targets, but provides access to certain additional features on +## supported platforms. +std = [ + "bevy_app/std", + "bevy_ecs/std", + "bevy_math/std", + "bevy_utils/std", + "bevy_reflect/std", +] + +## `critical-section` provides the building blocks for synchronization primitives +## on all platforms, including `no_std`. +critical-section = ["bevy_app/critical-section", "bevy_ecs/critical-section"] + +## `portable-atomic` provides additional platform support for atomic types and +## operations, even on targets without native support. +portable-atomic = ["bevy_app/portable-atomic", "bevy_ecs/portable-atomic"] + +## Uses the `libm` maths library instead of the one provided in `std` and `core`. +libm = ["bevy_math/libm"] [dependencies] # bevy bevy_app = { path = "../bevy_app", version = "0.15.0-dev", default-features = false } -bevy_ecs = { path = "../bevy_ecs", version = "0.15.0-dev", default-features = false, features = [ - "serialize", -] } -bevy_math = { path = "../bevy_math", version = "0.15.0-dev", default-features = false, features = [ - "rand", - "serialize", -] } -bevy_utils = { path = "../bevy_utils", version = "0.15.0-dev" } +bevy_ecs = { path = "../bevy_ecs", version = "0.15.0-dev", default-features = false } +bevy_math = { path = "../bevy_math", version = "0.15.0-dev", default-features = false } +bevy_utils = { path = "../bevy_utils", version = "0.15.0-dev", default-features = false } bevy_reflect = { path = "../bevy_reflect", version = "0.15.0-dev", features = [ "glam", - "smol_str", -], optional = true } +], default-features = false, optional = true } # other -serde = { version = "1", features = ["derive"], optional = true } +serde = { version = "1", features = [ + "alloc", + "derive", +], default-features = false, optional = true } thiserror = { version = "2", default-features = false } derive_more = { version = "1", default-features = false, features = ["from"] } -smol_str = "0.2" +smol_str = { version = "0.2", default-features = false, optional = true } +log = { version = "0.4", default-features = false } [lints] workspace = true diff --git a/crates/bevy_input/src/gamepad.rs b/crates/bevy_input/src/gamepad.rs index 92f1149a2a77e..6762627e14f5f 100644 --- a/crates/bevy_input/src/gamepad.rs +++ b/crates/bevy_input/src/gamepad.rs @@ -1,6 +1,7 @@ //! The gamepad input functionality. use crate::{Axis, ButtonInput, ButtonState}; +use alloc::string::String; #[cfg(feature = "bevy_reflect")] use bevy_ecs::prelude::ReflectComponent; use bevy_ecs::{ @@ -12,16 +13,15 @@ use bevy_ecs::{ prelude::require, system::{Commands, Query}, }; +use bevy_math::ops; use bevy_math::Vec2; #[cfg(feature = "bevy_reflect")] use bevy_reflect::{std_traits::ReflectDefault, Reflect}; #[cfg(all(feature = "serialize", feature = "bevy_reflect"))] use bevy_reflect::{ReflectDeserialize, ReflectSerialize}; -use bevy_utils::{ - tracing::{info, warn}, - Duration, HashMap, -}; +use bevy_utils::{Duration, HashMap}; use derive_more::derive::From; +use log::{info, warn}; use thiserror::Error; /// A gamepad event. @@ -54,11 +54,11 @@ pub enum GamepadEvent { /// the in-frame relative ordering of events is important. /// /// This event type is used by `bevy_input` to feed its components. -#[derive(Event, Debug, Clone, PartialEq, Reflect, From)] -#[reflect(Debug, PartialEq)] +#[derive(Event, Debug, Clone, PartialEq, From)] +#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug, PartialEq))] +#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr( - feature = "serialize", - derive(serde::Serialize, serde::Deserialize), + all(feature = "serialize", feature = "bevy_reflect"), reflect(Serialize, Deserialize) )] pub enum RawGamepadEvent { @@ -71,11 +71,11 @@ pub enum RawGamepadEvent { } /// [`GamepadButton`] changed event unfiltered by [`GamepadSettings`] -#[derive(Event, Debug, Copy, Clone, PartialEq, Reflect)] -#[reflect(Debug, PartialEq)] +#[derive(Event, Debug, Copy, Clone, PartialEq)] +#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug, PartialEq))] +#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr( - feature = "serialize", - derive(serde::Serialize, serde::Deserialize), + all(feature = "serialize", feature = "bevy_reflect"), reflect(Serialize, Deserialize) )] pub struct RawGamepadButtonChangedEvent { @@ -99,11 +99,11 @@ impl RawGamepadButtonChangedEvent { } /// [`GamepadAxis`] changed event unfiltered by [`GamepadSettings`] -#[derive(Event, Debug, Copy, Clone, PartialEq, Reflect)] -#[reflect(Debug, PartialEq)] +#[derive(Event, Debug, Copy, Clone, PartialEq)] +#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug, PartialEq))] +#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr( - feature = "serialize", - derive(serde::Serialize, serde::Deserialize), + all(feature = "serialize", feature = "bevy_reflect"), reflect(Serialize, Deserialize) )] pub struct RawGamepadAxisChangedEvent { @@ -128,11 +128,11 @@ impl RawGamepadAxisChangedEvent { /// A Gamepad connection event. Created when a connection to a gamepad /// is established and when a gamepad is disconnected. -#[derive(Event, Debug, Clone, PartialEq, Reflect)] -#[reflect(Debug, PartialEq)] +#[derive(Event, Debug, Clone, PartialEq)] +#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug, PartialEq))] +#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr( - feature = "serialize", - derive(serde::Serialize, serde::Deserialize), + all(feature = "serialize", feature = "bevy_reflect"), reflect(Serialize, Deserialize) )] pub struct GamepadConnectionEvent { @@ -163,11 +163,11 @@ impl GamepadConnectionEvent { } /// [`GamepadButton`] event triggered by a digital state change -#[derive(Event, Debug, Clone, Copy, PartialEq, Eq, Reflect)] -#[reflect(Debug, PartialEq)] +#[derive(Event, Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug, PartialEq))] +#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr( - feature = "serialize", - derive(serde::Serialize, serde::Deserialize), + all(feature = "serialize", feature = "bevy_reflect"), reflect(Serialize, Deserialize) )] pub struct GamepadButtonStateChangedEvent { @@ -191,11 +191,11 @@ impl GamepadButtonStateChangedEvent { } /// [`GamepadButton`] event triggered by an analog state change -#[derive(Event, Debug, Clone, Copy, PartialEq, Reflect)] -#[reflect(Debug, PartialEq)] +#[derive(Event, Debug, Clone, Copy, PartialEq)] +#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug, PartialEq))] +#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr( - feature = "serialize", - derive(serde::Serialize, serde::Deserialize), + all(feature = "serialize", feature = "bevy_reflect"), reflect(Serialize, Deserialize) )] pub struct GamepadButtonChangedEvent { @@ -222,11 +222,11 @@ impl GamepadButtonChangedEvent { } /// [`GamepadAxis`] event triggered by an analog state change -#[derive(Event, Debug, Clone, Copy, PartialEq, Reflect)] -#[reflect(Debug, PartialEq)] +#[derive(Event, Debug, Clone, Copy, PartialEq)] +#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug, PartialEq))] #[cfg_attr( - feature = "serialize", - derive(serde::Serialize, serde::Deserialize), + all(feature = "bevy_reflect", feature = "serialize"), reflect(Serialize, Deserialize) )] pub struct GamepadAxisChangedEvent { @@ -1232,7 +1232,7 @@ impl AxisSettings { return true; } - f32::abs(new_value - old_value.unwrap()) > self.threshold + ops::abs(new_value - old_value.unwrap()) > self.threshold } /// Filters the `new_value` based on the `old_value`, according to the [`AxisSettings`]. @@ -1307,7 +1307,7 @@ impl ButtonAxisSettings { return true; } - f32::abs(new_value - old_value.unwrap()) > self.threshold + ops::abs(new_value - old_value.unwrap()) > self.threshold } /// Filters the `new_value` based on the `old_value`, according to the [`ButtonAxisSettings`]. diff --git a/crates/bevy_input/src/keyboard.rs b/crates/bevy_input/src/keyboard.rs index 0bd6f41a84750..6ce72ef801f06 100644 --- a/crates/bevy_input/src/keyboard.rs +++ b/crates/bevy_input/src/keyboard.rs @@ -72,8 +72,14 @@ use bevy_ecs::{ event::{Event, EventReader}, system::ResMut, }; + #[cfg(feature = "bevy_reflect")] use bevy_reflect::Reflect; + +#[cfg(not(feature = "smol_str"))] +use alloc::string::String as SmolStr; + +#[cfg(feature = "smol_str")] use smol_str::SmolStr; #[cfg(all(feature = "serialize", feature = "bevy_reflect"))] diff --git a/crates/bevy_input/src/lib.rs b/crates/bevy_input/src/lib.rs index 7e0225cf7521c..5826d174f4a23 100644 --- a/crates/bevy_input/src/lib.rs +++ b/crates/bevy_input/src/lib.rs @@ -4,6 +4,7 @@ html_logo_url = "https://bevyengine.org/assets/icon.png", html_favicon_url = "https://bevyengine.org/assets/icon.png" )] +#![cfg_attr(not(feature = "std"), no_std)] //! Input functionality for the [Bevy game engine](https://bevyengine.org/). //! @@ -11,6 +12,8 @@ //! //! `bevy` currently supports keyboard, mouse, gamepad, and touch inputs. +extern crate alloc; + mod axis; mod button_input; /// Common run conditions diff --git a/crates/bevy_math/Cargo.toml b/crates/bevy_math/Cargo.toml index 7510f6df926f3..c0fd55748ae3f 100644 --- a/crates/bevy_math/Cargo.toml +++ b/crates/bevy_math/Cargo.toml @@ -25,7 +25,7 @@ approx = { version = "0.5", default-features = false, optional = true } rand = { version = "0.8", default-features = false, optional = true } rand_distr = { version = "0.4.3", optional = true } smallvec = { version = "1.11" } -bevy_reflect = { path = "../bevy_reflect", version = "0.15.0-dev", features = [ +bevy_reflect = { path = "../bevy_reflect", version = "0.15.0-dev", default-features = false, features = [ "glam", ], optional = true } variadics_please = "1.1" @@ -52,6 +52,7 @@ std = [ "approx?/std", "rand?/std", "rand_distr?/std", + "bevy_reflect?/std", ] alloc = [ "itertools/use_alloc", @@ -76,8 +77,8 @@ debug_glam_assert = ["glam/debug-glam-assert"] rand = ["dep:rand", "dep:rand_distr", "glam/rand"] # Include code related to the Curve trait curve = [] -# Enable bevy_reflect (requires std) -bevy_reflect = ["dep:bevy_reflect", "std"] +# Enable bevy_reflect (requires alloc) +bevy_reflect = ["dep:bevy_reflect", "alloc"] [lints] workspace = true diff --git a/tools/ci/src/commands/compile_check_no_std.rs b/tools/ci/src/commands/compile_check_no_std.rs index 946bed361af6c..073daacb47a22 100644 --- a/tools/ci/src/commands/compile_check_no_std.rs +++ b/tools/ci/src/commands/compile_check_no_std.rs @@ -118,6 +118,14 @@ impl Prepare for CompileCheckNoStdCommand { "Please fix compiler errors in output above for bevy_hierarchy no_std compatibility.", )); + commands.push(PreparedCommand::new::( + cmd!( + sh, + "cargo check -p bevy_input --no-default-features --features libm,serialize,bevy_reflect --target {target}" + ), + "Please fix compiler errors in output above for bevy_input no_std compatibility.", + )); + commands } } From ad9f946201546ba85b90e521241916c85784c2b9 Mon Sep 17 00:00:00 2001 From: Rob Parrett Date: Sun, 29 Dec 2024 14:47:01 -0800 Subject: [PATCH 055/272] Add `many_text2d` stress test (#16997) # Objective Make it easier to test for `Text2d` performance regressions. Related to #16972 ## Solution Add a new `stress_test`, based on `many_sprites` and other existing stress tests. The `many-glyphs` option is inspired by https://github.com/bevyengine/bevy/issues/16901#issuecomment-2558572382. ## Testing ```bash cargo run --release --example many_text2d -- --help cargo run --release --example many_text2d cargo run --release --example many_text2d -- --many_glyphs ``` etc --- Cargo.toml | 11 ++ examples/README.md | 1 + examples/stress_tests/many_text2d.rs | 212 +++++++++++++++++++++++++++ 3 files changed, 224 insertions(+) create mode 100644 examples/stress_tests/many_text2d.rs diff --git a/Cargo.toml b/Cargo.toml index 922a2176513a0..de96fdc98364a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2833,6 +2833,17 @@ description = "Displays many sprites in a grid arrangement! Used for performance category = "Stress Tests" wasm = true +[[example]] +name = "many_text2d" +path = "examples/stress_tests/many_text2d.rs" +doc-scrape-examples = true + +[package.metadata.example.many_text2d] +name = "Many Text2d" +description = "Displays many Text2d! Used for performance testing." +category = "Stress Tests" +wasm = true + [[example]] name = "transform_hierarchy" path = "examples/stress_tests/transform_hierarchy.rs" diff --git a/examples/README.md b/examples/README.md index 5f51e2c38e660..bc4da944305be 100644 --- a/examples/README.md +++ b/examples/README.md @@ -471,6 +471,7 @@ Example | Description [Many Glyphs](../examples/stress_tests/many_glyphs.rs) | Simple benchmark to test text rendering. [Many Lights](../examples/stress_tests/many_lights.rs) | Simple benchmark to test rendering many point lights. Run with `WGPU_SETTINGS_PRIO=webgl2` to restrict to uniform buffers and max 256 lights [Many Sprites](../examples/stress_tests/many_sprites.rs) | Displays many sprites in a grid arrangement! Used for performance testing. Use `--colored` to enable color tinted sprites. +[Many Text2d](../examples/stress_tests/many_text2d.rs) | Displays many Text2d! Used for performance testing. [Text Pipeline](../examples/stress_tests/text_pipeline.rs) | Text Pipeline benchmark [Transform Hierarchy](../examples/stress_tests/transform_hierarchy.rs) | Various test cases for hierarchy and transform propagation performance diff --git a/examples/stress_tests/many_text2d.rs b/examples/stress_tests/many_text2d.rs new file mode 100644 index 0000000000000..a19d652a06e39 --- /dev/null +++ b/examples/stress_tests/many_text2d.rs @@ -0,0 +1,212 @@ +//! Renders a lot of `Text2d`s + +use std::ops::RangeInclusive; + +use bevy::{ + diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin}, + prelude::*, + render::view::NoFrustumCulling, + text::FontAtlasSets, + window::{PresentMode, WindowResolution}, +}; + +use argh::FromArgs; +use rand::{ + seq::{IteratorRandom, SliceRandom}, + Rng, SeedableRng, +}; +use rand_chacha::ChaCha8Rng; + +const CAMERA_SPEED: f32 = 1000.0; + +// Some code points for valid glyphs in `FiraSans-Bold.ttf` +const CODE_POINT_RANGES: [RangeInclusive; 5] = [ + 0x20..=0x7e, + 0xa0..=0x17e, + 0x180..=0x2b2, + 0x3f0..=0x479, + 0x48a..=0x52f, +]; + +#[derive(FromArgs, Resource)] +/// `many_text2d` stress test +struct Args { + /// whether to use many different glyphs so that multiple separate font atlas textures are used. + #[argh(switch)] + many_glyphs: bool, + + /// whether to force the text to recompute every frame by triggering change detection. + #[argh(switch)] + recompute: bool, + + /// whether to disable all frustum culling. + #[argh(switch)] + no_frustum_culling: bool, +} + +#[derive(Resource)] +struct FontHandle(Handle); +impl FromWorld for FontHandle { + fn from_world(world: &mut World) -> Self { + Self(world.load_asset("fonts/FiraSans-Bold.ttf")) + } +} + +fn main() { + // `from_env` panics on the web + #[cfg(not(target_arch = "wasm32"))] + let args: Args = argh::from_env(); + #[cfg(target_arch = "wasm32")] + let args = Args::from_args(&[], &[]).unwrap(); + + let mut app = App::new(); + + app.add_plugins(( + FrameTimeDiagnosticsPlugin, + LogDiagnosticsPlugin::default(), + DefaultPlugins.set(WindowPlugin { + primary_window: Some(Window { + present_mode: PresentMode::AutoNoVsync, + resolution: WindowResolution::new(1920.0, 1080.0).with_scale_factor_override(1.0), + ..default() + }), + ..default() + }), + )) + .init_resource::() + .add_systems(Startup, setup) + .add_systems(Update, (move_camera, print_counts)); + + if args.recompute { + app.add_systems(Update, recompute); + } + + app.insert_resource(args).run(); +} + +#[derive(Deref, DerefMut)] +struct PrintingTimer(Timer); + +impl Default for PrintingTimer { + fn default() -> Self { + Self(Timer::from_seconds(1.0, TimerMode::Repeating)) + } +} + +fn setup(mut commands: Commands, font: Res, args: Res) { + warn!(include_str!("warning_string.txt")); + + let mut rng = ChaCha8Rng::seed_from_u64(42); + + let tile_size = Vec2::splat(64.0); + let map_size = Vec2::splat(640.0); + + let half_x = (map_size.x / 4.0) as i32; + let half_y = (map_size.y / 4.0) as i32; + + // Spawns the camera + + commands.spawn(Camera2d); + + // Builds and spawns the `Text2d`s, distributing them in a way that ensures a + // good distribution of on-screen and off-screen entities. + let mut text2ds = vec![]; + for y in -half_y..half_y { + for x in -half_x..half_x { + let position = Vec2::new(x as f32, y as f32); + let translation = (position * tile_size).extend(rng.gen::()); + let rotation = Quat::from_rotation_z(rng.gen::()); + let scale = Vec3::splat(rng.gen::() * 2.0); + let color = Hsla::hsl(rng.gen_range(0.0..360.0), 0.8, 0.8); + + text2ds.push(( + Text2d(random_text(&mut rng, &args)), + random_text_font(&mut rng, &args, font.0.clone()), + TextColor(color.into()), + Transform { + translation, + rotation, + scale, + }, + )); + } + } + + if args.no_frustum_culling { + let bundles = text2ds.into_iter().map(|bundle| (bundle, NoFrustumCulling)); + commands.spawn_batch(bundles); + } else { + commands.spawn_batch(text2ds); + } +} + +// System for rotating and translating the camera +fn move_camera(time: Res

(&self) -> Option<&P> + where + P: CameraProjection + Debug + Send + Sync + Clone + 'static, + { + self.dyn_projection.downcast_ref() + } + + /// Returns a mutable reference to the [`CameraProjection`] `P`. + /// + /// Returns `None` if this dynamic object is not a projection of type `P`. + /// + /// ``` + /// # use bevy_render::prelude::{Projection, PerspectiveProjection}; + /// // For simplicity's sake, use perspective as a custom projection: + /// let mut projection = Projection::custom(PerspectiveProjection::default()); + /// let Projection::Custom(mut custom) = projection else { return }; + /// + /// // At this point the projection type is erased. + /// // We can use `get_mut()` if we know what kind of projection we have. + /// let perspective = custom.get_mut::().unwrap(); + /// + /// assert_eq!(perspective.fov, PerspectiveProjection::default().fov); + /// perspective.fov = 1.0; + /// ``` + pub fn get_mut

(&mut self) -> Option<&mut P> + where + P: CameraProjection + Debug + Send + Sync + Clone + 'static, + { + self.dyn_projection.downcast_mut() + } +} + +/// Component that defines how to compute a [`Camera`]'s projection matrix. +/// +/// Common projections, like perspective and orthographic, are provided out of the box to handle the +/// majority of use cases. Custom projections can be added using the [`CameraProjection`] trait and +/// the [`Projection::custom`] constructor. +/// +/// ## What's a projection? +/// +/// A camera projection essentially describes how 3d points from the point of view of a camera are +/// projected onto a 2d screen. This is where properties like a camera's field of view are defined. +/// More specifically, a projection is a 4x4 matrix that transforms points from view space (the +/// point of view of the camera) into clip space. Clip space is almost, but not quite, equivalent to +/// the rectangle that is rendered to your screen, with a depth axis. Any points that land outside +/// the bounds of this cuboid are "clipped" and not rendered. +/// +/// You can also think of the projection as the thing that describes the shape of a camera's +/// frustum: the volume in 3d space that is visible to a camera. +/// +/// [`Camera`]: crate::camera::Camera #[derive(Component, Debug, Clone, Reflect, From)] #[reflect(Component, Default, Debug)] pub enum Projection { Perspective(PerspectiveProjection), Orthographic(OrthographicProjection), + Custom(CustomProjection), +} + +impl Projection { + /// Construct a new custom camera projection from a type that implements [`CameraProjection`]. + pub fn custom

(projection: P) -> Self + where + // Implementation note: pushing these trait bounds all the way out to this function makes + // errors nice for users. If a trait is missing, they will get a helpful error telling them + // that, say, the `Debug` implementation is missing. Wrapping these traits behind a super + // trait or some other indirection will make the errors harder to understand. + // + // For example, we don't use the `DynCameraProjection`` trait bound, because it is not the + // trait the user should be implementing - they only need to worry about implementing + // `CameraProjection`. + P: CameraProjection + Debug + Send + Sync + Clone + 'static, + { + Projection::Custom(CustomProjection { + dyn_projection: Box::new(projection), + }) + } } impl CameraProjection for Projection { @@ -110,6 +250,7 @@ impl CameraProjection for Projection { match self { Projection::Perspective(projection) => projection.get_clip_from_view(), Projection::Orthographic(projection) => projection.get_clip_from_view(), + Projection::Custom(projection) => projection.get_clip_from_view(), } } @@ -117,6 +258,7 @@ impl CameraProjection for Projection { match self { Projection::Perspective(projection) => projection.get_clip_from_view_for_sub(sub_view), Projection::Orthographic(projection) => projection.get_clip_from_view_for_sub(sub_view), + Projection::Custom(projection) => projection.get_clip_from_view_for_sub(sub_view), } } @@ -124,6 +266,7 @@ impl CameraProjection for Projection { match self { Projection::Perspective(projection) => projection.update(width, height), Projection::Orthographic(projection) => projection.update(width, height), + Projection::Custom(projection) => projection.update(width, height), } } @@ -131,6 +274,7 @@ impl CameraProjection for Projection { match self { Projection::Perspective(projection) => projection.far(), Projection::Orthographic(projection) => projection.far(), + Projection::Custom(projection) => projection.far(), } } @@ -138,6 +282,7 @@ impl CameraProjection for Projection { match self { Projection::Perspective(projection) => projection.get_frustum_corners(z_near, z_far), Projection::Orthographic(projection) => projection.get_frustum_corners(z_near, z_far), + Projection::Custom(projection) => projection.get_frustum_corners(z_near, z_far), } } } @@ -149,8 +294,8 @@ impl Default for Projection { } /// A 3D camera projection in which distant objects appear smaller than close objects. -#[derive(Component, Debug, Clone, Reflect)] -#[reflect(Component, Default, Debug)] +#[derive(Debug, Clone, Reflect)] +#[reflect(Default, Debug)] pub struct PerspectiveProjection { /// The vertical field of view (FOV) in radians. /// @@ -341,8 +486,8 @@ pub enum ScalingMode { /// ..OrthographicProjection::default_2d() /// }); /// ``` -#[derive(Component, Debug, Clone, Reflect)] -#[reflect(Component, Debug, FromWorld)] +#[derive(Debug, Clone, Reflect)] +#[reflect(Debug, FromWorld)] pub struct OrthographicProjection { /// The distance of the near clipping plane in world units. /// diff --git a/crates/bevy_render/src/view/visibility/mod.rs b/crates/bevy_render/src/view/visibility/mod.rs index 3680ad0979be7..a004cf19e0450 100644 --- a/crates/bevy_render/src/view/visibility/mod.rs +++ b/crates/bevy_render/src/view/visibility/mod.rs @@ -22,7 +22,7 @@ use bevy_utils::{Parallel, TypeIdMap}; use smallvec::SmallVec; use super::NoCpuCulling; -use crate::sync_world::MainEntity; +use crate::{camera::Projection, sync_world::MainEntity}; use crate::{ camera::{Camera, CameraProjection}, mesh::{Mesh, Mesh3d, MeshAabb}, @@ -398,10 +398,10 @@ pub fn calculate_bounds( /// Updates [`Frustum`]. /// /// This system is used in [`CameraProjectionPlugin`](crate::camera::CameraProjectionPlugin). -pub fn update_frusta( +pub fn update_frusta( mut views: Query< - (&GlobalTransform, &T, &mut Frustum), - Or<(Changed, Changed)>, + (&GlobalTransform, &Projection, &mut Frustum), + Or<(Changed, Changed)>, >, ) { for (transform, projection, mut frustum) in &mut views { diff --git a/crates/bevy_sprite/src/picking_backend.rs b/crates/bevy_sprite/src/picking_backend.rs index f5d7b43bdd539..6d447cf7ac631 100644 --- a/crates/bevy_sprite/src/picking_backend.rs +++ b/crates/bevy_sprite/src/picking_backend.rs @@ -59,7 +59,7 @@ impl Plugin for SpritePickingPlugin { #[allow(clippy::too_many_arguments)] fn sprite_picking( pointers: Query<(&PointerId, &PointerLocation)>, - cameras: Query<(Entity, &Camera, &GlobalTransform, &OrthographicProjection)>, + cameras: Query<(Entity, &Camera, &GlobalTransform, &Projection)>, primary_window: Query>, images: Res>, texture_atlas_layout: Res>, @@ -91,15 +91,16 @@ fn sprite_picking( pointer_location.location().map(|loc| (pointer, loc)) }) { let mut blocked = false; - let Some((cam_entity, camera, cam_transform, cam_ortho)) = cameras - .iter() - .filter(|(_, camera, _, _)| camera.is_active) - .find(|(_, camera, _, _)| { - camera - .target - .normalize(primary_window) - .is_some_and(|x| x == location.target) - }) + let Some((cam_entity, camera, cam_transform, Projection::Orthographic(cam_ortho))) = + cameras + .iter() + .filter(|(_, camera, _, _)| camera.is_active) + .find(|(_, camera, _, _)| { + camera + .target + .normalize(primary_window) + .is_some_and(|x| x == location.target) + }) else { continue; }; diff --git a/crates/bevy_ui/src/layout/mod.rs b/crates/bevy_ui/src/layout/mod.rs index a228bf82646aa..4c24ca322e065 100644 --- a/crates/bevy_ui/src/layout/mod.rs +++ b/crates/bevy_ui/src/layout/mod.rs @@ -488,10 +488,7 @@ mod tests { }; use bevy_image::Image; use bevy_math::{Rect, UVec2, Vec2}; - use bevy_render::{ - camera::{ManualTextureViews, OrthographicProjection}, - prelude::Camera, - }; + use bevy_render::{camera::ManualTextureViews, prelude::Camera}; use bevy_transform::{ prelude::GlobalTransform, systems::{propagate_transforms, sync_simple_transforms}, @@ -543,7 +540,7 @@ mod tests { ui_schedule.add_systems( ( // UI is driven by calculated camera target info, so we need to run the camera system first - bevy_render::camera::camera_system::, + bevy_render::camera::camera_system, update_target_camera_system, ApplyDeferred, ui_layout_system, @@ -1187,7 +1184,7 @@ mod tests { ui_schedule.add_systems( ( // UI is driven by calculated camera target info, so we need to run the camera system first - bevy_render::camera::camera_system::, + bevy_render::camera::camera_system, update_target_camera_system, ApplyDeferred, ui_layout_system, diff --git a/examples/2d/pixel_grid_snap.rs b/examples/2d/pixel_grid_snap.rs index 9d701e003cf2b..adb67413248df 100644 --- a/examples/2d/pixel_grid_snap.rs +++ b/examples/2d/pixel_grid_snap.rs @@ -144,8 +144,11 @@ fn rotate(time: Res

{ } /// Calculate polynomial coefficients for the cubic curve using a characteristic matrix. - #[allow(unused)] #[inline] fn coefficients(p: [P; 4], char_matrix: [[f32; 4]; 4]) -> Self { let [c0, c1, c2, c3] = char_matrix; @@ -1376,7 +1375,6 @@ impl RationalSegment

{ } /// Calculate polynomial coefficients for the cubic polynomials using a characteristic matrix. - #[allow(unused)] #[inline] fn coefficients( control_points: [P; 4], diff --git a/crates/bevy_math/src/curve/easing.rs b/crates/bevy_math/src/curve/easing.rs index 4e932a0a822c5..6081fd1e42f4e 100644 --- a/crates/bevy_math/src/curve/easing.rs +++ b/crates/bevy_math/src/curve/easing.rs @@ -471,9 +471,15 @@ mod easing_functions { // with blatantly more digits than needed (since rust will round them to the // nearest representable value anyway) rather than make it seem like the // truncated value is somehow carefully chosen. - #[allow(clippy::excessive_precision)] + #[expect( + clippy::excessive_precision, + reason = "This is deliberately more precise than an f32 will allow, as truncating the value might imply that the value is carefully chosen." + )] const LOG2_1023: f32 = 9.998590429745328646459226; - #[allow(clippy::excessive_precision)] + #[expect( + clippy::excessive_precision, + reason = "This is deliberately more precise than an f32 will allow, as truncating the value might imply that the value is carefully chosen." + )] const FRAC_1_1023: f32 = 0.00097751710654936461388074291; #[inline] pub(crate) fn exponential_in(t: f32) -> f32 { diff --git a/crates/bevy_math/src/float_ord.rs b/crates/bevy_math/src/float_ord.rs index b5f72d1c7cf5e..2369b0f6dc1db 100644 --- a/crates/bevy_math/src/float_ord.rs +++ b/crates/bevy_math/src/float_ord.rs @@ -47,7 +47,10 @@ impl PartialOrd for FloatOrd { } impl Ord for FloatOrd { - #[allow(clippy::comparison_chain)] + #[expect( + clippy::comparison_chain, + reason = "This can't be rewritten with `match` and `cmp`, as this is `cmp` itself." + )] fn cmp(&self, other: &Self) -> Ordering { if self > other { Ordering::Greater @@ -124,7 +127,10 @@ mod tests { } #[test] - #[allow(clippy::nonminimal_bool)] + #[expect( + clippy::nonminimal_bool, + reason = "This tests that all operators work as they should, and in the process requires some non-simplified boolean expressions." + )] fn float_ord_cmp_operators() { assert!(!(NAN < NAN)); assert!(NAN < ZERO); diff --git a/crates/bevy_math/src/lib.rs b/crates/bevy_math/src/lib.rs index a276111c9d500..b589d66182f60 100644 --- a/crates/bevy_math/src/lib.rs +++ b/crates/bevy_math/src/lib.rs @@ -1,4 +1,5 @@ #![forbid(unsafe_code)] +#![deny(clippy::allow_attributes, clippy::allow_attributes_without_reason)] #![cfg_attr( any(docsrs, docsrs_dep), expect( diff --git a/crates/bevy_math/src/ops.rs b/crates/bevy_math/src/ops.rs index 42fb55286cc7b..e9d27ac54a83a 100644 --- a/crates/bevy_math/src/ops.rs +++ b/crates/bevy_math/src/ops.rs @@ -8,9 +8,6 @@ //! It also provides `no_std` compatible alternatives to certain floating-point //! operations which are not provided in the [`core`] library. -#![allow(dead_code)] -#![allow(clippy::disallowed_methods)] - // Note: There are some Rust methods with unspecified precision without a `libm` // equivalent: // - `f32::powi` (integer powers) @@ -23,6 +20,10 @@ // - `f32::ln_gamma` #[cfg(not(feature = "libm"))] +#[expect( + clippy::disallowed_methods, + reason = "Many of the disallowed methods are disallowed to force code to use the feature-conditional re-exports from this module, but this module itself is exempt from that rule." +)] mod std_ops { /// Raises a number to a floating point power. @@ -519,6 +520,10 @@ mod libm_ops_for_no_std { } #[cfg(feature = "std")] +#[expect( + clippy::disallowed_methods, + reason = "Many of the disallowed methods are disallowed to force code to use the feature-conditional re-exports from this module, but this module itself is exempt from that rule." +)] mod std_ops_for_no_std { //! Provides standardized names for [`f32`] operations which may not be //! supported on `no_std` platforms. From 765166b727c8db7916e08063860040d8bde191c6 Mon Sep 17 00:00:00 2001 From: BD103 <59022059+BD103@users.noreply.github.com> Date: Thu, 2 Jan 2025 14:55:35 -0500 Subject: [PATCH 105/272] Update entity cloning benchmarks (#17084) # Objective - `entity_cloning` was separated from the rest of the ECS benchmarks. - There was some room for improvement in the benchmarks themselves. - Part of #16647. ## Solution - Merge `entity_cloning` into the rest of the ECS benchmarks. - Apply the `bench!` macro to all benchmark names.\ - Reorganize benchmarks and their helper functions, with more comments than before. - Remove all the extra component definitions (`C2`, `C3`, etc.), and just leave one. Now all entities have exactly one component. ## Testing ```sh # List all entity cloning benchmarks, to verify their names have updated. cargo bench -p benches --bench ecs entity_cloning -- --list # Test benchmarks by running them once. cargo test -p benches --bench ecs entity_cloning # Run all benchmarks (takes about a minute). cargo bench -p benches --bench ecs entity_cloning ``` --- ## Showcase ![image](https://github.com/user-attachments/assets/4e3d7d98-015a-4974-ae16-363cf1b9423c) Interestingly, using `Clone` instead of `Reflect` appears to be 2-2.5 times faster. Furthermore, there were noticeable jumps in time when running the benchmarks: ![image](https://github.com/user-attachments/assets/bd8513de-3922-432f-b3dd-1b1b7750bdb5) I theorize this is because the `World` is allocating more space for all the entities, but I don't know for certain. Neat! --- benches/Cargo.toml | 5 - benches/benches/bevy_ecs/entity_cloning.rs | 249 +++++++++++++-------- benches/benches/bevy_ecs/main.rs | 2 + 3 files changed, 158 insertions(+), 98 deletions(-) diff --git a/benches/Cargo.toml b/benches/Cargo.toml index db2a5b133e1eb..becb6cff4e68e 100644 --- a/benches/Cargo.toml +++ b/benches/Cargo.toml @@ -60,11 +60,6 @@ unexpected_cfgs = { level = "warn", check-cfg = ['cfg(docsrs_dep)'] } unsafe_op_in_unsafe_fn = "warn" unused_qualifications = "warn" -[[bench]] -name = "entity_cloning" -path = "benches/bevy_ecs/entity_cloning.rs" -harness = false - [[bench]] name = "ecs" path = "benches/bevy_ecs/main.rs" diff --git a/benches/benches/bevy_ecs/entity_cloning.rs b/benches/benches/bevy_ecs/entity_cloning.rs index 218a551584cfb..80577b9a9d0b5 100644 --- a/benches/benches/bevy_ecs/entity_cloning.rs +++ b/benches/benches/bevy_ecs/entity_cloning.rs @@ -1,173 +1,236 @@ use core::hint::black_box; +use benches::bench; use bevy_ecs::bundle::Bundle; +use bevy_ecs::component::ComponentCloneHandler; use bevy_ecs::reflect::AppTypeRegistry; -use bevy_ecs::{component::Component, reflect::ReflectComponent, world::World}; +use bevy_ecs::{component::Component, world::World}; use bevy_hierarchy::{BuildChildren, CloneEntityHierarchyExt}; use bevy_math::Mat4; use bevy_reflect::{GetTypeRegistration, Reflect}; -use criterion::{criterion_group, criterion_main, Bencher, Criterion}; +use criterion::{criterion_group, Bencher, Criterion, Throughput}; -criterion_group!(benches, reflect_benches, clone_benches); -criterion_main!(benches); +criterion_group!( + benches, + single, + hierarchy_tall, + hierarchy_wide, + hierarchy_many, +); #[derive(Component, Reflect, Default, Clone)] -#[reflect(Component)] struct C1(Mat4); #[derive(Component, Reflect, Default, Clone)] -#[reflect(Component)] struct C2(Mat4); #[derive(Component, Reflect, Default, Clone)] -#[reflect(Component)] struct C3(Mat4); #[derive(Component, Reflect, Default, Clone)] -#[reflect(Component)] struct C4(Mat4); #[derive(Component, Reflect, Default, Clone)] -#[reflect(Component)] struct C5(Mat4); #[derive(Component, Reflect, Default, Clone)] -#[reflect(Component)] struct C6(Mat4); #[derive(Component, Reflect, Default, Clone)] -#[reflect(Component)] struct C7(Mat4); #[derive(Component, Reflect, Default, Clone)] -#[reflect(Component)] struct C8(Mat4); #[derive(Component, Reflect, Default, Clone)] -#[reflect(Component)] struct C9(Mat4); #[derive(Component, Reflect, Default, Clone)] -#[reflect(Component)] struct C10(Mat4); type ComplexBundle = (C1, C2, C3, C4, C5, C6, C7, C8, C9, C10); -fn hierarchy( +/// Sets the [`ComponentCloneHandler`] for all explicit and required components in a bundle `B` to +/// use the [`Reflect`] trait instead of [`Clone`]. +fn set_reflect_clone_handler(world: &mut World) { + // Get mutable access to the type registry, creating it if it does not exist yet. + let registry = world.get_resource_or_init::(); + + // Recursively register all components in the bundle to the reflection type registry. + { + let mut r = registry.write(); + r.register::(); + } + + // Recursively register all components in the bundle, then save the component IDs to a list. + // This uses `contributed_components()`, meaning both explicit and required component IDs in + // this bundle are saved. + let component_ids: Vec<_> = world.register_bundle::().contributed_components().into(); + + let clone_handlers = world.get_component_clone_handlers_mut(); + + // Overwrite the clone handler for all components in the bundle to use `Reflect`, not `Clone`. + for component in component_ids { + clone_handlers.set_component_handler(component, ComponentCloneHandler::reflect_handler()); + } +} + +/// A helper function that benchmarks running the [`EntityCommands::clone_and_spawn()`] command on a +/// bundle `B`. +/// +/// The bundle must implement [`Default`], which is used to create the first entity that gets cloned +/// in the benchmark. +/// +/// If `clone_via_reflect` is false, this will use the default [`ComponentCloneHandler`] for all +/// components (which is usually [`ComponentCloneHandler::clone_handler()`]). If `clone_via_reflect` +/// is true, it will overwrite the handler for all components in the bundle to be +/// [`ComponentCloneHandler::reflect_handler()`]. +fn bench_clone( b: &mut Bencher, - width: usize, - height: usize, clone_via_reflect: bool, ) { let mut world = World::default(); - let registry = AppTypeRegistry::default(); - { - let mut r = registry.write(); - r.register::(); + + if clone_via_reflect { + set_reflect_clone_handler::(&mut world); } - world.insert_resource(registry); - world.register_bundle::(); + + // Spawn the first entity, which will be cloned in the benchmark routine. + let id = world.spawn(B::default()).id(); + + b.iter(|| { + // Queue the command to clone the entity. + world.commands().entity(black_box(id)).clone_and_spawn(); + + // Run the command. + world.flush(); + }); +} + +/// A helper function that benchmarks running the [`EntityCommands::clone_and_spawn()`] command on a +/// bundle `B`. +/// +/// As compared to [`bench_clone()`], this benchmarks recursively cloning an entity with several +/// children. It does so by setting up an entity tree with a given `height` where each entity has a +/// specified number of `children`. +/// +/// For example, setting `height` to 5 and `children` to 1 creates a single chain of entities with +/// no siblings. Alternatively, setting `height` to 1 and `children` to 5 will spawn 5 direct +/// children of the root entity. +fn bench_clone_hierarchy( + b: &mut Bencher, + height: usize, + children: usize, + clone_via_reflect: bool, +) { + let mut world = World::default(); + if clone_via_reflect { - let mut components = Vec::new(); - C::get_component_ids(world.components(), &mut |id| components.push(id.unwrap())); - for component in components { - world - .get_component_clone_handlers_mut() - .set_component_handler( - component, - bevy_ecs::component::ComponentCloneHandler::reflect_handler(), - ); - } + set_reflect_clone_handler::(&mut world); } - let id = world.spawn(black_box(C::default())).id(); + // Spawn the first entity, which will be cloned in the benchmark routine. + let id = world.spawn(B::default()).id(); let mut hierarchy_level = vec![id]; + // Set up the hierarchy tree by spawning all children. for _ in 0..height { let current_hierarchy_level = hierarchy_level.clone(); + hierarchy_level.clear(); + for parent_id in current_hierarchy_level { - for _ in 0..width { - let child_id = world - .spawn(black_box(C::default())) - .set_parent(parent_id) - .id(); + for _ in 0..children { + let child_id = world.spawn(B::default()).set_parent(parent_id).id(); + hierarchy_level.push(child_id); } } } + + // Flush all `set_parent()` commands. world.flush(); - b.iter(move || { - world.commands().entity(id).clone_and_spawn_with(|builder| { - builder.recursive(true); - }); + b.iter(|| { + world + .commands() + .entity(black_box(id)) + .clone_and_spawn_with(|builder| { + // Make the clone command recursive, so children are cloned as well. + builder.recursive(true); + }); + world.flush(); }); } -fn simple(b: &mut Bencher, clone_via_reflect: bool) { - let mut world = World::default(); - let registry = AppTypeRegistry::default(); - { - let mut r = registry.write(); - r.register::(); +// Each benchmark runs twice: using either the `Clone` or `Reflect` traits to clone entities. This +// constant represents this as an easy array that can be used in a `for` loop. +const SCENARIOS: [(&str, bool); 2] = [("clone", false), ("reflect", true)]; + +/// Benchmarks cloning a single entity with 10 components and no children. +fn single(c: &mut Criterion) { + let mut group = c.benchmark_group(bench!("single")); + + // We're cloning 1 entity. + group.throughput(Throughput::Elements(1)); + + for (id, clone_via_reflect) in SCENARIOS { + group.bench_function(id, |b| { + bench_clone::(b, clone_via_reflect); + }); } - world.insert_resource(registry); - world.register_bundle::(); - if clone_via_reflect { - let mut components = Vec::new(); - C::get_component_ids(world.components(), &mut |id| components.push(id.unwrap())); - for component in components { - world - .get_component_clone_handlers_mut() - .set_component_handler( - component, - bevy_ecs::component::ComponentCloneHandler::reflect_handler(), - ); - } + + group.finish(); +} + +/// Benchmarks cloning an an entity and its 50 descendents, each with only 1 component. +fn hierarchy_tall(c: &mut Criterion) { + let mut group = c.benchmark_group(bench!("hierarchy_tall")); + + // We're cloning both the root entity and its 50 descendents. + group.throughput(Throughput::Elements(51)); + + for (id, clone_via_reflect) in SCENARIOS { + group.bench_function(id, |b| { + bench_clone_hierarchy::(b, 50, 1, clone_via_reflect); + }); } - let id = world.spawn(black_box(C::default())).id(); - b.iter(move || { - world.commands().entity(id).clone_and_spawn(); - world.flush(); - }); + group.finish(); } -fn reflect_benches(c: &mut Criterion) { - c.bench_function("many components reflect", |b| { - simple::(b, true); - }); +/// Benchmarks cloning an an entity and its 50 direct children, each with only 1 component. +fn hierarchy_wide(c: &mut Criterion) { + let mut group = c.benchmark_group(bench!("hierarchy_wide")); - c.bench_function("hierarchy wide reflect", |b| { - hierarchy::(b, 10, 4, true); - }); + // We're cloning both the root entity and its 50 direct children. + group.throughput(Throughput::Elements(51)); - c.bench_function("hierarchy tall reflect", |b| { - hierarchy::(b, 1, 50, true); - }); + for (id, clone_via_reflect) in SCENARIOS { + group.bench_function(id, |b| { + bench_clone_hierarchy::(b, 1, 50, clone_via_reflect); + }); + } - c.bench_function("hierarchy many reflect", |b| { - hierarchy::(b, 5, 5, true); - }); + group.finish(); } -fn clone_benches(c: &mut Criterion) { - c.bench_function("many components clone", |b| { - simple::(b, false); - }); +/// Benchmarks cloning a large hierarchy of entities with several children each. Each entity has 10 +/// components. +fn hierarchy_many(c: &mut Criterion) { + let mut group = c.benchmark_group(bench!("hierarchy_many")); - c.bench_function("hierarchy wide clone", |b| { - hierarchy::(b, 10, 4, false); - }); + // We're cloning 364 entities total. This number was calculated by manually counting the number + // of entities spawned in `bench_clone_hierarchy()` with a `println!()` statement. :) + group.throughput(Throughput::Elements(364)); - c.bench_function("hierarchy tall clone", |b| { - hierarchy::(b, 1, 50, false); - }); + for (id, clone_via_reflect) in SCENARIOS { + group.bench_function(id, |b| { + bench_clone_hierarchy::(b, 5, 3, clone_via_reflect); + }); + } - c.bench_function("hierarchy many clone", |b| { - hierarchy::(b, 5, 5, false); - }); + group.finish(); } diff --git a/benches/benches/bevy_ecs/main.rs b/benches/benches/bevy_ecs/main.rs index 83f0cde0286d6..9fb9e55729e88 100644 --- a/benches/benches/bevy_ecs/main.rs +++ b/benches/benches/bevy_ecs/main.rs @@ -9,6 +9,7 @@ use criterion::criterion_main; mod change_detection; mod components; mod empty_archetypes; +mod entity_cloning; mod events; mod fragmentation; mod iteration; @@ -21,6 +22,7 @@ criterion_main!( change_detection::benches, components::benches, empty_archetypes::benches, + entity_cloning::benches, events::benches, iteration::benches, fragmentation::benches, From 22bf3b9a6202e023918351276c794dd45b5aecdc Mon Sep 17 00:00:00 2001 From: arunke Date: Thu, 2 Jan 2025 14:38:26 -0800 Subject: [PATCH 106/272] Fix documentation for system set method (#17106) # Objective Fix incorrect comment on `IntoSystemSetConfigs::after` likely caused by copy-paste error. It said "before" instead of "after". ## Solution Update the comment to the correct text. ## Testing CI tests pass. This is just updating a comment. --- crates/bevy_ecs/src/schedule/config.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_ecs/src/schedule/config.rs b/crates/bevy_ecs/src/schedule/config.rs index 7dc8ea03f798e..97b6392f85f01 100644 --- a/crates/bevy_ecs/src/schedule/config.rs +++ b/crates/bevy_ecs/src/schedule/config.rs @@ -637,7 +637,7 @@ where self.into_configs().before(set) } - /// Runs before all systems in `set`. If `set` has any systems that produce [`Commands`](crate::system::Commands) + /// Runs after all systems in `set`. If `set` has any systems that produce [`Commands`](crate::system::Commands) /// or other [`Deferred`](crate::system::Deferred) operations, all systems in `self` will see their effect. /// /// If automatically inserting [`ApplyDeferred`](crate::schedule::ApplyDeferred) like From 5a5ddb9e35655520d4fa13572535e5053274bbf3 Mon Sep 17 00:00:00 2001 From: MichiRecRoom <1008889+LikeLakers2@users.noreply.github.com> Date: Thu, 2 Jan 2025 17:42:25 -0500 Subject: [PATCH 107/272] bevy_tasks: Apply `#[deny(clippy::allow_attributes, clippy::allow_attributes_without_reason)]` (#17089) # Objective We want to deny the following lints: * `clippy::allow_attributes` - Because there's no reason to `#[allow(...)]` an attribute if it wouldn't lint against anything; you should always use `#[expect(...)]` * `clippy::allow_attributes_without_reason` - Because documenting the reason for allowing/expecting a lint is always good ## Solution Set the `clippy::allow_attributes` and `clippy::allow_attributes_without_reason` lints to `deny`, and bring `bevy_tasks` in line with the new restrictions. No code changes have been made - except if a lint that was previously `allow(...)`'d could be removed via small code changes. For example, `unused_variables` can be handled by adding a `_` to the beginning of a field's name. ## Testing I ran `cargo clippy`, and received no errors. --- crates/bevy_tasks/src/executor.rs | 2 ++ crates/bevy_tasks/src/lib.rs | 1 + crates/bevy_tasks/src/task_pool.rs | 1 - 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/crates/bevy_tasks/src/executor.rs b/crates/bevy_tasks/src/executor.rs index e545daf84d734..3c18ccd897fa1 100644 --- a/crates/bevy_tasks/src/executor.rs +++ b/crates/bevy_tasks/src/executor.rs @@ -50,6 +50,7 @@ pub struct LocalExecutor<'a>(LocalExecutorInner<'a>); impl Executor<'_> { /// Construct a new [`Executor`] + #[expect(clippy::allow_attributes, reason = "This lint may not always trigger.")] #[allow(dead_code, reason = "not all feature flags require this function")] pub const fn new() -> Self { Self(ExecutorInner::new()) @@ -58,6 +59,7 @@ impl Executor<'_> { impl LocalExecutor<'_> { /// Construct a new [`LocalExecutor`] + #[expect(clippy::allow_attributes, reason = "This lint may not always trigger.")] #[allow(dead_code, reason = "not all feature flags require this function")] pub const fn new() -> Self { Self(LocalExecutorInner::new()) diff --git a/crates/bevy_tasks/src/lib.rs b/crates/bevy_tasks/src/lib.rs index 1be5761bd0f86..97b13b0052b93 100644 --- a/crates/bevy_tasks/src/lib.rs +++ b/crates/bevy_tasks/src/lib.rs @@ -5,6 +5,7 @@ html_favicon_url = "https://bevyengine.org/assets/icon.png" )] #![cfg_attr(not(feature = "std"), no_std)] +#![deny(clippy::allow_attributes, clippy::allow_attributes_without_reason)] extern crate alloc; diff --git a/crates/bevy_tasks/src/task_pool.rs b/crates/bevy_tasks/src/task_pool.rs index 215981215f2f7..94f59bbce99ac 100644 --- a/crates/bevy_tasks/src/task_pool.rs +++ b/crates/bevy_tasks/src/task_pool.rs @@ -694,7 +694,6 @@ where } #[cfg(test)] -#[allow(clippy::disallowed_types)] mod tests { use super::*; use core::sync::atomic::{AtomicBool, AtomicI32, Ordering}; From 0403948aa23a748abd2a2aac05eef1209d66674e Mon Sep 17 00:00:00 2001 From: Zachary Harrold Date: Fri, 3 Jan 2025 12:58:43 +1100 Subject: [PATCH 108/272] Remove Implicit `std` Prelude from `no_std` Crates (#17086) # Background In `no_std` compatible crates, there is often an `std` feature which will allow access to the standard library. Currently, with the `std` feature _enabled_, the [`std::prelude`](https://doc.rust-lang.org/std/prelude/index.html) is implicitly imported in all modules. With the feature _disabled_, instead the [`core::prelude`](https://doc.rust-lang.org/core/prelude/index.html) is implicitly imported. This creates a subtle and pervasive issue where `alloc` items _may_ be implicitly included (if `std` is enabled), or must be explicitly included (if `std` is not enabled). # Objective - Make the implicit imports for `no_std` crates consistent regardless of what features are/not enabled. ## Solution - Replace the `cfg_attr` "double negative" `no_std` attribute with conditional compilation to _include_ `std` as an external crate. ```rust // Before #![cfg_attr(not(feature = "std"), no_std)] // After #![no_std] #[cfg(feature = "std")] extern crate std; ``` - Fix imports that are currently broken but are only now visible with the above fix. ## Testing - CI ## Notes I had previously used the "double negative" version of `no_std` based on general consensus that it was "cleaner" within the Rust embedded community. However, this implicit prelude issue likely was considered when forming this consensus. I believe the reason why is the items most affected by this issue are provided by the `alloc` crate, which is rarely used within embedded but extensively used within Bevy. --- crates/bevy_app/src/lib.rs | 5 +++- crates/bevy_app/src/plugin_group.rs | 1 + crates/bevy_color/src/lib.rs | 5 +++- crates/bevy_ecs/src/bundle.rs | 1 + crates/bevy_ecs/src/entity/clone_entities.rs | 2 ++ crates/bevy_ecs/src/entity/entity_set.rs | 3 +++ crates/bevy_ecs/src/entity/mod.rs | 3 ++- crates/bevy_ecs/src/entity/visit_entities.rs | 1 + crates/bevy_ecs/src/event/mod.rs | 1 + crates/bevy_ecs/src/intern.rs | 1 + crates/bevy_ecs/src/lib.rs | 12 +++++++-- crates/bevy_ecs/src/name.rs | 1 + crates/bevy_ecs/src/observer/mod.rs | 2 +- crates/bevy_ecs/src/query/access.rs | 1 + crates/bevy_ecs/src/query/builder.rs | 1 + crates/bevy_ecs/src/query/error.rs | 1 + crates/bevy_ecs/src/query/iter.rs | 3 +++ crates/bevy_ecs/src/query/mod.rs | 3 ++- crates/bevy_ecs/src/query/state.rs | 1 + .../bevy_ecs/src/reflect/entity_commands.rs | 1 + .../src/schedule/executor/multi_threaded.rs | 5 +++- .../bevy_ecs/src/schedule/executor/simple.rs | 4 +++ .../src/schedule/executor/single_threaded.rs | 4 +++ .../bevy_ecs/src/schedule/graph/graph_map.rs | 1 + crates/bevy_ecs/src/schedule/mod.rs | 1 + crates/bevy_ecs/src/schedule/stepping.rs | 2 ++ crates/bevy_ecs/src/storage/blob_array.rs | 8 ++++++ crates/bevy_ecs/src/storage/blob_vec.rs | 8 +++--- crates/bevy_ecs/src/storage/sparse_set.rs | 1 + crates/bevy_ecs/src/storage/table/mod.rs | 2 ++ crates/bevy_ecs/src/storage/thin_array_ptr.rs | 2 ++ crates/bevy_ecs/src/system/builder.rs | 1 + crates/bevy_ecs/src/system/commands/mod.rs | 2 +- .../src/system/exclusive_system_param.rs | 1 + crates/bevy_ecs/src/system/mod.rs | 4 +++ crates/bevy_ecs/src/system/system_name.rs | 1 + crates/bevy_ecs/src/world/command_queue.rs | 5 +++- crates/bevy_ecs/src/world/deferred_world.rs | 2 ++ crates/bevy_ecs/src/world/entity_ref.rs | 2 ++ crates/bevy_ecs/src/world/identifier.rs | 1 + crates/bevy_ecs/src/world/mod.rs | 10 +++++-- crates/bevy_hierarchy/src/child_builder.rs | 1 + crates/bevy_hierarchy/src/hierarchy.rs | 1 + crates/bevy_hierarchy/src/lib.rs | 5 +++- crates/bevy_hierarchy/src/query_extension.rs | 1 + crates/bevy_input/src/gamepad.rs | 1 + crates/bevy_input/src/lib.rs | 5 +++- .../src/bounding/bounded2d/primitive_impls.rs | 1 + crates/bevy_math/src/cubic_splines/mod.rs | 14 ++++++++++ crates/bevy_math/src/curve/adaptors.rs | 5 +++- crates/bevy_math/src/curve/cores.rs | 1 + crates/bevy_math/src/curve/interval.rs | 1 + crates/bevy_math/src/curve/mod.rs | 1 + crates/bevy_math/src/curve/sample_curves.rs | 2 ++ crates/bevy_math/src/direction.rs | 4 +++ crates/bevy_math/src/lib.rs | 5 +++- crates/bevy_mikktspace/src/lib.rs | 5 +++- .../derive/src/impls/func/into_return.rs | 2 +- crates/bevy_reflect/src/array.rs | 2 ++ crates/bevy_reflect/src/attributes.rs | 1 + crates/bevy_reflect/src/enums/mod.rs | 1 + crates/bevy_reflect/src/func/args/arg.rs | 5 +--- crates/bevy_reflect/src/func/args/error.rs | 3 --- crates/bevy_reflect/src/func/args/from_arg.rs | 3 --- crates/bevy_reflect/src/func/args/info.rs | 3 --- crates/bevy_reflect/src/func/args/list.rs | 4 +-- .../bevy_reflect/src/func/args/ownership.rs | 3 --- .../bevy_reflect/src/func/dynamic_function.rs | 4 +-- .../src/func/dynamic_function_internal.rs | 2 +- .../src/func/dynamic_function_mut.rs | 4 +-- crates/bevy_reflect/src/func/error.rs | 3 --- crates/bevy_reflect/src/func/function.rs | 4 +-- crates/bevy_reflect/src/func/info.rs | 5 +--- crates/bevy_reflect/src/func/into_function.rs | 3 --- .../src/func/into_function_mut.rs | 3 --- crates/bevy_reflect/src/func/macros.rs | 3 --- crates/bevy_reflect/src/func/reflect_fn.rs | 3 --- .../bevy_reflect/src/func/reflect_fn_mut.rs | 3 --- crates/bevy_reflect/src/func/registry.rs | 4 +-- crates/bevy_reflect/src/func/return_type.rs | 3 --- crates/bevy_reflect/src/func/signature.rs | 2 ++ crates/bevy_reflect/src/generics.rs | 1 + crates/bevy_reflect/src/impls/glam.rs | 2 +- crates/bevy_reflect/src/impls/std.rs | 2 +- crates/bevy_reflect/src/kind.rs | 1 + crates/bevy_reflect/src/lib.rs | 26 +++++++++++++++++-- crates/bevy_reflect/src/list.rs | 1 + crates/bevy_reflect/src/map.rs | 4 +++ crates/bevy_reflect/src/path/mod.rs | 1 + .../bevy_reflect/src/serde/de/error_utils.rs | 3 +++ crates/bevy_reflect/src/serde/de/mod.rs | 9 +++++-- crates/bevy_reflect/src/serde/mod.rs | 2 +- .../bevy_reflect/src/serde/ser/error_utils.rs | 3 +++ crates/bevy_reflect/src/serde/ser/mod.rs | 7 +++++ crates/bevy_reflect/src/set.rs | 1 + crates/bevy_reflect/src/type_info.rs | 3 +++ crates/bevy_reflect/src/type_info_stack.rs | 4 +-- crates/bevy_reflect/src/utility.rs | 1 + crates/bevy_state/src/lib.rs | 5 +++- crates/bevy_state/src/state/mod.rs | 1 + crates/bevy_tasks/src/lib.rs | 5 +++- .../src/single_threaded_task_pool.rs | 3 +++ crates/bevy_tasks/src/slice.rs | 1 + crates/bevy_tasks/src/task_pool.rs | 8 ++++-- crates/bevy_transform/src/helper.rs | 1 + crates/bevy_transform/src/lib.rs | 5 +++- crates/bevy_transform/src/systems.rs | 1 + crates/bevy_utils/src/lib.rs | 13 ++++++---- crates/bevy_utils/src/parallel_queue.rs | 2 -- crates/bevy_utils/src/synccell.rs | 2 ++ crates/bevy_utils/src/syncunsafecell.rs | 2 ++ crates/bevy_window/src/lib.rs | 5 +++- .../window-settings-wasm.patch | 2 +- 113 files changed, 267 insertions(+), 102 deletions(-) diff --git a/crates/bevy_app/src/lib.rs b/crates/bevy_app/src/lib.rs index b9e2902961e82..48aa26ec51cf5 100644 --- a/crates/bevy_app/src/lib.rs +++ b/crates/bevy_app/src/lib.rs @@ -12,10 +12,13 @@ html_logo_url = "https://bevyengine.org/assets/icon.png", html_favicon_url = "https://bevyengine.org/assets/icon.png" )] -#![cfg_attr(not(feature = "std"), no_std)] +#![no_std] //! This crate is about everything concerning the highest-level, application layer of a Bevy app. +#[cfg(feature = "std")] +extern crate std; + extern crate alloc; mod app; diff --git a/crates/bevy_app/src/plugin_group.rs b/crates/bevy_app/src/plugin_group.rs index 312b94ad91847..49d05f32290ba 100644 --- a/crates/bevy_app/src/plugin_group.rs +++ b/crates/bevy_app/src/plugin_group.rs @@ -554,6 +554,7 @@ impl PluginGroup for NoopPluginGroup { #[cfg(test)] mod tests { + use alloc::vec; use core::{any::TypeId, fmt::Debug}; use super::PluginGroupBuilder; diff --git a/crates/bevy_color/src/lib.rs b/crates/bevy_color/src/lib.rs index 4a4a9596d545d..e80e68bb9e078 100644 --- a/crates/bevy_color/src/lib.rs +++ b/crates/bevy_color/src/lib.rs @@ -4,7 +4,7 @@ html_logo_url = "https://bevyengine.org/assets/icon.png", html_favicon_url = "https://bevyengine.org/assets/icon.png" )] -#![cfg_attr(not(feature = "std"), no_std)] +#![no_std] //! Representations of colors in various color spaces. //! @@ -90,6 +90,9 @@ //! println!("Hsla: {:?}", hsla); //! ``` +#[cfg(feature = "std")] +extern crate std; + #[cfg(feature = "alloc")] extern crate alloc; diff --git a/crates/bevy_ecs/src/bundle.rs b/crates/bevy_ecs/src/bundle.rs index 14d80cf9aba2f..9a0403f2d9aae 100644 --- a/crates/bevy_ecs/src/bundle.rs +++ b/crates/bevy_ecs/src/bundle.rs @@ -1643,6 +1643,7 @@ fn sorted_remove(source: &mut Vec, remove: &[T]) { mod tests { use crate as bevy_ecs; use crate::{component::ComponentId, prelude::*, world::DeferredWorld}; + use alloc::vec; #[derive(Component)] struct A; diff --git a/crates/bevy_ecs/src/entity/clone_entities.rs b/crates/bevy_ecs/src/entity/clone_entities.rs index 4a326cc955d75..2b51c1f937b34 100644 --- a/crates/bevy_ecs/src/entity/clone_entities.rs +++ b/crates/bevy_ecs/src/entity/clone_entities.rs @@ -671,6 +671,7 @@ mod tests { entity::EntityCloneBuilder, world::{DeferredWorld, World}, }; + use alloc::vec::Vec; use bevy_ecs_macros::require; use bevy_ptr::OwningPtr; use core::alloc::Layout; @@ -679,6 +680,7 @@ mod tests { mod reflect { use super::*; use crate::reflect::{AppTypeRegistry, ReflectComponent, ReflectFromWorld}; + use alloc::vec; use bevy_reflect::{std_traits::ReflectDefault, FromType, Reflect, ReflectFromPtr}; #[test] diff --git a/crates/bevy_ecs/src/entity/entity_set.rs b/crates/bevy_ecs/src/entity/entity_set.rs index 34e48551fde7b..be8ac88d04595 100644 --- a/crates/bevy_ecs/src/entity/entity_set.rs +++ b/crates/bevy_ecs/src/entity/entity_set.rs @@ -135,6 +135,7 @@ unsafe impl TrustedEntityBorrow for Arc {} /// [`into_iter()`]: IntoIterator::into_iter /// [`iter_many_unique`]: crate::system::Query::iter_many_unique /// [`iter_many_unique_mut`]: crate::system::Query::iter_many_unique_mut +/// [`Vec`]: alloc::vec::Vec pub trait EntitySet: IntoIterator {} impl> EntitySet for T {} @@ -379,6 +380,8 @@ impl + Debug> Debug for UniqueEntityIter< #[cfg(test)] mod tests { + use alloc::{vec, vec::Vec}; + #[allow(unused_imports)] use crate::prelude::{Schedule, World}; diff --git a/crates/bevy_ecs/src/entity/mod.rs b/crates/bevy_ecs/src/entity/mod.rs index 8d5489add0846..1a177e7ff5e86 100644 --- a/crates/bevy_ecs/src/entity/mod.rs +++ b/crates/bevy_ecs/src/entity/mod.rs @@ -74,7 +74,7 @@ use core::{fmt, hash::Hash, mem, num::NonZero}; use log::warn; #[cfg(feature = "track_location")] -use core::panic::Location; +use {alloc::format, core::panic::Location}; #[cfg(feature = "serialize")] use serde::{Deserialize, Serialize}; @@ -1065,6 +1065,7 @@ impl EntityLocation { #[cfg(test)] mod tests { use super::*; + use alloc::format; #[test] fn entity_niche_optimization() { diff --git a/crates/bevy_ecs/src/entity/visit_entities.rs b/crates/bevy_ecs/src/entity/visit_entities.rs index abce76853d403..a9f5e8dcbd847 100644 --- a/crates/bevy_ecs/src/entity/visit_entities.rs +++ b/crates/bevy_ecs/src/entity/visit_entities.rs @@ -61,6 +61,7 @@ mod tests { entity::{EntityHashMap, MapEntities, SceneEntityMapper}, world::World, }; + use alloc::{string::String, vec, vec::Vec}; use bevy_utils::HashSet; use super::*; diff --git a/crates/bevy_ecs/src/event/mod.rs b/crates/bevy_ecs/src/event/mod.rs index 46b04c8a18ae6..1dbcf1ba0cb14 100644 --- a/crates/bevy_ecs/src/event/mod.rs +++ b/crates/bevy_ecs/src/event/mod.rs @@ -32,6 +32,7 @@ pub use writer::EventWriter; #[cfg(test)] mod tests { use crate as bevy_ecs; + use alloc::{vec, vec::Vec}; use bevy_ecs::{event::*, system::assert_is_read_only_system}; use bevy_ecs_macros::Event; diff --git a/crates/bevy_ecs/src/intern.rs b/crates/bevy_ecs/src/intern.rs index e606b0d546315..18e866e87d953 100644 --- a/crates/bevy_ecs/src/intern.rs +++ b/crates/bevy_ecs/src/intern.rs @@ -180,6 +180,7 @@ impl Default for Interner { #[cfg(test)] mod tests { + use alloc::{boxed::Box, string::ToString}; use bevy_utils::FixedHasher; use core::hash::{BuildHasher, Hash, Hasher}; diff --git a/crates/bevy_ecs/src/lib.rs b/crates/bevy_ecs/src/lib.rs index ab949045ef5b6..ef5db63779e1e 100644 --- a/crates/bevy_ecs/src/lib.rs +++ b/crates/bevy_ecs/src/lib.rs @@ -16,7 +16,10 @@ html_logo_url = "https://bevyengine.org/assets/icon.png", html_favicon_url = "https://bevyengine.org/assets/icon.png" )] -#![cfg_attr(not(feature = "std"), no_std)] +#![no_std] + +#[cfg(feature = "std")] +extern crate std; #[cfg(target_pointer_width = "16")] compile_error!("bevy_ecs cannot safely compile for a 16-bit platform."); @@ -120,7 +123,12 @@ mod tests { system::Resource, world::{EntityMut, EntityRef, Mut, World}, }; - use alloc::{sync::Arc, vec}; + use alloc::{ + string::{String, ToString}, + sync::Arc, + vec, + vec::Vec, + }; use bevy_ecs_macros::{VisitEntities, VisitEntitiesMut}; use bevy_tasks::{ComputeTaskPool, TaskPool}; use bevy_utils::HashSet; diff --git a/crates/bevy_ecs/src/name.rs b/crates/bevy_ecs/src/name.rs index 4697bb50e9435..05bc0e8c9cd8e 100644 --- a/crates/bevy_ecs/src/name.rs +++ b/crates/bevy_ecs/src/name.rs @@ -264,6 +264,7 @@ impl<'de> Visitor<'de> for NameVisitor { mod tests { use super::*; use crate::world::World; + use alloc::string::ToString; #[test] fn test_display_of_debug_name() { diff --git a/crates/bevy_ecs/src/observer/mod.rs b/crates/bevy_ecs/src/observer/mod.rs index 2f690973481f3..8194c1e04ea65 100644 --- a/crates/bevy_ecs/src/observer/mod.rs +++ b/crates/bevy_ecs/src/observer/mod.rs @@ -744,7 +744,7 @@ impl World { #[cfg(test)] mod tests { - use alloc::vec; + use alloc::{vec, vec::Vec}; use bevy_ptr::OwningPtr; use bevy_utils::HashMap; diff --git a/crates/bevy_ecs/src/query/access.rs b/crates/bevy_ecs/src/query/access.rs index a526d4765e39e..be97d19c6845c 100644 --- a/crates/bevy_ecs/src/query/access.rs +++ b/crates/bevy_ecs/src/query/access.rs @@ -1338,6 +1338,7 @@ mod tests { use crate::query::{ access::AccessFilters, Access, AccessConflicts, FilteredAccess, FilteredAccessSet, }; + use alloc::vec; use core::marker::PhantomData; use fixedbitset::FixedBitSet; diff --git a/crates/bevy_ecs/src/query/builder.rs b/crates/bevy_ecs/src/query/builder.rs index aeb328afe6cb0..55473cf050209 100644 --- a/crates/bevy_ecs/src/query/builder.rs +++ b/crates/bevy_ecs/src/query/builder.rs @@ -278,6 +278,7 @@ impl<'w, D: QueryData, F: QueryFilter> QueryBuilder<'w, D, F> { mod tests { use crate as bevy_ecs; use crate::{prelude::*, world::FilteredEntityRef}; + use std::dbg; #[derive(Component, PartialEq, Debug)] struct A(usize); diff --git a/crates/bevy_ecs/src/query/error.rs b/crates/bevy_ecs/src/query/error.rs index 9746471c66852..b2dafc64a9bf8 100644 --- a/crates/bevy_ecs/src/query/error.rs +++ b/crates/bevy_ecs/src/query/error.rs @@ -119,6 +119,7 @@ pub enum QuerySingleError { mod test { use crate as bevy_ecs; use crate::prelude::World; + use alloc::format; use bevy_ecs_macros::Component; #[test] diff --git a/crates/bevy_ecs/src/query/iter.rs b/crates/bevy_ecs/src/query/iter.rs index c7303382f68d8..0f4cfe2bed3e1 100644 --- a/crates/bevy_ecs/src/query/iter.rs +++ b/crates/bevy_ecs/src/query/iter.rs @@ -2950,6 +2950,9 @@ impl Ord for NeutralOrd { #[cfg(test)] mod tests { + use alloc::vec::Vec; + use std::println; + #[allow(unused_imports)] use crate::component::Component; #[allow(unused_imports)] diff --git a/crates/bevy_ecs/src/query/mod.rs b/crates/bevy_ecs/src/query/mod.rs index c6c1383ceb7b9..720489d6d15bf 100644 --- a/crates/bevy_ecs/src/query/mod.rs +++ b/crates/bevy_ecs/src/query/mod.rs @@ -117,9 +117,10 @@ mod tests { system::{assert_is_system, IntoSystem, Query, System, SystemState}, world::{unsafe_world_cell::UnsafeWorldCell, World}, }; + use alloc::{vec, vec::Vec}; use bevy_ecs_macros::QueryFilter; use core::{any::type_name, fmt::Debug, hash::Hash}; - use std::collections::HashSet; + use std::{collections::HashSet, println}; #[derive(Component, Debug, Hash, Eq, PartialEq, Clone, Copy, PartialOrd, Ord)] struct A(usize); diff --git a/crates/bevy_ecs/src/query/state.rs b/crates/bevy_ecs/src/query/state.rs index 36d82772d4783..b6025e5d7da5d 100644 --- a/crates/bevy_ecs/src/query/state.rs +++ b/crates/bevy_ecs/src/query/state.rs @@ -1883,6 +1883,7 @@ mod tests { use crate::{ component::Component, prelude::*, query::QueryEntityError, world::FilteredEntityRef, }; + use alloc::vec::Vec; #[test] fn get_many_unchecked_manual_uniqueness() { diff --git a/crates/bevy_ecs/src/reflect/entity_commands.rs b/crates/bevy_ecs/src/reflect/entity_commands.rs index 0d77e4b4b873d..a6346b3a69353 100644 --- a/crates/bevy_ecs/src/reflect/entity_commands.rs +++ b/crates/bevy_ecs/src/reflect/entity_commands.rs @@ -403,6 +403,7 @@ mod tests { system::{Commands, SystemState}, world::World, }; + use alloc::{borrow::ToOwned, boxed::Box}; use bevy_ecs_macros::Resource; use bevy_reflect::{PartialReflect, Reflect, TypeRegistry}; diff --git a/crates/bevy_ecs/src/schedule/executor/multi_threaded.rs b/crates/bevy_ecs/src/schedule/executor/multi_threaded.rs index 87f58a52de4fb..1185931fd64ac 100644 --- a/crates/bevy_ecs/src/schedule/executor/multi_threaded.rs +++ b/crates/bevy_ecs/src/schedule/executor/multi_threaded.rs @@ -4,7 +4,10 @@ use bevy_utils::{default, syncunsafecell::SyncUnsafeCell}; use concurrent_queue::ConcurrentQueue; use core::{any::Any, panic::AssertUnwindSafe}; use fixedbitset::FixedBitSet; -use std::sync::{Mutex, MutexGuard}; +use std::{ + eprintln, + sync::{Mutex, MutexGuard}, +}; #[cfg(feature = "trace")] use tracing::{info_span, Span}; diff --git a/crates/bevy_ecs/src/schedule/executor/simple.rs b/crates/bevy_ecs/src/schedule/executor/simple.rs index 91a3d83b3df14..122414f45e07b 100644 --- a/crates/bevy_ecs/src/schedule/executor/simple.rs +++ b/crates/bevy_ecs/src/schedule/executor/simple.rs @@ -1,8 +1,12 @@ use core::panic::AssertUnwindSafe; use fixedbitset::FixedBitSet; + #[cfg(feature = "trace")] use tracing::info_span; +#[cfg(feature = "std")] +use std::eprintln; + use crate::{ schedule::{ executor::is_apply_deferred, BoxedCondition, ExecutorKind, SystemExecutor, SystemSchedule, diff --git a/crates/bevy_ecs/src/schedule/executor/single_threaded.rs b/crates/bevy_ecs/src/schedule/executor/single_threaded.rs index d92519ee63f24..b42b419e9fe37 100644 --- a/crates/bevy_ecs/src/schedule/executor/single_threaded.rs +++ b/crates/bevy_ecs/src/schedule/executor/single_threaded.rs @@ -1,8 +1,12 @@ use core::panic::AssertUnwindSafe; use fixedbitset::FixedBitSet; + #[cfg(feature = "trace")] use tracing::info_span; +#[cfg(feature = "std")] +use std::eprintln; + use crate::{ schedule::{is_apply_deferred, BoxedCondition, ExecutorKind, SystemExecutor, SystemSchedule}, world::World, diff --git a/crates/bevy_ecs/src/schedule/graph/graph_map.rs b/crates/bevy_ecs/src/schedule/graph/graph_map.rs index 91ced56e5d251..e24926499f106 100644 --- a/crates/bevy_ecs/src/schedule/graph/graph_map.rs +++ b/crates/bevy_ecs/src/schedule/graph/graph_map.rs @@ -393,6 +393,7 @@ impl CompactNodeIdPair { #[cfg(test)] mod tests { use super::*; + use alloc::vec; /// The `Graph` type _must_ preserve the order that nodes are inserted in if /// no removals occur. Removals are permitted to swap the latest node into the diff --git a/crates/bevy_ecs/src/schedule/mod.rs b/crates/bevy_ecs/src/schedule/mod.rs index dc2a907949bd1..4a5b5a09912b6 100644 --- a/crates/bevy_ecs/src/schedule/mod.rs +++ b/crates/bevy_ecs/src/schedule/mod.rs @@ -17,6 +17,7 @@ pub use self::graph::NodeId; #[cfg(test)] mod tests { use super::*; + use alloc::{string::ToString, vec, vec::Vec}; use core::sync::atomic::{AtomicU32, Ordering}; pub use crate as bevy_ecs; diff --git a/crates/bevy_ecs/src/schedule/stepping.rs b/crates/bevy_ecs/src/schedule/stepping.rs index df71139db400e..867fb5f9870eb 100644 --- a/crates/bevy_ecs/src/schedule/stepping.rs +++ b/crates/bevy_ecs/src/schedule/stepping.rs @@ -823,6 +823,8 @@ impl ScheduleState { mod tests { use super::*; use crate::{prelude::*, schedule::ScheduleLabel}; + use alloc::{format, vec}; + use std::println; pub use crate as bevy_ecs; diff --git a/crates/bevy_ecs/src/storage/blob_array.rs b/crates/bevy_ecs/src/storage/blob_array.rs index c508b78c9853c..86315386a8d1f 100644 --- a/crates/bevy_ecs/src/storage/blob_array.rs +++ b/crates/bevy_ecs/src/storage/blob_array.rs @@ -77,6 +77,8 @@ impl BlobArray { /// # Safety /// - The element at index `index` is safe to access. /// (If the safety requirements of every method that has been used on `Self` have been fulfilled, the caller just needs to ensure that `index` < `len`) + /// + /// [`Vec::len`]: alloc::vec::Vec::len #[inline] pub unsafe fn get_unchecked(&self, index: usize) -> Ptr<'_> { #[cfg(debug_assertions)] @@ -98,6 +100,8 @@ impl BlobArray { /// # Safety /// - The element with at index `index` is safe to access. /// (If the safety requirements of every method that has been used on `Self` have been fulfilled, the caller just needs to ensure that `index` < `len`) + /// + /// [`Vec::len`]: alloc::vec::Vec::len #[inline] pub unsafe fn get_unchecked_mut(&mut self, index: usize) -> PtrMut<'_> { #[cfg(debug_assertions)] @@ -134,6 +138,8 @@ impl BlobArray { /// # Safety /// - The type `T` must be the type of the items in this [`BlobArray`]. /// - `slice_len` <= `len` + /// + /// [`Vec::len`]: alloc::vec::Vec::len pub unsafe fn get_sub_slice(&self, slice_len: usize) -> &[UnsafeCell] { #[cfg(debug_assertions)] debug_assert!(slice_len <= self.capacity); @@ -151,6 +157,8 @@ impl BlobArray { /// # Safety /// - For every element with index `i`, if `i` < `len`: It must be safe to call [`Self::get_unchecked_mut`] with `i`. /// (If the safety requirements of every method that has been used on `Self` have been fulfilled, the caller just needs to ensure that `len` is correct.) + /// + /// [`Vec::clear`]: alloc::vec::Vec::clear pub unsafe fn clear(&mut self, len: usize) { #[cfg(debug_assertions)] debug_assert!(self.capacity >= len); diff --git a/crates/bevy_ecs/src/storage/blob_vec.rs b/crates/bevy_ecs/src/storage/blob_vec.rs index d42c63a6f1605..51a3d49e3c08d 100644 --- a/crates/bevy_ecs/src/storage/blob_vec.rs +++ b/crates/bevy_ecs/src/storage/blob_vec.rs @@ -497,11 +497,13 @@ const fn padding_needed_for(layout: &Layout, align: usize) -> usize { #[cfg(test)] mod tests { + use super::BlobVec; use crate as bevy_ecs; // required for derive macros use crate::{component::Component, ptr::OwningPtr, world::World}; - - use super::BlobVec; - use alloc::rc::Rc; + use alloc::{ + rc::Rc, + string::{String, ToString}, + }; use core::{alloc::Layout, cell::RefCell}; /// # Safety diff --git a/crates/bevy_ecs/src/storage/sparse_set.rs b/crates/bevy_ecs/src/storage/sparse_set.rs index 55034ab98ad3d..518333fa272d8 100644 --- a/crates/bevy_ecs/src/storage/sparse_set.rs +++ b/crates/bevy_ecs/src/storage/sparse_set.rs @@ -666,6 +666,7 @@ mod tests { entity::Entity, storage::SparseSet, }; + use alloc::{vec, vec::Vec}; #[derive(Debug, Eq, PartialEq)] struct Foo(usize); diff --git a/crates/bevy_ecs/src/storage/table/mod.rs b/crates/bevy_ecs/src/storage/table/mod.rs index 65e6eff55219e..ce33890751ccd 100644 --- a/crates/bevy_ecs/src/storage/table/mod.rs +++ b/crates/bevy_ecs/src/storage/table/mod.rs @@ -822,6 +822,8 @@ mod tests { ptr::OwningPtr, storage::{Storages, TableBuilder, TableId, TableRow, Tables}, }; + use alloc::vec::Vec; + #[cfg(feature = "track_location")] use core::panic::Location; diff --git a/crates/bevy_ecs/src/storage/thin_array_ptr.rs b/crates/bevy_ecs/src/storage/thin_array_ptr.rs index 9c073324559d3..5654b6da67944 100644 --- a/crates/bevy_ecs/src/storage/thin_array_ptr.rs +++ b/crates/bevy_ecs/src/storage/thin_array_ptr.rs @@ -14,6 +14,8 @@ use core::{ /// /// This type can be treated as a `ManuallyDrop>` without a built in length. To avoid /// memory leaks, [`drop`](Self::drop) must be called when no longer in use. +/// +/// [`Vec`]: alloc::vec::Vec pub struct ThinArrayPtr { data: NonNull, #[cfg(debug_assertions)] diff --git a/crates/bevy_ecs/src/system/builder.rs b/crates/bevy_ecs/src/system/builder.rs index fe68ea6bfa6a7..9e7a015006213 100644 --- a/crates/bevy_ecs/src/system/builder.rs +++ b/crates/bevy_ecs/src/system/builder.rs @@ -684,6 +684,7 @@ mod tests { prelude::{Component, Query}, system::{Local, RunSystemOnce}, }; + use alloc::vec; use super::*; diff --git a/crates/bevy_ecs/src/system/commands/mod.rs b/crates/bevy_ecs/src/system/commands/mod.rs index aebe1d8bd70ef..a9302615e0a6e 100644 --- a/crates/bevy_ecs/src/system/commands/mod.rs +++ b/crates/bevy_ecs/src/system/commands/mod.rs @@ -2284,7 +2284,7 @@ mod tests { system::{Commands, Resource}, world::{CommandQueue, FromWorld, World}, }; - use alloc::sync::Arc; + use alloc::{string::String, sync::Arc, vec, vec::Vec}; use core::{ any::TypeId, sync::atomic::{AtomicUsize, Ordering}, diff --git a/crates/bevy_ecs/src/system/exclusive_system_param.rs b/crates/bevy_ecs/src/system/exclusive_system_param.rs index cc24cb7904304..678d4452331b2 100644 --- a/crates/bevy_ecs/src/system/exclusive_system_param.rs +++ b/crates/bevy_ecs/src/system/exclusive_system_param.rs @@ -126,6 +126,7 @@ all_tuples!( mod tests { use crate as bevy_ecs; use crate::{schedule::Schedule, system::Local, world::World}; + use alloc::vec::Vec; use bevy_ecs_macros::Resource; use core::marker::PhantomData; diff --git a/crates/bevy_ecs/src/system/mod.rs b/crates/bevy_ecs/src/system/mod.rs index b5020313c6656..03b3d532385e3 100644 --- a/crates/bevy_ecs/src/system/mod.rs +++ b/crates/bevy_ecs/src/system/mod.rs @@ -116,6 +116,8 @@ //! - [`DynSystemParam`] //! - [`Vec

`] where `P: SystemParam` //! - [`ParamSet>`] where `P: SystemParam` +//! +//! [`Vec

`]: alloc::vec::Vec mod adapter_system; mod builder; @@ -317,8 +319,10 @@ pub fn assert_system_does_not_conflict); impl DropCheck { diff --git a/crates/bevy_ecs/src/world/deferred_world.rs b/crates/bevy_ecs/src/world/deferred_world.rs index ba1aa3bfff47b..446045d65b77d 100644 --- a/crates/bevy_ecs/src/world/deferred_world.rs +++ b/crates/bevy_ecs/src/world/deferred_world.rs @@ -192,6 +192,7 @@ impl<'w> DeferredWorld<'w> { /// [`EntityMut`]: crate::world::EntityMut /// [`&EntityHashSet`]: crate::entity::EntityHashSet /// [`EntityHashMap`]: crate::entity::EntityHashMap + /// [`Vec`]: alloc::vec::Vec #[inline] pub fn get_entity_mut( &mut self, @@ -323,6 +324,7 @@ impl<'w> DeferredWorld<'w> { /// [`EntityMut`]: crate::world::EntityMut /// [`&EntityHashSet`]: crate::entity::EntityHashSet /// [`EntityHashMap`]: crate::entity::EntityHashMap + /// [`Vec`]: alloc::vec::Vec #[inline] pub fn entity_mut(&mut self, entities: F) -> F::DeferredMut<'_> { self.get_entity_mut(entities).unwrap() diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index a0117636872e7..9080be1b9bba8 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -4115,8 +4115,10 @@ unsafe impl DynamicComponentFetch for &'_ HashSet { #[cfg(test)] mod tests { + use alloc::{vec, vec::Vec}; use bevy_ptr::{OwningPtr, Ptr}; use core::panic::AssertUnwindSafe; + #[cfg(feature = "track_location")] use core::panic::Location; #[cfg(feature = "track_location")] diff --git a/crates/bevy_ecs/src/world/identifier.rs b/crates/bevy_ecs/src/world/identifier.rs index b1342e04dcc35..4ab38a2cdbb93 100644 --- a/crates/bevy_ecs/src/world/identifier.rs +++ b/crates/bevy_ecs/src/world/identifier.rs @@ -99,6 +99,7 @@ impl SparseSetIndex for WorldId { #[cfg(test)] mod tests { use super::*; + use alloc::vec::Vec; #[test] fn world_ids_unique() { diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index 1a7f87cd91a31..8e8afdb34e42a 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -3766,7 +3766,13 @@ mod tests { system::Resource, world::error::EntityFetchError, }; - use alloc::sync::Arc; + use alloc::{ + borrow::ToOwned, + string::{String, ToString}, + sync::Arc, + vec, + vec::Vec, + }; use bevy_ecs_macros::Component; use bevy_utils::{HashMap, HashSet}; use core::{ @@ -3774,7 +3780,7 @@ mod tests { panic, sync::atomic::{AtomicBool, AtomicU32, Ordering}, }; - use std::sync::Mutex; + use std::{println, sync::Mutex}; // For bevy_ecs_macros use crate as bevy_ecs; diff --git a/crates/bevy_hierarchy/src/child_builder.rs b/crates/bevy_hierarchy/src/child_builder.rs index 0da5c1c68ab2a..8dd9cd9e5d017 100644 --- a/crates/bevy_hierarchy/src/child_builder.rs +++ b/crates/bevy_hierarchy/src/child_builder.rs @@ -597,6 +597,7 @@ mod tests { components::{Children, Parent}, HierarchyEvent::{self, ChildAdded, ChildMoved, ChildRemoved}, }; + use alloc::{vec, vec::Vec}; use smallvec::{smallvec, SmallVec}; use bevy_ecs::{ diff --git a/crates/bevy_hierarchy/src/hierarchy.rs b/crates/bevy_hierarchy/src/hierarchy.rs index 43d6cdd1fb157..7ab31a588535c 100644 --- a/crates/bevy_hierarchy/src/hierarchy.rs +++ b/crates/bevy_hierarchy/src/hierarchy.rs @@ -247,6 +247,7 @@ fn component_clone_parent(world: &mut DeferredWorld, ctx: &mut ComponentCloneCtx #[cfg(test)] mod tests { + use alloc::{borrow::ToOwned, string::String, vec, vec::Vec}; use bevy_ecs::{ component::Component, system::Commands, diff --git a/crates/bevy_hierarchy/src/lib.rs b/crates/bevy_hierarchy/src/lib.rs index ec42846b14b23..2e8beea501f7d 100644 --- a/crates/bevy_hierarchy/src/lib.rs +++ b/crates/bevy_hierarchy/src/lib.rs @@ -4,7 +4,7 @@ html_logo_url = "https://bevyengine.org/assets/icon.png", html_favicon_url = "https://bevyengine.org/assets/icon.png" )] -#![cfg_attr(not(feature = "std"), no_std)] +#![no_std] //! Parent-child relationships for Bevy entities. //! @@ -52,6 +52,9 @@ //! [plugin]: HierarchyPlugin //! [query extension methods]: HierarchyQueryExt +#[cfg(feature = "std")] +extern crate std; + extern crate alloc; mod components; diff --git a/crates/bevy_hierarchy/src/query_extension.rs b/crates/bevy_hierarchy/src/query_extension.rs index 35ebaf8025c36..1ab3ec9385fea 100644 --- a/crates/bevy_hierarchy/src/query_extension.rs +++ b/crates/bevy_hierarchy/src/query_extension.rs @@ -312,6 +312,7 @@ where #[cfg(test)] mod tests { + use alloc::vec::Vec; use bevy_ecs::{ prelude::Component, system::{Query, SystemState}, diff --git a/crates/bevy_input/src/gamepad.rs b/crates/bevy_input/src/gamepad.rs index 6762627e14f5f..cf4ad9e31560d 100644 --- a/crates/bevy_input/src/gamepad.rs +++ b/crates/bevy_input/src/gamepad.rs @@ -1641,6 +1641,7 @@ mod tests { RawGamepadButtonChangedEvent, RawGamepadEvent, }; use crate::ButtonState; + use alloc::string::ToString; use bevy_app::{App, PreUpdate}; use bevy_ecs::entity::Entity; use bevy_ecs::event::Events; diff --git a/crates/bevy_input/src/lib.rs b/crates/bevy_input/src/lib.rs index 5826d174f4a23..2da2c89cce8ca 100644 --- a/crates/bevy_input/src/lib.rs +++ b/crates/bevy_input/src/lib.rs @@ -4,7 +4,7 @@ html_logo_url = "https://bevyengine.org/assets/icon.png", html_favicon_url = "https://bevyengine.org/assets/icon.png" )] -#![cfg_attr(not(feature = "std"), no_std)] +#![no_std] //! Input functionality for the [Bevy game engine](https://bevyengine.org/). //! @@ -12,6 +12,9 @@ //! //! `bevy` currently supports keyboard, mouse, gamepad, and touch inputs. +#[cfg(feature = "std")] +extern crate std; + extern crate alloc; mod axis; diff --git a/crates/bevy_math/src/bounding/bounded2d/primitive_impls.rs b/crates/bevy_math/src/bounding/bounded2d/primitive_impls.rs index e524d973a6750..fa0292572f248 100644 --- a/crates/bevy_math/src/bounding/bounded2d/primitive_impls.rs +++ b/crates/bevy_math/src/bounding/bounded2d/primitive_impls.rs @@ -443,6 +443,7 @@ impl Bounded2d for Capsule2d { #[cfg(test)] mod tests { use core::f32::consts::{FRAC_PI_2, FRAC_PI_3, FRAC_PI_4, FRAC_PI_6, TAU}; + use std::println; use approx::assert_abs_diff_eq; use glam::Vec2; diff --git a/crates/bevy_math/src/cubic_splines/mod.rs b/crates/bevy_math/src/cubic_splines/mod.rs index e179c89210cfa..32e13f6720a92 100644 --- a/crates/bevy_math/src/cubic_splines/mod.rs +++ b/crates/bevy_math/src/cubic_splines/mod.rs @@ -995,6 +995,13 @@ impl CubicSegment

{ } /// Calculate polynomial coefficients for the cubic curve using a characteristic matrix. + #[cfg_attr( + not(feature = "alloc"), + expect( + dead_code, + reason = "Method only used when `alloc` feature is enabled." + ) + )] #[inline] fn coefficients(p: [P; 4], char_matrix: [[f32; 4]; 4]) -> Self { let [c0, c1, c2, c3] = char_matrix; @@ -1375,6 +1382,13 @@ impl RationalSegment

{ } /// Calculate polynomial coefficients for the cubic polynomials using a characteristic matrix. + #[cfg_attr( + not(feature = "alloc"), + expect( + dead_code, + reason = "Method only used when `alloc` feature is enabled." + ) + )] #[inline] fn coefficients( control_points: [P; 4], diff --git a/crates/bevy_math/src/curve/adaptors.rs b/crates/bevy_math/src/curve/adaptors.rs index 8b27dc538720f..afc34837d3b13 100644 --- a/crates/bevy_math/src/curve/adaptors.rs +++ b/crates/bevy_math/src/curve/adaptors.rs @@ -10,7 +10,10 @@ use core::fmt::{self, Debug}; use core::marker::PhantomData; #[cfg(feature = "bevy_reflect")] -use bevy_reflect::{utility::GenericTypePathCell, FromReflect, Reflect, TypePath}; +use { + alloc::format, + bevy_reflect::{utility::GenericTypePathCell, FromReflect, Reflect, TypePath}, +}; #[cfg(feature = "bevy_reflect")] mod paths { diff --git a/crates/bevy_math/src/curve/cores.rs b/crates/bevy_math/src/curve/cores.rs index a88aecfb674b5..35bffe9018364 100644 --- a/crates/bevy_math/src/curve/cores.rs +++ b/crates/bevy_math/src/curve/cores.rs @@ -697,6 +697,7 @@ pub fn uneven_interp(times: &[f32], t: f32) -> InterpolationDatum { mod tests { use super::{ChunkedUnevenCore, EvenCore, UnevenCore}; use crate::curve::{cores::InterpolationDatum, interval}; + use alloc::vec; use approx::{assert_abs_diff_eq, AbsDiffEq}; fn approx_between(datum: InterpolationDatum, start: T, end: T, p: f32) -> bool diff --git a/crates/bevy_math/src/curve/interval.rs b/crates/bevy_math/src/curve/interval.rs index 6e5f4465aed91..007e523c95be6 100644 --- a/crates/bevy_math/src/curve/interval.rs +++ b/crates/bevy_math/src/curve/interval.rs @@ -201,6 +201,7 @@ mod tests { use crate::ops; use super::*; + use alloc::vec::Vec; use approx::{assert_abs_diff_eq, AbsDiffEq}; #[test] diff --git a/crates/bevy_math/src/curve/mod.rs b/crates/bevy_math/src/curve/mod.rs index 43548f0893f56..659f4fee686c5 100644 --- a/crates/bevy_math/src/curve/mod.rs +++ b/crates/bevy_math/src/curve/mod.rs @@ -1005,6 +1005,7 @@ pub enum ResamplingError { mod tests { use super::*; use crate::{ops, Quat}; + use alloc::vec::Vec; use approx::{assert_abs_diff_eq, AbsDiffEq}; use core::f32::consts::TAU; use glam::*; diff --git a/crates/bevy_math/src/curve/sample_curves.rs b/crates/bevy_math/src/curve/sample_curves.rs index 15ca51c2a3ba8..681500328b2da 100644 --- a/crates/bevy_math/src/curve/sample_curves.rs +++ b/crates/bevy_math/src/curve/sample_curves.rs @@ -4,6 +4,7 @@ use super::cores::{EvenCore, EvenCoreError, UnevenCore, UnevenCoreError}; use super::{Curve, Interval}; use crate::StableInterpolate; +use alloc::format; use core::any::type_name; use core::fmt::{self, Debug}; @@ -369,6 +370,7 @@ mod tests { //! - function pointers use super::{SampleCurve, UnevenSampleCurve}; use crate::{curve::Interval, VectorSpace}; + use alloc::boxed::Box; use bevy_reflect::Reflect; #[test] diff --git a/crates/bevy_math/src/direction.rs b/crates/bevy_math/src/direction.rs index 5e11d1434bf42..2da99c687bef6 100644 --- a/crates/bevy_math/src/direction.rs +++ b/crates/bevy_math/src/direction.rs @@ -8,9 +8,13 @@ use derive_more::derive::Into; #[cfg(feature = "bevy_reflect")] use bevy_reflect::Reflect; + #[cfg(all(feature = "serialize", feature = "bevy_reflect"))] use bevy_reflect::{ReflectDeserialize, ReflectSerialize}; +#[cfg(all(debug_assertions, feature = "std"))] +use std::eprintln; + /// An error indicating that a direction is invalid. #[derive(Debug, PartialEq)] pub enum InvalidDirectionError { diff --git a/crates/bevy_math/src/lib.rs b/crates/bevy_math/src/lib.rs index b589d66182f60..b66dffcf56523 100644 --- a/crates/bevy_math/src/lib.rs +++ b/crates/bevy_math/src/lib.rs @@ -13,7 +13,7 @@ html_logo_url = "https://bevyengine.org/assets/icon.png", html_favicon_url = "https://bevyengine.org/assets/icon.png" )] -#![cfg_attr(not(feature = "std"), no_std)] +#![no_std] //! Provides math types and functionality for the Bevy game engine. //! @@ -21,6 +21,9 @@ //! matrices like [`Mat2`], [`Mat3`] and [`Mat4`] and orientation representations //! like [`Quat`]. +#[cfg(feature = "std")] +extern crate std; + #[cfg(feature = "alloc")] extern crate alloc; diff --git a/crates/bevy_mikktspace/src/lib.rs b/crates/bevy_mikktspace/src/lib.rs index fe514e208eb47..519e5894f0f7f 100644 --- a/crates/bevy_mikktspace/src/lib.rs +++ b/crates/bevy_mikktspace/src/lib.rs @@ -11,7 +11,10 @@ html_logo_url = "https://bevyengine.org/assets/icon.png", html_favicon_url = "https://bevyengine.org/assets/icon.png" )] -#![cfg_attr(not(feature = "std"), no_std)] +#![no_std] + +#[cfg(feature = "std")] +extern crate std; extern crate alloc; diff --git a/crates/bevy_reflect/derive/src/impls/func/into_return.rs b/crates/bevy_reflect/derive/src/impls/func/into_return.rs index c47c328f9ae13..f7d1e0b8893e4 100644 --- a/crates/bevy_reflect/derive/src/impls/func/into_return.rs +++ b/crates/bevy_reflect/derive/src/impls/func/into_return.rs @@ -14,7 +14,7 @@ pub(crate) fn impl_into_return( quote! { impl #impl_generics #bevy_reflect::func::IntoReturn for #type_path #ty_generics #where_reflect_clause { fn into_return<'into_return>(self) -> #bevy_reflect::func::Return<'into_return> where Self: 'into_return { - #bevy_reflect::func::Return::Owned(Box::new(self)) + #bevy_reflect::func::Return::Owned(#bevy_reflect::__macro_exports::alloc_utils::Box::new(self)) } } diff --git a/crates/bevy_reflect/src/array.rs b/crates/bevy_reflect/src/array.rs index 0753c0e345c1a..0fee779b0513b 100644 --- a/crates/bevy_reflect/src/array.rs +++ b/crates/bevy_reflect/src/array.rs @@ -513,6 +513,8 @@ pub fn array_debug(dyn_array: &dyn Array, f: &mut Formatter<'_>) -> core::fmt::R #[cfg(test)] mod tests { use crate::Reflect; + use alloc::boxed::Box; + #[test] fn next_index_increment() { const SIZE: usize = if cfg!(debug_assertions) { diff --git a/crates/bevy_reflect/src/attributes.rs b/crates/bevy_reflect/src/attributes.rs index 0e751fa57a258..0e31b603985c3 100644 --- a/crates/bevy_reflect/src/attributes.rs +++ b/crates/bevy_reflect/src/attributes.rs @@ -181,6 +181,7 @@ mod tests { use super::*; use crate as bevy_reflect; use crate::{type_info::Typed, TypeInfo, VariantInfo}; + use alloc::{format, string::String}; use core::ops::RangeInclusive; #[derive(Reflect, PartialEq, Debug)] diff --git a/crates/bevy_reflect/src/enums/mod.rs b/crates/bevy_reflect/src/enums/mod.rs index 95a94e68e97e1..89b5f17b1c8b8 100644 --- a/crates/bevy_reflect/src/enums/mod.rs +++ b/crates/bevy_reflect/src/enums/mod.rs @@ -12,6 +12,7 @@ pub use variants::*; mod tests { use crate as bevy_reflect; use crate::*; + use alloc::boxed::Box; #[derive(Reflect, Debug, PartialEq)] enum MyEnum { diff --git a/crates/bevy_reflect/src/func/args/arg.rs b/crates/bevy_reflect/src/func/args/arg.rs index 60698a3d7e0a7..35e32c606758e 100644 --- a/crates/bevy_reflect/src/func/args/arg.rs +++ b/crates/bevy_reflect/src/func/args/arg.rs @@ -2,12 +2,9 @@ use crate::{ func::args::{ArgError, FromArg, Ownership}, PartialReflect, Reflect, TypePath, }; -use alloc::boxed::Box; +use alloc::{boxed::Box, string::ToString}; use core::ops::Deref; -#[cfg(not(feature = "std"))] -use alloc::{format, vec}; - /// Represents an argument that can be passed to a [`DynamicFunction`] or [`DynamicFunctionMut`]. /// /// [`DynamicFunction`]: crate::func::DynamicFunction diff --git a/crates/bevy_reflect/src/func/args/error.rs b/crates/bevy_reflect/src/func/args/error.rs index 65c4caa6e8449..bd32bd5e5aadd 100644 --- a/crates/bevy_reflect/src/func/args/error.rs +++ b/crates/bevy_reflect/src/func/args/error.rs @@ -4,9 +4,6 @@ use thiserror::Error; use crate::func::args::Ownership; -#[cfg(not(feature = "std"))] -use alloc::{boxed::Box, format, vec}; - /// An error that occurs when converting an [argument]. /// /// [argument]: crate::func::args::Arg diff --git a/crates/bevy_reflect/src/func/args/from_arg.rs b/crates/bevy_reflect/src/func/args/from_arg.rs index ddb1e014eb8b7..88d04aefe7525 100644 --- a/crates/bevy_reflect/src/func/args/from_arg.rs +++ b/crates/bevy_reflect/src/func/args/from_arg.rs @@ -1,8 +1,5 @@ use crate::func::args::{Arg, ArgError}; -#[cfg(not(feature = "std"))] -use alloc::{boxed::Box, format, vec}; - /// A trait for types that can be created from an [`Arg`]. /// /// This trait exists so that types can be automatically converted into an [`Arg`] diff --git a/crates/bevy_reflect/src/func/args/info.rs b/crates/bevy_reflect/src/func/args/info.rs index 3919771f349d4..b1a81f3059a2e 100644 --- a/crates/bevy_reflect/src/func/args/info.rs +++ b/crates/bevy_reflect/src/func/args/info.rs @@ -6,9 +6,6 @@ use crate::{ Type, TypePath, }; -#[cfg(not(feature = "std"))] -use alloc::{boxed::Box, format, vec}; - /// Type information for an [`Arg`] used in a [`DynamicFunction`] or [`DynamicFunctionMut`]. /// /// [`Arg`]: crate::func::args::Arg diff --git a/crates/bevy_reflect/src/func/args/list.rs b/crates/bevy_reflect/src/func/args/list.rs index 145414424f4b1..de8bcdcb92ede 100644 --- a/crates/bevy_reflect/src/func/args/list.rs +++ b/crates/bevy_reflect/src/func/args/list.rs @@ -10,9 +10,6 @@ use alloc::{ collections::vec_deque::{Iter, VecDeque}, }; -#[cfg(not(feature = "std"))] -use alloc::{boxed::Box, format, vec}; - /// A list of arguments that can be passed to a [`DynamicFunction`] or [`DynamicFunctionMut`]. /// /// # Example @@ -308,6 +305,7 @@ impl<'a> ArgList<'a> { #[cfg(test)] mod tests { use super::*; + use alloc::string::String; #[test] fn should_push_arguments_in_order() { diff --git a/crates/bevy_reflect/src/func/args/ownership.rs b/crates/bevy_reflect/src/func/args/ownership.rs index 448efed9f6b1f..b9395c742f404 100644 --- a/crates/bevy_reflect/src/func/args/ownership.rs +++ b/crates/bevy_reflect/src/func/args/ownership.rs @@ -1,8 +1,5 @@ use core::fmt::{Display, Formatter}; -#[cfg(not(feature = "std"))] -use alloc::{boxed::Box, format, vec}; - /// A trait for getting the ownership of a type. /// /// This trait exists so that [`TypedFunction`] can automatically generate diff --git a/crates/bevy_reflect/src/func/dynamic_function.rs b/crates/bevy_reflect/src/func/dynamic_function.rs index d513cb68084b5..408cdda640015 100644 --- a/crates/bevy_reflect/src/func/dynamic_function.rs +++ b/crates/bevy_reflect/src/func/dynamic_function.rs @@ -15,9 +15,6 @@ use alloc::{borrow::Cow, boxed::Box, sync::Arc}; use bevy_reflect_derive::impl_type_path; use core::fmt::{Debug, Formatter}; -#[cfg(not(feature = "std"))] -use alloc::{boxed::Box, format, vec}; - /// An [`Arc`] containing a callback to a reflected function. /// /// The `Arc` is used to both ensure that it is `Send + Sync` @@ -473,6 +470,7 @@ mod tests { use crate::func::signature::ArgumentSignature; use crate::func::{FunctionError, IntoReturn, SignatureInfo}; use crate::Type; + use alloc::{format, string::String, vec, vec::Vec}; use bevy_utils::HashSet; use core::ops::Add; diff --git a/crates/bevy_reflect/src/func/dynamic_function_internal.rs b/crates/bevy_reflect/src/func/dynamic_function_internal.rs index 427de1263d14a..4f06c5c2d7082 100644 --- a/crates/bevy_reflect/src/func/dynamic_function_internal.rs +++ b/crates/bevy_reflect/src/func/dynamic_function_internal.rs @@ -1,7 +1,7 @@ use crate::func::args::ArgCount; use crate::func::signature::{ArgListSignature, ArgumentSignature}; use crate::func::{ArgList, FunctionError, FunctionInfo, FunctionOverloadError}; -use alloc::borrow::Cow; +use alloc::{borrow::Cow, vec, vec::Vec}; use bevy_utils::HashMap; use core::fmt::{Debug, Formatter}; diff --git a/crates/bevy_reflect/src/func/dynamic_function_mut.rs b/crates/bevy_reflect/src/func/dynamic_function_mut.rs index bcbf6a96337ab..c9615f2d91ee0 100644 --- a/crates/bevy_reflect/src/func/dynamic_function_mut.rs +++ b/crates/bevy_reflect/src/func/dynamic_function_mut.rs @@ -1,9 +1,6 @@ use alloc::{borrow::Cow, boxed::Box, sync::Arc}; use core::fmt::{Debug, Formatter}; -#[cfg(not(feature = "std"))] -use alloc::{boxed::Box, format, vec}; - use crate::func::{ args::{ArgCount, ArgList}, dynamic_function_internal::DynamicFunctionInternal, @@ -381,6 +378,7 @@ impl<'env> IntoFunctionMut<'env, ()> for DynamicFunctionMut<'env> { mod tests { use super::*; use crate::func::{FunctionError, IntoReturn, SignatureInfo}; + use alloc::vec; use core::ops::Add; #[test] diff --git a/crates/bevy_reflect/src/func/error.rs b/crates/bevy_reflect/src/func/error.rs index ad6796be19f1e..7f0fae4662aef 100644 --- a/crates/bevy_reflect/src/func/error.rs +++ b/crates/bevy_reflect/src/func/error.rs @@ -7,9 +7,6 @@ use alloc::borrow::Cow; use bevy_utils::HashSet; use thiserror::Error; -#[cfg(not(feature = "std"))] -use alloc::{boxed::Box, format, vec}; - /// An error that occurs when calling a [`DynamicFunction`] or [`DynamicFunctionMut`]. /// /// [`DynamicFunction`]: crate::func::DynamicFunction diff --git a/crates/bevy_reflect/src/func/function.rs b/crates/bevy_reflect/src/func/function.rs index 0d8e94ca95aff..2c4c8f7527d0f 100644 --- a/crates/bevy_reflect/src/func/function.rs +++ b/crates/bevy_reflect/src/func/function.rs @@ -8,9 +8,6 @@ use crate::{ use alloc::borrow::Cow; use core::fmt::Debug; -#[cfg(not(feature = "std"))] -use alloc::{boxed::Box, format, vec}; - /// A trait used to power [function-like] operations via [reflection]. /// /// This trait allows types to be called like regular functions @@ -74,6 +71,7 @@ pub trait Function: PartialReflect + Debug { mod tests { use super::*; use crate::func::IntoFunction; + use alloc::boxed::Box; #[test] fn should_call_dyn_function() { diff --git a/crates/bevy_reflect/src/func/info.rs b/crates/bevy_reflect/src/func/info.rs index 797b97e7e1bac..40f90aa505f82 100644 --- a/crates/bevy_reflect/src/func/info.rs +++ b/crates/bevy_reflect/src/func/info.rs @@ -1,9 +1,6 @@ -use alloc::{borrow::Cow, vec}; +use alloc::{borrow::Cow, boxed::Box, vec, vec::Vec}; use core::fmt::{Debug, Formatter}; -#[cfg(not(feature = "std"))] -use alloc::{boxed::Box, format, vec}; - use crate::{ func::args::{ArgCount, ArgCountOutOfBoundsError, ArgInfo, GetOwnership, Ownership}, func::signature::ArgumentSignature, diff --git a/crates/bevy_reflect/src/func/into_function.rs b/crates/bevy_reflect/src/func/into_function.rs index e913045f8cc2a..d3276a6bd7828 100644 --- a/crates/bevy_reflect/src/func/into_function.rs +++ b/crates/bevy_reflect/src/func/into_function.rs @@ -1,8 +1,5 @@ use crate::func::{DynamicFunction, ReflectFn, TypedFunction}; -#[cfg(not(feature = "std"))] -use alloc::{boxed::Box, format, vec}; - /// A trait for types that can be converted into a [`DynamicFunction`]. /// /// This trait is automatically implemented for any type that implements diff --git a/crates/bevy_reflect/src/func/into_function_mut.rs b/crates/bevy_reflect/src/func/into_function_mut.rs index 8f7f1b0a6dd1a..38eae0aee4f84 100644 --- a/crates/bevy_reflect/src/func/into_function_mut.rs +++ b/crates/bevy_reflect/src/func/into_function_mut.rs @@ -1,8 +1,5 @@ use crate::func::{DynamicFunctionMut, ReflectFnMut, TypedFunction}; -#[cfg(not(feature = "std"))] -use alloc::{boxed::Box, format, vec}; - /// A trait for types that can be converted into a [`DynamicFunctionMut`]. /// /// This trait is automatically implemented for any type that implements diff --git a/crates/bevy_reflect/src/func/macros.rs b/crates/bevy_reflect/src/func/macros.rs index 410aaba456acd..3fb93a2230610 100644 --- a/crates/bevy_reflect/src/func/macros.rs +++ b/crates/bevy_reflect/src/func/macros.rs @@ -1,6 +1,3 @@ -#[cfg(not(feature = "std"))] -use alloc::{boxed::Box, format, vec}; - /// Helper macro to implement the necessary traits for function reflection. /// /// This macro calls the following macros: diff --git a/crates/bevy_reflect/src/func/reflect_fn.rs b/crates/bevy_reflect/src/func/reflect_fn.rs index 38a18141fcf43..5d6f61db700f2 100644 --- a/crates/bevy_reflect/src/func/reflect_fn.rs +++ b/crates/bevy_reflect/src/func/reflect_fn.rs @@ -1,8 +1,5 @@ use variadics_please::all_tuples; -#[cfg(not(feature = "std"))] -use alloc::{boxed::Box, format, vec}; - use crate::{ func::{ args::{ArgCount, FromArg}, diff --git a/crates/bevy_reflect/src/func/reflect_fn_mut.rs b/crates/bevy_reflect/src/func/reflect_fn_mut.rs index 760e657037c5b..e77adc82cc307 100644 --- a/crates/bevy_reflect/src/func/reflect_fn_mut.rs +++ b/crates/bevy_reflect/src/func/reflect_fn_mut.rs @@ -1,8 +1,5 @@ use variadics_please::all_tuples; -#[cfg(not(feature = "std"))] -use alloc::{boxed::Box, format, vec}; - use crate::{ func::{ args::{ArgCount, FromArg}, diff --git a/crates/bevy_reflect/src/func/registry.rs b/crates/bevy_reflect/src/func/registry.rs index cd9a0d270d14f..4bb38603fbea5 100644 --- a/crates/bevy_reflect/src/func/registry.rs +++ b/crates/bevy_reflect/src/func/registry.rs @@ -2,9 +2,6 @@ use alloc::{borrow::Cow, sync::Arc}; use core::fmt::Debug; use std::sync::{PoisonError, RwLock, RwLockReadGuard, RwLockWriteGuard}; -#[cfg(not(feature = "std"))] -use alloc::{boxed::Box, format, vec}; - use bevy_utils::HashMap; use crate::func::{ @@ -359,6 +356,7 @@ impl FunctionRegistryArc { mod tests { use super::*; use crate::func::{ArgList, IntoFunction}; + use alloc::format; #[test] fn should_register_function() { diff --git a/crates/bevy_reflect/src/func/return_type.rs b/crates/bevy_reflect/src/func/return_type.rs index 3d1153912cc75..bab3c04b25962 100644 --- a/crates/bevy_reflect/src/func/return_type.rs +++ b/crates/bevy_reflect/src/func/return_type.rs @@ -1,9 +1,6 @@ use crate::PartialReflect; use alloc::boxed::Box; -#[cfg(not(feature = "std"))] -use alloc::{format, vec}; - /// The return type of a [`DynamicFunction`] or [`DynamicFunctionMut`]. /// /// [`DynamicFunction`]: crate::func::DynamicFunction diff --git a/crates/bevy_reflect/src/func/signature.rs b/crates/bevy_reflect/src/func/signature.rs index 965ff401e00b8..278f18f1f5776 100644 --- a/crates/bevy_reflect/src/func/signature.rs +++ b/crates/bevy_reflect/src/func/signature.rs @@ -14,6 +14,7 @@ use crate::func::args::ArgInfo; use crate::func::{ArgList, SignatureInfo}; use crate::Type; +use alloc::boxed::Box; use bevy_utils::hashbrown::Equivalent; use core::borrow::Borrow; use core::fmt::{Debug, Formatter}; @@ -203,6 +204,7 @@ impl From<&ArgList<'_>> for ArgumentSignature { mod tests { use super::*; use crate::func::TypedFunction; + use alloc::{format, string::String, vec}; #[test] fn should_generate_signature_from_function_info() { diff --git a/crates/bevy_reflect/src/generics.rs b/crates/bevy_reflect/src/generics.rs index fa91fc35c5126..f64cefe2d9ed2 100644 --- a/crates/bevy_reflect/src/generics.rs +++ b/crates/bevy_reflect/src/generics.rs @@ -241,6 +241,7 @@ mod tests { use super::*; use crate as bevy_reflect; use crate::{Reflect, Typed}; + use alloc::string::String; use core::fmt::Debug; #[test] diff --git a/crates/bevy_reflect/src/impls/glam.rs b/crates/bevy_reflect/src/impls/glam.rs index cd7551cf740e9..d8d70d10dd7d4 100644 --- a/crates/bevy_reflect/src/impls/glam.rs +++ b/crates/bevy_reflect/src/impls/glam.rs @@ -378,7 +378,7 @@ impl_reflect_opaque!(::glam::BVec4A(Debug, Default, Deserialize, Serialize)); #[cfg(test)] mod tests { - use alloc::format; + use alloc::{format, string::String}; use ron::{ ser::{to_string_pretty, PrettyConfig}, Deserializer, diff --git a/crates/bevy_reflect/src/impls/std.rs b/crates/bevy_reflect/src/impls/std.rs index c5e8cc3a254f2..2e79883c334d2 100644 --- a/crates/bevy_reflect/src/impls/std.rs +++ b/crates/bevy_reflect/src/impls/std.rs @@ -2428,7 +2428,7 @@ mod tests { self as bevy_reflect, Enum, FromReflect, PartialReflect, Reflect, ReflectSerialize, TypeInfo, TypeRegistry, Typed, VariantInfo, VariantType, }; - use alloc::collections::BTreeMap; + use alloc::{collections::BTreeMap, string::String, vec}; use bevy_utils::{Duration, HashMap, Instant}; use core::f32::consts::{PI, TAU}; use static_assertions::assert_impl_all; diff --git a/crates/bevy_reflect/src/kind.rs b/crates/bevy_reflect/src/kind.rs index d5d16715c09c4..3eef10d0e55eb 100644 --- a/crates/bevy_reflect/src/kind.rs +++ b/crates/bevy_reflect/src/kind.rs @@ -274,6 +274,7 @@ impl ReflectOwned { #[cfg(test)] mod tests { + use alloc::vec; use std::collections::HashSet; use super::*; diff --git a/crates/bevy_reflect/src/lib.rs b/crates/bevy_reflect/src/lib.rs index c6e5ba0b4515d..e84bee274b1b4 100644 --- a/crates/bevy_reflect/src/lib.rs +++ b/crates/bevy_reflect/src/lib.rs @@ -557,7 +557,10 @@ //! [`ArgList`]: crate::func::ArgList //! [derive `Reflect`]: derive@crate::Reflect -#![cfg_attr(not(feature = "std"), no_std)] +#![no_std] + +#[cfg(feature = "std")] +extern crate std; extern crate alloc; @@ -712,7 +715,14 @@ pub mod __macro_exports { #[allow(clippy::disallowed_types, clippy::approx_constant)] mod tests { use ::serde::{de::DeserializeSeed, Deserialize, Serialize}; - use alloc::borrow::Cow; + use alloc::{ + borrow::Cow, + boxed::Box, + format, + string::{String, ToString}, + vec, + vec::Vec, + }; use bevy_utils::HashMap; use core::{ any::TypeId, @@ -2556,6 +2566,8 @@ bevy_reflect::tests::Test { #[test] fn should_reflect_remote_type() { mod external_crate { + use alloc::string::String; + #[derive(Debug, Default)] pub struct TheirType { pub value: String, @@ -2631,6 +2643,8 @@ bevy_reflect::tests::Test { #[test] fn should_reflect_remote_value_type() { mod external_crate { + use alloc::string::String; + #[derive(Clone, Debug, Default)] pub struct TheirType { pub value: String, @@ -2714,6 +2728,8 @@ bevy_reflect::tests::Test { // error[E0433]: failed to resolve: use of undeclared crate or module `external_crate` // ``` pub mod external_crate { + use alloc::string::String; + pub struct TheirType { pub value: String, } @@ -2735,6 +2751,8 @@ bevy_reflect::tests::Test { #[test] fn should_reflect_remote_enum() { mod external_crate { + use alloc::string::String; + #[derive(Debug, PartialEq, Eq)] pub enum TheirType { Unit, @@ -2899,6 +2917,8 @@ bevy_reflect::tests::Test { #[test] fn should_take_remote_type() { mod external_crate { + use alloc::string::String; + #[derive(Debug, Default, PartialEq, Eq)] pub struct TheirType { pub value: String, @@ -2931,6 +2951,8 @@ bevy_reflect::tests::Test { #[test] fn should_try_take_remote_type() { mod external_crate { + use alloc::string::String; + #[derive(Debug, Default, PartialEq, Eq)] pub struct TheirType { pub value: String, diff --git a/crates/bevy_reflect/src/list.rs b/crates/bevy_reflect/src/list.rs index 58fca368b88da..2aff62241f252 100644 --- a/crates/bevy_reflect/src/list.rs +++ b/crates/bevy_reflect/src/list.rs @@ -535,6 +535,7 @@ pub fn list_debug(dyn_list: &dyn List, f: &mut Formatter<'_>) -> core::fmt::Resu mod tests { use super::DynamicList; use crate::Reflect; + use alloc::{boxed::Box, vec}; use core::assert_eq; #[test] diff --git a/crates/bevy_reflect/src/map.rs b/crates/bevy_reflect/src/map.rs index e5205e90afa38..74743a0df1cbe 100644 --- a/crates/bevy_reflect/src/map.rs +++ b/crates/bevy_reflect/src/map.rs @@ -631,6 +631,10 @@ pub fn map_try_apply(a: &mut M, b: &dyn PartialReflect) -> Result<(), Ap #[cfg(test)] mod tests { use super::{DynamicMap, Map}; + use alloc::{ + borrow::ToOwned, + string::{String, ToString}, + }; #[test] fn test_into_iter() { diff --git a/crates/bevy_reflect/src/path/mod.rs b/crates/bevy_reflect/src/path/mod.rs index 3fe0504cf7408..f25870d31d946 100644 --- a/crates/bevy_reflect/src/path/mod.rs +++ b/crates/bevy_reflect/src/path/mod.rs @@ -507,6 +507,7 @@ mod tests { use super::*; use crate as bevy_reflect; use crate::*; + use alloc::vec; #[derive(Reflect)] struct A { diff --git a/crates/bevy_reflect/src/serde/de/error_utils.rs b/crates/bevy_reflect/src/serde/de/error_utils.rs index f028976805791..d570c47f0c369 100644 --- a/crates/bevy_reflect/src/serde/de/error_utils.rs +++ b/crates/bevy_reflect/src/serde/de/error_utils.rs @@ -1,6 +1,9 @@ use core::fmt::Display; use serde::de::Error; +#[cfg(feature = "debug_stack")] +use std::thread_local; + #[cfg(feature = "debug_stack")] thread_local! { /// The thread-local [`TypeInfoStack`] used for debugging. diff --git a/crates/bevy_reflect/src/serde/de/mod.rs b/crates/bevy_reflect/src/serde/de/mod.rs index e55897166e926..e8b2df862ddb6 100644 --- a/crates/bevy_reflect/src/serde/de/mod.rs +++ b/crates/bevy_reflect/src/serde/de/mod.rs @@ -24,11 +24,16 @@ mod tuples; #[cfg(test)] mod tests { + use alloc::{ + boxed::Box, + string::{String, ToString}, + vec, + vec::Vec, + }; use bincode::Options; use core::{any::TypeId, f32::consts::PI, ops::RangeInclusive}; - use serde::{de::IgnoredAny, Deserializer}; - use serde::{de::DeserializeSeed, Deserialize}; + use serde::{de::IgnoredAny, Deserializer}; use bevy_utils::{HashMap, HashSet}; diff --git a/crates/bevy_reflect/src/serde/mod.rs b/crates/bevy_reflect/src/serde/mod.rs index 3fcaa6aafc7b3..d35af8922e822 100644 --- a/crates/bevy_reflect/src/serde/mod.rs +++ b/crates/bevy_reflect/src/serde/mod.rs @@ -189,7 +189,7 @@ mod tests { use crate::serde::{DeserializeWithRegistry, ReflectDeserializeWithRegistry}; use crate::serde::{ReflectSerializeWithRegistry, SerializeWithRegistry}; use crate::{ReflectFromReflect, TypePath}; - use alloc::sync::Arc; + use alloc::{format, string::String, sync::Arc, vec, vec::Vec}; use bevy_reflect_derive::reflect_trait; use core::any::TypeId; use core::fmt::{Debug, Formatter}; diff --git a/crates/bevy_reflect/src/serde/ser/error_utils.rs b/crates/bevy_reflect/src/serde/ser/error_utils.rs index 8e6570c6691a2..d252e7f591d69 100644 --- a/crates/bevy_reflect/src/serde/ser/error_utils.rs +++ b/crates/bevy_reflect/src/serde/ser/error_utils.rs @@ -1,6 +1,9 @@ use core::fmt::Display; use serde::ser::Error; +#[cfg(feature = "debug_stack")] +use std::thread_local; + #[cfg(feature = "debug_stack")] thread_local! { /// The thread-local [`TypeInfoStack`] used for debugging. diff --git a/crates/bevy_reflect/src/serde/ser/mod.rs b/crates/bevy_reflect/src/serde/ser/mod.rs index 53afacde37430..77f2b9d0fe5d6 100644 --- a/crates/bevy_reflect/src/serde/ser/mod.rs +++ b/crates/bevy_reflect/src/serde/ser/mod.rs @@ -25,6 +25,12 @@ mod tests { serde::{ReflectSerializer, ReflectSerializerProcessor}, PartialReflect, Reflect, ReflectSerialize, Struct, TypeRegistry, }; + use alloc::{ + boxed::Box, + string::{String, ToString}, + vec, + vec::Vec, + }; use bevy_utils::{HashMap, HashSet}; use core::{any::TypeId, f32::consts::PI, ops::RangeInclusive}; use ron::{extensions::Extensions, ser::PrettyConfig}; @@ -647,6 +653,7 @@ mod tests { mod functions { use super::*; use crate::func::{DynamicFunction, IntoFunction}; + use alloc::string::ToString; #[test] fn should_not_serialize_function() { diff --git a/crates/bevy_reflect/src/set.rs b/crates/bevy_reflect/src/set.rs index 0d46d9f9df771..663b99715c0ee 100644 --- a/crates/bevy_reflect/src/set.rs +++ b/crates/bevy_reflect/src/set.rs @@ -498,6 +498,7 @@ pub fn set_try_apply(a: &mut S, b: &dyn PartialReflect) -> Result<(), Ap #[cfg(test)] mod tests { use super::DynamicSet; + use alloc::string::{String, ToString}; #[test] fn test_into_iter() { diff --git a/crates/bevy_reflect/src/type_info.rs b/crates/bevy_reflect/src/type_info.rs index 4ccac40508a43..2add261aa2b68 100644 --- a/crates/bevy_reflect/src/type_info.rs +++ b/crates/bevy_reflect/src/type_info.rs @@ -547,6 +547,8 @@ pub(crate) use impl_type_methods; /// For example, [`i32`] cannot be broken down any further, so it is represented by an [`OpaqueInfo`]. /// And while [`String`] itself is a struct, its fields are private, so we don't really treat /// it _as_ a struct. It therefore makes more sense to represent it as an [`OpaqueInfo`]. +/// +/// [`String`]: alloc::string::String #[derive(Debug, Clone)] pub struct OpaqueInfo { ty: Type, @@ -585,6 +587,7 @@ impl OpaqueInfo { #[cfg(test)] mod tests { use super::*; + use alloc::vec::Vec; #[test] fn should_return_error_on_invalid_cast() { diff --git a/crates/bevy_reflect/src/type_info_stack.rs b/crates/bevy_reflect/src/type_info_stack.rs index 8f1161485f1aa..cdc19244de295 100644 --- a/crates/bevy_reflect/src/type_info_stack.rs +++ b/crates/bevy_reflect/src/type_info_stack.rs @@ -1,12 +1,10 @@ use crate::TypeInfo; +use alloc::vec::Vec; use core::{ fmt::{Debug, Formatter}, slice::Iter, }; -#[cfg(not(feature = "std"))] -use alloc::{boxed::Box, format, vec}; - /// Helper struct for managing a stack of [`TypeInfo`] instances. /// /// This is useful for tracking the type hierarchy when serializing and deserializing types. diff --git a/crates/bevy_reflect/src/utility.rs b/crates/bevy_reflect/src/utility.rs index f106baf622135..996a661c70a49 100644 --- a/crates/bevy_reflect/src/utility.rs +++ b/crates/bevy_reflect/src/utility.rs @@ -24,6 +24,7 @@ pub trait TypedProperty: sealed::Sealed { /// Used to store a [`String`] in a [`GenericTypePathCell`] as part of a [`TypePath`] implementation. /// /// [`TypePath`]: crate::TypePath +/// [`String`]: alloc::string::String pub struct TypePathComponent; mod sealed { diff --git a/crates/bevy_state/src/lib.rs b/crates/bevy_state/src/lib.rs index 572330de4c6d0..cdcb37f4ca24d 100644 --- a/crates/bevy_state/src/lib.rs +++ b/crates/bevy_state/src/lib.rs @@ -1,4 +1,4 @@ -#![cfg_attr(not(feature = "std"), no_std)] +#![no_std] //! In Bevy, states are app-wide interdependent, finite state machines that are generally used to model the large scale structure of your program: whether a game is paused, if the player is in combat, if assets are loaded and so on. //! @@ -38,6 +38,9 @@ )] #![cfg_attr(any(docsrs, docsrs_dep), feature(rustdoc_internals))] +#[cfg(feature = "std")] +extern crate std; + extern crate alloc; #[cfg(feature = "bevy_app")] diff --git a/crates/bevy_state/src/state/mod.rs b/crates/bevy_state/src/state/mod.rs index d02d3a32ed452..c71827cc5f06b 100644 --- a/crates/bevy_state/src/state/mod.rs +++ b/crates/bevy_state/src/state/mod.rs @@ -17,6 +17,7 @@ pub use transitions::*; #[cfg(test)] mod tests { + use alloc::vec::Vec; use bevy_ecs::{event::EventRegistry, prelude::*}; use bevy_state_macros::{States, SubStates}; diff --git a/crates/bevy_tasks/src/lib.rs b/crates/bevy_tasks/src/lib.rs index 97b13b0052b93..1cd6d6786e136 100644 --- a/crates/bevy_tasks/src/lib.rs +++ b/crates/bevy_tasks/src/lib.rs @@ -4,8 +4,11 @@ html_logo_url = "https://bevyengine.org/assets/icon.png", html_favicon_url = "https://bevyengine.org/assets/icon.png" )] -#![cfg_attr(not(feature = "std"), no_std)] #![deny(clippy::allow_attributes, clippy::allow_attributes_without_reason)] +#![no_std] + +#[cfg(feature = "std")] +extern crate std; extern crate alloc; diff --git a/crates/bevy_tasks/src/single_threaded_task_pool.rs b/crates/bevy_tasks/src/single_threaded_task_pool.rs index 0a5678b066edf..668408fefb0b2 100644 --- a/crates/bevy_tasks/src/single_threaded_task_pool.rs +++ b/crates/bevy_tasks/src/single_threaded_task_pool.rs @@ -3,6 +3,9 @@ use core::{cell::RefCell, future::Future, marker::PhantomData, mem}; use crate::Task; +#[cfg(feature = "std")] +use std::thread_local; + #[cfg(feature = "portable-atomic")] use portable_atomic_util::Arc; diff --git a/crates/bevy_tasks/src/slice.rs b/crates/bevy_tasks/src/slice.rs index 5f964a4561778..a705314a34502 100644 --- a/crates/bevy_tasks/src/slice.rs +++ b/crates/bevy_tasks/src/slice.rs @@ -215,6 +215,7 @@ impl ParallelSliceMut for S where S: AsMut<[T]> {} #[cfg(test)] mod tests { use crate::*; + use alloc::vec; #[test] fn test_par_chunks_map() { diff --git a/crates/bevy_tasks/src/task_pool.rs b/crates/bevy_tasks/src/task_pool.rs index 94f59bbce99ac..9e5509300febf 100644 --- a/crates/bevy_tasks/src/task_pool.rs +++ b/crates/bevy_tasks/src/task_pool.rs @@ -1,12 +1,16 @@ +use alloc::{boxed::Box, format, string::String, vec::Vec}; use core::{future::Future, marker::PhantomData, mem, panic::AssertUnwindSafe}; -use std::thread::{self, JoinHandle}; +use std::{ + thread::{self, JoinHandle}, + thread_local, +}; use crate::executor::FallibleTask; use concurrent_queue::ConcurrentQueue; use futures_lite::FutureExt; #[cfg(feature = "portable-atomic")] -use {alloc::boxed::Box, portable_atomic_util::Arc}; +use portable_atomic_util::Arc; #[cfg(not(feature = "portable-atomic"))] use alloc::sync::Arc; diff --git a/crates/bevy_transform/src/helper.rs b/crates/bevy_transform/src/helper.rs index 2911c1620a168..75e9e313d32fb 100644 --- a/crates/bevy_transform/src/helper.rs +++ b/crates/bevy_transform/src/helper.rs @@ -80,6 +80,7 @@ pub enum ComputeGlobalTransformError { #[cfg(test)] mod tests { + use alloc::{vec, vec::Vec}; use core::f32::consts::TAU; use bevy_app::App; diff --git a/crates/bevy_transform/src/lib.rs b/crates/bevy_transform/src/lib.rs index d9b67f7cfca93..b8e08c2c1bacd 100644 --- a/crates/bevy_transform/src/lib.rs +++ b/crates/bevy_transform/src/lib.rs @@ -4,7 +4,10 @@ html_logo_url = "https://bevyengine.org/assets/icon.png", html_favicon_url = "https://bevyengine.org/assets/icon.png" )] -#![cfg_attr(not(feature = "std"), no_std)] +#![no_std] + +#[cfg(feature = "std")] +extern crate std; #[cfg(feature = "alloc")] extern crate alloc; diff --git a/crates/bevy_transform/src/systems.rs b/crates/bevy_transform/src/systems.rs index f41fc50bd3a14..e3f544d8658c0 100644 --- a/crates/bevy_transform/src/systems.rs +++ b/crates/bevy_transform/src/systems.rs @@ -186,6 +186,7 @@ unsafe fn propagate_recursive( #[cfg(test)] mod test { + use alloc::vec; use bevy_app::prelude::*; use bevy_ecs::{prelude::*, world::CommandQueue}; use bevy_math::{vec3, Vec3}; diff --git a/crates/bevy_utils/src/lib.rs b/crates/bevy_utils/src/lib.rs index fea65f11ed686..b4c224d94b0f3 100644 --- a/crates/bevy_utils/src/lib.rs +++ b/crates/bevy_utils/src/lib.rs @@ -1,18 +1,17 @@ #![cfg_attr(docsrs, feature(doc_auto_cfg))] -#![expect( - unsafe_code, - reason = "Some utilities, such as cells, require unsafe code." -)] #![doc( html_logo_url = "https://bevyengine.org/assets/icon.png", html_favicon_url = "https://bevyengine.org/assets/icon.png" )] -#![cfg_attr(not(feature = "std"), no_std)] +#![no_std] //! General utilities for first-party [Bevy] engine crates. //! //! [Bevy]: https://bevyengine.org/ +#[cfg(feature = "std")] +extern crate std; + #[cfg(feature = "alloc")] extern crate alloc; @@ -367,6 +366,10 @@ impl OnDrop { impl Drop for OnDrop { fn drop(&mut self) { + #![expect( + unsafe_code, + reason = "Taking from a ManuallyDrop requires unsafe code." + )] // 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(); diff --git a/crates/bevy_utils/src/parallel_queue.rs b/crates/bevy_utils/src/parallel_queue.rs index 01377c7573beb..f9c4c66ca092a 100644 --- a/crates/bevy_utils/src/parallel_queue.rs +++ b/crates/bevy_utils/src/parallel_queue.rs @@ -1,6 +1,4 @@ -#[cfg(all(feature = "alloc", not(feature = "std")))] use alloc::vec::Vec; - use core::{cell::RefCell, ops::DerefMut}; use thread_local::ThreadLocal; diff --git a/crates/bevy_utils/src/synccell.rs b/crates/bevy_utils/src/synccell.rs index 628fb2756dbac..557555d82668f 100644 --- a/crates/bevy_utils/src/synccell.rs +++ b/crates/bevy_utils/src/synccell.rs @@ -1,3 +1,5 @@ +#![expect(unsafe_code, reason = "SyncCell requires unsafe code.")] + //! A reimplementation of the currently unstable [`std::sync::Exclusive`] //! //! [`std::sync::Exclusive`]: https://doc.rust-lang.org/nightly/std/sync/struct.Exclusive.html diff --git a/crates/bevy_utils/src/syncunsafecell.rs b/crates/bevy_utils/src/syncunsafecell.rs index e447f7122619c..0a4e427fe469f 100644 --- a/crates/bevy_utils/src/syncunsafecell.rs +++ b/crates/bevy_utils/src/syncunsafecell.rs @@ -1,3 +1,5 @@ +#![expect(unsafe_code, reason = "SyncUnsafeCell requires unsafe code.")] + //! A reimplementation of the currently unstable [`std::cell::SyncUnsafeCell`] //! //! [`std::cell::SyncUnsafeCell`]: https://doc.rust-lang.org/nightly/std/cell/struct.SyncUnsafeCell.html diff --git a/crates/bevy_window/src/lib.rs b/crates/bevy_window/src/lib.rs index a4c7ccc655735..8ef4cb9c0c7b9 100644 --- a/crates/bevy_window/src/lib.rs +++ b/crates/bevy_window/src/lib.rs @@ -3,7 +3,7 @@ html_logo_url = "https://bevyengine.org/assets/icon.png", html_favicon_url = "https://bevyengine.org/assets/icon.png" )] -#![cfg_attr(not(feature = "std"), no_std)] +#![no_std] //! `bevy_window` provides a platform-agnostic interface for windowing in Bevy. //! @@ -12,6 +12,9 @@ //! The [`WindowPlugin`] sets up some global window-related parameters and //! is part of the [`DefaultPlugins`](https://docs.rs/bevy/latest/bevy/struct.DefaultPlugins.html). +#[cfg(feature = "std")] +extern crate std; + extern crate alloc; use alloc::sync::Arc; diff --git a/tools/example-showcase/window-settings-wasm.patch b/tools/example-showcase/window-settings-wasm.patch index f5d51baa3ed68..56d16cde96d0f 100644 --- a/tools/example-showcase/window-settings-wasm.patch +++ b/tools/example-showcase/window-settings-wasm.patch @@ -10,7 +10,7 @@ index ccc861a78..0cae580cd 100644 + fit_canvas_to_parent: true, prevent_default_event_handling: true, - canvas: None, -+ canvas: Some("#bevy".to_string()), ++ canvas: Some("#bevy".to_owned()), window_theme: None, visible: true, skip_taskbar: false, From effc9df572417ee828547517a9f85a12e1416cd6 Mon Sep 17 00:00:00 2001 From: Rob Parrett Date: Fri, 3 Jan 2025 09:38:12 -0800 Subject: [PATCH 109/272] Show working urls to screenshot comparisons in output (#17125) # Objective When a PR introduces changes that result in differences in the rendering one of the "testbeds," contributors get a nice email informing them about a [failed workflow run](https://github.com/bevyengine/bevy/actions/runs/12599884045/job/35117729566). However, this email contains links that just go to a json blob which isn't as helpful as it could be. If you squint real hard, you can figure out which testbed failed and at which frame the screenshot took place. Not ideal. This changes the urls presented as output in that workflow so that they lead to the actual screenshot comparison screen on pixel-eagle. ![image](https://github.com/user-attachments/assets/36af7f29-9319-4a1f-bf0a-7f7ffd5d613f) image ## Solution Change the urls in the output from e.g. https://pixel-eagle.vleue.com/B04F67C0-C054-4A6F-92EC-F599FEC2FD1D/runs/4591/compare/4568 To https://pixel-eagle.com/project/B04F67C0-C054-4A6F-92EC-F599FEC2FD1D/run/4591/compare/4568 Without messing with any of the URLs involved in uploading the screenshots. ## Testing I have not tested this (yet) beyond manual testing of the new URL structure. I suspect it's not possible to test in a fork. --- .../send-screenshots-to-pixeleagle.yml | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/send-screenshots-to-pixeleagle.yml b/.github/workflows/send-screenshots-to-pixeleagle.yml index 4372d75ec865a..b43a316f2593e 100644 --- a/.github/workflows/send-screenshots-to-pixeleagle.yml +++ b/.github/workflows/send-screenshots-to-pixeleagle.yml @@ -34,13 +34,13 @@ jobs: if: ${{ ! fromJSON(env.PIXELEAGLE_TOKEN_EXISTS) }} run: | echo "The PIXELEAGLE_TOKEN secret does not exist, so uploading screenshots to Pixel Eagle was skipped." >> $GITHUB_STEP_SUMMARY - + - name: Download artifact if: ${{ fromJSON(env.PIXELEAGLE_TOKEN_EXISTS) }} uses: actions/download-artifact@v4 with: pattern: ${{ inputs.artifact }} - + - name: Send to Pixel Eagle if: ${{ fromJSON(env.PIXELEAGLE_TOKEN_EXISTS) }} env: @@ -49,11 +49,11 @@ jobs: # Create a new run with its associated metadata metadata='{"os":"${{ inputs.os }}", "commit": "${{ inputs.commit }}", "branch": "${{ inputs.branch }}"}' run=`curl https://pixel-eagle.vleue.com/$project/runs --json "$metadata" --oauth2-bearer ${{ secrets.PIXELEAGLE_TOKEN }} | jq '.id'` - + SAVEIFS=$IFS - + cd ${{ inputs.artifact }} - + # Read the hashes of the screenshot for fast comparison when they are equal IFS=$'\n' # Build a json array of screenshots and their hashes @@ -67,7 +67,7 @@ jobs: done hashes=`echo $hashes | rev | cut -c 2- | rev` hashes="$hashes]" - + IFS=$SAVEIFS # Upload screenshots with unknown hashes @@ -78,7 +78,7 @@ jobs: curl https://pixel-eagle.vleue.com/$project/runs/$run/screenshots -F "data=@./screenshots-$name" -F "screenshot=$name" --oauth2-bearer ${{ secrets.PIXELEAGLE_TOKEN }} echo done - + IFS=$SAVEIFS cd .. @@ -93,17 +93,17 @@ jobs: missing=`cat pixeleagle.json | jq '.missing | length'` if [ ! $missing -eq 0 ]; then echo "There are $missing missing screenshots" - echo "::warning title=$missing missing screenshots on ${{ inputs.os }}::https://pixel-eagle.vleue.com/$project/runs/$run/compare/$compared_with" + echo "::warning title=$missing missing screenshots on ${{ inputs.os }}::https://pixel-eagle.com/project/$project/run/$run/compare/$compared_with" status=1 fi diff=`cat pixeleagle.json | jq '.diff | length'` if [ ! $diff -eq 0 ]; then echo "There are $diff screenshots with a difference" - echo "::warning title=$diff different screenshots on ${{ inputs.os }}::https://pixel-eagle.vleue.com/$project/runs/$run/compare/$compared_with" + echo "::warning title=$diff different screenshots on ${{ inputs.os }}::https://pixel-eagle.com/project/$project/run/$run/compare/$compared_with" status=1 fi - echo "created run $run: https://pixel-eagle.vleue.com/$project/runs/$run/compare/$compared_with" + echo "created run $run: https://pixel-eagle.com/project/$project/run/$run/compare/$compared_with" exit $status From 651b22f31fb77c7a4760689184ad3198a3eb42a9 Mon Sep 17 00:00:00 2001 From: Rob Parrett Date: Fri, 3 Jan 2025 09:44:26 -0800 Subject: [PATCH 110/272] Update `typos` (#17126) # Objective Use the latest version of `typos` and fix the typos that it now detects # Additional Info By the way, `typos` has a "low priority typo suggestions issue" where we can throw typos we find that `typos` doesn't catch. (This link may go stale) https://github.com/crate-ci/typos/issues/1200 --- .github/workflows/ci.yml | 2 +- assets/shaders/specialized_mesh_pipeline.wgsl | 2 +- crates/bevy_core_pipeline/src/smaa/smaa.wgsl | 2 +- crates/bevy_ecs/src/observer/entity_observer.rs | 2 +- crates/bevy_ecs/src/schedule/graph/graph_map.rs | 2 +- crates/bevy_ecs/src/world/entity_fetch.rs | 2 +- crates/bevy_math/src/primitives/dim2.rs | 4 ++-- crates/bevy_pbr/src/render/pbr_transmission.wgsl | 4 ++-- crates/bevy_picking/src/events.rs | 2 +- crates/bevy_reflect/src/func/mod.rs | 4 ++-- crates/bevy_sprite/src/picking_backend.rs | 2 +- 11 files changed, 14 insertions(+), 14 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a0c63e6d1af8b..13feb4b8050b2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -243,7 +243,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Check for typos - uses: crate-ci/typos@v1.28.4 + uses: crate-ci/typos@v1.29.4 - name: Typos info if: failure() run: | diff --git a/assets/shaders/specialized_mesh_pipeline.wgsl b/assets/shaders/specialized_mesh_pipeline.wgsl index 82b5cea911658..e307a7c48c406 100644 --- a/assets/shaders/specialized_mesh_pipeline.wgsl +++ b/assets/shaders/specialized_mesh_pipeline.wgsl @@ -30,7 +30,7 @@ struct VertexOutput { fn vertex(vertex: Vertex) -> VertexOutput { var out: VertexOutput; // This is how bevy computes the world position - // The vertex.instance_index is very important. Esepecially if you are using batching and gpu preprocessing + // The vertex.instance_index is very important. Especially if you are using batching and gpu preprocessing var world_from_local = mesh_functions::get_world_from_local(vertex.instance_index); out.world_position = mesh_functions::mesh_position_local_to_world(world_from_local, vec4(vertex.position, 1.0)); out.clip_position = position_world_to_clip(out.world_position.xyz); diff --git a/crates/bevy_core_pipeline/src/smaa/smaa.wgsl b/crates/bevy_core_pipeline/src/smaa/smaa.wgsl index 5c95c18c2602f..3542afbe1266e 100644 --- a/crates/bevy_core_pipeline/src/smaa/smaa.wgsl +++ b/crates/bevy_core_pipeline/src/smaa/smaa.wgsl @@ -44,7 +44,7 @@ * Here you'll find instructions to get the shader up and running as fast as * possible. * - * IMPORTANTE NOTICE: when updating, remember to update both this file and the + * IMPORTANT NOTICE: when updating, remember to update both this file and the * precomputed textures! They may change from version to version. * * The shader has three passes, chained together as follows: diff --git a/crates/bevy_ecs/src/observer/entity_observer.rs b/crates/bevy_ecs/src/observer/entity_observer.rs index ee94cfa62a73e..e86f6814a8ff6 100644 --- a/crates/bevy_ecs/src/observer/entity_observer.rs +++ b/crates/bevy_ecs/src/observer/entity_observer.rs @@ -50,7 +50,7 @@ impl Component for ObservedBy { /// Trait that holds functions for configuring interaction with observers during entity cloning. pub trait CloneEntityWithObserversExt { - /// Sets the option to automatically add cloned entities to the obsevers targeting source entity. + /// Sets the option to automatically add cloned entities to the observers targeting source entity. fn add_observers(&mut self, add_observers: bool) -> &mut Self; } diff --git a/crates/bevy_ecs/src/schedule/graph/graph_map.rs b/crates/bevy_ecs/src/schedule/graph/graph_map.rs index e24926499f106..66ae7a08f5d1b 100644 --- a/crates/bevy_ecs/src/schedule/graph/graph_map.rs +++ b/crates/bevy_ecs/src/schedule/graph/graph_map.rs @@ -437,7 +437,7 @@ mod tests { assert_eq!(graph.nodes().collect::>(), vec![]); } - /// Nodes that have bidrectional edges (or any edge in the case of undirected graphs) are + /// Nodes that have bidirectional edges (or any edge in the case of undirected graphs) are /// considered strongly connected. A strongly connected component is a collection of /// nodes where there exists a path from any node to any other node in the collection. #[test] diff --git a/crates/bevy_ecs/src/world/entity_fetch.rs b/crates/bevy_ecs/src/world/entity_fetch.rs index 8d01970bdbef8..e89835844f1fa 100644 --- a/crates/bevy_ecs/src/world/entity_fetch.rs +++ b/crates/bevy_ecs/src/world/entity_fetch.rs @@ -21,7 +21,7 @@ use crate::{ /// /// # Performance /// -/// - The slice and array implementations perform an aliased mutabiltiy check +/// - The slice and array implementations perform an aliased mutability check /// in [`WorldEntityFetch::fetch_mut`] that is `O(N^2)`. /// - The [`EntityHashSet`] implementation performs no such check as the type /// itself guarantees no duplicates. diff --git a/crates/bevy_math/src/primitives/dim2.rs b/crates/bevy_math/src/primitives/dim2.rs index d476fd86077eb..cdd5b805cde3a 100644 --- a/crates/bevy_math/src/primitives/dim2.rs +++ b/crates/bevy_math/src/primitives/dim2.rs @@ -2242,9 +2242,9 @@ mod tests { let mut rotated_vertices = polygon.vertices(core::f32::consts::FRAC_PI_4).into_iter(); // Distance from the origin to the middle of a side, derived using Pythagorean theorem - let side_sistance = FRAC_1_SQRT_2; + let side_distance = FRAC_1_SQRT_2; assert!( - (rotated_vertices.next().unwrap() - Vec2::new(-side_sistance, side_sistance)).length() + (rotated_vertices.next().unwrap() - Vec2::new(-side_distance, side_distance)).length() < 1e-7, ); } diff --git a/crates/bevy_pbr/src/render/pbr_transmission.wgsl b/crates/bevy_pbr/src/render/pbr_transmission.wgsl index 83a71096ebdfe..720a42bca9631 100644 --- a/crates/bevy_pbr/src/render/pbr_transmission.wgsl +++ b/crates/bevy_pbr/src/render/pbr_transmission.wgsl @@ -15,7 +15,7 @@ #endif fn specular_transmissive_light(world_position: vec4, frag_coord: vec3, view_z: f32, N: vec3, V: vec3, F0: vec3, ior: f32, thickness: f32, perceptual_roughness: f32, specular_transmissive_color: vec3, transmitted_environment_light_specular: vec3) -> vec3 { - // Calculate the ratio between refaction indexes. Assume air/vacuum for the space outside the mesh + // Calculate the ratio between refraction indexes. Assume air/vacuum for the space outside the mesh let eta = 1.0 / ior; // Calculate incidence vector (opposite to view vector) and its dot product with the mesh normal @@ -26,7 +26,7 @@ fn specular_transmissive_light(world_position: vec4, frag_coord: vec3, let k = 1.0 - eta * eta * (1.0 - NdotI * NdotI); let T = eta * I - (eta * NdotI + sqrt(k)) * N; - // Calculate the exit position of the refracted ray, by propagating refacted direction through thickness + // Calculate the exit position of the refracted ray, by propagating refracted direction through thickness let exit_position = world_position.xyz + T * thickness; // Transform exit_position into clip space diff --git a/crates/bevy_picking/src/events.rs b/crates/bevy_picking/src/events.rs index 74b6394c021b7..9d8bda32bda72 100644 --- a/crates/bevy_picking/src/events.rs +++ b/crates/bevy_picking/src/events.rs @@ -393,7 +393,7 @@ pub struct PickingEventWriters<'w> { /// Both [`Click`] and [`Released`] target the entity hovered in the *previous frame*, /// rather than the current frame. This is because touch pointers hover nothing /// on the frame they are released. The end effect is that these two events can -/// be received sequentally after an [`Out`] event (but always on the same frame +/// be received sequentially after an [`Out`] event (but always on the same frame /// as the [`Out`] event). /// /// Note: Though it is common for the [`PointerInput`] stream may contain diff --git a/crates/bevy_reflect/src/func/mod.rs b/crates/bevy_reflect/src/func/mod.rs index f990e135f017d..f7d581ab465b8 100644 --- a/crates/bevy_reflect/src/func/mod.rs +++ b/crates/bevy_reflect/src/func/mod.rs @@ -96,7 +96,7 @@ //! //! # Generic Functions //! -//! In Rust, generic functions are [monomophized] by the compiler, +//! In Rust, generic functions are [monomorphized] by the compiler, //! which means that a separate copy of the function is generated for each concrete set of type parameters. //! //! When converting a generic function to a [`DynamicFunction`] or [`DynamicFunctionMut`], @@ -153,7 +153,7 @@ //! [`Reflect`]: crate::Reflect //! [lack of variadic generics]: https://poignardazur.github.io/2024/05/25/report-on-rustnl-variadics/ //! [coherence issues]: https://doc.rust-lang.org/rustc/lints/listing/warn-by-default.html#coherence-leak-check -//! [monomophized]: https://en.wikipedia.org/wiki/Monomorphization +//! [monomorphized]: https://en.wikipedia.org/wiki/Monomorphization //! [overloading]: #overloading-functions //! [function overloading]: https://en.wikipedia.org/wiki/Function_overloading //! [variadic functions]: https://en.wikipedia.org/wiki/Variadic_function diff --git a/crates/bevy_sprite/src/picking_backend.rs b/crates/bevy_sprite/src/picking_backend.rs index 6d447cf7ac631..4202939e2410a 100644 --- a/crates/bevy_sprite/src/picking_backend.rs +++ b/crates/bevy_sprite/src/picking_backend.rs @@ -34,7 +34,7 @@ pub enum SpritePickingMode { pub struct SpritePickingSettings { /// Should the backend count transparent pixels as part of the sprite for picking purposes or should it use the bounding box of the sprite alone. /// - /// Defaults to an incusive alpha threshold of 0.1 + /// Defaults to an inclusive alpha threshold of 0.1 pub picking_mode: SpritePickingMode, } From 66a0e74a21952fa6dc2f4c3ec7f93cc683adc2f2 Mon Sep 17 00:00:00 2001 From: MichiRecRoom <1008889+LikeLakers2@users.noreply.github.com> Date: Fri, 3 Jan 2025 17:20:51 -0500 Subject: [PATCH 111/272] Truncate the floats in `bevy_color::oklaba` (#17109) # Objective Remove the reliance on `#[allow(clippy::excessive_precision)]`. ## Solution Remove the `#[allow(clippy::excessive_precision)]`, and truncate the float literals to the value rustc would normally truncate them to. ## Testing I ran `cargo test -p bevy_color`, and received no errors. --- crates/bevy_color/src/oklaba.rs | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/crates/bevy_color/src/oklaba.rs b/crates/bevy_color/src/oklaba.rs index 0ffb35ddd33db..946f0f6aa557b 100644 --- a/crates/bevy_color/src/oklaba.rs +++ b/crates/bevy_color/src/oklaba.rs @@ -216,7 +216,6 @@ impl ColorToComponents for Oklaba { } } -#[allow(clippy::excessive_precision)] impl From for Oklaba { fn from(value: LinearRgba) -> Self { let LinearRgba { @@ -226,20 +225,20 @@ impl From for Oklaba { alpha, } = value; // From https://github.com/DougLau/pix - let l = 0.4122214708 * red + 0.5363325363 * green + 0.0514459929 * blue; - let m = 0.2119034982 * red + 0.6806995451 * green + 0.1073969566 * blue; - let s = 0.0883024619 * red + 0.2817188376 * green + 0.6299787005 * blue; + // Floats literals are truncated from the source code above, to avoid excessive precision. + let l = 0.41222146 * red + 0.53633255 * green + 0.051445995 * blue; + let m = 0.2119035 * red + 0.6806995 * green + 0.10739696 * blue; + let s = 0.08830246 * red + 0.28171885 * green + 0.6299787 * blue; let l_ = ops::cbrt(l); let m_ = ops::cbrt(m); let s_ = ops::cbrt(s); - let l = 0.2104542553 * l_ + 0.7936177850 * m_ - 0.0040720468 * s_; - let a = 1.9779984951 * l_ - 2.4285922050 * m_ + 0.4505937099 * s_; - let b = 0.0259040371 * l_ + 0.7827717662 * m_ - 0.8086757660 * s_; + let l = 0.21045426 * l_ + 0.7936178 * m_ - 0.004072047 * s_; + let a = 1.9779985 * l_ - 2.4285922 * m_ + 0.4505937 * s_; + let b = 0.025904037 * l_ + 0.78277177 * m_ - 0.80867577 * s_; Oklaba::new(l, a, b, alpha) } } -#[allow(clippy::excessive_precision)] impl From for LinearRgba { fn from(value: Oklaba) -> Self { let Oklaba { @@ -250,17 +249,18 @@ impl From for LinearRgba { } = value; // From https://github.com/Ogeon/palette/blob/e75eab2fb21af579353f51f6229a510d0d50a311/palette/src/oklab.rs#L312-L332 - let l_ = lightness + 0.3963377774 * a + 0.2158037573 * b; - let m_ = lightness - 0.1055613458 * a - 0.0638541728 * b; - let s_ = lightness - 0.0894841775 * a - 1.2914855480 * b; + // Floats literals are truncated from the source code above, to avoid excessive precision. + let l_ = lightness + 0.39633778 * a + 0.21580376 * b; + let m_ = lightness - 0.105561346 * a - 0.06385417 * b; + let s_ = lightness - 0.08948418 * a - 1.2914855 * b; let l = l_ * l_ * l_; let m = m_ * m_ * m_; let s = s_ * s_ * s_; - let red = 4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s; - let green = -1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s; - let blue = -0.0041960863 * l - 0.7034186147 * m + 1.7076147010 * s; + let red = 4.0767417 * l - 3.3077116 * m + 0.23096994 * s; + let green = -1.268438 * l + 2.6097574 * m - 0.34131938 * s; + let blue = -0.0041960863 * l - 0.7034186 * m + 1.7076147 * s; Self { red, From 120b733ab5f1693ac642c188ce08575f599958d7 Mon Sep 17 00:00:00 2001 From: MichiRecRoom <1008889+LikeLakers2@users.noreply.github.com> Date: Fri, 3 Jan 2025 17:22:34 -0500 Subject: [PATCH 112/272] bevy_reflect: Apply `#[deny(clippy::allow_attributes, clippy::allow_attributes_without_reason)]` (#17092) # Objective We want to deny the following lints: * `clippy::allow_attributes` - Because there's no reason to `#[allow(...)]` an attribute if it wouldn't lint against anything; you should always use `#[expect(...)]` * `clippy::allow_attributes_without_reason` - Because documenting the reason for allowing/expecting a lint is always good ## Solution Set the `clippy::allow_attributes` and `clippy::allow_attributes_without_reason` lints to `deny`, and bring `bevy_reflect` in line with the new restrictions. No code changes have been made - except if a lint that was previously `allow(...)`'d could be removed via small code changes. For example, `unused_variables` can be handled by adding a `_` to the beginning of a field's name. ## Testing I ran `cargo clippy`, and received no errors. --- crates/bevy_reflect/src/attributes.rs | 1 - crates/bevy_reflect/src/from_reflect.rs | 1 - crates/bevy_reflect/src/func/info.rs | 4 ---- crates/bevy_reflect/src/func/reflect_fn.rs | 9 ++++++++- crates/bevy_reflect/src/func/reflect_fn_mut.rs | 9 ++++++++- crates/bevy_reflect/src/impls/glam.rs | 5 ++++- crates/bevy_reflect/src/impls/std.rs | 7 ++++--- crates/bevy_reflect/src/lib.rs | 16 +++++++++++----- crates/bevy_reflect/src/path/mod.rs | 5 ++++- crates/bevy_reflect/src/path/parse.rs | 5 ++++- crates/bevy_reflect/src/serde/mod.rs | 2 +- crates/bevy_reflect/src/type_registry.rs | 18 +++++++++++++----- 12 files changed, 57 insertions(+), 25 deletions(-) diff --git a/crates/bevy_reflect/src/attributes.rs b/crates/bevy_reflect/src/attributes.rs index 0e31b603985c3..cc952d93f15c8 100644 --- a/crates/bevy_reflect/src/attributes.rs +++ b/crates/bevy_reflect/src/attributes.rs @@ -152,7 +152,6 @@ macro_rules! impl_custom_attribute_methods { $self.custom_attributes().get::() } - #[allow(rustdoc::redundant_explicit_links)] /// Gets a custom attribute by its [`TypeId`](core::any::TypeId). /// /// This is the dynamic equivalent of [`get_attribute`](Self::get_attribute). diff --git a/crates/bevy_reflect/src/from_reflect.rs b/crates/bevy_reflect/src/from_reflect.rs index 71a0dd571591c..dc113d869f3c7 100644 --- a/crates/bevy_reflect/src/from_reflect.rs +++ b/crates/bevy_reflect/src/from_reflect.rs @@ -112,7 +112,6 @@ impl ReflectFromReflect { /// /// This will convert the object to a concrete type if it wasn't already, and return /// the value as `Box`. - #[allow(clippy::wrong_self_convention)] pub fn from_reflect(&self, reflect_value: &dyn PartialReflect) -> Option> { (self.from_reflect)(reflect_value) } diff --git a/crates/bevy_reflect/src/func/info.rs b/crates/bevy_reflect/src/func/info.rs index 40f90aa505f82..53737fd891dbd 100644 --- a/crates/bevy_reflect/src/func/info.rs +++ b/crates/bevy_reflect/src/func/info.rs @@ -612,7 +612,6 @@ macro_rules! impl_typed_function { FunctionInfo::new( create_info::() .with_args({ - #[allow(unused_mut)] let mut _index = 0; vec![ $(ArgInfo::new::<$Arg>({ @@ -638,7 +637,6 @@ macro_rules! impl_typed_function { FunctionInfo::new( create_info::() .with_args({ - #[allow(unused_mut)] let mut _index = 1; vec![ ArgInfo::new::<&Receiver>(0), @@ -665,7 +663,6 @@ macro_rules! impl_typed_function { FunctionInfo::new( create_info::() .with_args({ - #[allow(unused_mut)] let mut _index = 1; vec![ ArgInfo::new::<&mut Receiver>(0), @@ -692,7 +689,6 @@ macro_rules! impl_typed_function { FunctionInfo::new( create_info::() .with_args({ - #[allow(unused_mut)] let mut _index = 1; vec![ ArgInfo::new::<&mut Receiver>(0), diff --git a/crates/bevy_reflect/src/func/reflect_fn.rs b/crates/bevy_reflect/src/func/reflect_fn.rs index 5d6f61db700f2..98e1b010f3dca 100644 --- a/crates/bevy_reflect/src/func/reflect_fn.rs +++ b/crates/bevy_reflect/src/func/reflect_fn.rs @@ -88,7 +88,14 @@ macro_rules! impl_reflect_fn { // This clause essentially asserts that `Arg::This` is the same type as `Arg` Function: for<'a> Fn($($Arg::This<'a>),*) -> ReturnType + 'env, { - #[allow(unused_mut)] + #[expect( + clippy::allow_attributes, + reason = "This lint is part of a macro, which may not always trigger the `unused_mut` lint." + )] + #[allow( + unused_mut, + reason = "Some invocations of this macro may trigger the `unused_mut` lint, where others won't." + )] fn reflect_call<'a>(&self, mut args: ArgList<'a>) -> FunctionResult<'a> { const COUNT: usize = count_tokens!($($Arg)*); diff --git a/crates/bevy_reflect/src/func/reflect_fn_mut.rs b/crates/bevy_reflect/src/func/reflect_fn_mut.rs index e77adc82cc307..15353e46b8741 100644 --- a/crates/bevy_reflect/src/func/reflect_fn_mut.rs +++ b/crates/bevy_reflect/src/func/reflect_fn_mut.rs @@ -95,7 +95,14 @@ macro_rules! impl_reflect_fn_mut { // This clause essentially asserts that `Arg::This` is the same type as `Arg` Function: for<'a> FnMut($($Arg::This<'a>),*) -> ReturnType + 'env, { - #[allow(unused_mut)] + #[expect( + clippy::allow_attributes, + reason = "This lint is part of a macro, which may not always trigger the `unused_mut` lint." + )] + #[allow( + unused_mut, + reason = "Some invocations of this macro may trigger the `unused_mut` lint, where others won't." + )] fn reflect_call_mut<'a>(&mut self, mut args: ArgList<'a>) -> FunctionResult<'a> { const COUNT: usize = count_tokens!($($Arg)*); diff --git a/crates/bevy_reflect/src/impls/glam.rs b/crates/bevy_reflect/src/impls/glam.rs index d8d70d10dd7d4..a9e451bf63d98 100644 --- a/crates/bevy_reflect/src/impls/glam.rs +++ b/crates/bevy_reflect/src/impls/glam.rs @@ -10,7 +10,10 @@ macro_rules! reflect_enum { impl_reflect!($(#[$meta])* enum $ident { $($ty)* }); #[assert_type_match($ident, test_only)] - #[allow(clippy::upper_case_acronyms)] + #[expect( + clippy::upper_case_acronyms, + reason = "The variants used are not acronyms." + )] enum $ident { $($ty)* } }; } diff --git a/crates/bevy_reflect/src/impls/std.rs b/crates/bevy_reflect/src/impls/std.rs index 2e79883c334d2..0626a76f02d6b 100644 --- a/crates/bevy_reflect/src/impls/std.rs +++ b/crates/bevy_reflect/src/impls/std.rs @@ -1,5 +1,7 @@ -// Temporary workaround for impl_reflect!(Option/Result false-positive -#![allow(unused_qualifications)] +#![expect( + unused_qualifications, + reason = "Temporary workaround for impl_reflect!(Option/Result false-positive" +)] use crate::{ self as bevy_reflect, impl_type_path, map_apply, map_partial_eq, map_try_apply, @@ -236,7 +238,6 @@ macro_rules! impl_reflect_for_atomic { #[cfg(feature = "functions")] crate::func::macros::impl_function_traits!($ty); - #[allow(unused_mut)] impl GetTypeRegistration for $ty where $ty: Any + Send + Sync, diff --git a/crates/bevy_reflect/src/lib.rs b/crates/bevy_reflect/src/lib.rs index e84bee274b1b4..1d2017c942b44 100644 --- a/crates/bevy_reflect/src/lib.rs +++ b/crates/bevy_reflect/src/lib.rs @@ -1,4 +1,9 @@ #![expect(missing_docs, reason = "Not all docs are written yet, see #3492.")] +#![deny( + clippy::allow_attributes, + clippy::allow_attributes_without_reason, + reason = "See #17111; To be removed once all crates are in-line with these attributes" +)] #![cfg_attr( any(docsrs, docsrs_dep), expect( @@ -686,8 +691,7 @@ pub mod __macro_exports { note = "consider annotating `{Self}` with `#[derive(Reflect)]`" )] pub trait RegisterForReflection { - #[allow(unused_variables)] - fn __register(registry: &mut TypeRegistry) {} + fn __register(_registry: &mut TypeRegistry) {} } impl RegisterForReflection for T { @@ -712,7 +716,10 @@ pub mod __macro_exports { } #[cfg(test)] -#[allow(clippy::disallowed_types, clippy::approx_constant)] +#[expect( + clippy::approx_constant, + reason = "We don't need the exact value of Pi here." +)] mod tests { use ::serde::{de::DeserializeSeed, Deserialize, Serialize}; use alloc::{ @@ -876,7 +883,6 @@ mod tests { } #[test] - #[allow(clippy::disallowed_types)] fn reflect_unit_struct() { #[derive(Reflect)] struct Foo(u32, u64); @@ -2148,7 +2154,7 @@ mod tests { enum_struct: SomeEnum, custom: CustomDebug, #[reflect(ignore)] - #[allow(dead_code)] + #[expect(dead_code, reason = "This value is intended to not be reflected.")] ignored: isize, } diff --git a/crates/bevy_reflect/src/path/mod.rs b/crates/bevy_reflect/src/path/mod.rs index f25870d31d946..96741b2c6a500 100644 --- a/crates/bevy_reflect/src/path/mod.rs +++ b/crates/bevy_reflect/src/path/mod.rs @@ -502,7 +502,10 @@ impl core::ops::IndexMut for ParsedPath { } #[cfg(test)] -#[allow(clippy::float_cmp, clippy::approx_constant)] +#[expect( + clippy::approx_constant, + reason = "We don't need the exact value of Pi here." +)] mod tests { use super::*; use crate as bevy_reflect; diff --git a/crates/bevy_reflect/src/path/parse.rs b/crates/bevy_reflect/src/path/parse.rs index bc48fe9c01be0..2ab2939a30ae4 100644 --- a/crates/bevy_reflect/src/path/parse.rs +++ b/crates/bevy_reflect/src/path/parse.rs @@ -64,7 +64,10 @@ impl<'a> PathParser<'a> { // the last byte before an ASCII utf-8 character (ie: it is a char // boundary). // - The slice always starts after a symbol ie: an ASCII character's boundary. - #[allow(unsafe_code)] + #[expect( + unsafe_code, + reason = "We have fulfilled the Safety requirements for `from_utf8_unchecked`." + )] let ident = unsafe { from_utf8_unchecked(ident) }; self.remaining = remaining; diff --git a/crates/bevy_reflect/src/serde/mod.rs b/crates/bevy_reflect/src/serde/mod.rs index d35af8922e822..dcc38c3cc5987 100644 --- a/crates/bevy_reflect/src/serde/mod.rs +++ b/crates/bevy_reflect/src/serde/mod.rs @@ -199,7 +199,7 @@ mod tests { #[reflect_trait] trait Enemy: Reflect + Debug { - #[allow(dead_code, reason = "this method is purely for testing purposes")] + #[expect(dead_code, reason = "this method is purely for testing purposes")] fn hp(&self) -> u8; } diff --git a/crates/bevy_reflect/src/type_registry.rs b/crates/bevy_reflect/src/type_registry.rs index 609c66d1856ac..6c97825bccca3 100644 --- a/crates/bevy_reflect/src/type_registry.rs +++ b/crates/bevy_reflect/src/type_registry.rs @@ -79,8 +79,7 @@ pub trait GetTypeRegistration: 'static { /// /// This method is called by [`TypeRegistry::register`] to register any other required types. /// Often, this is done for fields of structs and enum variants to ensure all types are properly registered. - #[allow(unused_variables)] - fn register_type_dependencies(registry: &mut TypeRegistry) {} + fn register_type_dependencies(_registry: &mut TypeRegistry) {} } impl Default for TypeRegistry { @@ -785,7 +784,10 @@ pub struct ReflectFromPtr { from_ptr_mut: unsafe fn(PtrMut) -> &mut dyn Reflect, } -#[allow(unsafe_code)] +#[expect( + unsafe_code, + reason = "We must interact with pointers here, which are inherently unsafe." +)] impl ReflectFromPtr { /// Returns the [`TypeId`] that the [`ReflectFromPtr`] was constructed for. pub fn type_id(&self) -> TypeId { @@ -837,7 +839,10 @@ impl ReflectFromPtr { } } -#[allow(unsafe_code)] +#[expect( + unsafe_code, + reason = "We must interact with pointers here, which are inherently unsafe." +)] impl FromType for ReflectFromPtr { fn from_type() -> Self { ReflectFromPtr { @@ -857,7 +862,10 @@ impl FromType for ReflectFromPtr { } #[cfg(test)] -#[allow(unsafe_code)] +#[expect( + unsafe_code, + reason = "We must interact with pointers here, which are inherently unsafe." +)] mod test { use super::*; use crate as bevy_reflect; From 43db44ca3a9b80fb1a48bbb1d1285de8af238e9a Mon Sep 17 00:00:00 2001 From: Benjamin Brienen Date: Fri, 3 Jan 2025 17:27:59 -0500 Subject: [PATCH 113/272] Scale input to account for deadzones (#17015) # Objective Fixes #3450 ## Solution Scale the input to account for the range ## Testing Updated unit tests ## Migration Guide `GamepadButtonChangedEvent.value` is now linearly rescaled to be from `0.0..=1.0` (instead of `low..=high`) and `GamepadAxisChangedEvent.value` is now linearly rescaled to be from `-1.0..=0.0`/`0.0..=1.0` (accounting for the deadzone). --- crates/bevy_input/src/gamepad.rs | 404 +++++++++++++++++++++---------- 1 file changed, 277 insertions(+), 127 deletions(-) diff --git a/crates/bevy_input/src/gamepad.rs b/crates/bevy_input/src/gamepad.rs index cf4ad9e31560d..be122d73200ad 100644 --- a/crates/bevy_input/src/gamepad.rs +++ b/crates/bevy_input/src/gamepad.rs @@ -1,5 +1,7 @@ //! The gamepad input functionality. +use core::ops::RangeInclusive; + use crate::{Axis, ButtonInput, ButtonState}; use alloc::string::String; #[cfg(feature = "bevy_reflect")] @@ -30,7 +32,7 @@ use thiserror::Error; /// [`GamepadButtonChangedEvent`] and [`GamepadAxisChangedEvent`] when /// the in-frame relative ordering of events is important. /// -/// This event is produced by `bevy_input` +/// This event is produced by `bevy_input`. #[derive(Event, Debug, Clone, PartialEq, From)] #[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug, PartialEq))] #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] @@ -70,7 +72,7 @@ pub enum RawGamepadEvent { Axis(RawGamepadAxisChangedEvent), } -/// [`GamepadButton`] changed event unfiltered by [`GamepadSettings`] +/// [`GamepadButton`] changed event unfiltered by [`GamepadSettings`]. #[derive(Event, Debug, Copy, Clone, PartialEq)] #[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug, PartialEq))] #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] @@ -98,7 +100,7 @@ impl RawGamepadButtonChangedEvent { } } -/// [`GamepadAxis`] changed event unfiltered by [`GamepadSettings`] +/// [`GamepadAxis`] changed event unfiltered by [`GamepadSettings`]. #[derive(Event, Debug, Copy, Clone, PartialEq)] #[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug, PartialEq))] #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] @@ -151,18 +153,18 @@ impl GamepadConnectionEvent { } } - /// Is the gamepad connected? + /// Whether the gamepad is connected. pub fn connected(&self) -> bool { matches!(self.connection, GamepadConnection::Connected { .. }) } - /// Is the gamepad disconnected? + /// Whether the gamepad is disconnected. pub fn disconnected(&self) -> bool { !self.connected() } } -/// [`GamepadButton`] event triggered by a digital state change +/// [`GamepadButton`] event triggered by a digital state change. #[derive(Event, Debug, Clone, Copy, PartialEq, Eq)] #[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug, PartialEq))] #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] @@ -180,7 +182,7 @@ pub struct GamepadButtonStateChangedEvent { } impl GamepadButtonStateChangedEvent { - /// Creates a new [`GamepadButtonStateChangedEvent`] + /// Creates a new [`GamepadButtonStateChangedEvent`]. pub fn new(entity: Entity, button: GamepadButton, state: ButtonState) -> Self { Self { entity, @@ -190,7 +192,7 @@ impl GamepadButtonStateChangedEvent { } } -/// [`GamepadButton`] event triggered by an analog state change +/// [`GamepadButton`] event triggered by an analog state change. #[derive(Event, Debug, Clone, Copy, PartialEq)] #[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug, PartialEq))] #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] @@ -205,12 +207,12 @@ pub struct GamepadButtonChangedEvent { pub button: GamepadButton, /// The pressed state of the button. pub state: ButtonState, - /// The analog value of the button. + /// The analog value of the button (rescaled to be in the 0.0..=1.0 range). pub value: f32, } impl GamepadButtonChangedEvent { - /// Creates a new [`GamepadButtonChangedEvent`] + /// Creates a new [`GamepadButtonChangedEvent`]. pub fn new(entity: Entity, button: GamepadButton, state: ButtonState, value: f32) -> Self { Self { entity, @@ -221,7 +223,7 @@ impl GamepadButtonChangedEvent { } } -/// [`GamepadAxis`] event triggered by an analog state change +/// [`GamepadAxis`] event triggered by an analog state change. #[derive(Event, Debug, Clone, Copy, PartialEq)] #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug, PartialEq))] @@ -234,12 +236,12 @@ pub struct GamepadAxisChangedEvent { pub entity: Entity, /// The gamepad axis assigned to the event. pub axis: GamepadAxis, - /// The value of this axis. + /// The value of this axis (rescaled to account for axis settings). pub value: f32, } impl GamepadAxisChangedEvent { - /// Creates a new [`GamepadAxisChangedEvent`] + /// Creates a new [`GamepadAxisChangedEvent`]. pub fn new(entity: Entity, axis: GamepadAxis, value: f32) -> Self { Self { entity, @@ -339,12 +341,10 @@ pub struct Gamepad { /// The USB vendor ID as assigned by the USB-IF, if available. pub(crate) vendor_id: Option, - /// The USB product ID as assigned by the [vendor], if available. - /// - /// [vendor]: Self::vendor_id + /// The USB product ID as assigned by the [vendor][Self::vendor_id], if available. pub(crate) product_id: Option, - /// [`ButtonInput`] of [`GamepadButton`] representing their digital state + /// [`ButtonInput`] of [`GamepadButton`] representing their digital state. pub(crate) digital: ButtonInput, /// [`Axis`] of [`GamepadButton`] representing their analog state. @@ -378,7 +378,7 @@ impl Gamepad { self.analog.get_unclamped(input.into()) } - /// Returns the left stick as a [`Vec2`] + /// Returns the left stick as a [`Vec2`]. pub fn left_stick(&self) -> Vec2 { Vec2 { x: self.get(GamepadAxis::LeftStickX).unwrap_or(0.0), @@ -386,7 +386,7 @@ impl Gamepad { } } - /// Returns the right stick as a [`Vec2`] + /// Returns the right stick as a [`Vec2`]. pub fn right_stick(&self) -> Vec2 { Vec2 { x: self.get(GamepadAxis::RightStickX).unwrap_or(0.0), @@ -394,7 +394,7 @@ impl Gamepad { } } - /// Returns the directional pad as a [`Vec2`] + /// Returns the directional pad as a [`Vec2`]. pub fn dpad(&self) -> Vec2 { Vec2 { x: self.get(GamepadButton::DPadRight).unwrap_or(0.0) @@ -480,14 +480,12 @@ impl Gamepad { self.digital.get_just_released() } - /// Returns an iterator over all analog [axes]. - /// - /// [axes]: GamepadInput + /// Returns an iterator over all analog [axes][GamepadInput]. pub fn get_analog_axes(&self) -> impl Iterator { self.analog.all_axes() } - /// [`ButtonInput`] of [`GamepadButton`] representing their digital state + /// [`ButtonInput`] of [`GamepadButton`] representing their digital state. pub fn digital(&self) -> &ButtonInput { &self.digital } @@ -531,7 +529,7 @@ impl Default for Gamepad { /// /// ## Usage /// -/// This is used to determine which button has changed its value when receiving gamepad button events +/// This is used to determine which button has changed its value when receiving gamepad button events. /// It is also used in the [`Gamepad`] component. #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] #[cfg_attr( @@ -593,7 +591,7 @@ pub enum GamepadButton { } impl GamepadButton { - /// Returns an array of all the standard [`GamepadButton`] + /// Returns an array of all the standard [`GamepadButton`]. pub const fn all() -> [GamepadButton; 19] { [ GamepadButton::South, @@ -619,7 +617,7 @@ impl GamepadButton { } } -/// Represents gamepad input types that are mapped in the range [-1.0, 1.0] +/// Represents gamepad input types that are mapped in the range [-1.0, 1.0]. /// /// ## Usage /// @@ -665,14 +663,14 @@ impl GamepadAxis { } } -/// Encapsulation over [`GamepadAxis`] and [`GamepadButton`] +/// Encapsulation over [`GamepadAxis`] and [`GamepadButton`]. // This is done so Gamepad can share a single Axis and simplifies the API by having only one get/get_unclamped method #[derive(Debug, Copy, Clone, Eq, Hash, PartialEq, From)] #[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug, PartialEq))] pub enum GamepadInput { - /// A [`GamepadAxis`] + /// A [`GamepadAxis`]. Axis(GamepadAxis), - /// A [`GamepadButton`] + /// A [`GamepadButton`]. Button(GamepadButton), } @@ -928,9 +926,9 @@ impl ButtonSettings { /// threshold for an axis. /// Values that are higher than `livezone_upperbound` will be rounded up to 1.0. /// Values that are lower than `livezone_lowerbound` will be rounded down to -1.0. -/// Values that are in-between `deadzone_lowerbound` and `deadzone_upperbound` will be rounded -/// to 0.0. -/// Otherwise, values will not be rounded. +/// Values that are in-between `deadzone_lowerbound` and `deadzone_upperbound` will be rounded to 0.0. +/// Otherwise, values will be linearly rescaled to fit into the sensitivity range. +/// For example, a value that is one fourth of the way from `deadzone_upperbound` to `livezone_upperbound` will be scaled to 0.25. /// /// The valid range is `[-1.0, 1.0]`. #[derive(Debug, Clone, PartialEq)] @@ -1041,7 +1039,7 @@ impl AxisSettings { /// /// # Errors /// - /// If the value passed is less than the dead zone upper bound, + /// If the value passed is less than the deadzone upper bound, /// returns `AxisSettingsError::DeadZoneUpperBoundGreaterThanLiveZoneUpperBound`. /// If the value passed is not in range [0.0..=1.0], returns `AxisSettingsError::LiveZoneUpperBoundOutOfRange`. pub fn try_set_livezone_upperbound(&mut self, value: f32) -> Result<(), AxisSettingsError> { @@ -1117,7 +1115,7 @@ impl AxisSettings { /// /// # Errors /// - /// If the value passed is less than the dead zone lower bound, + /// If the value passed is less than the deadzone lower bound, /// returns `AxisSettingsError::LiveZoneLowerBoundGreaterThanDeadZoneLowerBound`. /// If the value passed is not in range [-1.0..=0.0], returns `AxisSettingsError::LiveZoneLowerBoundOutOfRange`. pub fn try_set_livezone_lowerbound(&mut self, value: f32) -> Result<(), AxisSettingsError> { @@ -1213,39 +1211,135 @@ impl AxisSettings { } /// Clamps the `raw_value` according to the `AxisSettings`. - pub fn clamp(&self, new_value: f32) -> f32 { - if self.deadzone_lowerbound <= new_value && new_value <= self.deadzone_upperbound { + pub fn clamp(&self, raw_value: f32) -> f32 { + if self.deadzone_lowerbound <= raw_value && raw_value <= self.deadzone_upperbound { 0.0 - } else if new_value >= self.livezone_upperbound { + } else if raw_value >= self.livezone_upperbound { 1.0 - } else if new_value <= self.livezone_lowerbound { + } else if raw_value <= self.livezone_lowerbound { -1.0 } else { - new_value + raw_value } } - /// Determines whether the change from `old_value` to `new_value` should + /// Determines whether the change from `old_raw_value` to `new_raw_value` should /// be registered as a change, according to the [`AxisSettings`]. - fn should_register_change(&self, new_value: f32, old_value: Option) -> bool { - if old_value.is_none() { - return true; + fn should_register_change(&self, new_raw_value: f32, old_raw_value: Option) -> bool { + match old_raw_value { + None => true, + Some(old_raw_value) => ops::abs(new_raw_value - old_raw_value) >= self.threshold, } - - ops::abs(new_value - old_value.unwrap()) > self.threshold } - /// Filters the `new_value` based on the `old_value`, according to the [`AxisSettings`]. + /// Filters the `new_raw_value` based on the `old_raw_value`, according to the [`AxisSettings`]. /// - /// Returns the clamped `new_value` if the change exceeds the settings threshold, + /// Returns the clamped and scaled `new_raw_value` if the change exceeds the settings threshold, /// and `None` otherwise. - pub fn filter(&self, new_value: f32, old_value: Option) -> Option { - let new_value = self.clamp(new_value); + fn filter( + &self, + new_raw_value: f32, + old_raw_value: Option, + ) -> Option { + let clamped_unscaled = self.clamp(new_raw_value); + match self.should_register_change(clamped_unscaled, old_raw_value) { + true => Some(FilteredAxisPosition { + scaled: self.get_axis_position_from_value(clamped_unscaled), + raw: new_raw_value, + }), + false => None, + } + } + + #[inline(always)] + fn get_axis_position_from_value(&self, value: f32) -> ScaledAxisWithDeadZonePosition { + if value < self.deadzone_upperbound && value > self.deadzone_lowerbound { + ScaledAxisWithDeadZonePosition::Dead + } else if value > self.livezone_upperbound { + ScaledAxisWithDeadZonePosition::AboveHigh + } else if value < self.livezone_lowerbound { + ScaledAxisWithDeadZonePosition::BelowLow + } else if value >= self.deadzone_upperbound { + ScaledAxisWithDeadZonePosition::High(linear_remapping( + value, + self.deadzone_upperbound..=self.livezone_upperbound, + 0.0..=1.0, + )) + } else if value <= self.deadzone_lowerbound { + ScaledAxisWithDeadZonePosition::Low(linear_remapping( + value, + self.livezone_lowerbound..=self.deadzone_lowerbound, + -1.0..=0.0, + )) + } else { + unreachable!(); + } + } +} - if self.should_register_change(new_value, old_value) { - return Some(new_value); +/// A linear remapping of `value` from `old` to `new`. +fn linear_remapping(value: f32, old: RangeInclusive, new: RangeInclusive) -> f32 { + // https://stackoverflow.com/a/929104 + ((value - old.start()) / (old.end() - old.start())) * (new.end() - new.start()) + new.start() +} + +#[derive(Debug, Clone, Copy)] +/// Deadzone-aware axis position. +enum ScaledAxisWithDeadZonePosition { + /// The input clipped below the valid range of the axis. + BelowLow, + /// The input is lower than the deadzone. + Low(f32), + /// The input falls within the deadzone, meaning it is counted as 0. + Dead, + /// The input is higher than the deadzone. + High(f32), + /// The input clipped above the valid range of the axis. + AboveHigh, +} + +struct FilteredAxisPosition { + scaled: ScaledAxisWithDeadZonePosition, + raw: f32, +} + +impl ScaledAxisWithDeadZonePosition { + /// Converts the value into a float in the range [-1, 1]. + fn to_f32(self) -> f32 { + match self { + ScaledAxisWithDeadZonePosition::BelowLow => -1., + ScaledAxisWithDeadZonePosition::Low(scaled) + | ScaledAxisWithDeadZonePosition::High(scaled) => scaled, + ScaledAxisWithDeadZonePosition::Dead => 0., + ScaledAxisWithDeadZonePosition::AboveHigh => 1., + } + } +} + +#[derive(Debug, Clone, Copy)] +/// Low/High-aware axis position. +enum ScaledAxisPosition { + /// The input fell short of the "low" value. + ClampedLow, + /// The input was in the normal range. + Scaled(f32), + /// The input surpassed the "high" value. + ClampedHigh, +} + +struct FilteredButtonAxisPosition { + scaled: ScaledAxisPosition, + raw: f32, +} + +impl ScaledAxisPosition { + /// Converts the value into a float in the range [0, 1]. + fn to_f32(self) -> f32 { + match self { + ScaledAxisPosition::ClampedLow => 0., + ScaledAxisPosition::Scaled(scaled) => scaled, + ScaledAxisPosition::ClampedHigh => 1., } - None } } @@ -1300,27 +1394,48 @@ impl ButtonAxisSettings { raw_value } - /// Determines whether the change from an `old_value` to a `new_value` should + /// Determines whether the change from an `old_raw_value` to a `new_raw_value` should /// be registered as a change event, according to the specified settings. - fn should_register_change(&self, new_value: f32, old_value: Option) -> bool { - if old_value.is_none() { - return true; + fn should_register_change(&self, new_raw_value: f32, old_raw_value: Option) -> bool { + match old_raw_value { + None => true, + Some(old_raw_value) => ops::abs(new_raw_value - old_raw_value) >= self.threshold, } - - ops::abs(new_value - old_value.unwrap()) > self.threshold } - /// Filters the `new_value` based on the `old_value`, according to the [`ButtonAxisSettings`]. + /// Filters the `new_raw_value` based on the `old_raw_value`, according to the [`ButtonAxisSettings`]. /// - /// Returns the clamped `new_value`, according to the [`ButtonAxisSettings`], if the change + /// Returns the clamped and scaled `new_raw_value`, according to the [`ButtonAxisSettings`], if the change /// exceeds the settings threshold, and `None` otherwise. - pub fn filter(&self, new_value: f32, old_value: Option) -> Option { - let new_value = self.clamp(new_value); + fn filter( + &self, + new_raw_value: f32, + old_raw_value: Option, + ) -> Option { + let clamped_unscaled = self.clamp(new_raw_value); + match self.should_register_change(clamped_unscaled, old_raw_value) { + true => Some(FilteredButtonAxisPosition { + scaled: self.get_axis_position_from_value(clamped_unscaled), + raw: new_raw_value, + }), + false => None, + } + } - if self.should_register_change(new_value, old_value) { - return Some(new_value); + /// Clamps and scales the `value` according to the specified settings. + /// + /// If the `value` is: + /// - lower than or equal to `low` it will be rounded to 0.0. + /// - higher than or equal to `high` it will be rounded to 1.0. + /// - Otherwise, it will be scaled from (low, high) to (0, 1). + fn get_axis_position_from_value(&self, value: f32) -> ScaledAxisPosition { + if value <= self.low { + ScaledAxisPosition::ClampedLow + } else if value >= self.high { + ScaledAxisPosition::ClampedHigh + } else { + ScaledAxisPosition::Scaled(linear_remapping(value, self.low..=self.high, 0.0..=1.0)) } - None } } @@ -1328,7 +1443,7 @@ impl ButtonAxisSettings { /// /// On connection, adds the components representing a [`Gamepad`] to the entity. /// On disconnection, removes the [`Gamepad`] and other related components. -/// Entities are left alive and might leave components like [`GamepadSettings`] to preserve state in the case of a reconnection +/// Entities are left alive and might leave components like [`GamepadSettings`] to preserve state in the case of a reconnection. /// /// ## Note /// @@ -1441,9 +1556,9 @@ pub fn gamepad_event_processing_system( else { continue; }; - - gamepad_axis.analog.set(axis, filtered_value); - let send_event = GamepadAxisChangedEvent::new(gamepad, axis, filtered_value); + gamepad_axis.analog.set(axis, filtered_value.raw); + let send_event = + GamepadAxisChangedEvent::new(gamepad, axis, filtered_value.scaled.to_f32()); processed_axis_events.send(send_event); processed_events.send(GamepadEvent::from(send_event)); } @@ -1463,9 +1578,9 @@ pub fn gamepad_event_processing_system( continue; }; let button_settings = settings.get_button_settings(button); - gamepad_buttons.analog.set(button, filtered_value); + gamepad_buttons.analog.set(button, filtered_value.raw); - if button_settings.is_released(filtered_value) { + if button_settings.is_released(filtered_value.raw) { // Check if button was previously pressed if gamepad_buttons.pressed(button) { processed_digital_events.send(GamepadButtonStateChangedEvent::new( @@ -1477,7 +1592,7 @@ pub fn gamepad_event_processing_system( // We don't have to check if the button was previously pressed here // because that check is performed within Input::release() gamepad_buttons.digital.release(button); - } else if button_settings.is_pressed(filtered_value) { + } else if button_settings.is_pressed(filtered_value.raw) { // Check if button was previously not pressed if !gamepad_buttons.pressed(button) { processed_digital_events.send(GamepadButtonStateChangedEvent::new( @@ -1494,8 +1609,12 @@ pub fn gamepad_event_processing_system( } else { ButtonState::Released }; - let send_event = - GamepadButtonChangedEvent::new(gamepad, button, button_state, filtered_value); + let send_event = GamepadButtonChangedEvent::new( + gamepad, + button, + button_state, + filtered_value.scaled.to_f32(), + ); processed_analog_events.send(send_event); processed_events.send(GamepadEvent::from(send_event)); } @@ -1649,130 +1768,161 @@ mod tests { fn test_button_axis_settings_filter( settings: ButtonAxisSettings, - new_value: f32, - old_value: Option, + new_raw_value: f32, + old_raw_value: Option, expected: Option, ) { - let actual = settings.filter(new_value, old_value); + let actual = settings + .filter(new_raw_value, old_raw_value) + .map(|f| f.scaled.to_f32()); assert_eq!( expected, actual, - "Testing filtering for {settings:?} with new_value = {new_value:?}, old_value = {old_value:?}", + "Testing filtering for {settings:?} with new_raw_value = {new_raw_value:?}, old_raw_value = {old_raw_value:?}", ); } #[test] fn test_button_axis_settings_default_filter() { let cases = [ + // clamped (1.0, None, Some(1.0)), (0.99, None, Some(1.0)), (0.96, None, Some(1.0)), (0.95, None, Some(1.0)), - (0.9499, None, Some(0.9499)), - (0.84, None, Some(0.84)), - (0.43, None, Some(0.43)), - (0.05001, None, Some(0.05001)), + // linearly rescaled from 0.05..=0.95 to 0.0..=1.0 + (0.9499, None, Some(0.9998889)), + (0.84, None, Some(0.87777776)), + (0.43, None, Some(0.42222223)), + (0.05001, None, Some(0.000011109644)), + // clamped (0.05, None, Some(0.0)), (0.04, None, Some(0.0)), (0.01, None, Some(0.0)), (0.0, None, Some(0.0)), ]; - for (new_value, old_value, expected) in cases { + for (new_raw_value, old_raw_value, expected) in cases { let settings = ButtonAxisSettings::default(); - test_button_axis_settings_filter(settings, new_value, old_value, expected); + test_button_axis_settings_filter(settings, new_raw_value, old_raw_value, expected); } } #[test] - fn test_button_axis_settings_default_filter_with_old_value() { + fn test_button_axis_settings_default_filter_with_old_raw_value() { let cases = [ - (0.43, Some(0.44001), Some(0.43)), + // 0.43 gets rescaled to 0.42222223 (0.05..=0.95 -> 0.0..=1.0) + (0.43, Some(0.44001), Some(0.42222223)), (0.43, Some(0.44), None), (0.43, Some(0.43), None), - (0.43, Some(0.41999), Some(0.43)), - (0.43, Some(0.17), Some(0.43)), - (0.43, Some(0.84), Some(0.43)), + (0.43, Some(0.41999), Some(0.42222223)), + (0.43, Some(0.17), Some(0.42222223)), + (0.43, Some(0.84), Some(0.42222223)), (0.05, Some(0.055), Some(0.0)), (0.95, Some(0.945), Some(1.0)), ]; - for (new_value, old_value, expected) in cases { + for (new_raw_value, old_raw_value, expected) in cases { let settings = ButtonAxisSettings::default(); - test_button_axis_settings_filter(settings, new_value, old_value, expected); + test_button_axis_settings_filter(settings, new_raw_value, old_raw_value, expected); } } fn test_axis_settings_filter( settings: AxisSettings, - new_value: f32, - old_value: Option, + new_raw_value: f32, + old_raw_value: Option, expected: Option, ) { - let actual = settings.filter(new_value, old_value); + let actual = settings.filter(new_raw_value, old_raw_value); assert_eq!( - expected, actual, - "Testing filtering for {settings:?} with new_value = {new_value:?}, old_value = {old_value:?}", + expected, actual.map(|f| f.scaled.to_f32()), + "Testing filtering for {settings:?} with new_raw_value = {new_raw_value:?}, old_raw_value = {old_raw_value:?}", ); } #[test] fn test_axis_settings_default_filter() { + // new (raw), expected (rescaled linearly) let cases = [ + // high enough to round to 1.0 (1.0, Some(1.0)), (0.99, Some(1.0)), (0.96, Some(1.0)), (0.95, Some(1.0)), - (0.9499, Some(0.9499)), - (0.84, Some(0.84)), - (0.43, Some(0.43)), - (0.05001, Some(0.05001)), + // for the following, remember that 0.05 is the "low" value and 0.95 is the "high" value + // barely below the high value means barely below 1 after scaling + (0.9499, Some(0.9998889)), // scaled as: (0.9499 - 0.05) / (0.95 - 0.05) + (0.84, Some(0.87777776)), // scaled as: (0.84 - 0.05) / (0.95 - 0.05) + (0.43, Some(0.42222223)), // scaled as: (0.43 - 0.05) / (0.95 - 0.05) + // barely above the low value means barely above 0 after scaling + (0.05001, Some(0.000011109644)), // scaled as: (0.05001 - 0.05) / (0.95 - 0.05) + // low enough to be rounded to 0 (dead zone) (0.05, Some(0.0)), (0.04, Some(0.0)), (0.01, Some(0.0)), (0.0, Some(0.0)), + // same exact tests as above, but below 0 (bottom half of the dead zone and live zone) + // low enough to be rounded to -1 (-1.0, Some(-1.0)), (-0.99, Some(-1.0)), (-0.96, Some(-1.0)), (-0.95, Some(-1.0)), - (-0.9499, Some(-0.9499)), - (-0.84, Some(-0.84)), - (-0.43, Some(-0.43)), - (-0.05001, Some(-0.05001)), + // scaled inputs + (-0.9499, Some(-0.9998889)), // scaled as: (-0.9499 - -0.05) / (-0.95 - -0.05) + (-0.84, Some(-0.87777776)), // scaled as: (-0.84 - -0.05) / (-0.95 - -0.05) + (-0.43, Some(-0.42222226)), // scaled as: (-0.43 - -0.05) / (-0.95 - -0.05) + (-0.05001, Some(-0.000011146069)), // scaled as: (-0.05001 - -0.05) / (-0.95 - -0.05) + // high enough to be rounded to 0 (dead zone) (-0.05, Some(0.0)), (-0.04, Some(0.0)), (-0.01, Some(0.0)), ]; - for (new_value, expected) in cases { + for (new_raw_value, expected) in cases { let settings = AxisSettings::new(-0.95, -0.05, 0.05, 0.95, 0.01).unwrap(); - test_axis_settings_filter(settings, new_value, None, expected); + test_axis_settings_filter(settings, new_raw_value, None, expected); } } #[test] - fn test_axis_settings_default_filter_with_old_values() { + fn test_axis_settings_default_filter_with_old_raw_values() { + let threshold = 0.01; + // expected values are hardcoded to be rescaled to from 0.05..=0.95 to 0.0..=1.0 + // new (raw), old (raw), expected let cases = [ - (0.43, Some(0.44001), Some(0.43)), - (0.43, Some(0.44), None), - (0.43, Some(0.43), None), - (0.43, Some(0.41999), Some(0.43)), - (0.43, Some(0.17), Some(0.43)), - (0.43, Some(0.84), Some(0.43)), - (0.05, Some(0.055), Some(0.0)), - (0.95, Some(0.945), Some(1.0)), - (-0.43, Some(-0.44001), Some(-0.43)), - (-0.43, Some(-0.44), None), - (-0.43, Some(-0.43), None), - (-0.43, Some(-0.41999), Some(-0.43)), - (-0.43, Some(-0.17), Some(-0.43)), - (-0.43, Some(-0.84), Some(-0.43)), - (-0.05, Some(-0.055), Some(0.0)), - (-0.95, Some(-0.945), Some(-1.0)), + // enough increase to change + (0.43, Some(0.43 + threshold * 1.1), Some(0.42222223)), + // enough decrease to change + (0.43, Some(0.43 - threshold * 1.1), Some(0.42222223)), + // not enough increase to change + (0.43, Some(0.43 + threshold * 0.9), None), + // not enough decrease to change + (0.43, Some(0.43 - threshold * 0.9), None), + // enough increase to change + (-0.43, Some(-0.43 + threshold * 1.1), Some(-0.42222226)), + // enough decrease to change + (-0.43, Some(-0.43 - threshold * 1.1), Some(-0.42222226)), + // not enough increase to change + (-0.43, Some(-0.43 + threshold * 0.9), None), + // not enough decrease to change + (-0.43, Some(-0.43 - threshold * 0.9), None), + // test upper deadzone logic + (0.05, Some(0.0), None), + (0.06, Some(0.0), Some(0.0111111095)), + // test lower deadzone logic + (-0.05, Some(0.0), None), + (-0.06, Some(0.0), Some(-0.011111081)), + // test upper livezone logic + (0.95, Some(1.0), None), + (0.94, Some(1.0), Some(0.9888889)), + // test lower livezone logic + (-0.95, Some(-1.0), None), + (-0.94, Some(-1.0), Some(-0.9888889)), ]; - for (new_value, old_value, expected) in cases { - let settings = AxisSettings::new(-0.95, -0.05, 0.05, 0.95, 0.01).unwrap(); - test_axis_settings_filter(settings, new_value, old_value, expected); + for (new_raw_value, old_raw_value, expected) in cases { + let settings = AxisSettings::new(-0.95, -0.05, 0.05, 0.95, threshold).unwrap(); + test_axis_settings_filter(settings, new_raw_value, old_raw_value, expected); } } From 39f38a191eca962278f4c540c0890e2f219e2933 Mon Sep 17 00:00:00 2001 From: Benjamin Brienen Date: Fri, 3 Jan 2025 17:28:56 -0500 Subject: [PATCH 114/272] Add `-D warnings` in a few steps (#17081) # Objective I missed a couple checks in #17011 ## Solution Add env ## Testing CI --- .github/workflows/ci.yml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 13feb4b8050b2..71d418804bf02 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,6 +12,7 @@ env: CARGO_TERM_COLOR: always # If nightly is breaking CI, modify this variable to target a specific nightly version. NIGHTLY_TOOLCHAIN: nightly + RUSTFLAGS: "-D warnings" concurrency: group: ${{github.workflow}}-${{github.ref}} @@ -154,7 +155,6 @@ jobs: build-wasm: runs-on: ubuntu-latest timeout-minutes: 30 - needs: build steps: - uses: actions/checkout@v4 - uses: actions/cache@v4 @@ -170,14 +170,11 @@ jobs: with: target: wasm32-unknown-unknown - name: Check wasm - env: - RUSTFLAGS: "-D warnings" run: cargo check --target wasm32-unknown-unknown build-wasm-atomics: runs-on: ubuntu-latest timeout-minutes: 30 - needs: build steps: - uses: actions/checkout@v4 - uses: actions/cache@v4 @@ -400,7 +397,6 @@ jobs: msrv: runs-on: ubuntu-latest timeout-minutes: 30 - needs: build steps: - uses: actions/checkout@v4 - uses: actions/cache@v4 From 859c2d77f9659c229fa8bd76f0c554ba4f73d243 Mon Sep 17 00:00:00 2001 From: Rob Parrett Date: Fri, 3 Jan 2025 16:22:18 -0800 Subject: [PATCH 115/272] Revert "Fix sprite performance regression since retained render world (#17078)" (#17123) # Objective Fixes #17098 It seems that it's not totally obvious how to fix this, but that reverting might be part of the solution anyway. Let's get the repo back into a working state. ## Solution Revert the [recent optimization](https://github.com/bevyengine/bevy/pull/17078) that broke "many-to-one main->render world entities" for 2d. ## Testing `cargo run --example text2d` `cargo run --example sprite_slice` --- crates/bevy_sprite/src/render/mod.rs | 37 +++++++++---------- .../src/texture_slice/computed_slices.rs | 2 - crates/bevy_text/src/text2d.rs | 6 ++- examples/stress_tests/bevymark.rs | 23 +++++------- 4 files changed, 31 insertions(+), 37 deletions(-) diff --git a/crates/bevy_sprite/src/render/mod.rs b/crates/bevy_sprite/src/render/mod.rs index f509a5750384a..3adbfc9417dde 100644 --- a/crates/bevy_sprite/src/render/mod.rs +++ b/crates/bevy_sprite/src/render/mod.rs @@ -19,6 +19,7 @@ use bevy_ecs::{ }; use bevy_image::{BevyDefault, Image, ImageSampler, TextureFormatPixelInfo}; use bevy_math::{Affine3A, FloatOrd, Quat, Rect, Vec2, Vec4}; +use bevy_render::sync_world::MainEntity; use bevy_render::view::RenderVisibleEntities; use bevy_render::{ render_asset::RenderAssets, @@ -31,7 +32,7 @@ use bevy_render::{ *, }, renderer::{RenderDevice, RenderQueue}, - sync_world::{MainEntityHashMap, RenderEntity, TemporaryRenderEntity}, + sync_world::{RenderEntity, TemporaryRenderEntity}, texture::{DefaultImageSampler, FallbackImage, GpuImage}, view::{ ExtractedView, Msaa, ViewTarget, ViewUniform, ViewUniformOffset, ViewUniforms, @@ -340,12 +341,11 @@ pub struct ExtractedSprite { /// For cases where additional [`ExtractedSprites`] are created during extraction, this stores the /// entity that caused that creation for use in determining visibility. pub original_entity: Option, - pub render_entity: Entity, } #[derive(Resource, Default)] pub struct ExtractedSprites { - pub sprites: MainEntityHashMap, + pub sprites: HashMap<(Entity, MainEntity), ExtractedSprite>, } #[derive(Resource, Default)] @@ -390,13 +390,16 @@ pub fn extract_sprites( if let Some(slices) = slices { extracted_sprites.sprites.extend( slices - .extract_sprites( - transform, - original_entity, - commands.spawn(TemporaryRenderEntity).id(), - sprite, - ) - .map(|e| (original_entity.into(), e)), + .extract_sprites(transform, original_entity, sprite) + .map(|e| { + ( + ( + commands.spawn(TemporaryRenderEntity).id(), + original_entity.into(), + ), + e, + ) + }), ); } else { let atlas_rect = sprite @@ -417,7 +420,7 @@ pub fn extract_sprites( // PERF: we don't check in this function that the `Image` asset is ready, since it should be in most cases and hashing the handle is expensive extracted_sprites.sprites.insert( - original_entity.into(), + (entity, original_entity.into()), ExtractedSprite { color: sprite.color.into(), transform: *transform, @@ -429,7 +432,6 @@ pub fn extract_sprites( image_handle_id: sprite.image.id(), anchor: sprite.anchor.as_vec(), original_entity: Some(original_entity), - render_entity: entity, }, ); } @@ -556,11 +558,8 @@ pub fn queue_sprites( .items .reserve(extracted_sprites.sprites.len()); - for (main_entity, extracted_sprite) in extracted_sprites.sprites.iter() { - let index = extracted_sprite - .original_entity - .unwrap_or(extracted_sprite.render_entity) - .index(); + for ((entity, main_entity), extracted_sprite) in extracted_sprites.sprites.iter() { + let index = extracted_sprite.original_entity.unwrap_or(*entity).index(); if !view_entities.contains(index as usize) { continue; @@ -573,7 +572,7 @@ pub fn queue_sprites( transparent_phase.add(Transparent2d { draw_function: draw_sprite_function, pipeline, - entity: (extracted_sprite.render_entity, *main_entity), + entity: (*entity, *main_entity), sort_key, // batch_range and dynamic_offset will be calculated in prepare_sprites batch_range: 0..0, @@ -663,7 +662,7 @@ pub fn prepare_sprite_image_bind_groups( // Compatible items share the same entity. for item_index in 0..transparent_phase.items.len() { let item = &transparent_phase.items[item_index]; - let Some(extracted_sprite) = extracted_sprites.sprites.get(&item.entity.1) else { + let Some(extracted_sprite) = extracted_sprites.sprites.get(&item.entity) else { // If there is a phase item that is not a sprite, then we must start a new // batch to draw the other phase item(s) and to respect draw order. This can be // done by invalidating the batch_image_handle diff --git a/crates/bevy_sprite/src/texture_slice/computed_slices.rs b/crates/bevy_sprite/src/texture_slice/computed_slices.rs index eedf2c849e400..490071a6005ed 100644 --- a/crates/bevy_sprite/src/texture_slice/computed_slices.rs +++ b/crates/bevy_sprite/src/texture_slice/computed_slices.rs @@ -28,7 +28,6 @@ impl ComputedTextureSlices { &'a self, transform: &'a GlobalTransform, original_entity: Entity, - render_entity: Entity, sprite: &'a Sprite, ) -> impl ExactSizeIterator + 'a { let mut flip = Vec2::ONE; @@ -54,7 +53,6 @@ impl ComputedTextureSlices { flip_y, image_handle_id: sprite.image.id(), anchor: Self::redepend_anchor_from_sprite_to_slice(sprite, slice), - render_entity, } }) } diff --git a/crates/bevy_text/src/text2d.rs b/crates/bevy_text/src/text2d.rs index 2c90fc8c1f622..e3f65b43115db 100644 --- a/crates/bevy_text/src/text2d.rs +++ b/crates/bevy_text/src/text2d.rs @@ -206,7 +206,10 @@ pub fn extract_text2d_sprite( let atlas = texture_atlases.get(&atlas_info.texture_atlas).unwrap(); extracted_sprites.sprites.insert( - original_entity.into(), + ( + commands.spawn(TemporaryRenderEntity).id(), + original_entity.into(), + ), ExtractedSprite { transform: transform * GlobalTransform::from_translation(position.extend(0.)), color, @@ -217,7 +220,6 @@ pub fn extract_text2d_sprite( flip_y: false, anchor: Anchor::Center.as_vec(), original_entity: Some(original_entity), - render_entity: commands.spawn(TemporaryRenderEntity).id(), }, ); } diff --git a/examples/stress_tests/bevymark.rs b/examples/stress_tests/bevymark.rs index 26efbf164ffa1..761b997344cfb 100644 --- a/examples/stress_tests/bevymark.rs +++ b/examples/stress_tests/bevymark.rs @@ -13,7 +13,7 @@ use bevy::{ render_asset::RenderAssetUsages, render_resource::{Extent3d, TextureDimension, TextureFormat}, }, - sprite::{AlphaMode2d, SpritePlugin}, + sprite::AlphaMode2d, utils::Duration, window::{PresentMode, WindowResolution}, winit::{UpdateMode, WinitSettings}, @@ -132,21 +132,16 @@ fn main() { App::new() .add_plugins(( - DefaultPlugins - .set(WindowPlugin { - primary_window: Some(Window { - title: "BevyMark".into(), - resolution: WindowResolution::new(1920.0, 1080.0) - .with_scale_factor_override(1.0), - present_mode: PresentMode::AutoNoVsync, - ..default() - }), + DefaultPlugins.set(WindowPlugin { + primary_window: Some(Window { + title: "BevyMark".into(), + resolution: WindowResolution::new(1920.0, 1080.0) + .with_scale_factor_override(1.0), + present_mode: PresentMode::AutoNoVsync, ..default() - }) - .set(SpritePlugin { - #[cfg(feature = "bevy_sprite_picking_backend")] - add_picking: false, }), + ..default() + }), FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin::default(), )) From 4fde223831c978310df798847a4bd47da0cc5f3c Mon Sep 17 00:00:00 2001 From: JaySpruce Date: Sat, 4 Jan 2025 20:01:01 -0600 Subject: [PATCH 116/272] Optimize `Entities::entity_does_not_exist_error_details_message`, remove `UnsafeWorldCell` from error (#17115) ## Objective The error `EntityFetchError::NoSuchEntity` has an `UnsafeWorldCell` inside it, which it uses to call `Entities::entity_does_not_exist_error_details_message` when being printed. That method returns a `String` that, if the `track_location` feature is enabled, contains the location of whoever despawned the relevant entity. I initially had to modify this error while working on #17043. The `UnsafeWorldCell` was causing borrow problems when being returned from a command, so I tried replacing it with the `String` that the method returns, since that was the world cell's only purpose. Unfortunately, `String`s are slow, and it significantly impacted performance (on top of that PR's performance hit):

17043 benchmarks ### With `String` ![error_handling_insert_slow](https://github.com/user-attachments/assets/5629ba6d-69fc-4c16-84c9-8be7e449232d) ### No `String` ![error_handling_insert_fixed](https://github.com/user-attachments/assets/6393e2d6-e61a-4558-8ff1-471ff8356c1c)
For that PR, I just removed the error details entirely, but I figured I'd try to find a way to keep them around. ## Solution - Replace the `String` with a helper struct that holds the location, and only turn it into a string when someone actually wants to print it. - Replace the `UnsafeWorldCell` with the aforementioned struct. - Do the same for `QueryEntityError::NoSuchEntity`. ## Benchmarking This had some interesting performance impact:
This PR vs main ![dne_rework_1](https://github.com/user-attachments/assets/05bf91b4-dddc-4d76-b2c4-41c9d25c7a57) ![dne_rework_2](https://github.com/user-attachments/assets/34aa76b2-d8a7-41e0-9670-c213207e457d) ![dne_rework_3](https://github.com/user-attachments/assets/8b9bd4e4-77c8-45a7-b058-dc0dfd3dd323)
## Other work `QueryEntityError::QueryDoesNotMatch` also has an `UnsafeWorldCell` inside it. This one would be more complicated to rework while keeping the same functionality. ## Migration Guide The errors `EntityFetchError::NoSuchEntity` and `QueryEntityError::NoSuchEntity` now contain an `EntityDoesNotExistDetails` struct instead of an `UnsafeWorldCell`. If you were just printing these, they should work identically. --------- Co-authored-by: Benjamin Brienen --- crates/bevy_ecs/src/entity/mod.rs | 43 ++++++++++----- crates/bevy_ecs/src/query/error.rs | 30 +++++------ crates/bevy_ecs/src/query/state.rs | 7 ++- crates/bevy_ecs/src/world/deferred_world.rs | 6 ++- crates/bevy_ecs/src/world/entity_fetch.rs | 38 +++++++++----- crates/bevy_ecs/src/world/error.rs | 58 +++++---------------- crates/bevy_ecs/src/world/mod.rs | 6 ++- 7 files changed, 96 insertions(+), 92 deletions(-) diff --git a/crates/bevy_ecs/src/entity/mod.rs b/crates/bevy_ecs/src/entity/mod.rs index 1a177e7ff5e86..44e500a15bb36 100644 --- a/crates/bevy_ecs/src/entity/mod.rs +++ b/crates/bevy_ecs/src/entity/mod.rs @@ -69,12 +69,12 @@ use crate::{ }, storage::{SparseSetIndex, TableId, TableRow}, }; -use alloc::{borrow::ToOwned, string::String, vec::Vec}; +use alloc::vec::Vec; use core::{fmt, hash::Hash, mem, num::NonZero}; use log::warn; #[cfg(feature = "track_location")] -use {alloc::format, core::panic::Location}; +use core::panic::Location; #[cfg(feature = "serialize")] use serde::{Deserialize, Serialize}; @@ -991,19 +991,38 @@ impl Entities { } /// Constructs a message explaining why an entity does not exists, if known. - pub(crate) fn entity_does_not_exist_error_details_message(&self, _entity: Entity) -> String { + pub(crate) fn entity_does_not_exist_error_details_message( + &self, + _entity: Entity, + ) -> EntityDoesNotExistDetails { + EntityDoesNotExistDetails { + #[cfg(feature = "track_location")] + location: self.entity_get_spawned_or_despawned_by(_entity), + } + } +} + +/// Helper struct that, when printed, will write the appropriate details +/// regarding an entity that did not exist. +#[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub struct EntityDoesNotExistDetails { + #[cfg(feature = "track_location")] + location: Option<&'static Location<'static>>, +} + +impl fmt::Display for EntityDoesNotExistDetails { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { #[cfg(feature = "track_location")] - { - if let Some(location) = self.entity_get_spawned_or_despawned_by(_entity) { - format!("was despawned by {location}",) - } else { - "was never spawned".to_owned() - } + if let Some(location) = self.location { + write!(f, "was despawned by {}", location) + } else { + write!(f, "was never spawned") } #[cfg(not(feature = "track_location"))] - { - "does not exist (enable `track_location` feature for more details)".to_owned() - } + write!( + f, + "does not exist (enable `track_location` feature for more details)" + ) } } diff --git a/crates/bevy_ecs/src/query/error.rs b/crates/bevy_ecs/src/query/error.rs index b2dafc64a9bf8..2031f390009cc 100644 --- a/crates/bevy_ecs/src/query/error.rs +++ b/crates/bevy_ecs/src/query/error.rs @@ -1,6 +1,9 @@ use thiserror::Error; -use crate::{entity::Entity, world::unsafe_world_cell::UnsafeWorldCell}; +use crate::{ + entity::{Entity, EntityDoesNotExistDetails}, + world::unsafe_world_cell::UnsafeWorldCell, +}; /// An error that occurs when retrieving a specific [`Entity`]'s query result from [`Query`](crate::system::Query) or [`QueryState`](crate::query::QueryState). // TODO: return the type_name as part of this error @@ -11,7 +14,7 @@ pub enum QueryEntityError<'w> { /// Either it does not have a requested component, or it has a component which the query filters out. QueryDoesNotMatch(Entity, UnsafeWorldCell<'w>), /// The given [`Entity`] does not exist. - NoSuchEntity(Entity, UnsafeWorldCell<'w>), + NoSuchEntity(Entity, EntityDoesNotExistDetails), /// The [`Entity`] was requested mutably more than once. /// /// See [`QueryState::get_many_mut`](crate::query::QueryState::get_many_mut) for an example. @@ -30,18 +33,15 @@ impl<'w> core::fmt::Display for QueryEntityError<'w> { )?; format_archetype(f, world, entity) } - Self::NoSuchEntity(entity, world) => { + Self::NoSuchEntity(entity, details) => { + write!(f, "The entity with ID {entity} {details}") + } + Self::AliasedMutability(entity) => { write!( f, - "Entity {entity} {}", - world - .entities() - .entity_does_not_exist_error_details_message(entity) + "The entity with ID {entity} was requested mutably more than once" ) } - Self::AliasedMutability(entity) => { - write!(f, "Entity {entity} was requested mutably more than once") - } } } } @@ -54,14 +54,8 @@ impl<'w> core::fmt::Debug for QueryEntityError<'w> { format_archetype(f, world, entity)?; write!(f, ")") } - Self::NoSuchEntity(entity, world) => { - write!( - f, - "NoSuchEntity({entity} {})", - world - .entities() - .entity_does_not_exist_error_details_message(entity) - ) + Self::NoSuchEntity(entity, details) => { + write!(f, "NoSuchEntity({entity} {details})") } Self::AliasedMutability(entity) => write!(f, "AliasedMutability({entity})"), } diff --git a/crates/bevy_ecs/src/query/state.rs b/crates/bevy_ecs/src/query/state.rs index b6025e5d7da5d..ae47d4e54e208 100644 --- a/crates/bevy_ecs/src/query/state.rs +++ b/crates/bevy_ecs/src/query/state.rs @@ -1020,7 +1020,12 @@ impl QueryState { let location = world .entities() .get(entity) - .ok_or(QueryEntityError::NoSuchEntity(entity, world))?; + .ok_or(QueryEntityError::NoSuchEntity( + entity, + world + .entities() + .entity_does_not_exist_error_details_message(entity), + ))?; if !self .matched_archetypes .contains(location.archetype_id.index()) diff --git a/crates/bevy_ecs/src/world/deferred_world.rs b/crates/bevy_ecs/src/world/deferred_world.rs index 446045d65b77d..36b8176e3cb1a 100644 --- a/crates/bevy_ecs/src/world/deferred_world.rs +++ b/crates/bevy_ecs/src/world/deferred_world.rs @@ -109,7 +109,11 @@ impl<'w> DeferredWorld<'w> { return Err(EntityFetchError::AliasedMutability(entity)) } Err(EntityFetchError::NoSuchEntity(..)) => { - return Err(EntityFetchError::NoSuchEntity(entity, self.world)) + return Err(EntityFetchError::NoSuchEntity( + entity, + self.entities() + .entity_does_not_exist_error_details_message(entity), + )) } }; diff --git a/crates/bevy_ecs/src/world/entity_fetch.rs b/crates/bevy_ecs/src/world/entity_fetch.rs index e89835844f1fa..d6121bf048408 100644 --- a/crates/bevy_ecs/src/world/entity_fetch.rs +++ b/crates/bevy_ecs/src/world/entity_fetch.rs @@ -124,7 +124,11 @@ unsafe impl WorldEntityFetch for Entity { let location = cell .entities() .get(self) - .ok_or(EntityFetchError::NoSuchEntity(self, cell))?; + .ok_or(EntityFetchError::NoSuchEntity( + self, + cell.entities() + .entity_does_not_exist_error_details_message(self), + ))?; // SAFETY: caller ensures that the world cell has mutable access to the entity. let world = unsafe { cell.world_mut() }; // SAFETY: location was fetched from the same world's `Entities`. @@ -135,9 +139,11 @@ unsafe impl WorldEntityFetch for Entity { self, cell: UnsafeWorldCell<'_>, ) -> Result, EntityFetchError> { - let ecell = cell - .get_entity(self) - .ok_or(EntityFetchError::NoSuchEntity(self, cell))?; + let ecell = cell.get_entity(self).ok_or(EntityFetchError::NoSuchEntity( + self, + cell.entities() + .entity_does_not_exist_error_details_message(self), + ))?; // SAFETY: caller ensures that the world cell has mutable access to the entity. Ok(unsafe { EntityMut::new(ecell) }) } @@ -209,9 +215,11 @@ unsafe impl WorldEntityFetch for &'_ [Entity; N] { let mut refs = [const { MaybeUninit::uninit() }; N]; for (r, &id) in core::iter::zip(&mut refs, self) { - let ecell = cell - .get_entity(id) - .ok_or(EntityFetchError::NoSuchEntity(id, cell))?; + let ecell = cell.get_entity(id).ok_or(EntityFetchError::NoSuchEntity( + id, + cell.entities() + .entity_does_not_exist_error_details_message(id), + ))?; // SAFETY: caller ensures that the world cell has mutable access to the entity. *r = MaybeUninit::new(unsafe { EntityMut::new(ecell) }); } @@ -267,9 +275,11 @@ unsafe impl WorldEntityFetch for &'_ [Entity] { let mut refs = Vec::with_capacity(self.len()); for &id in self { - let ecell = cell - .get_entity(id) - .ok_or(EntityFetchError::NoSuchEntity(id, cell))?; + let ecell = cell.get_entity(id).ok_or(EntityFetchError::NoSuchEntity( + id, + cell.entities() + .entity_does_not_exist_error_details_message(id), + ))?; // SAFETY: caller ensures that the world cell has mutable access to the entity. refs.push(unsafe { EntityMut::new(ecell) }); } @@ -312,9 +322,11 @@ unsafe impl WorldEntityFetch for &'_ EntityHashSet { ) -> Result, EntityFetchError> { let mut refs = EntityHashMap::with_capacity(self.len()); for &id in self { - let ecell = cell - .get_entity(id) - .ok_or(EntityFetchError::NoSuchEntity(id, cell))?; + let ecell = cell.get_entity(id).ok_or(EntityFetchError::NoSuchEntity( + id, + cell.entities() + .entity_does_not_exist_error_details_message(id), + ))?; // SAFETY: caller ensures that the world cell has mutable access to the entity. refs.insert(id, unsafe { EntityMut::new(ecell) }); } diff --git a/crates/bevy_ecs/src/world/error.rs b/crates/bevy_ecs/src/world/error.rs index 7f137fa012fee..1c6b5043bcea8 100644 --- a/crates/bevy_ecs/src/world/error.rs +++ b/crates/bevy_ecs/src/world/error.rs @@ -2,9 +2,11 @@ use thiserror::Error; -use crate::{component::ComponentId, entity::Entity, schedule::InternedScheduleLabel}; - -use super::unsafe_world_cell::UnsafeWorldCell; +use crate::{ + component::ComponentId, + entity::{Entity, EntityDoesNotExistDetails}, + schedule::InternedScheduleLabel, +}; /// The error type returned by [`World::try_run_schedule`] if the provided schedule does not exist. /// @@ -25,53 +27,17 @@ pub enum EntityComponentError { } /// An error that occurs when fetching entities mutably from a world. -#[derive(Clone, Copy)] -pub enum EntityFetchError<'w> { +#[derive(Error, Debug, Clone, Copy)] +pub enum EntityFetchError { /// The entity with the given ID does not exist. - NoSuchEntity(Entity, UnsafeWorldCell<'w>), + #[error("The entity with ID {0} {1}")] + NoSuchEntity(Entity, EntityDoesNotExistDetails), /// The entity with the given ID was requested mutably more than once. + #[error("The entity with ID {0} was requested mutably more than once")] AliasedMutability(Entity), } -impl<'w> core::error::Error for EntityFetchError<'w> {} - -impl<'w> core::fmt::Display for EntityFetchError<'w> { - fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { - match *self { - Self::NoSuchEntity(entity, world) => { - write!( - f, - "Entity {entity} {}", - world - .entities() - .entity_does_not_exist_error_details_message(entity) - ) - } - Self::AliasedMutability(entity) => { - write!(f, "Entity {entity} was requested mutably more than once") - } - } - } -} - -impl<'w> core::fmt::Debug for EntityFetchError<'w> { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - match *self { - Self::NoSuchEntity(entity, world) => { - write!( - f, - "NoSuchEntity({entity} {})", - world - .entities() - .entity_does_not_exist_error_details_message(entity) - ) - } - Self::AliasedMutability(entity) => write!(f, "AliasedMutability({entity})"), - } - } -} - -impl<'w> PartialEq for EntityFetchError<'w> { +impl PartialEq for EntityFetchError { fn eq(&self, other: &Self) -> bool { match (self, other) { (Self::NoSuchEntity(e1, _), Self::NoSuchEntity(e2, _)) if e1 == e2 => true, @@ -81,4 +47,4 @@ impl<'w> PartialEq for EntityFetchError<'w> { } } -impl<'w> Eq for EntityFetchError<'w> {} +impl Eq for EntityFetchError {} diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index 8e8afdb34e42a..98c0f9e03b8e9 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -1327,7 +1327,11 @@ impl World { return Err(EntityFetchError::AliasedMutability(entity)) } Err(EntityFetchError::NoSuchEntity(..)) => { - return Err(EntityFetchError::NoSuchEntity(entity, self.into())) + return Err(EntityFetchError::NoSuchEntity( + entity, + self.entities() + .entity_does_not_exist_error_details_message(entity), + )) } }; From 5b0406c72243e8fe92ec3389228e230bbc411c80 Mon Sep 17 00:00:00 2001 From: MichiRecRoom <1008889+LikeLakers2@users.noreply.github.com> Date: Sat, 4 Jan 2025 21:01:20 -0500 Subject: [PATCH 117/272] bevy_color: Apply `#![deny(clippy::allow_attributes, clippy::allow_attributes_without_reason)]` (#17090) # Objective - https://github.com/bevyengine/bevy/issues/17111 ## Solution Set the `clippy::allow_attributes` and `clippy::allow_attributes_without_reason` lints to `deny`, and bring `bevy_audio` in line with the new restrictions. No code changes have been made - except if a lint that was previously `allow(...)`'d could be removed via small code changes. For example, `unused_variables` can be handled by adding a `_` to the beginning of a field's name. ## Testing I ran `cargo clippy`, and received no errors. --- crates/bevy_color/src/lib.rs | 14 +++++++++++++- crates/bevy_color/src/palettes/css.rs | 1 - 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/crates/bevy_color/src/lib.rs b/crates/bevy_color/src/lib.rs index e80e68bb9e078..cf13c86360062 100644 --- a/crates/bevy_color/src/lib.rs +++ b/crates/bevy_color/src/lib.rs @@ -1,5 +1,10 @@ #![cfg_attr(docsrs, feature(doc_auto_cfg))] #![forbid(unsafe_code)] +#![deny( + clippy::allow_attributes, + clippy::allow_attributes_without_reason, + reason = "See #17111; To be removed once all crates are in-line with these attributes" +)] #![doc( html_logo_url = "https://bevyengine.org/assets/icon.png", html_favicon_url = "https://bevyengine.org/assets/icon.png" @@ -145,7 +150,14 @@ pub use srgba::*; pub use xyza::*; /// Describes the traits that a color should implement for consistency. -#[allow(dead_code)] // This is an internal marker trait used to ensure that our color types impl the required traits +#[expect( + clippy::allow_attributes, + reason = "If the below attribute on `dead_code` is removed, then rustc complains that `StandardColor` is dead code. However, if we `expect` the `dead_code` lint, then rustc complains of an unfulfilled expectation." +)] +#[allow( + dead_code, + reason = "This is an internal marker trait used to ensure that our color types impl the required traits" +)] pub(crate) trait StandardColor where Self: core::fmt::Debug, diff --git a/crates/bevy_color/src/palettes/css.rs b/crates/bevy_color/src/palettes/css.rs index 0c1e073bec4b6..9b0dd7fe7e18f 100644 --- a/crates/bevy_color/src/palettes/css.rs +++ b/crates/bevy_color/src/palettes/css.rs @@ -4,7 +4,6 @@ use crate::Srgba; // The CSS4 colors are a superset of the CSS1 colors, so we can just re-export the CSS1 colors. -#[allow(unused_imports)] pub use crate::palettes::basic::*; ///
From c87ec09674cf0ec32a37dea9ab03882f5b804b1e Mon Sep 17 00:00:00 2001 From: Sean Kim Date: Sat, 4 Jan 2025 18:02:30 -0800 Subject: [PATCH 118/272] Fix 2D Gizmos not always drawn on top (#17085) # Objective - As stated in the linked issue, if a Mesh2D is drawn with elements with a positive Z value, resulting gizmos get drawn behind instead of in front of them. - Fixes #17053 ## Solution - Similar to the change done for the `SpritePipeline` in the relevant commit (5abc32ceda1a440271c9f9ef71279bd9f7f9ff5c), this PR changes both line gizmos to avoid writing to the depth buffer and always pass the depth test to ensure they are not filtered out. ## Testing - Tested with the provided snippet in #17053 - I looked over the `2d_gizmos` example, but it seemed like adding more elements there to demonstrate this might not be the best idea? Looking for guidance here on if that should be updated or if a new gizmo example needs to be made. --- crates/bevy_gizmos/src/pipeline_2d.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/bevy_gizmos/src/pipeline_2d.rs b/crates/bevy_gizmos/src/pipeline_2d.rs index 89d6cec6260b6..2a885a186642e 100644 --- a/crates/bevy_gizmos/src/pipeline_2d.rs +++ b/crates/bevy_gizmos/src/pipeline_2d.rs @@ -142,8 +142,8 @@ impl SpecializedRenderPipeline for LineGizmoPipeline { primitive: PrimitiveState::default(), depth_stencil: Some(DepthStencilState { format: CORE_2D_DEPTH_FORMAT, - depth_write_enabled: true, - depth_compare: CompareFunction::GreaterEqual, + depth_write_enabled: false, + depth_compare: CompareFunction::Always, stencil: StencilState { front: StencilFaceState::IGNORE, back: StencilFaceState::IGNORE, @@ -243,8 +243,8 @@ impl SpecializedRenderPipeline for LineJointGizmoPipeline { primitive: PrimitiveState::default(), depth_stencil: Some(DepthStencilState { format: CORE_2D_DEPTH_FORMAT, - depth_write_enabled: true, - depth_compare: CompareFunction::GreaterEqual, + depth_write_enabled: false, + depth_compare: CompareFunction::Always, stencil: StencilState { front: StencilFaceState::IGNORE, back: StencilFaceState::IGNORE, From fe589935773d6205a6bf508b9b308b8bca45a97a Mon Sep 17 00:00:00 2001 From: JMS55 <47158642+JMS55@users.noreply.github.com> Date: Sat, 4 Jan 2025 18:03:26 -0800 Subject: [PATCH 119/272] METIS-based meshlet generation (#16947) # Objective Improve DAG building for virtual geometry ## Solution - Use METIS to group triangles into meshlets which lets us minimize locked vertices which improves simplification, instead of using meshopt which prioritizes culling efficiency. Also some other minor tweaks. - Currently most meshlets have 126 triangles, and not 128. Fixing this might involve calling METIS recursively ourselves to manually bisect the graph, not sure. Not going to attempt to fix this in this PR. ## Testing - Did you test these changes? If so, how? - Tested on bunny.glb and cliff.glb - Are there any parts that need more testing? - No - How can other people (reviewers) test your changes? Is there anything specific they need to know? - Download the new bunny asset, run the meshlet example. --- ## Showcase New ![image](https://github.com/user-attachments/assets/68f5d2f0-a4ca-41e1-90d5-35a2c6969c21) Old ![image](https://github.com/user-attachments/assets/a3d97a09-773d-44b2-9990-25e1f6b51ec9) --------- Co-authored-by: IceSentry --- Cargo.toml | 2 +- crates/bevy_pbr/Cargo.toml | 2 +- crates/bevy_pbr/src/meshlet/from_mesh.rs | 153 ++++++++++++++++++++--- examples/3d/meshlet.rs | 2 +- 4 files changed, 137 insertions(+), 22 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 323c8ffa04fd5..1d0c7d9118ff4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1236,7 +1236,7 @@ setup = [ "curl", "-o", "assets/models/bunny.meshlet_mesh", - "https://raw.githubusercontent.com/JMS55/bevy_meshlet_asset/defbd9b32072624d40d57de7d345c66a9edf5d0b/bunny.meshlet_mesh", + "https://raw.githubusercontent.com/JMS55/bevy_meshlet_asset/7a7c14138021f63904b584d5f7b73b695c7f4bbf/bunny.meshlet_mesh", ], ] diff --git a/crates/bevy_pbr/Cargo.toml b/crates/bevy_pbr/Cargo.toml index cd42bfebb8d05..afaa1d19852f5 100644 --- a/crates/bevy_pbr/Cargo.toml +++ b/crates/bevy_pbr/Cargo.toml @@ -60,7 +60,7 @@ lz4_flex = { version = "0.11", default-features = false, features = [ ], optional = true } range-alloc = { version = "0.1.3", optional = true } half = { version = "2", features = ["bytemuck"], optional = true } -meshopt = { version = "0.4", optional = true } +meshopt = { version = "0.4.1", optional = true } metis = { version = "0.2", optional = true } itertools = { version = "0.13", optional = true } bitvec = { version = "1", optional = true } diff --git a/crates/bevy_pbr/src/meshlet/from_mesh.rs b/crates/bevy_pbr/src/meshlet/from_mesh.rs index 697d3d4aacd82..6cd868e49d8cf 100644 --- a/crates/bevy_pbr/src/meshlet/from_mesh.rs +++ b/crates/bevy_pbr/src/meshlet/from_mesh.rs @@ -16,7 +16,7 @@ use meshopt::{ build_meshlets, ffi::meshopt_Meshlet, generate_vertex_remap_multi, simplify_with_attributes_and_locks, Meshlets, SimplifyOptions, VertexDataAdapter, VertexStream, }; -use metis::Graph; +use metis::{option::Opt, Graph}; use smallvec::SmallVec; use thiserror::Error; @@ -67,12 +67,29 @@ impl MeshletMesh { // Validate mesh format let indices = validate_input_mesh(mesh)?; - // Split the mesh into an initial list of meshlets (LOD 0) + // Get meshlet vertices let vertex_buffer = mesh.create_packed_vertex_buffer_data(); let vertex_stride = mesh.get_vertex_size() as usize; let vertices = VertexDataAdapter::new(&vertex_buffer, vertex_stride, 0).unwrap(); let vertex_normals = bytemuck::cast_slice(&vertex_buffer[12..16]); - let mut meshlets = compute_meshlets(&indices, &vertices); + + // Generate a position-only vertex buffer for determining triangle/meshlet connectivity + let (position_only_vertex_count, position_only_vertex_remap) = generate_vertex_remap_multi( + vertices.vertex_count, + &[VertexStream::new_with_stride::( + vertex_buffer.as_ptr(), + vertex_stride, + )], + Some(&indices), + ); + + // Split the mesh into an initial list of meshlets (LOD 0) + let mut meshlets = compute_meshlets( + &indices, + &vertices, + &position_only_vertex_remap, + position_only_vertex_count, + ); let mut bounding_spheres = meshlets .iter() .map(|meshlet| compute_meshlet_bounds(meshlet, &vertices)) @@ -92,16 +109,6 @@ impl MeshletMesh { .take(meshlets.len()) .collect::>(); - // Generate a position-only vertex buffer for determining what meshlets are connected for use in grouping - let (position_only_vertex_count, position_only_vertex_remap) = generate_vertex_remap_multi( - vertices.vertex_count, - &[VertexStream::new_with_stride::( - vertex_buffer.as_ptr(), - vertex_stride, - )], - Some(&indices), - ); - let mut vertex_locks = vec![false; vertices.vertex_count]; // Build further LODs @@ -163,6 +170,8 @@ impl MeshletMesh { let new_meshlets_count = split_simplified_group_into_new_meshlets( &simplified_group_indices, &vertices, + &position_only_vertex_remap, + position_only_vertex_count, &mut meshlets, ); @@ -243,8 +252,103 @@ fn validate_input_mesh(mesh: &Mesh) -> Result, MeshToMeshletMeshC } } -fn compute_meshlets(indices: &[u32], vertices: &VertexDataAdapter) -> Meshlets { - build_meshlets(indices, vertices, 255, 128, 0.0) // Meshoptimizer won't currently let us do 256 vertices +fn compute_meshlets( + indices: &[u32], + vertices: &VertexDataAdapter, + position_only_vertex_remap: &[u32], + position_only_vertex_count: usize, +) -> Meshlets { + // For each vertex, build a list of all triangles that use it + let mut vertices_to_triangles = vec![Vec::new(); position_only_vertex_count]; + for (i, index) in indices.iter().enumerate() { + let vertex_id = position_only_vertex_remap[*index as usize]; + let vertex_to_triangles = &mut vertices_to_triangles[vertex_id as usize]; + vertex_to_triangles.push(i / 3); + } + + // For each triangle pair, count how many vertices they share + let mut triangle_pair_to_shared_vertex_count = >::default(); + for vertex_triangle_ids in vertices_to_triangles { + for (triangle_id1, triangle_id2) in vertex_triangle_ids.into_iter().tuple_combinations() { + let count = triangle_pair_to_shared_vertex_count + .entry(( + triangle_id1.min(triangle_id2), + triangle_id1.max(triangle_id2), + )) + .or_insert(0); + *count += 1; + } + } + + // For each triangle, gather all other triangles that share at least one vertex along with their shared vertex count + let triangle_count = indices.len() / 3; + let mut connected_triangles_per_triangle = vec![Vec::new(); triangle_count]; + for ((triangle_id1, triangle_id2), shared_vertex_count) in triangle_pair_to_shared_vertex_count + { + // We record both id1->id2 and id2->id1 as adjacency is symmetrical + connected_triangles_per_triangle[triangle_id1].push((triangle_id2, shared_vertex_count)); + connected_triangles_per_triangle[triangle_id2].push((triangle_id1, shared_vertex_count)); + } + + // The order of triangles depends on hash traversal order; to produce deterministic results, sort them + for list in connected_triangles_per_triangle.iter_mut() { + list.sort_unstable(); + } + + let mut xadj = Vec::with_capacity(triangle_count + 1); + let mut adjncy = Vec::new(); + let mut adjwgt = Vec::new(); + for connected_triangles in connected_triangles_per_triangle { + xadj.push(adjncy.len() as i32); + for (connected_triangle_id, shared_vertex_count) in connected_triangles { + adjncy.push(connected_triangle_id as i32); + adjwgt.push(shared_vertex_count); + // TODO: Additional weight based on triangle center spatial proximity? + } + } + xadj.push(adjncy.len() as i32); + + let mut options = [-1; metis::NOPTIONS]; + options[metis::option::Seed::INDEX] = 17; + options[metis::option::UFactor::INDEX] = 1; // Important that there's very little imbalance between partitions + + let mut meshlet_per_triangle = vec![0; triangle_count]; + let partition_count = triangle_count.div_ceil(126); // Need to undershoot to prevent METIS from going over 128 triangles per meshlet + Graph::new(1, partition_count as i32, &xadj, &adjncy) + .unwrap() + .set_options(&options) + .set_adjwgt(&adjwgt) + .part_recursive(&mut meshlet_per_triangle) + .unwrap(); + + let mut indices_per_meshlet = vec![Vec::new(); partition_count]; + for (triangle_id, meshlet) in meshlet_per_triangle.into_iter().enumerate() { + let meshlet_indices = &mut indices_per_meshlet[meshlet as usize]; + let base_index = triangle_id * 3; + meshlet_indices.extend_from_slice(&indices[base_index..(base_index + 3)]); + } + + // Use meshopt to build meshlets from the sets of triangles + let mut meshlets = Meshlets { + meshlets: Vec::new(), + vertices: Vec::new(), + triangles: Vec::new(), + }; + for meshlet_indices in &indices_per_meshlet { + let meshlet = build_meshlets(meshlet_indices, vertices, 255, 128, 0.0); + let vertex_offset = meshlets.vertices.len() as u32; + let triangle_offset = meshlets.triangles.len() as u32; + meshlets.vertices.extend_from_slice(&meshlet.vertices); + meshlets.triangles.extend_from_slice(&meshlet.triangles); + meshlets + .meshlets + .extend(meshlet.meshlets.into_iter().map(|mut meshlet| { + meshlet.vertex_offset += vertex_offset; + meshlet.triangle_offset += triangle_offset; + meshlet + })); + } + meshlets } fn find_connected_meshlets( @@ -315,15 +419,19 @@ fn group_meshlets( } xadj.push(adjncy.len() as i32); + let mut options = [-1; metis::NOPTIONS]; + options[metis::option::Seed::INDEX] = 17; + options[metis::option::UFactor::INDEX] = 200; + let mut group_per_meshlet = vec![0; simplification_queue.len()]; let partition_count = simplification_queue .len() .div_ceil(TARGET_MESHLETS_PER_GROUP); // TODO: Nanite uses groups of 8-32, probably based on some kind of heuristic Graph::new(1, partition_count as i32, &xadj, &adjncy) .unwrap() - .set_option(metis::option::Seed(17)) + .set_options(&options) .set_adjwgt(&adjwgt) - .part_kway(&mut group_per_meshlet) + .part_recursive(&mut group_per_meshlet) .unwrap(); let mut groups = vec![SmallVec::new(); partition_count]; @@ -462,9 +570,16 @@ fn compute_lod_group_data( fn split_simplified_group_into_new_meshlets( simplified_group_indices: &[u32], vertices: &VertexDataAdapter<'_>, + position_only_vertex_remap: &[u32], + position_only_vertex_count: usize, meshlets: &mut Meshlets, ) -> usize { - let simplified_meshlets = compute_meshlets(simplified_group_indices, vertices); + let simplified_meshlets = compute_meshlets( + simplified_group_indices, + vertices, + position_only_vertex_remap, + position_only_vertex_count, + ); let new_meshlets_count = simplified_meshlets.len(); let vertex_offset = meshlets.vertices.len() as u32; @@ -610,7 +725,7 @@ fn pack2x16snorm(v: Vec2) -> u32 { pub enum MeshToMeshletMeshConversionError { #[error("Mesh primitive topology is not TriangleList")] WrongMeshPrimitiveTopology, - #[error("Mesh attributes are not {{POSITION, NORMAL, UV_0}}")] + #[error("Mesh vertex attributes are not {{POSITION, NORMAL, UV_0}}")] WrongMeshVertexAttributes, #[error("Mesh has no indices")] MeshMissingIndices, diff --git a/examples/3d/meshlet.rs b/examples/3d/meshlet.rs index 60e8791b7ddf3..5db69a4a7995a 100644 --- a/examples/3d/meshlet.rs +++ b/examples/3d/meshlet.rs @@ -17,7 +17,7 @@ use camera_controller::{CameraController, CameraControllerPlugin}; use std::{f32::consts::PI, path::Path, process::ExitCode}; const ASSET_URL: &str = - "https://raw.githubusercontent.com/JMS55/bevy_meshlet_asset/defbd9b32072624d40d57de7d345c66a9edf5d0b/bunny.meshlet_mesh"; + "https://raw.githubusercontent.com/JMS55/bevy_meshlet_asset/7a7c14138021f63904b584d5f7b73b695c7f4bbf/bunny.meshlet_mesh"; fn main() -> ExitCode { if !Path::new("./assets/models/bunny.meshlet_mesh").exists() { From f2931dc4330c92cbd92d175964940c860863f766 Mon Sep 17 00:00:00 2001 From: AlephCubed <76791009+AlephCubed@users.noreply.github.com> Date: Sat, 4 Jan 2025 18:35:36 -0800 Subject: [PATCH 120/272] Updated nixos link in `linux_dependencies.md`. (#17146) Fixes dead link to nixos file and includes a slight wording change. Adopts #16601 and closes #16811. --------- Co-authored-by: Thomas M. DuBuisson --- docs/linux_dependencies.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/linux_dependencies.md b/docs/linux_dependencies.md index cc77ae8eb98d0..32d387f5ce403 100644 --- a/docs/linux_dependencies.md +++ b/docs/linux_dependencies.md @@ -144,15 +144,15 @@ to link graphics drivers into the context of software installed by nix: This is also possible with [Nix flakes](https://nixos.org/manual/nix/unstable/command-ref/new-cli/nix3-flake.html). Instead of creating `shell.nix`, you just need to add the derivation (`mkShell`) -to your `devShells` in `flake.nix`. Run `nix develop` to enter the shell and -`nix develop -c cargo run` to run the program. See +to your `devShells` in `flake.nix`. Run `nix develop` to enter the shell or +`nix develop -c cargo run` to just run the program. See [Nix's documentation](https://nixos.org/manual/nix/unstable/command-ref/new-cli/nix3-develop.html) for more information about `devShells`. Note that this template does not add Rust to the environment because there are many ways to do it. For example, to use stable Rust from nixpkgs, you can add `cargo` and `rustc` to `nativeBuildInputs`. -[Here](https://github.com/NixOS/nixpkgs/blob/master/pkgs/games/jumpy/default.nix) +[Here](https://github.com/NixOS/nixpkgs/blob/master/pkgs/by-name/ju/jumpy/package.nix) is an example of packaging a Bevy program in nix. ## [OpenSUSE](https://www.opensuse.org/) From 9ed76b7a56a082d962db296ce83aa184927f7ffb Mon Sep 17 00:00:00 2001 From: ickshonpe Date: Sun, 5 Jan 2025 02:44:29 +0000 Subject: [PATCH 121/272] More combinator cleanup (#17148) # Objective Cleanup some more awkward combinator chains. --- crates/bevy_diagnostic/src/diagnostic.rs | 3 +-- tools/build-templated-pages/src/examples.rs | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/crates/bevy_diagnostic/src/diagnostic.rs b/crates/bevy_diagnostic/src/diagnostic.rs index c9fbe5d600eea..56c1788661501 100644 --- a/crates/bevy_diagnostic/src/diagnostic.rs +++ b/crates/bevy_diagnostic/src/diagnostic.rs @@ -351,8 +351,7 @@ impl<'w, 's> Diagnostics<'w, 's> { if self .store .get(path) - .filter(|diagnostic| diagnostic.is_enabled) - .is_some() + .is_some_and(|diagnostic| diagnostic.is_enabled) { let measurement = DiagnosticMeasurement { time: Instant::now(), diff --git a/tools/build-templated-pages/src/examples.rs b/tools/build-templated-pages/src/examples.rs index 9e1978a58be86..b3329018dbe4c 100644 --- a/tools/build-templated-pages/src/examples.rs +++ b/tools/build-templated-pages/src/examples.rs @@ -64,8 +64,7 @@ fn parse_examples(panic_on_missing: bool) -> Vec { .get(&technical_name) .and_then(|metadata| metadata.get("hidden")) .and_then(Item::as_bool) - .and_then(|hidden| hidden.then_some(())) - .is_some() + .unwrap_or(false) { return None; } From cf6c65522fca08be3f036a61f3a67b18dc16decc Mon Sep 17 00:00:00 2001 From: AlephCubed <76791009+AlephCubed@users.noreply.github.com> Date: Sat, 4 Jan 2025 18:45:09 -0800 Subject: [PATCH 122/272] Derived `Default` for all public unit components. (#17139) Derived `Default` for all public unit structs that already derive from `Component`. This allows them to be used more easily as required components. To avoid clutter in tests/examples, only public components were affected, but this could easily be expanded to affect all unit components. Fixes #17052. --- crates/bevy_audio/src/audio_output.rs | 4 ++-- crates/bevy_pbr/src/render/gpu_preprocess.rs | 2 +- crates/bevy_render/src/batching/mod.rs | 2 +- crates/bevy_render/src/view/mod.rs | 4 ++-- crates/bevy_render/src/view/window/screenshot.rs | 4 ++-- crates/bevy_sprite/src/mesh2d/mesh.rs | 2 +- crates/bevy_ui/src/ui_node.rs | 2 +- crates/bevy_window/src/window.rs | 2 +- 8 files changed, 11 insertions(+), 11 deletions(-) diff --git a/crates/bevy_audio/src/audio_output.rs b/crates/bevy_audio/src/audio_output.rs index e34b8d815285e..ee65eaf01f605 100644 --- a/crates/bevy_audio/src/audio_output.rs +++ b/crates/bevy_audio/src/audio_output.rs @@ -47,11 +47,11 @@ impl Default for AudioOutput { } /// Marker for internal use, to despawn entities when playback finishes. -#[derive(Component)] +#[derive(Component, Default)] pub struct PlaybackDespawnMarker; /// Marker for internal use, to remove audio components when playback finishes. -#[derive(Component)] +#[derive(Component, Default)] pub struct PlaybackRemoveMarker; #[derive(SystemParam)] diff --git a/crates/bevy_pbr/src/render/gpu_preprocess.rs b/crates/bevy_pbr/src/render/gpu_preprocess.rs index e96cf324c3491..144539d524eda 100644 --- a/crates/bevy_pbr/src/render/gpu_preprocess.rs +++ b/crates/bevy_pbr/src/render/gpu_preprocess.rs @@ -119,7 +119,7 @@ pub struct PreprocessBindGroup(BindGroup); /// Stops the `GpuPreprocessNode` attempting to generate the buffer for this view /// useful to avoid duplicating effort if the bind group is shared between views -#[derive(Component)] +#[derive(Component, Default)] pub struct SkipGpuPreprocess; impl Plugin for GpuMeshPreprocessPlugin { diff --git a/crates/bevy_render/src/batching/mod.rs b/crates/bevy_render/src/batching/mod.rs index 4906d36f1fc3a..f43a85510a407 100644 --- a/crates/bevy_render/src/batching/mod.rs +++ b/crates/bevy_render/src/batching/mod.rs @@ -20,7 +20,7 @@ pub mod gpu_preprocessing; pub mod no_gpu_preprocessing; /// Add this component to mesh entities to disable automatic batching -#[derive(Component)] +#[derive(Component, Default)] pub struct NoAutomaticBatching; /// Data necessary to be equal for two draw commands to be mergeable diff --git a/crates/bevy_render/src/view/mod.rs b/crates/bevy_render/src/view/mod.rs index 06e2db79f28df..ecadba107fbe9 100644 --- a/crates/bevy_render/src/view/mod.rs +++ b/crates/bevy_render/src/view/mod.rs @@ -631,10 +631,10 @@ impl From for ColorGradingUniform { /// /// The vast majority of applications will not need to use this component, as it /// generally reduces rendering performance. -#[derive(Component)] +#[derive(Component, Default)] pub struct NoIndirectDrawing; -#[derive(Component)] +#[derive(Component, Default)] pub struct NoCpuCulling; impl ViewTarget { diff --git a/crates/bevy_render/src/view/window/screenshot.rs b/crates/bevy_render/src/view/window/screenshot.rs index 10bb42932e53f..1b7db5b4211e7 100644 --- a/crates/bevy_render/src/view/window/screenshot.rs +++ b/crates/bevy_render/src/view/window/screenshot.rs @@ -74,12 +74,12 @@ pub struct ScreenshotCaptured(pub Image); pub struct Screenshot(pub RenderTarget); /// A marker component that indicates that a screenshot is currently being captured. -#[derive(Component)] +#[derive(Component, Default)] pub struct Capturing; /// A marker component that indicates that a screenshot has been captured, the image is ready, and /// the screenshot entity can be despawned. -#[derive(Component)] +#[derive(Component, Default)] pub struct Captured; impl Screenshot { diff --git a/crates/bevy_sprite/src/mesh2d/mesh.rs b/crates/bevy_sprite/src/mesh2d/mesh.rs index 7ad483079b8df..191615db814bf 100644 --- a/crates/bevy_sprite/src/mesh2d/mesh.rs +++ b/crates/bevy_sprite/src/mesh2d/mesh.rs @@ -203,7 +203,7 @@ pub struct RenderMesh2dInstance { #[derive(Default, Resource, Deref, DerefMut)] pub struct RenderMesh2dInstances(MainEntityHashMap); -#[derive(Component)] +#[derive(Component, Default)] pub struct Mesh2dMarker; pub fn extract_mesh2d( diff --git a/crates/bevy_ui/src/ui_node.rs b/crates/bevy_ui/src/ui_node.rs index 392ee86d115c9..7f14532fddbc1 100644 --- a/crates/bevy_ui/src/ui_node.rs +++ b/crates/bevy_ui/src/ui_node.rs @@ -2622,7 +2622,6 @@ impl TargetCamera { } } -#[derive(Component)] /// Marker used to identify default cameras, they will have priority over the [`PrimaryWindow`] camera. /// /// This is useful if the [`PrimaryWindow`] has two cameras, one of them used @@ -2656,6 +2655,7 @@ impl TargetCamera { /// )); /// } /// ``` +#[derive(Component, Default)] pub struct IsDefaultUiCamera; #[derive(SystemParam)] diff --git a/crates/bevy_window/src/window.rs b/crates/bevy_window/src/window.rs index 08a377f20b687..b5418927e631e 100644 --- a/crates/bevy_window/src/window.rs +++ b/crates/bevy_window/src/window.rs @@ -1379,7 +1379,7 @@ impl Default for EnabledButtons { /// Marker component for a [`Window`] that has been requested to close and /// is in the process of closing (on the next frame). -#[derive(Component)] +#[derive(Component, Default)] pub struct ClosingWindow; #[cfg(test)] From f90a41ff72a97217c8644170a5ca05d5b51c71ce Mon Sep 17 00:00:00 2001 From: Sean Kim Date: Sat, 4 Jan 2025 18:45:26 -0800 Subject: [PATCH 123/272] Update GetPath unit test and documentation for empty path usecase (#17150) # Objective - `GetPath` `path` related methods allow an empty string as the parameter, but this is not included as a test or in documentation. This PR adds both. - Fixes #13459 ## Solution - Updates the `bevy_reflect` `GetPath` documentation and unit tests ## Testing - `cargo run -p ci` --- crates/bevy_reflect/src/path/mod.rs | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/crates/bevy_reflect/src/path/mod.rs b/crates/bevy_reflect/src/path/mod.rs index 96741b2c6a500..320c414cdeed2 100644 --- a/crates/bevy_reflect/src/path/mod.rs +++ b/crates/bevy_reflect/src/path/mod.rs @@ -127,10 +127,12 @@ impl<'a> ReflectPath<'a> for &'a str { /// Note that a leading dot (`.`) or hash (`#`) token is implied for the first item in a path, /// and may therefore be omitted. /// +/// Additionally, an empty path may be used to get the struct itself. +/// /// ### Example /// ``` /// # use bevy_reflect::{GetPath, Reflect}; -/// #[derive(Reflect)] +/// #[derive(Reflect, PartialEq, Debug)] /// struct MyStruct { /// value: u32 /// } @@ -140,6 +142,8 @@ impl<'a> ReflectPath<'a> for &'a str { /// assert_eq!(my_struct.path::(".value").unwrap(), &123); /// // Access via field index /// assert_eq!(my_struct.path::("#0").unwrap(), &123); +/// // Access self +/// assert_eq!(*my_struct.path::("").unwrap(), my_struct); /// ``` /// /// ## Tuples and Tuple Structs @@ -512,7 +516,7 @@ mod tests { use crate::*; use alloc::vec; - #[derive(Reflect)] + #[derive(Reflect, PartialEq, Debug)] struct A { w: usize, x: B, @@ -525,21 +529,21 @@ mod tests { tuple: (bool, f32), } - #[derive(Reflect)] + #[derive(Reflect, PartialEq, Debug)] struct B { foo: usize, łørđ: C, } - #[derive(Reflect)] + #[derive(Reflect, PartialEq, Debug)] struct C { mосква: f32, } - #[derive(Reflect)] + #[derive(Reflect, PartialEq, Debug)] struct D(E); - #[derive(Reflect)] + #[derive(Reflect, PartialEq, Debug)] struct E(f32, usize); #[derive(Reflect, PartialEq, Debug)] @@ -739,6 +743,7 @@ mod tests { fn reflect_path() { let mut a = a_sample(); + assert_eq!(*a.path::
("").unwrap(), a); assert_eq!(*a.path::("w").unwrap(), 1); assert_eq!(*a.path::("x.foo").unwrap(), 10); assert_eq!(*a.path::("x.łørđ.mосква").unwrap(), 3.14); From 818c57821d06b1af32fa44300f0c01683bc17d4d Mon Sep 17 00:00:00 2001 From: Rob Parrett Date: Sun, 5 Jan 2025 11:40:07 -0800 Subject: [PATCH 124/272] Allow users to easily use `bevy_sprite` and `bevy_ui` without picking (#17175) # Objective RIght now it's not possible to opt out of sprite or ui picking at the feature level without jumping through some hoops. If you add the `bevy_sprite` feature, you get `bevy_sprite_picking_backend`. If you add the `bevy_ui` feature, you get `bevy_ui_picking_backend` To get `bevy_sprite` without picking, I think you would have to do something like this, which seems **very** annoying. ```toml [dependencies] bevy = { version = "0.15", default-features = false, features = [ # ... omitted # "bevy_sprite", # "bevy_ui", # this also brings in bevy_sprite # "bevy_text", # this also brings in bevy_sprite "bevy_render", "bevy_core_pipeline", "bevy_color", ] } bevy_internal = { version = "0.15", default-features = false, features = [ "bevy_sprite", "bevy_ui", "bevy_text" ] } ``` ## Solution - Remove `bevy_sprite_picking_backend` from the `bevy_sprite` feature. - Remove `bevy_ui_picking_backend` from the `bevy_ui` feature. These are still in Bevy's `default-plugins`. ## Testing I did some basic testing in a minimal project based on the `sprite_picking` example to verify that "picking stuff" didn't get included with `bevy_sprite`. I would appreciate help testing. I am just unraveling these features and think that this is correct, but I am not 100% sure. ## Migration Guide `bevy_sprite_picking_backend` is no longer included by default when using the `bevy_sprite` feature. If you are using Bevy without default features and relied on sprite picking, add this feature to your `Cargo.toml`. `bevy_ui_picking_backend` is no longer included by default when using the `bevy_ui` feature. If you are using Bevy without default features and relied on sprite picking, add this feature to your `Cargo.toml`. ## Additional info It looks like we attempted to fix this earlier in #16469, but the fix was incomplete? --- Cargo.toml | 2 -- 1 file changed, 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 1d0c7d9118ff4..45de925e0e5ac 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -213,7 +213,6 @@ bevy_sprite = [ "bevy_render", "bevy_core_pipeline", "bevy_color", - "bevy_sprite_picking_backend", ] # Provides text functionality @@ -226,7 +225,6 @@ bevy_ui = [ "bevy_text", "bevy_sprite", "bevy_color", - "bevy_ui_picking_backend", ] # Windowing layer From 5f5876b1c98d70ed10d814e913f1bd70193e2d88 Mon Sep 17 00:00:00 2001 From: MichiRecRoom <1008889+LikeLakers2@users.noreply.github.com> Date: Sun, 5 Jan 2025 15:27:20 -0500 Subject: [PATCH 125/272] Change `bevy_reflect::RegisterForReflection::__register()` to expect unused variables, rather than putting underscores on the parameter names (#17171) # Objective While checking over https://github.com/bevyengine/bevy/pull/17160, it occurred to me that rust-analyzer will copy the method signature exactly, when using tab completion trait methods. This includes provided trait methods that use underscores to silence the `unused_variables` lint. This probably isn't good for users, seeing as how they'll have to remove the underscore if they want to use the parameters. (I mean, they technically don't have to remove the underscore... but usually you don't keep a leading underscore on parameters you're using.) ## Solution Changes `bevy_reflect::RegisterForReflection::__register()` to `#[expect(unused_variables)]`, and removes the underscores from its parameter names. ## Testing N/A --- crates/bevy_reflect/src/lib.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/crates/bevy_reflect/src/lib.rs b/crates/bevy_reflect/src/lib.rs index 1d2017c942b44..ea0dd188abc0c 100644 --- a/crates/bevy_reflect/src/lib.rs +++ b/crates/bevy_reflect/src/lib.rs @@ -691,7 +691,11 @@ pub mod __macro_exports { note = "consider annotating `{Self}` with `#[derive(Reflect)]`" )] pub trait RegisterForReflection { - fn __register(_registry: &mut TypeRegistry) {} + #[expect( + unused_variables, + reason = "The parameters here are intentionally unused by the default implementation; however, putting underscores here will result in the underscores being copied by rust-analyzer's tab completion." + )] + fn __register(registry: &mut TypeRegistry) {} } impl RegisterForReflection for T { From 20c1a7e9ce171a7a77a22730be355b0cebddb478 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Mockers?= Date: Sun, 5 Jan 2025 21:27:39 +0100 Subject: [PATCH 126/272] remove daily mobile jobs (#17163) # Objective - Daily mobile job has been failing for a long time - It's in a weird state of hard to fix without knowing some passwords, hard to monitor, and not that used anyway - I keep a working version in the example runner repository that runs more often ## Solution - Remove the workflow from the Bevy repo --- .github/workflows/daily.yml | 147 ------------------------------------ 1 file changed, 147 deletions(-) delete mode 100644 .github/workflows/daily.yml diff --git a/.github/workflows/daily.yml b/.github/workflows/daily.yml deleted file mode 100644 index d187165a763e9..0000000000000 --- a/.github/workflows/daily.yml +++ /dev/null @@ -1,147 +0,0 @@ -name: Daily Jobs - -on: - schedule: - - cron: '0 12 * * *' - workflow_dispatch: - -env: - CARGO_TERM_COLOR: always - -jobs: - build-for-iOS: - if: github.repository == 'bevyengine/bevy' - runs-on: macos-latest - timeout-minutes: 30 - steps: - - uses: actions/checkout@v4 - - - uses: dtolnay/rust-toolchain@stable - - - name: Add iOS targets - run: rustup target add aarch64-apple-ios x86_64-apple-ios - - - name: Build app for iOS - run: | - cd examples/mobile - make xcodebuild-iphone - mkdir Payload - mv build/Build/Products/Debug-iphoneos/bevy_mobile_example.app Payload - zip -r bevy_mobile_example.zip Payload - mv bevy_mobile_example.zip bevy_mobile_example.ipa - - - name: Upload to Browser Stack - run: | - curl -u "${{ secrets.BROWSERSTACK_USERNAME }}:${{ secrets.BROWSERSTACK_ACCESS_KEY }}" \ - -X POST "https://api-cloud.browserstack.com/app-automate/upload" \ - -F "file=@examples/mobile/bevy_mobile_example.ipa" \ - -F "custom_id=$GITHUB_RUN_ID" - - build-for-Android: - if: github.repository == 'bevyengine/bevy' - runs-on: ubuntu-latest - timeout-minutes: 30 - steps: - - uses: actions/checkout@v4 - - - uses: dtolnay/rust-toolchain@stable - - - name: Set up JDK 17 - uses: actions/setup-java@v4 - with: - java-version: '17' - distribution: 'temurin' - - - name: Add Android targets - run: rustup target add aarch64-linux-android - - - name: Install Cargo NDK - run: cargo install --force cargo-ndk - - - name: Build .so file - run: cargo ndk -t arm64-v8a -o android_example/app/src/main/jniLibs build --package bevy_mobile_example - env: - # This will reduce the APK size from 1GB to ~200MB - CARGO_PROFILE_DEV_DEBUG: false - - - name: Build app for Android - run: cd examples/mobile/android_example && chmod +x gradlew && ./gradlew build - - - name: Upload to Browser Stack - run: | - curl -u "${{ secrets.BROWSERSTACK_USERNAME }}:${{ secrets.BROWSERSTACK_ACCESS_KEY }}" \ - -X POST "https://api-cloud.browserstack.com/app-automate/upload" \ - -F "file=@app/build/outputs/apk/debug/app-debug.apk" \ - -F "custom_id=$GITHUB_RUN_ID" - - nonce: - if: github.repository == 'bevyengine/bevy' - runs-on: ubuntu-latest - timeout-minutes: 30 - outputs: - result: ${{ steps.nonce.outputs.result }} - steps: - - id: nonce - run: echo "result=${{ github.run_id }}-$(date +%s)" >> $GITHUB_OUTPUT - - run: - if: github.repository == 'bevyengine/bevy' - runs-on: ubuntu-latest - timeout-minutes: 30 - needs: [nonce, build-for-iOS, build-for-Android] - env: - PERCY_PARALLEL_NONCE: ${{ needs.nonce.outputs.result }} - PERCY_PARALLEL_TOTAL: ${{ strategy.job-total }} - strategy: - matrix: - include: - - device: "iPhone 13" - os_version: "15" - - device: "iPhone 14" - os_version: "16" - - device: "iPhone 15" - os_version: "17" - - device: "Xiaomi Redmi Note 11" - os_version: "11.0" - - device: "Google Pixel 6" - os_version: "12.0" - - device: "Samsung Galaxy S23" - os_version: "13.0" - - device: "Google Pixel 8" - os_version: "14.0" - steps: - - uses: actions/checkout@v4 - - - name: Run Example - run: | - cd .github/start-mobile-example - npm install - npm install -g @percy/cli@latest - npx percy app:exec --parallel -- npm run mobile - env: - BROWSERSTACK_APP_ID: ${{ github.run_id }} - BROWSERSTACK_USERNAME: ${{ secrets.BROWSERSTACK_USERNAME }} - BROWSERSTACK_ACCESS_KEY: ${{ secrets.BROWSERSTACK_ACCESS_KEY }} - PERCY_TOKEN: ${{ secrets.PERCY_TOKEN }} - DEVICE: ${{ matrix.device }} - OS_VERSION: ${{ matrix.os_version }} - - - name: Save screenshots - if: ${{ always() }} - uses: actions/upload-artifact@v4 - with: - name: screenshots-${{ matrix.device }}-${{ matrix.os_version }} - path: .github/start-mobile-example/*.png - - check-result: - if: github.repository == 'bevyengine/bevy' - runs-on: ubuntu-latest - timeout-minutes: 30 - needs: [run] - steps: - - name: Wait for screenshots comparison - run: | - npm install -g @percy/cli@latest - npx percy build:wait --project dede4209/Bevy-Mobile-Example --commit ${{ github.sha }} --fail-on-changes --pass-if-approved - env: - PERCY_TOKEN: ${{ secrets.PERCY_TOKEN }} From cc4aace1aacefff2b69d397b6d3ad4657d0a47ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Mockers?= Date: Sun, 5 Jan 2025 21:27:58 +0100 Subject: [PATCH 127/272] Fix workflows for version bump (#17162) # Objective - Version bump workflow fails due to a permission issue - Fixes #17153 ## Solution - Add the required permissions - Also remove workflow not used anymore since the RC --- .github/workflows/post-release.yml | 5 ++- .github/workflows/release.yml | 55 ------------------------------ 2 files changed, 4 insertions(+), 56 deletions(-) delete mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/post-release.yml b/.github/workflows/post-release.yml index 7902584a9fdb9..91a98f3ea7acc 100644 --- a/.github/workflows/post-release.yml +++ b/.github/workflows/post-release.yml @@ -8,9 +8,12 @@ env: CARGO_TERM_COLOR: always jobs: - ci: + bump: if: github.repository == 'bevyengine/bevy' runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index 32e481b23047c..0000000000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,55 +0,0 @@ -name: Release - -# how to trigger: https://docs.github.com/en/actions/managing-workflow-runs/manually-running-a-workflow -on: - workflow_dispatch: - -env: - CARGO_TERM_COLOR: always - -jobs: - ci: - if: github.repository == 'bevyengine/bevy' - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: Install cargo-release - run: cargo install cargo-release - - - name: Setup release - run: | - # Set the commit author to the github-actions bot. See discussion here for more information: - # https://github.com/actions/checkout/issues/13#issuecomment-724415212 - # https://github.community/t/github-actions-bot-email-address/17204/6 - git config user.name 'Bevy Auto Releaser' - git config user.email '41898282+github-actions[bot]@users.noreply.github.com' - # release: remove the dev suffix, like going from 0.X.0-dev to 0.X.0 - # --workspace: updating all crates in the workspace - # --no-publish: do not publish to crates.io - # --execute: not a dry run - # --no-tag: do not push tag for each new version - # --no-push: do not push the update commits - # --dependent-version upgrade: change 0.X.0-dev in internal dependencies to 0.X.0 - # --exclude: ignore those packages - cargo release release \ - --workspace \ - --no-publish \ - --execute \ - --no-tag \ - --no-confirm \ - --no-push \ - --dependent-version upgrade \ - --exclude ci \ - --exclude errors \ - --exclude bevy_mobile_example \ - --exclude build-wasm-example - - - name: Create PR - uses: peter-evans/create-pull-request@v7 - with: - delete-branch: true - base: "main" - title: "Preparing Next Release" - body: | - Preparing next release. This PR has been auto-generated. From 8898c9e14221a5e5ed05005927bfc5b185753dab Mon Sep 17 00:00:00 2001 From: Rob Parrett Date: Sun, 5 Jan 2025 12:28:11 -0800 Subject: [PATCH 128/272] Use `radsort` for sprite picking (#17174) # Objective Optimization for sprite picking ## Solution Use `radsort` for the sort. We already have `radsort` in tree for sorting various phase items (including `Transparent2d` / sprites). It's a stable parallel radix sort. ## Testing Tested on an M1 Max. `cargo run --example sprite_picking` `cargo run --example bevymark --release --features=trace,trace_tracy -- --waves 100 --per-wave 1000 --benchmark` image --- crates/bevy_sprite/src/picking_backend.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/crates/bevy_sprite/src/picking_backend.rs b/crates/bevy_sprite/src/picking_backend.rs index 4202939e2410a..5d1e652db3a7d 100644 --- a/crates/bevy_sprite/src/picking_backend.rs +++ b/crates/bevy_sprite/src/picking_backend.rs @@ -2,15 +2,13 @@ //! sprites with arbitrary transforms. Picking is done based on sprite bounds, not visible pixels. //! This means a partially transparent sprite is pickable even in its transparent areas. -use core::cmp::Reverse; - use crate::{Sprite, TextureAtlasLayout}; use bevy_app::prelude::*; use bevy_asset::prelude::*; use bevy_color::Alpha; use bevy_ecs::prelude::*; use bevy_image::Image; -use bevy_math::{prelude::*, FloatExt, FloatOrd}; +use bevy_math::{prelude::*, FloatExt}; use bevy_picking::backend::prelude::*; use bevy_reflect::prelude::*; use bevy_render::prelude::*; @@ -83,7 +81,11 @@ fn sprite_picking( } }) .collect(); - sorted_sprites.sort_by_key(|x| Reverse(FloatOrd(x.2.translation().z))); + + // radsort is a stable radix sort that performed better than `slice::sort_by_key` + radsort::sort_by_key(&mut sorted_sprites, |(_, _, transform, _)| { + -transform.translation().z + }); let primary_window = primary_window.get_single().ok(); From 1e03f2a7c18f193d89a0d0e157ce7a9d482ef3ba Mon Sep 17 00:00:00 2001 From: MichiRecRoom <1008889+LikeLakers2@users.noreply.github.com> Date: Sun, 5 Jan 2025 15:32:15 -0500 Subject: [PATCH 129/272] bevy_dev_tools: Apply `#![deny(clippy::allow_attributes, clippy::allow_attributes_without_reason)]` (#17159) # Objective - https://github.com/bevyengine/bevy/issues/17111 ## Solution Set the `clippy::allow_attributes` and `clippy::allow_attributes_without_reason` lints to `deny`, and bring `bevy_audio` in line with the new restrictions. No code changes have been made - except if a lint that was previously `allow(...)`'d could be removed via small code changes. For example, `unused_variables` can be handled by adding a `_` to the beginning of a field's name. ## Testing `cargo clippy`, `cargo clippy --package bevy_dev_tools` and cargo test --package bevy_dev_tools` were run, and no errors were encountered. (Except for one warning from bevy_sprite, but I plan to fix that when I get to bevy_sprite) --- crates/bevy_dev_tools/src/lib.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/crates/bevy_dev_tools/src/lib.rs b/crates/bevy_dev_tools/src/lib.rs index b49604e6c885d..8b328a95042db 100644 --- a/crates/bevy_dev_tools/src/lib.rs +++ b/crates/bevy_dev_tools/src/lib.rs @@ -1,5 +1,10 @@ #![cfg_attr(docsrs, feature(doc_auto_cfg))] #![forbid(unsafe_code)] +#![deny( + clippy::allow_attributes, + clippy::allow_attributes_without_reason, + reason = "See #17111; To be removed once all crates are in-line with these attributes" +)] #![doc( html_logo_url = "https://bevyengine.org/assets/icon.png", html_favicon_url = "https://bevyengine.org/assets/icon.png" From 7112d5594e2ed4152f3aa65625d5e251752a0300 Mon Sep 17 00:00:00 2001 From: Benjamin Brienen Date: Sun, 5 Jan 2025 15:33:39 -0500 Subject: [PATCH 130/272] Remove all `deprecated` code (#16338) # Objective Release cycle things ## Solution Delete items deprecated in 0.15 and migrate bevy itself. ## Testing CI --- crates/bevy_animation/src/lib.rs | 7 - crates/bevy_asset/src/processor/process.rs | 20 -- crates/bevy_audio/src/audio.rs | 54 ----- crates/bevy_audio/src/lib.rs | 6 +- crates/bevy_audio/src/pitch.rs | 11 +- crates/bevy_color/src/color.rs | 46 ---- .../src/auto_exposure/mod.rs | 3 +- .../src/auto_exposure/settings.rs | 3 - crates/bevy_core_pipeline/src/bloom/mod.rs | 5 +- .../bevy_core_pipeline/src/bloom/settings.rs | 6 - .../src/contrast_adaptive_sharpening/mod.rs | 3 - .../src/core_2d/camera_2d.rs | 11 +- .../src/core_3d/camera_3d.rs | 58 +---- crates/bevy_core_pipeline/src/dof/mod.rs | 3 - crates/bevy_core_pipeline/src/lib.rs | 10 +- .../bevy_core_pipeline/src/motion_blur/mod.rs | 15 -- crates/bevy_core_pipeline/src/smaa/mod.rs | 3 - crates/bevy_core_pipeline/src/taa/mod.rs | 20 +- crates/bevy_ecs/src/query/builder.rs | 1 - crates/bevy_ecs/src/query/state.rs | 1 - crates/bevy_ecs/src/system/commands/mod.rs | 34 +-- crates/bevy_ecs/src/world/entity_fetch.rs | 2 - crates/bevy_ecs/src/world/mod.rs | 45 ---- crates/bevy_math/src/direction.rs | 14 -- crates/bevy_math/src/rotation2d.rs | 10 - crates/bevy_pbr/src/bundle.rs | 214 ----------------- crates/bevy_pbr/src/components.rs | 89 +++++++ crates/bevy_pbr/src/fog.rs | 3 - crates/bevy_pbr/src/lib.rs | 20 +- .../src/light_probe/environment_map.rs | 30 +-- crates/bevy_pbr/src/meshlet/mod.rs | 46 +--- crates/bevy_pbr/src/ssao/mod.rs | 19 +- crates/bevy_pbr/src/ssr/mod.rs | 22 -- crates/bevy_pbr/src/volumetric_fog/mod.rs | 33 +-- crates/bevy_reflect/src/array.rs | 5 - crates/bevy_render/src/lib.rs | 5 +- crates/bevy_render/src/spatial_bundle.rs | 72 ------ crates/bevy_render/src/view/visibility/mod.rs | 24 -- crates/bevy_scene/src/bundle.rs | 188 --------------- crates/bevy_scene/src/lib.rs | 7 +- crates/bevy_scene/src/scene_spawner.rs | 132 +++++++++- crates/bevy_sprite/src/bundle.rs | 31 --- crates/bevy_sprite/src/lib.rs | 6 +- .../bevy_sprite/src/mesh2d/color_material.rs | 11 +- crates/bevy_sprite/src/mesh2d/material.rs | 38 +-- .../bevy_sprite/src/texture_atlas_builder.rs | 10 - crates/bevy_text/src/lib.rs | 3 - crates/bevy_text/src/text2d.rs | 12 - crates/bevy_transform/src/bundles.rs | 66 ----- .../src/components/global_transform.rs | 2 - .../src/components/transform.rs | 2 - crates/bevy_transform/src/lib.rs | 6 - crates/bevy_ui/src/lib.rs | 5 - crates/bevy_ui/src/node_bundles.rs | 227 ------------------ crates/bevy_ui/src/widget/text.rs | 12 - errors/B0004.md | 4 +- examples/ecs/dynamic.rs | 2 - 57 files changed, 249 insertions(+), 1488 deletions(-) delete mode 100644 crates/bevy_pbr/src/bundle.rs create mode 100644 crates/bevy_pbr/src/components.rs delete mode 100644 crates/bevy_render/src/spatial_bundle.rs delete mode 100644 crates/bevy_scene/src/bundle.rs delete mode 100644 crates/bevy_sprite/src/bundle.rs delete mode 100644 crates/bevy_transform/src/bundles.rs delete mode 100644 crates/bevy_ui/src/node_bundles.rs diff --git a/crates/bevy_animation/src/lib.rs b/crates/bevy_animation/src/lib.rs index ba5d8fbf042c9..9baacf61c5d3a 100644 --- a/crates/bevy_animation/src/lib.rs +++ b/crates/bevy_animation/src/lib.rs @@ -939,13 +939,6 @@ impl AnimationPlayer { pub fn animation_mut(&mut self, animation: AnimationNodeIndex) -> Option<&mut ActiveAnimation> { self.active_animations.get_mut(&animation) } - - #[deprecated = "Use `is_playing_animation` instead"] - /// Returns true if the animation is currently playing or paused, or false - /// if the animation is stopped. - pub fn animation_is_playing(&self, animation: AnimationNodeIndex) -> bool { - self.active_animations.contains_key(&animation) - } } /// A system that triggers untargeted animation events for the currently-playing animations. diff --git a/crates/bevy_asset/src/processor/process.rs b/crates/bevy_asset/src/processor/process.rs index 77c70f636fffe..3dd3b5127168c 100644 --- a/crates/bevy_asset/src/processor/process.rs +++ b/crates/bevy_asset/src/processor/process.rs @@ -105,26 +105,6 @@ impl< } } -/// A flexible [`Process`] implementation that loads the source [`Asset`] using the `L` [`AssetLoader`], then -/// saves that `L` asset using the `S` [`AssetSaver`]. -/// -/// This is a specialized use case of [`LoadTransformAndSave`] and is useful where there is no asset manipulation -/// such as when compressing assets. -/// -/// This uses [`LoadAndSaveSettings`] to configure the processor. -/// -/// [`Asset`]: crate::Asset -#[deprecated = "Use `LoadTransformAndSave::Asset>, S>` instead"] -pub type LoadAndSave = - LoadTransformAndSave::Asset>, S>; - -/// Settings for the [`LoadAndSave`] [`Process::Settings`] implementation. -/// -/// `LoaderSettings` corresponds to [`AssetLoader::Settings`] and `SaverSettings` corresponds to [`AssetSaver::Settings`]. -#[deprecated = "Use `LoadTransformAndSaveSettings` instead"] -pub type LoadAndSaveSettings = - LoadTransformAndSaveSettings; - /// An error that is encountered during [`Process::process`]. #[derive(Error, Debug)] pub enum ProcessError { diff --git a/crates/bevy_audio/src/audio.rs b/crates/bevy_audio/src/audio.rs index 25f2e07df95df..1d0149381bd57 100644 --- a/crates/bevy_audio/src/audio.rs +++ b/crates/bevy_audio/src/audio.rs @@ -1,5 +1,3 @@ -#![expect(deprecated)] - use crate::{AudioSource, Decodable, Volume}; use bevy_asset::{Asset, Handle}; use bevy_ecs::prelude::*; @@ -207,13 +205,6 @@ impl Default for SpatialScale { #[reflect(Resource, Default)] pub struct DefaultSpatialScale(pub SpatialScale); -/// Bundle for playing a standard bevy audio asset -#[deprecated( - since = "0.15.0", - note = "Use the `AudioPlayer` component instead. Inserting it will now also insert a `PlaybackSettings` component automatically." -)] -pub type AudioBundle = AudioSourceBundle; - /// A component for playing a sound. /// /// Insert this component onto an entity to trigger an audio source to begin playing. @@ -252,48 +243,3 @@ impl AudioPlayer { Self(source) } } - -/// Bundle for playing a sound. -/// -/// Insert this bundle onto an entity to trigger a sound source to begin playing. -/// -/// If the handle refers to an unavailable asset (such as if it has not finished loading yet), -/// the audio will not begin playing immediately. The audio will play when the asset is ready. -/// -/// When Bevy begins the audio playback, an [`AudioSink`][crate::AudioSink] component will be -/// added to the entity. You can use that component to control the audio settings during playback. -#[derive(Bundle)] -#[deprecated( - since = "0.15.0", - note = "Use the `AudioPlayer` component instead. Inserting it will now also insert a `PlaybackSettings` component automatically." -)] -pub struct AudioSourceBundle -where - Source: Asset + Decodable, -{ - /// Asset containing the audio data to play. - pub source: AudioPlayer, - /// Initial settings that the audio starts playing with. - /// If you would like to control the audio while it is playing, - /// query for the [`AudioSink`][crate::AudioSink] component. - /// Changes to this component will *not* be applied to already-playing audio. - pub settings: PlaybackSettings, -} - -impl Clone for AudioSourceBundle { - fn clone(&self) -> Self { - Self { - source: self.source.clone(), - settings: self.settings, - } - } -} - -impl Default for AudioSourceBundle { - fn default() -> Self { - Self { - source: AudioPlayer(Handle::default()), - settings: Default::default(), - } - } -} diff --git a/crates/bevy_audio/src/lib.rs b/crates/bevy_audio/src/lib.rs index a8de9393d15e1..babae2f8a9be9 100644 --- a/crates/bevy_audio/src/lib.rs +++ b/crates/bevy_audio/src/lib.rs @@ -39,13 +39,11 @@ mod volume; /// The audio prelude. /// /// This includes the most common types in this crate, re-exported for your convenience. -#[expect(deprecated)] pub mod prelude { #[doc(hidden)] pub use crate::{ - AudioBundle, AudioPlayer, AudioSink, AudioSinkPlayback, AudioSource, AudioSourceBundle, - Decodable, GlobalVolume, Pitch, PitchBundle, PlaybackSettings, SpatialAudioSink, - SpatialListener, + AudioPlayer, AudioSink, AudioSinkPlayback, AudioSource, Decodable, GlobalVolume, Pitch, + PlaybackSettings, SpatialAudioSink, SpatialListener, }; } diff --git a/crates/bevy_audio/src/pitch.rs b/crates/bevy_audio/src/pitch.rs index 02863d6c62781..d85b9b31cf071 100644 --- a/crates/bevy_audio/src/pitch.rs +++ b/crates/bevy_audio/src/pitch.rs @@ -1,6 +1,4 @@ -#![expect(deprecated)] - -use crate::{AudioSourceBundle, Decodable}; +use crate::Decodable; use bevy_asset::Asset; use bevy_reflect::TypePath; use rodio::{ @@ -35,10 +33,3 @@ impl Decodable for Pitch { SineWave::new(self.frequency).take_duration(self.duration) } } - -/// Bundle for playing a bevy note sound -#[deprecated( - since = "0.15.0", - note = "Use the `AudioPlayer` component instead. Inserting it will now also insert a `PlaybackSettings` component automatically." -)] -pub type PitchBundle = AudioSourceBundle; diff --git a/crates/bevy_color/src/color.rs b/crates/bevy_color/src/color.rs index d2e4cb792187c..29a1b4853cc77 100644 --- a/crates/bevy_color/src/color.rs +++ b/crates/bevy_color/src/color.rs @@ -84,12 +84,6 @@ impl Color { (*self).into() } - #[deprecated = "Use `Color::srgba` instead"] - /// Creates a new [`Color`] object storing a [`Srgba`] color. - pub const fn rgba(red: f32, green: f32, blue: f32, alpha: f32) -> Self { - Self::srgba(red, green, blue, alpha) - } - /// Creates a new [`Color`] object storing a [`Srgba`] color. pub const fn srgba(red: f32, green: f32, blue: f32, alpha: f32) -> Self { Self::Srgba(Srgba { @@ -100,12 +94,6 @@ impl Color { }) } - #[deprecated = "Use `Color::srgb` instead"] - /// Creates a new [`Color`] object storing a [`Srgba`] color with an alpha of 1.0. - pub const fn rgb(red: f32, green: f32, blue: f32) -> Self { - Self::srgb(red, green, blue) - } - /// Creates a new [`Color`] object storing a [`Srgba`] color with an alpha of 1.0. pub const fn srgb(red: f32, green: f32, blue: f32) -> Self { Self::Srgba(Srgba { @@ -116,12 +104,6 @@ impl Color { }) } - #[deprecated = "Use `Color::srgb_from_array` instead"] - /// Reads an array of floats to creates a new [`Color`] object storing a [`Srgba`] color with an alpha of 1.0. - pub fn rgb_from_array([r, g, b]: [f32; 3]) -> Self { - Self::Srgba(Srgba::rgb(r, g, b)) - } - /// Reads an array of floats to creates a new [`Color`] object storing a [`Srgba`] color with an alpha of 1.0. pub const fn srgb_from_array(array: [f32; 3]) -> Self { Self::Srgba(Srgba { @@ -132,14 +114,6 @@ impl Color { }) } - #[deprecated = "Use `Color::srgba_u8` instead"] - /// Creates a new [`Color`] object storing a [`Srgba`] color from [`u8`] values. - /// - /// A value of 0 is interpreted as 0.0, and a value of 255 is interpreted as 1.0. - pub fn rgba_u8(red: u8, green: u8, blue: u8, alpha: u8) -> Self { - Self::srgba_u8(red, green, blue, alpha) - } - /// Creates a new [`Color`] object storing a [`Srgba`] color from [`u8`] values. /// /// A value of 0 is interpreted as 0.0, and a value of 255 is interpreted as 1.0. @@ -152,14 +126,6 @@ impl Color { }) } - #[deprecated = "Use `Color::srgb_u8` instead"] - /// Creates a new [`Color`] object storing a [`Srgba`] color from [`u8`] values with an alpha of 1.0. - /// - /// A value of 0 is interpreted as 0.0, and a value of 255 is interpreted as 1.0. - pub fn rgb_u8(red: u8, green: u8, blue: u8) -> Self { - Self::srgb_u8(red, green, blue) - } - /// Creates a new [`Color`] object storing a [`Srgba`] color from [`u8`] values with an alpha of 1.0. /// /// A value of 0 is interpreted as 0.0, and a value of 255 is interpreted as 1.0. @@ -172,12 +138,6 @@ impl Color { }) } - #[deprecated = "Use Color::linear_rgba instead."] - /// Creates a new [`Color`] object storing a [`LinearRgba`] color. - pub const fn rbga_linear(red: f32, green: f32, blue: f32, alpha: f32) -> Self { - Self::linear_rgba(red, green, blue, alpha) - } - /// Creates a new [`Color`] object storing a [`LinearRgba`] color. pub const fn linear_rgba(red: f32, green: f32, blue: f32, alpha: f32) -> Self { Self::LinearRgba(LinearRgba { @@ -188,12 +148,6 @@ impl Color { }) } - #[deprecated = "Use Color::linear_rgb instead."] - /// Creates a new [`Color`] object storing a [`LinearRgba`] color with an alpha of 1.0. - pub const fn rgb_linear(red: f32, green: f32, blue: f32) -> Self { - Self::linear_rgb(red, green, blue) - } - /// Creates a new [`Color`] object storing a [`LinearRgba`] color with an alpha of 1.0. pub const fn linear_rgb(red: f32, green: f32, blue: f32) -> Self { Self::LinearRgba(LinearRgba { diff --git a/crates/bevy_core_pipeline/src/auto_exposure/mod.rs b/crates/bevy_core_pipeline/src/auto_exposure/mod.rs index 59f314d12e1ab..f94a61d09be16 100644 --- a/crates/bevy_core_pipeline/src/auto_exposure/mod.rs +++ b/crates/bevy_core_pipeline/src/auto_exposure/mod.rs @@ -24,8 +24,7 @@ use node::AutoExposureNode; use pipeline::{ AutoExposurePass, AutoExposurePipeline, ViewAutoExposurePipeline, METERING_SHADER_HANDLE, }; -#[allow(deprecated)] -pub use settings::{AutoExposure, AutoExposureSettings}; +pub use settings::AutoExposure; use crate::{ auto_exposure::compensation_curve::GpuAutoExposureCompensationCurve, diff --git a/crates/bevy_core_pipeline/src/auto_exposure/settings.rs b/crates/bevy_core_pipeline/src/auto_exposure/settings.rs index 91bdf836eebee..b5039030ac969 100644 --- a/crates/bevy_core_pipeline/src/auto_exposure/settings.rs +++ b/crates/bevy_core_pipeline/src/auto_exposure/settings.rs @@ -88,9 +88,6 @@ pub struct AutoExposure { pub compensation_curve: Handle, } -#[deprecated(since = "0.15.0", note = "Renamed to `AutoExposure`")] -pub type AutoExposureSettings = AutoExposure; - impl Default for AutoExposure { fn default() -> Self { Self { diff --git a/crates/bevy_core_pipeline/src/bloom/mod.rs b/crates/bevy_core_pipeline/src/bloom/mod.rs index bfd7ee22dbd10..8f242931d71cf 100644 --- a/crates/bevy_core_pipeline/src/bloom/mod.rs +++ b/crates/bevy_core_pipeline/src/bloom/mod.rs @@ -3,10 +3,7 @@ mod settings; mod upsampling_pipeline; use bevy_color::{Gray, LinearRgba}; -#[allow(deprecated)] -pub use settings::{ - Bloom, BloomCompositeMode, BloomPrefilter, BloomPrefilterSettings, BloomSettings, -}; +pub use settings::{Bloom, BloomCompositeMode, BloomPrefilter}; use crate::{ core_2d::graph::{Core2d, Node2d}, diff --git a/crates/bevy_core_pipeline/src/bloom/settings.rs b/crates/bevy_core_pipeline/src/bloom/settings.rs index effa135677f3b..7816eece51d7c 100644 --- a/crates/bevy_core_pipeline/src/bloom/settings.rs +++ b/crates/bevy_core_pipeline/src/bloom/settings.rs @@ -118,9 +118,6 @@ pub struct Bloom { pub uv_offset: f32, } -#[deprecated(since = "0.15.0", note = "Renamed to `Bloom`")] -pub type BloomSettings = Bloom; - impl Bloom { const DEFAULT_MAX_MIP_DIMENSION: u32 = 512; const DEFAULT_UV_OFFSET: f32 = 0.004; @@ -203,9 +200,6 @@ pub struct BloomPrefilter { pub threshold_softness: f32, } -#[deprecated(since = "0.15.0", note = "Renamed to `BloomPrefilter`")] -pub type BloomPrefilterSettings = BloomPrefilter; - #[derive(Debug, Clone, Reflect, PartialEq, Eq, Hash, Copy)] pub enum BloomCompositeMode { EnergyConserving, diff --git a/crates/bevy_core_pipeline/src/contrast_adaptive_sharpening/mod.rs b/crates/bevy_core_pipeline/src/contrast_adaptive_sharpening/mod.rs index fbc3ecfec3f75..7067058833c7c 100644 --- a/crates/bevy_core_pipeline/src/contrast_adaptive_sharpening/mod.rs +++ b/crates/bevy_core_pipeline/src/contrast_adaptive_sharpening/mod.rs @@ -54,9 +54,6 @@ pub struct ContrastAdaptiveSharpening { pub denoise: bool, } -#[deprecated(since = "0.15.0", note = "Renamed to `ContrastAdaptiveSharpening`")] -pub type ContrastAdaptiveSharpeningSettings = ContrastAdaptiveSharpening; - impl Default for ContrastAdaptiveSharpening { fn default() -> Self { ContrastAdaptiveSharpening { diff --git a/crates/bevy_core_pipeline/src/core_2d/camera_2d.rs b/crates/bevy_core_pipeline/src/core_2d/camera_2d.rs index d8f51395707a8..783ec4b75a414 100644 --- a/crates/bevy_core_pipeline/src/core_2d/camera_2d.rs +++ b/crates/bevy_core_pipeline/src/core_2d/camera_2d.rs @@ -1,21 +1,18 @@ -#![expect(deprecated)] - use crate::{ core_2d::graph::Core2d, tonemapping::{DebandDither, Tonemapping}, }; use bevy_ecs::prelude::*; use bevy_reflect::{std_traits::ReflectDefault, Reflect}; -use bevy_render::sync_world::SyncToRenderWorld; use bevy_render::{ camera::{ Camera, CameraMainTextureUsages, CameraProjection, CameraRenderGraph, OrthographicProjection, Projection, }, extract_component::ExtractComponent, - prelude::Msaa, primitives::Frustum, - view::VisibleEntities, + sync_world::SyncToRenderWorld, + view::{Msaa, VisibleEntities}, }; use bevy_transform::prelude::{GlobalTransform, Transform}; @@ -34,10 +31,6 @@ use bevy_transform::prelude::{GlobalTransform, Transform}; pub struct Camera2d; #[derive(Bundle, Clone)] -#[deprecated( - since = "0.15.0", - note = "Use the `Camera2d` component instead. Inserting it will now also insert the other components required by it automatically." -)] pub struct Camera2dBundle { pub camera: Camera, pub camera_render_graph: CameraRenderGraph, diff --git a/crates/bevy_core_pipeline/src/core_3d/camera_3d.rs b/crates/bevy_core_pipeline/src/core_3d/camera_3d.rs index 2053b96882817..418d3b8d482a3 100644 --- a/crates/bevy_core_pipeline/src/core_3d/camera_3d.rs +++ b/crates/bevy_core_pipeline/src/core_3d/camera_3d.rs @@ -1,5 +1,3 @@ -#![expect(deprecated)] - use crate::{ core_3d::graph::Core3d, tonemapping::{DebandDither, Tonemapping}, @@ -7,14 +5,11 @@ use crate::{ use bevy_ecs::prelude::*; use bevy_reflect::{std_traits::ReflectDefault, Reflect, ReflectDeserialize, ReflectSerialize}; use bevy_render::{ - camera::{Camera, CameraMainTextureUsages, CameraRenderGraph, Exposure, Projection}, + camera::{Camera, CameraRenderGraph, Exposure, Projection}, extract_component::ExtractComponent, - primitives::Frustum, render_resource::{LoadOp, TextureUsages}, - sync_world::SyncToRenderWorld, - view::{ColorGrading, Msaa, VisibleEntities}, + view::ColorGrading, }; -use bevy_transform::prelude::{GlobalTransform, Transform}; use serde::{Deserialize, Serialize}; /// A 3D camera component. Enables the main 3D render graph for a [`Camera`]. @@ -147,52 +142,3 @@ pub enum ScreenSpaceTransmissionQuality { /// `num_taps` = 32 Ultra, } - -/// The camera coordinate space is right-handed x-right, y-up, z-back. -/// This means "forward" is -Z. -#[derive(Bundle, Clone)] -#[deprecated( - since = "0.15.0", - note = "Use the `Camera3d` component instead. Inserting it will now also insert the other components required by it automatically." -)] -pub struct Camera3dBundle { - pub camera: Camera, - pub camera_render_graph: CameraRenderGraph, - pub projection: Projection, - pub visible_entities: VisibleEntities, - pub frustum: Frustum, - pub transform: Transform, - pub global_transform: GlobalTransform, - pub camera_3d: Camera3d, - pub tonemapping: Tonemapping, - pub deband_dither: DebandDither, - pub color_grading: ColorGrading, - pub exposure: Exposure, - pub main_texture_usages: CameraMainTextureUsages, - pub msaa: Msaa, - /// Marker component that indicates that its entity needs to be synchronized to the render world - pub sync: SyncToRenderWorld, -} - -// NOTE: ideally Perspective and Orthographic defaults can share the same impl, but sadly it breaks rust's type inference -impl Default for Camera3dBundle { - fn default() -> Self { - Self { - camera_render_graph: CameraRenderGraph::new(Core3d), - camera: Default::default(), - projection: Default::default(), - visible_entities: Default::default(), - frustum: Default::default(), - transform: Default::default(), - global_transform: Default::default(), - camera_3d: Default::default(), - tonemapping: Default::default(), - color_grading: Default::default(), - exposure: Default::default(), - main_texture_usages: Default::default(), - deband_dither: DebandDither::Enabled, - msaa: Default::default(), - sync: Default::default(), - } - } -} diff --git a/crates/bevy_core_pipeline/src/dof/mod.rs b/crates/bevy_core_pipeline/src/dof/mod.rs index 06cbbe3e9d312..88e25e75c2430 100644 --- a/crates/bevy_core_pipeline/src/dof/mod.rs +++ b/crates/bevy_core_pipeline/src/dof/mod.rs @@ -119,9 +119,6 @@ pub struct DepthOfField { pub max_depth: f32, } -#[deprecated(since = "0.15.0", note = "Renamed to `DepthOfField`")] -pub type DepthOfFieldSettings = DepthOfField; - /// Controls the appearance of the effect. #[derive(Clone, Copy, Default, PartialEq, Debug, Reflect)] #[reflect(Default, PartialEq)] diff --git a/crates/bevy_core_pipeline/src/lib.rs b/crates/bevy_core_pipeline/src/lib.rs index e94daa90f4bdc..6c2ee5bec489b 100644 --- a/crates/bevy_core_pipeline/src/lib.rs +++ b/crates/bevy_core_pipeline/src/lib.rs @@ -33,11 +33,9 @@ pub use skybox::Skybox; /// /// Expect bugs, missing features, compatibility issues, low performance, and/or future breaking changes. pub mod experimental { - #[expect(deprecated)] pub mod taa { pub use crate::taa::{ - TemporalAntiAliasBundle, TemporalAntiAliasNode, TemporalAntiAliasPlugin, - TemporalAntiAliasSettings, TemporalAntiAliasing, + TemporalAntiAliasNode, TemporalAntiAliasPlugin, TemporalAntiAliasing, }; } } @@ -45,13 +43,9 @@ pub mod experimental { /// The core pipeline prelude. /// /// This includes the most common types in this crate, re-exported for your convenience. -#[expect(deprecated)] pub mod prelude { #[doc(hidden)] - pub use crate::{ - core_2d::{Camera2d, Camera2dBundle}, - core_3d::{Camera3d, Camera3dBundle}, - }; + pub use crate::{core_2d::Camera2d, core_3d::Camera3d}; } use crate::{ diff --git a/crates/bevy_core_pipeline/src/motion_blur/mod.rs b/crates/bevy_core_pipeline/src/motion_blur/mod.rs index c6eb8524ca3ef..e4839b25b44c9 100644 --- a/crates/bevy_core_pipeline/src/motion_blur/mod.rs +++ b/crates/bevy_core_pipeline/src/motion_blur/mod.rs @@ -2,8 +2,6 @@ //! //! Add the [`MotionBlur`] component to a camera to enable motion blur. -#![expect(deprecated)] - use crate::{ core_3d::graph::{Core3d, Node3d}, prepass::{DepthPrepass, MotionVectorPrepass}, @@ -11,7 +9,6 @@ use crate::{ use bevy_app::{App, Plugin}; use bevy_asset::{load_internal_asset, Handle}; use bevy_ecs::{ - bundle::Bundle, component::{require, Component}, query::With, reflect::ReflectComponent, @@ -29,18 +26,6 @@ use bevy_render::{ pub mod node; pub mod pipeline; -/// Adds [`MotionBlur`] and the required depth and motion vector prepasses to a camera entity. -#[derive(Bundle, Default)] -#[deprecated( - since = "0.15.0", - note = "Use the `MotionBlur` component instead. Inserting it will now also insert the other components required by it automatically." -)] -pub struct MotionBlurBundle { - pub motion_blur: MotionBlur, - pub depth_prepass: DepthPrepass, - pub motion_vector_prepass: MotionVectorPrepass, -} - /// A component that enables and configures motion blur when added to a camera. /// /// Motion blur is an effect that simulates how moving objects blur as they change position during diff --git a/crates/bevy_core_pipeline/src/smaa/mod.rs b/crates/bevy_core_pipeline/src/smaa/mod.rs index 7471cdb09ab76..d8eb1c90e76e3 100644 --- a/crates/bevy_core_pipeline/src/smaa/mod.rs +++ b/crates/bevy_core_pipeline/src/smaa/mod.rs @@ -101,9 +101,6 @@ pub struct Smaa { pub preset: SmaaPreset, } -#[deprecated(since = "0.15.0", note = "Renamed to `Smaa`")] -pub type SmaaSettings = Smaa; - /// A preset quality level for SMAA. /// /// Higher values are slower but result in a higher-quality image. diff --git a/crates/bevy_core_pipeline/src/taa/mod.rs b/crates/bevy_core_pipeline/src/taa/mod.rs index 559ce4e3a55bc..7698a15623033 100644 --- a/crates/bevy_core_pipeline/src/taa/mod.rs +++ b/crates/bevy_core_pipeline/src/taa/mod.rs @@ -1,5 +1,3 @@ -#![expect(deprecated)] - use crate::{ core_3d::graph::{Core3d, Node3d}, fullscreen_vertex_shader::fullscreen_shader_vertex_state, @@ -10,7 +8,7 @@ use bevy_app::{App, Plugin}; use bevy_asset::{load_internal_asset, Handle}; use bevy_diagnostic::FrameCount; use bevy_ecs::{ - prelude::{require, Bundle, Component, Entity, ReflectComponent}, + prelude::{require, Component, Entity, ReflectComponent}, query::{QueryItem, With}, schedule::IntoSystemConfigs, system::{Commands, Query, Res, ResMut, Resource}, @@ -92,19 +90,6 @@ impl Plugin for TemporalAntiAliasPlugin { } } -/// Bundle to apply temporal anti-aliasing. -#[derive(Bundle, Default, Clone)] -#[deprecated( - since = "0.15.0", - note = "Use the `TemporalAntiAlias` component instead. Inserting it will now also insert the other components required by it automatically." -)] -pub struct TemporalAntiAliasBundle { - pub settings: TemporalAntiAliasing, - pub jitter: TemporalJitter, - pub depth_prepass: DepthPrepass, - pub motion_vector_prepass: MotionVectorPrepass, -} - /// Component to apply temporal anti-aliasing to a 3D perspective camera. /// /// Temporal anti-aliasing (TAA) is a form of image smoothing/filtering, like @@ -159,9 +144,6 @@ pub struct TemporalAntiAliasing { pub reset: bool, } -#[deprecated(since = "0.15.0", note = "Renamed to `TemporalAntiAliasing`")] -pub type TemporalAntiAliasSettings = TemporalAntiAliasing; - impl Default for TemporalAntiAliasing { fn default() -> Self { Self { reset: true } diff --git a/crates/bevy_ecs/src/query/builder.rs b/crates/bevy_ecs/src/query/builder.rs index 55473cf050209..d699cb3aea4ac 100644 --- a/crates/bevy_ecs/src/query/builder.rs +++ b/crates/bevy_ecs/src/query/builder.rs @@ -81,7 +81,6 @@ impl<'w, D: QueryData, F: QueryFilter> QueryBuilder<'w, D, F> { .is_some_and(|info| info.storage_type() == StorageType::Table) }; - #[allow(deprecated)] let (mut component_reads_and_writes, component_reads_and_writes_inverted) = self.access.access().component_reads_and_writes(); if component_reads_and_writes_inverted { diff --git a/crates/bevy_ecs/src/query/state.rs b/crates/bevy_ecs/src/query/state.rs index ae47d4e54e208..09c7f12e003ec 100644 --- a/crates/bevy_ecs/src/query/state.rs +++ b/crates/bevy_ecs/src/query/state.rs @@ -566,7 +566,6 @@ impl QueryState { ) { // As a fast path, we can iterate directly over the components involved // if the `access` isn't inverted. - #[allow(deprecated)] let (component_reads_and_writes, component_reads_and_writes_inverted) = self.component_access.access.component_reads_and_writes(); let (component_writes, component_writes_inverted) = diff --git a/crates/bevy_ecs/src/system/commands/mod.rs b/crates/bevy_ecs/src/system/commands/mod.rs index a9302615e0a6e..43d4c465856e6 100644 --- a/crates/bevy_ecs/src/system/commands/mod.rs +++ b/crates/bevy_ecs/src/system/commands/mod.rs @@ -315,38 +315,6 @@ impl<'w, 's> Commands<'w, 's> { } } - /// Pushes a [`Command`] to the queue for creating a new [`Entity`] if the given one does not exists, - /// and returns its corresponding [`EntityCommands`]. - /// - /// This method silently fails by returning [`EntityCommands`] - /// even if the given `Entity` cannot be spawned. - /// - /// See [`World::get_or_spawn`] for more details. - /// - /// # Note - /// - /// Spawning a specific `entity` value is rarely the right choice. Most apps should favor - /// [`Commands::spawn`]. This method should generally only be used for sharing entities across - /// apps, and only when they have a scheme worked out to share an ID space (which doesn't happen - /// by default). - #[deprecated(since = "0.15.0", note = "use Commands::spawn instead")] - #[track_caller] - pub fn get_or_spawn(&mut self, entity: Entity) -> EntityCommands { - #[cfg(feature = "track_location")] - let caller = Location::caller(); - self.queue(move |world: &mut World| { - world.get_or_spawn_with_caller( - entity, - #[cfg(feature = "track_location")] - caller, - ); - }); - EntityCommands { - entity, - commands: self.reborrow(), - } - } - /// Pushes a [`Command`] to the queue for creating a new entity with the given [`Bundle`]'s components, /// and returns its corresponding [`EntityCommands`]. /// @@ -618,7 +586,7 @@ impl<'w, 's> Commands<'w, 's> { /// Then, the `Bundle` is added to the entity. /// /// This method is equivalent to iterating `bundles_iter`, - /// calling [`get_or_spawn`](Self::get_or_spawn) for each bundle, + /// calling [`spawn`](Self::spawn) for each bundle, /// and passing it to [`insert`](EntityCommands::insert), /// but it is faster due to memory pre-allocation. /// diff --git a/crates/bevy_ecs/src/world/entity_fetch.rs b/crates/bevy_ecs/src/world/entity_fetch.rs index d6121bf048408..8fbf96777de36 100644 --- a/crates/bevy_ecs/src/world/entity_fetch.rs +++ b/crates/bevy_ecs/src/world/entity_fetch.rs @@ -23,8 +23,6 @@ use crate::{ /// /// - The slice and array implementations perform an aliased mutability check /// in [`WorldEntityFetch::fetch_mut`] that is `O(N^2)`. -/// - The [`EntityHashSet`] implementation performs no such check as the type -/// itself guarantees no duplicates. /// - The single [`Entity`] implementation performs no such check as only one /// reference is returned. /// diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index 98c0f9e03b8e9..16948c5a9f740 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -612,8 +612,6 @@ impl World { /// - Pass an [`Entity`] to receive a single [`EntityRef`]. /// - Pass a slice of [`Entity`]s to receive a [`Vec`]. /// - Pass an array of [`Entity`]s to receive an equally-sized array of [`EntityRef`]s. - /// - Pass a reference to a [`EntityHashSet`] to receive an - /// [`EntityHashMap`](crate::entity::EntityHashMap). /// /// # Panics /// @@ -879,49 +877,6 @@ impl World { .filter_map(|id| self.components().get_info(id)) } - /// Returns an [`EntityWorldMut`] for the given `entity` (if it exists) or spawns one if it doesn't exist. - /// This will return [`None`] if the `entity` exists with a different generation. - /// - /// # Note - /// Spawning a specific `entity` value is rarely the right choice. Most apps should favor [`World::spawn`]. - /// This method should generally only be used for sharing entities across apps, and only when they have a - /// scheme worked out to share an ID space (which doesn't happen by default). - #[inline] - #[deprecated(since = "0.15.0", note = "use `World::spawn` instead")] - pub fn get_or_spawn(&mut self, entity: Entity) -> Option { - self.get_or_spawn_with_caller( - entity, - #[cfg(feature = "track_location")] - Location::caller(), - ) - } - - #[inline] - pub(crate) fn get_or_spawn_with_caller( - &mut self, - entity: Entity, - #[cfg(feature = "track_location")] caller: &'static Location, - ) -> Option { - self.flush(); - match self.entities.alloc_at_without_replacement(entity) { - AllocAtWithoutReplacement::Exists(location) => { - // SAFETY: `entity` exists and `location` is that entity's location - Some(unsafe { EntityWorldMut::new(self, entity, location) }) - } - AllocAtWithoutReplacement::DidNotExist => { - // SAFETY: entity was just allocated - Some(unsafe { - self.spawn_at_empty_internal( - entity, - #[cfg(feature = "track_location")] - caller, - ) - }) - } - AllocAtWithoutReplacement::ExistsWithWrongGeneration => None, - } - } - /// Returns [`EntityRef`]s that expose read-only operations for the given /// `entities`, returning [`Err`] if any of the given entities do not exist. /// Instead of immediately unwrapping the value returned from this function, diff --git a/crates/bevy_math/src/direction.rs b/crates/bevy_math/src/direction.rs index 2da99c687bef6..3e32d782babe4 100644 --- a/crates/bevy_math/src/direction.rs +++ b/crates/bevy_math/src/direction.rs @@ -80,20 +80,6 @@ fn assert_is_normalized(message: &str, length_squared: f32) { } } -/// A normalized vector pointing in a direction in 2D space -#[deprecated( - since = "0.14.0", - note = "`Direction2d` has been renamed. Please use `Dir2` instead." -)] -pub type Direction2d = Dir2; - -/// A normalized vector pointing in a direction in 3D space -#[deprecated( - since = "0.14.0", - note = "`Direction3d` has been renamed. Please use `Dir3` instead." -)] -pub type Direction3d = Dir3; - /// A normalized vector pointing in a direction in 2D space #[derive(Clone, Copy, Debug, PartialEq)] #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] diff --git a/crates/bevy_math/src/rotation2d.rs b/crates/bevy_math/src/rotation2d.rs index 5b0bc816bc5ca..40760dcb84273 100644 --- a/crates/bevy_math/src/rotation2d.rs +++ b/crates/bevy_math/src/rotation2d.rs @@ -329,16 +329,6 @@ impl Rot2 { self.cos > 0.0 && ops::abs(self.sin) < threshold_angle_sin } - /// Returns the angle in radians needed to make `self` and `other` coincide. - #[inline] - #[deprecated( - since = "0.15.0", - note = "Use `angle_to` instead, the semantics of `angle_between` will change in the future." - )] - pub fn angle_between(self, other: Self) -> f32 { - self.angle_to(other) - } - /// Returns the angle in radians needed to make `self` and `other` coincide. #[inline] pub fn angle_to(self, other: Self) -> f32 { diff --git a/crates/bevy_pbr/src/bundle.rs b/crates/bevy_pbr/src/bundle.rs deleted file mode 100644 index bdfdd695f5904..0000000000000 --- a/crates/bevy_pbr/src/bundle.rs +++ /dev/null @@ -1,214 +0,0 @@ -#![expect(deprecated)] - -use crate::{ - CascadeShadowConfig, Cascades, DirectionalLight, Material, MeshMaterial3d, PointLight, - SpotLight, StandardMaterial, -}; -use bevy_derive::{Deref, DerefMut}; -use bevy_ecs::{ - bundle::Bundle, - component::Component, - entity::{Entity, EntityHashMap}, - reflect::ReflectComponent, -}; -use bevy_reflect::{std_traits::ReflectDefault, Reflect}; -use bevy_render::sync_world::MainEntity; -use bevy_render::{ - mesh::Mesh3d, - primitives::{CascadesFrusta, CubemapFrusta, Frustum}, - sync_world::SyncToRenderWorld, - view::{InheritedVisibility, ViewVisibility, Visibility}, -}; -use bevy_transform::components::{GlobalTransform, Transform}; - -/// A component bundle for PBR entities with a [`Mesh3d`] and a [`MeshMaterial3d`]. -#[deprecated( - since = "0.15.0", - note = "Use the `Mesh3d` and `MeshMaterial3d` components instead. Inserting them will now also insert the other components required by them automatically." -)] -pub type PbrBundle = MaterialMeshBundle; - -/// A component bundle for entities with a [`Mesh3d`] and a [`MeshMaterial3d`]. -#[derive(Bundle, Clone)] -#[deprecated( - since = "0.15.0", - note = "Use the `Mesh3d` and `MeshMaterial3d` components instead. Inserting them will now also insert the other components required by them automatically." -)] -pub struct MaterialMeshBundle { - pub mesh: Mesh3d, - pub material: MeshMaterial3d, - pub transform: Transform, - pub global_transform: GlobalTransform, - /// User indication of whether an entity is visible - pub visibility: Visibility, - /// Inherited visibility of an entity. - pub inherited_visibility: InheritedVisibility, - /// Algorithmically-computed indication of whether an entity is visible and should be extracted for rendering - pub view_visibility: ViewVisibility, -} - -impl Default for MaterialMeshBundle { - fn default() -> Self { - Self { - mesh: Default::default(), - material: Default::default(), - transform: Default::default(), - global_transform: Default::default(), - visibility: Default::default(), - inherited_visibility: Default::default(), - view_visibility: Default::default(), - } - } -} - -/// Collection of mesh entities visible for 3D lighting. -/// -/// This component contains all mesh entities visible from the current light view. -/// The collection is updated automatically by [`crate::SimulationLightSystems`]. -#[derive(Component, Clone, Debug, Default, Reflect, Deref, DerefMut)] -#[reflect(Component, Debug, Default)] -pub struct VisibleMeshEntities { - #[reflect(ignore)] - pub entities: Vec, -} - -#[derive(Component, Clone, Debug, Default, Reflect, Deref, DerefMut)] -#[reflect(Component, Debug, Default)] -pub struct RenderVisibleMeshEntities { - #[reflect(ignore)] - pub entities: Vec<(Entity, MainEntity)>, -} - -#[derive(Component, Clone, Debug, Default, Reflect)] -#[reflect(Component, Debug, Default)] -pub struct CubemapVisibleEntities { - #[reflect(ignore)] - data: [VisibleMeshEntities; 6], -} - -impl CubemapVisibleEntities { - pub fn get(&self, i: usize) -> &VisibleMeshEntities { - &self.data[i] - } - - pub fn get_mut(&mut self, i: usize) -> &mut VisibleMeshEntities { - &mut self.data[i] - } - - pub fn iter(&self) -> impl DoubleEndedIterator { - self.data.iter() - } - - pub fn iter_mut(&mut self) -> impl DoubleEndedIterator { - self.data.iter_mut() - } -} - -#[derive(Component, Clone, Debug, Default, Reflect)] -#[reflect(Component, Debug, Default)] -pub struct RenderCubemapVisibleEntities { - #[reflect(ignore)] - pub(crate) data: [RenderVisibleMeshEntities; 6], -} - -impl RenderCubemapVisibleEntities { - pub fn get(&self, i: usize) -> &RenderVisibleMeshEntities { - &self.data[i] - } - - pub fn get_mut(&mut self, i: usize) -> &mut RenderVisibleMeshEntities { - &mut self.data[i] - } - - pub fn iter(&self) -> impl DoubleEndedIterator { - self.data.iter() - } - - pub fn iter_mut(&mut self) -> impl DoubleEndedIterator { - self.data.iter_mut() - } -} - -#[derive(Component, Clone, Debug, Default, Reflect)] -#[reflect(Component)] -pub struct CascadesVisibleEntities { - /// Map of view entity to the visible entities for each cascade frustum. - #[reflect(ignore)] - pub entities: EntityHashMap>, -} - -#[derive(Component, Clone, Debug, Default, Reflect)] -#[reflect(Component)] -pub struct RenderCascadesVisibleEntities { - /// Map of view entity to the visible entities for each cascade frustum. - #[reflect(ignore)] - pub entities: EntityHashMap>, -} - -/// A component bundle for [`PointLight`] entities. -#[derive(Debug, Bundle, Default, Clone)] -#[deprecated( - since = "0.15.0", - note = "Use the `PointLight` component instead. Inserting it will now also insert the other components required by it automatically." -)] -pub struct PointLightBundle { - pub point_light: PointLight, - pub cubemap_visible_entities: CubemapVisibleEntities, - pub cubemap_frusta: CubemapFrusta, - pub transform: Transform, - pub global_transform: GlobalTransform, - /// Enables or disables the light - pub visibility: Visibility, - /// Inherited visibility of an entity. - pub inherited_visibility: InheritedVisibility, - /// Algorithmically-computed indication of whether an entity is visible and should be extracted for rendering - pub view_visibility: ViewVisibility, - /// Marker component that indicates that its entity needs to be synchronized to the render world - pub sync: SyncToRenderWorld, -} - -/// A component bundle for spot light entities -#[derive(Debug, Bundle, Default, Clone)] -#[deprecated( - since = "0.15.0", - note = "Use the `SpotLight` component instead. Inserting it will now also insert the other components required by it automatically." -)] -pub struct SpotLightBundle { - pub spot_light: SpotLight, - pub visible_entities: VisibleMeshEntities, - pub frustum: Frustum, - pub transform: Transform, - pub global_transform: GlobalTransform, - /// Enables or disables the light - pub visibility: Visibility, - /// Inherited visibility of an entity. - pub inherited_visibility: InheritedVisibility, - /// Algorithmically-computed indication of whether an entity is visible and should be extracted for rendering - pub view_visibility: ViewVisibility, - /// Marker component that indicates that its entity needs to be synchronized to the render world - pub sync: SyncToRenderWorld, -} - -/// A component bundle for [`DirectionalLight`] entities. -#[derive(Debug, Bundle, Default, Clone)] -#[deprecated( - since = "0.15.0", - note = "Use the `DirectionalLight` component instead. Inserting it will now also insert the other components required by it automatically." -)] -pub struct DirectionalLightBundle { - pub directional_light: DirectionalLight, - pub frusta: CascadesFrusta, - pub cascades: Cascades, - pub cascade_shadow_config: CascadeShadowConfig, - pub visible_entities: CascadesVisibleEntities, - pub transform: Transform, - pub global_transform: GlobalTransform, - /// Enables or disables the light - pub visibility: Visibility, - /// Inherited visibility of an entity. - pub inherited_visibility: InheritedVisibility, - /// Algorithmically-computed indication of whether an entity is visible and should be extracted for rendering - pub view_visibility: ViewVisibility, - /// Marker component that indicates that its entity needs to be synchronized to the render world - pub sync: SyncToRenderWorld, -} diff --git a/crates/bevy_pbr/src/components.rs b/crates/bevy_pbr/src/components.rs new file mode 100644 index 0000000000000..189862cc55e9a --- /dev/null +++ b/crates/bevy_pbr/src/components.rs @@ -0,0 +1,89 @@ +use bevy_derive::{Deref, DerefMut}; +use bevy_ecs::component::Component; +use bevy_ecs::entity::{Entity, EntityHashMap}; +use bevy_ecs::reflect::ReflectComponent; +use bevy_reflect::{std_traits::ReflectDefault, Reflect}; +use bevy_render::sync_world::MainEntity; +/// Collection of mesh entities visible for 3D lighting. +/// +/// This component contains all mesh entities visible from the current light view. +/// The collection is updated automatically by [`crate::SimulationLightSystems`]. +#[derive(Component, Clone, Debug, Default, Reflect, Deref, DerefMut)] +#[reflect(Component, Debug, Default)] +pub struct VisibleMeshEntities { + #[reflect(ignore)] + pub entities: Vec, +} + +#[derive(Component, Clone, Debug, Default, Reflect, Deref, DerefMut)] +#[reflect(Component, Debug, Default)] +pub struct RenderVisibleMeshEntities { + #[reflect(ignore)] + pub entities: Vec<(Entity, MainEntity)>, +} + +#[derive(Component, Clone, Debug, Default, Reflect)] +#[reflect(Component, Debug, Default)] +pub struct CubemapVisibleEntities { + #[reflect(ignore)] + data: [VisibleMeshEntities; 6], +} + +impl CubemapVisibleEntities { + pub fn get(&self, i: usize) -> &VisibleMeshEntities { + &self.data[i] + } + + pub fn get_mut(&mut self, i: usize) -> &mut VisibleMeshEntities { + &mut self.data[i] + } + + pub fn iter(&self) -> impl DoubleEndedIterator { + self.data.iter() + } + + pub fn iter_mut(&mut self) -> impl DoubleEndedIterator { + self.data.iter_mut() + } +} + +#[derive(Component, Clone, Debug, Default, Reflect)] +#[reflect(Component, Debug, Default)] +pub struct RenderCubemapVisibleEntities { + #[reflect(ignore)] + pub(crate) data: [RenderVisibleMeshEntities; 6], +} + +impl RenderCubemapVisibleEntities { + pub fn get(&self, i: usize) -> &RenderVisibleMeshEntities { + &self.data[i] + } + + pub fn get_mut(&mut self, i: usize) -> &mut RenderVisibleMeshEntities { + &mut self.data[i] + } + + pub fn iter(&self) -> impl DoubleEndedIterator { + self.data.iter() + } + + pub fn iter_mut(&mut self) -> impl DoubleEndedIterator { + self.data.iter_mut() + } +} + +#[derive(Component, Clone, Debug, Default, Reflect)] +#[reflect(Component)] +pub struct CascadesVisibleEntities { + /// Map of view entity to the visible entities for each cascade frustum. + #[reflect(ignore)] + pub entities: EntityHashMap>, +} + +#[derive(Component, Clone, Debug, Default, Reflect)] +#[reflect(Component)] +pub struct RenderCascadesVisibleEntities { + /// Map of view entity to the visible entities for each cascade frustum. + #[reflect(ignore)] + pub entities: EntityHashMap>, +} diff --git a/crates/bevy_pbr/src/fog.rs b/crates/bevy_pbr/src/fog.rs index 198d218334dc2..831ec6928c424 100644 --- a/crates/bevy_pbr/src/fog.rs +++ b/crates/bevy_pbr/src/fog.rs @@ -70,9 +70,6 @@ pub struct DistanceFog { pub falloff: FogFalloff, } -#[deprecated(since = "0.15.0", note = "Renamed to `DistanceFog`")] -pub type FogSettings = DistanceFog; - /// Allows switching between different fog falloff modes, and configuring their parameters. /// /// ## Convenience Methods diff --git a/crates/bevy_pbr/src/lib.rs b/crates/bevy_pbr/src/lib.rs index 490c023e7f18d..15f8795ffbac4 100644 --- a/crates/bevy_pbr/src/lib.rs +++ b/crates/bevy_pbr/src/lib.rs @@ -24,8 +24,8 @@ pub mod experimental { } } -mod bundle; mod cluster; +mod components; pub mod deferred; mod extended_material; mod fog; @@ -47,8 +47,8 @@ use crate::material_bind_groups::FallbackBindlessResources; use bevy_color::{Color, LinearRgba}; -pub use bundle::*; pub use cluster::*; +pub use components::*; pub use extended_material::*; pub use fog::*; pub use light::*; @@ -62,29 +62,17 @@ pub use prepass::*; pub use render::*; pub use ssao::*; pub use ssr::*; -#[allow(deprecated)] -pub use volumetric_fog::{ - FogVolume, FogVolumeBundle, VolumetricFog, VolumetricFogPlugin, VolumetricFogSettings, - VolumetricLight, -}; +pub use volumetric_fog::{FogVolume, VolumetricFog, VolumetricFogPlugin, VolumetricLight}; /// The PBR prelude. /// /// This includes the most common types in this crate, re-exported for your convenience. -#[expect(deprecated)] pub mod prelude { #[doc(hidden)] pub use crate::{ - bundle::{ - DirectionalLightBundle, MaterialMeshBundle, PbrBundle, PointLightBundle, - SpotLightBundle, - }, fog::{DistanceFog, FogFalloff}, light::{light_consts, AmbientLight, DirectionalLight, PointLight, SpotLight}, - light_probe::{ - environment_map::{EnvironmentMapLight, ReflectionProbeBundle}, - LightProbe, - }, + light_probe::{environment_map::EnvironmentMapLight, LightProbe}, material::{Material, MaterialPlugin}, mesh_material::MeshMaterial3d, parallax::ParallaxMappingMethod, diff --git a/crates/bevy_pbr/src/light_probe/environment_map.rs b/crates/bevy_pbr/src/light_probe/environment_map.rs index 34a673582f73d..06985ef1b727e 100644 --- a/crates/bevy_pbr/src/light_probe/environment_map.rs +++ b/crates/bevy_pbr/src/light_probe/environment_map.rs @@ -15,7 +15,7 @@ //! environment maps are added to every point of the scene, including //! interior enclosed areas. //! -//! 2. If attached to a [`LightProbe`], environment maps represent the immediate +//! 2. If attached to a [`crate::LightProbe`], environment maps represent the immediate //! surroundings of a specific location in the scene. These types of //! environment maps are known as *reflection probes*. //! @@ -44,19 +44,15 @@ //! //! [several pre-filtered environment maps]: https://github.com/KhronosGroup/glTF-Sample-Environments -#![expect(deprecated)] - use bevy_asset::{AssetId, Handle}; use bevy_ecs::{ - bundle::Bundle, component::Component, query::QueryItem, reflect::ReflectComponent, - system::lifetimeless::Read, + component::Component, query::QueryItem, reflect::ReflectComponent, system::lifetimeless::Read, }; use bevy_image::Image; use bevy_math::Quat; use bevy_reflect::{std_traits::ReflectDefault, Reflect}; use bevy_render::{ extract_instances::ExtractInstance, - prelude::SpatialBundle, render_asset::RenderAssets, render_resource::{ binding_types::{self, uniform_buffer}, @@ -70,7 +66,7 @@ use bevy_render::{ use core::{num::NonZero, ops::Deref}; use crate::{ - add_cubemap_texture_view, binding_arrays_are_usable, EnvironmentMapUniform, LightProbe, + add_cubemap_texture_view, binding_arrays_are_usable, EnvironmentMapUniform, MAX_VIEW_LIGHT_PROBES, }; @@ -142,26 +138,6 @@ pub struct EnvironmentMapIds { pub(crate) specular: AssetId, } -/// A bundle that contains everything needed to make an entity a reflection -/// probe. -/// -/// A reflection probe is a type of environment map that specifies the light -/// surrounding a region in space. For more information, see -/// [`crate::environment_map`]. -#[derive(Bundle, Clone)] -#[deprecated( - since = "0.15.0", - note = "Use the `LightProbe` and `EnvironmentMapLight` components instead. Inserting them will now also insert the other components required by them automatically." -)] -pub struct ReflectionProbeBundle { - /// Contains a transform that specifies the position of this reflection probe in space. - pub spatial: SpatialBundle, - /// Marks this environment map as a light probe. - pub light_probe: LightProbe, - /// The cubemaps that make up this environment map. - pub environment_map: EnvironmentMapLight, -} - /// All the bind group entries necessary for PBR shaders to access the /// environment maps exposed to a view. pub(crate) enum RenderViewEnvironmentMapBindGroupEntries<'a> { diff --git a/crates/bevy_pbr/src/meshlet/mod.rs b/crates/bevy_pbr/src/meshlet/mod.rs index 0ad880877d1cb..5486fd72e0371 100644 --- a/crates/bevy_pbr/src/meshlet/mod.rs +++ b/crates/bevy_pbr/src/meshlet/mod.rs @@ -1,4 +1,3 @@ -#![expect(deprecated)] //! Render high-poly 3d meshes using an efficient GPU-driven method. See [`MeshletPlugin`] and [`MeshletMesh`] for details. mod asset; @@ -40,7 +39,6 @@ pub use self::asset::{ pub use self::from_mesh::{ MeshToMeshletMeshConversionError, MESHLET_DEFAULT_VERTEX_POSITION_QUANTIZATION_FACTOR, }; - use self::{ graph::NodeMeshlet, instance_manager::extract_meshlet_mesh_entities, @@ -58,7 +56,8 @@ use self::{ }, visibility_buffer_raster_node::MeshletVisibilityBufferRasterPassNode, }; -use crate::{graph::NodePbr, Material, MeshMaterial3d, PreviousGlobalTransform}; +use crate::graph::NodePbr; +use crate::PreviousGlobalTransform; use bevy_app::{App, Plugin}; use bevy_asset::{load_internal_asset, AssetApp, AssetId, Handle}; use bevy_core_pipeline::{ @@ -67,7 +66,6 @@ use bevy_core_pipeline::{ }; use bevy_derive::{Deref, DerefMut}; use bevy_ecs::{ - bundle::Bundle, component::{require, Component}, entity::Entity, query::Has, @@ -81,13 +79,10 @@ use bevy_render::{ render_resource::Shader, renderer::RenderDevice, settings::WgpuFeatures, - view::{ - self, prepare_view_targets, InheritedVisibility, Msaa, ViewVisibility, Visibility, - VisibilityClass, - }, + view::{self, prepare_view_targets, Msaa, Visibility, VisibilityClass}, ExtractSchedule, Render, RenderApp, RenderSet, }; -use bevy_transform::components::{GlobalTransform, Transform}; +use bevy_transform::components::Transform; use bevy_utils::tracing::error; use derive_more::From; @@ -314,39 +309,6 @@ impl From<&MeshletMesh3d> for AssetId { } } -/// A component bundle for entities with a [`MeshletMesh`] and a [`Material`]. -#[derive(Bundle, Clone)] -#[deprecated( - since = "0.15.0", - note = "Use the `MeshletMesh3d` and `MeshMaterial3d` components instead. Inserting them will now also insert the other components required by them automatically." -)] -pub struct MaterialMeshletMeshBundle { - pub meshlet_mesh: MeshletMesh3d, - pub material: MeshMaterial3d, - pub transform: Transform, - pub global_transform: GlobalTransform, - /// User indication of whether an entity is visible - pub visibility: Visibility, - /// Inherited visibility of an entity. - pub inherited_visibility: InheritedVisibility, - /// Algorithmically-computed indication of whether an entity is visible and should be extracted for rendering - pub view_visibility: ViewVisibility, -} - -impl Default for MaterialMeshletMeshBundle { - fn default() -> Self { - Self { - meshlet_mesh: Default::default(), - material: Default::default(), - transform: Default::default(), - global_transform: Default::default(), - visibility: Default::default(), - inherited_visibility: Default::default(), - view_visibility: Default::default(), - } - } -} - fn configure_meshlet_views( mut views_3d: Query<( Entity, diff --git a/crates/bevy_pbr/src/ssao/mod.rs b/crates/bevy_pbr/src/ssao/mod.rs index fd47511da5d16..5784c41b2a0db 100644 --- a/crates/bevy_pbr/src/ssao/mod.rs +++ b/crates/bevy_pbr/src/ssao/mod.rs @@ -1,5 +1,3 @@ -#![expect(deprecated)] - use crate::NodePbr; use bevy_app::{App, Plugin}; use bevy_asset::{load_internal_asset, Handle}; @@ -9,7 +7,7 @@ use bevy_core_pipeline::{ prepass::{DepthPrepass, NormalPrepass, ViewPrepassTextures}, }; use bevy_ecs::{ - prelude::{require, Bundle, Component, Entity}, + prelude::{require, Component, Entity}, query::{Has, QueryItem, With}, reflect::ReflectComponent, schedule::IntoSystemConfigs, @@ -132,18 +130,6 @@ impl Plugin for ScreenSpaceAmbientOcclusionPlugin { } } -/// Bundle to apply screen space ambient occlusion. -#[derive(Bundle, Default, Clone)] -#[deprecated( - since = "0.15.0", - note = "Use the `ScreenSpaceAmbientOcclusion` component instead. Inserting it will now also insert the other components required by it automatically." -)] -pub struct ScreenSpaceAmbientOcclusionBundle { - pub settings: ScreenSpaceAmbientOcclusion, - pub depth_prepass: DepthPrepass, - pub normal_prepass: NormalPrepass, -} - /// Component to apply screen space ambient occlusion to a 3d camera. /// /// Screen space ambient occlusion (SSAO) approximates small-scale, @@ -185,9 +171,6 @@ impl Default for ScreenSpaceAmbientOcclusion { } } -#[deprecated(since = "0.15.0", note = "Renamed to `ScreenSpaceAmbientOcclusion`")] -pub type ScreenSpaceAmbientOcclusionSettings = ScreenSpaceAmbientOcclusion; - #[derive(Reflect, PartialEq, Eq, Hash, Clone, Copy, Default, Debug)] pub enum ScreenSpaceAmbientOcclusionQualityLevel { Low, diff --git a/crates/bevy_pbr/src/ssr/mod.rs b/crates/bevy_pbr/src/ssr/mod.rs index abf3e32220d8c..996c120041948 100644 --- a/crates/bevy_pbr/src/ssr/mod.rs +++ b/crates/bevy_pbr/src/ssr/mod.rs @@ -1,7 +1,5 @@ //! Screen space reflections implemented via raymarching. -#![expect(deprecated)] - use bevy_app::{App, Plugin}; use bevy_asset::{load_internal_asset, Handle}; use bevy_core_pipeline::{ @@ -14,7 +12,6 @@ use bevy_core_pipeline::{ }; use bevy_derive::{Deref, DerefMut}; use bevy_ecs::{ - bundle::Bundle, component::{require, Component}, entity::Entity, query::{Has, QueryItem, With}, @@ -58,22 +55,6 @@ const RAYMARCH_SHADER_HANDLE: Handle = Handle::weak_from_u128(8517409683 /// Screen-space reflections are currently only supported with deferred rendering. pub struct ScreenSpaceReflectionsPlugin; -/// A convenient bundle to add screen space reflections to a camera, along with -/// the depth and deferred prepasses required to enable them. -#[derive(Bundle, Default)] -#[deprecated( - since = "0.15.0", - note = "Use the `ScreenSpaceReflections` components instead. Inserting it will now also insert the other components required by it automatically." -)] -pub struct ScreenSpaceReflectionsBundle { - /// The component that enables SSR. - pub settings: ScreenSpaceReflections, - /// The depth prepass, needed for SSR. - pub depth_prepass: DepthPrepass, - /// The deferred prepass, needed for SSR. - pub deferred_prepass: DeferredPrepass, -} - /// Add this component to a camera to enable *screen-space reflections* (SSR). /// /// Screen-space reflections currently require deferred rendering in order to @@ -142,9 +123,6 @@ pub struct ScreenSpaceReflections { pub use_secant: bool, } -#[deprecated(since = "0.15.0", note = "Renamed to `ScreenSpaceReflections`")] -pub type ScreenSpaceReflectionsSettings = ScreenSpaceReflections; - /// A version of [`ScreenSpaceReflections`] for upload to the GPU. /// /// For more information on these fields, see the corresponding documentation in diff --git a/crates/bevy_pbr/src/volumetric_fog/mod.rs b/crates/bevy_pbr/src/volumetric_fog/mod.rs index 0d998b2a06daa..4b90d63afccb7 100644 --- a/crates/bevy_pbr/src/volumetric_fog/mod.rs +++ b/crates/bevy_pbr/src/volumetric_fog/mod.rs @@ -29,8 +29,6 @@ //! //! [Henyey-Greenstein phase function]: https://www.pbr-book.org/4ed/Volume_Scattering/Phase_Functions#TheHenyeyndashGreensteinPhaseFunction -#![expect(deprecated)] - use bevy_app::{App, Plugin}; use bevy_asset::{load_internal_asset, Assets, Handle}; use bevy_color::Color; @@ -39,7 +37,6 @@ use bevy_core_pipeline::core_3d::{ prepare_core_3d_depth_textures, }; use bevy_ecs::{ - bundle::Bundle, component::{require, Component}, reflect::ReflectComponent, schedule::IntoSystemConfigs as _, @@ -55,10 +52,10 @@ use bevy_render::{ render_graph::{RenderGraphApp, ViewNodeRunner}, render_resource::{Shader, SpecializedRenderPipelines}, sync_component::SyncComponentPlugin, - view::{InheritedVisibility, ViewVisibility, Visibility}, + view::Visibility, ExtractSchedule, Render, RenderApp, RenderSet, }; -use bevy_transform::components::{GlobalTransform, Transform}; +use bevy_transform::components::Transform; use render::{ VolumetricFogNode, VolumetricFogPipeline, VolumetricFogUniformBuffer, CUBE_MESH, PLANE_MESH, VOLUMETRIC_FOG_HANDLE, @@ -120,32 +117,6 @@ pub struct VolumetricFog { pub step_count: u32, } -#[deprecated(since = "0.15.0", note = "Renamed to `VolumetricFog`")] -pub type VolumetricFogSettings = VolumetricFog; - -/// A convenient [`Bundle`] that contains all components necessary to generate a -/// fog volume. -#[derive(Bundle, Clone, Debug, Default)] -#[deprecated( - since = "0.15.0", - note = "Use the `FogVolume` component instead. Inserting it will now also insert the other components required by it automatically." -)] -pub struct FogVolumeBundle { - /// The actual fog volume. - pub fog_volume: FogVolume, - /// Visibility. - pub visibility: Visibility, - /// Inherited visibility. - pub inherited_visibility: InheritedVisibility, - /// View visibility. - pub view_visibility: ViewVisibility, - /// The local transform. Set this to change the position, and scale of the - /// fog's axis-aligned bounding box (AABB). - pub transform: Transform, - /// The global transform. - pub global_transform: GlobalTransform, -} - #[derive(Clone, Component, Debug, Reflect)] #[reflect(Component, Default, Debug)] #[require(Transform, Visibility)] diff --git a/crates/bevy_reflect/src/array.rs b/crates/bevy_reflect/src/array.rs index 0fee779b0513b..3deff6f315c6e 100644 --- a/crates/bevy_reflect/src/array.rs +++ b/crates/bevy_reflect/src/array.rs @@ -175,11 +175,6 @@ impl DynamicArray { } } - #[deprecated(since = "0.15.0", note = "use from_iter")] - pub fn from_vec(values: Vec) -> Self { - Self::from_iter(values) - } - /// Sets the [type] to be represented by this `DynamicArray`. /// /// # Panics diff --git a/crates/bevy_render/src/lib.rs b/crates/bevy_render/src/lib.rs index 7e206ffd2fb25..88483342d2b09 100644 --- a/crates/bevy_render/src/lib.rs +++ b/crates/bevy_render/src/lib.rs @@ -40,7 +40,6 @@ pub mod render_phase; pub mod render_resource; pub mod renderer; pub mod settings; -mod spatial_bundle; pub mod storage; pub mod sync_component; pub mod sync_world; @@ -50,7 +49,6 @@ pub mod view; /// The render prelude. /// /// This includes the most common types in this crate, re-exported for your convenience. -#[expect(deprecated)] pub mod prelude { #[doc(hidden)] pub use crate::{ @@ -64,9 +62,8 @@ pub mod prelude { Mesh3d, }, render_resource::Shader, - spatial_bundle::SpatialBundle, texture::ImagePlugin, - view::{InheritedVisibility, Msaa, ViewVisibility, Visibility, VisibilityBundle}, + view::{InheritedVisibility, Msaa, ViewVisibility, Visibility}, ExtractSchedule, }; } diff --git a/crates/bevy_render/src/spatial_bundle.rs b/crates/bevy_render/src/spatial_bundle.rs deleted file mode 100644 index d50bd31dfd3fd..0000000000000 --- a/crates/bevy_render/src/spatial_bundle.rs +++ /dev/null @@ -1,72 +0,0 @@ -#![expect(deprecated)] -use bevy_ecs::prelude::Bundle; -use bevy_transform::prelude::{GlobalTransform, Transform}; - -use crate::view::{InheritedVisibility, ViewVisibility, Visibility}; - -/// A [`Bundle`] that allows the correct positional rendering of an entity. -/// -/// It consists of transform components, -/// controlling position, rotation and scale of the entity, -/// but also visibility components, -/// which determine whether the entity is visible or not. -/// -/// Parent-child hierarchies of entities must contain -/// all the [`Component`]s in this `Bundle` -/// to be rendered correctly. -/// -/// [`Component`]: bevy_ecs::component::Component -#[derive(Bundle, Clone, Debug, Default)] -#[deprecated( - since = "0.15.0", - note = "Use the `Transform` and `Visibility` components instead. - Inserting `Transform` will now also insert a `GlobalTransform` automatically. - Inserting 'Visibility' will now also insert `InheritedVisibility` and `ViewVisibility` automatically." -)] -pub struct SpatialBundle { - /// The visibility of the entity. - pub visibility: Visibility, - /// The inherited visibility of the entity. - pub inherited_visibility: InheritedVisibility, - /// The view visibility of the entity. - pub view_visibility: ViewVisibility, - /// The transform of the entity. - pub transform: Transform, - /// The global transform of the entity. - pub global_transform: GlobalTransform, -} - -impl SpatialBundle { - /// Creates a new [`SpatialBundle`] from a [`Transform`]. - /// - /// This initializes [`GlobalTransform`] as identity, and visibility as visible - #[inline] - pub const fn from_transform(transform: Transform) -> Self { - SpatialBundle { - transform, - ..Self::INHERITED_IDENTITY - } - } - - /// A [`SpatialBundle`] with inherited visibility and identity transform. - pub const INHERITED_IDENTITY: Self = SpatialBundle { - visibility: Visibility::Inherited, - inherited_visibility: InheritedVisibility::HIDDEN, - view_visibility: ViewVisibility::HIDDEN, - transform: Transform::IDENTITY, - global_transform: GlobalTransform::IDENTITY, - }; - - /// An invisible [`SpatialBundle`] with identity transform. - pub const HIDDEN_IDENTITY: Self = SpatialBundle { - visibility: Visibility::Hidden, - ..Self::INHERITED_IDENTITY - }; -} - -impl From for SpatialBundle { - #[inline] - fn from(transform: Transform) -> Self { - Self::from_transform(transform) - } -} diff --git a/crates/bevy_render/src/view/visibility/mod.rs b/crates/bevy_render/src/view/visibility/mod.rs index a004cf19e0450..b3b7c15bb2687 100644 --- a/crates/bevy_render/src/view/visibility/mod.rs +++ b/crates/bevy_render/src/view/visibility/mod.rs @@ -1,5 +1,3 @@ -#![expect(deprecated)] - mod range; mod render_layers; @@ -200,28 +198,6 @@ impl ViewVisibility { } } -/// A [`Bundle`] of the [`Visibility`], [`InheritedVisibility`], and [`ViewVisibility`] -/// [`Component`]s, which describe the visibility of an entity. -/// -/// * To show or hide an entity, you should set its [`Visibility`]. -/// * To get the inherited visibility of an entity, you should get its [`InheritedVisibility`]. -/// * For visibility hierarchies to work correctly, you must have both all of [`Visibility`], [`InheritedVisibility`], and [`ViewVisibility`]. -/// * ~~You may use the [`VisibilityBundle`] to guarantee this.~~ [`VisibilityBundle`] is now deprecated. -/// [`InheritedVisibility`] and [`ViewVisibility`] are automatically inserted whenever [`Visibility`] is inserted. -#[derive(Bundle, Debug, Clone, Default)] -#[deprecated( - since = "0.15.0", - note = "Use the `Visibility` component instead. Inserting it will now also insert `InheritedVisibility` and `ViewVisibility` automatically." -)] -pub struct VisibilityBundle { - /// The visibility of the entity. - pub visibility: Visibility, - // The inherited visibility of the entity. - pub inherited_visibility: InheritedVisibility, - // The computed visibility of the entity. - pub view_visibility: ViewVisibility, -} - /// Use this component to opt-out of built-in frustum culling for entities, see /// [`Frustum`]. /// diff --git a/crates/bevy_scene/src/bundle.rs b/crates/bevy_scene/src/bundle.rs deleted file mode 100644 index 0024b2f77729b..0000000000000 --- a/crates/bevy_scene/src/bundle.rs +++ /dev/null @@ -1,188 +0,0 @@ -#![expect(deprecated)] - -use bevy_derive::{Deref, DerefMut}; -use bevy_ecs::{ - bundle::Bundle, - change_detection::ResMut, - entity::Entity, - prelude::{Changed, Component, Without}, - system::{Commands, Query}, -}; -#[cfg(feature = "bevy_render")] -use bevy_render::prelude::{InheritedVisibility, ViewVisibility, Visibility}; -use bevy_transform::components::{GlobalTransform, Transform}; - -use crate::{DynamicSceneRoot, InstanceId, SceneRoot, SceneSpawner}; - -/// [`InstanceId`] of a spawned scene. It can be used with the [`SceneSpawner`] to -/// interact with the spawned scene. -#[derive(Component, Deref, DerefMut)] -pub struct SceneInstance(pub(crate) InstanceId); - -/// A component bundle for a [`Scene`](crate::Scene) root. -/// -/// The scene from `scene` will be spawned as a child of the entity with this component. -/// Once it's spawned, the entity will have a [`SceneInstance`] component. -#[derive(Default, Bundle, Clone)] -#[deprecated( - since = "0.15.0", - note = "Use the `SceneRoot` component instead. Inserting `SceneRoot` will also insert the other components required by scenes automatically." -)] -pub struct SceneBundle { - /// Handle to the scene to spawn. - pub scene: SceneRoot, - /// Transform of the scene root entity. - pub transform: Transform, - /// Global transform of the scene root entity. - pub global_transform: GlobalTransform, - - /// User-driven visibility of the scene root entity. - #[cfg(feature = "bevy_render")] - pub visibility: Visibility, - /// Inherited visibility of the scene root entity. - #[cfg(feature = "bevy_render")] - pub inherited_visibility: InheritedVisibility, - /// Algorithmically-computed visibility of the scene root entity for rendering. - #[cfg(feature = "bevy_render")] - pub view_visibility: ViewVisibility, -} - -/// A component bundle for a [`DynamicScene`](crate::DynamicScene) root. -/// -/// The dynamic scene from `scene` will be spawn as a child of the entity with this component. -/// Once it's spawned, the entity will have a [`SceneInstance`] component. -#[derive(Default, Bundle, Clone)] -#[deprecated( - since = "0.15.0", - note = "Use the `DynamicSceneRoot` component instead. Inserting `DynamicSceneRoot` will also insert the other components required by scenes automatically." -)] -pub struct DynamicSceneBundle { - /// Handle to the scene to spawn. - pub scene: DynamicSceneRoot, - /// Transform of the scene root entity. - pub transform: Transform, - /// Global transform of the scene root entity. - pub global_transform: GlobalTransform, - - /// User-driven visibility of the scene root entity. - #[cfg(feature = "bevy_render")] - pub visibility: Visibility, - /// Inherited visibility of the scene root entity. - #[cfg(feature = "bevy_render")] - pub inherited_visibility: InheritedVisibility, - /// Algorithmically-computed visibility of the scene root entity for rendering. - #[cfg(feature = "bevy_render")] - pub view_visibility: ViewVisibility, -} - -/// System that will spawn scenes from the [`SceneRoot`] and [`DynamicSceneRoot`] components. -pub fn scene_spawner( - mut commands: Commands, - mut scene_to_spawn: Query< - (Entity, &SceneRoot, Option<&mut SceneInstance>), - (Changed, Without), - >, - mut dynamic_scene_to_spawn: Query< - (Entity, &DynamicSceneRoot, Option<&mut SceneInstance>), - (Changed, Without), - >, - mut scene_spawner: ResMut, -) { - for (entity, scene, instance) in &mut scene_to_spawn { - let new_instance = scene_spawner.spawn_as_child(scene.0.clone(), entity); - if let Some(mut old_instance) = instance { - scene_spawner.despawn_instance(**old_instance); - *old_instance = SceneInstance(new_instance); - } else { - commands.entity(entity).insert(SceneInstance(new_instance)); - } - } - for (entity, dynamic_scene, instance) in &mut dynamic_scene_to_spawn { - let new_instance = scene_spawner.spawn_dynamic_as_child(dynamic_scene.0.clone(), entity); - if let Some(mut old_instance) = instance { - scene_spawner.despawn_instance(**old_instance); - *old_instance = SceneInstance(new_instance); - } else { - commands.entity(entity).insert(SceneInstance(new_instance)); - } - } -} - -#[cfg(test)] -mod tests { - use crate::{DynamicScene, DynamicSceneRoot, ScenePlugin, SceneSpawner}; - use bevy_app::{App, ScheduleRunnerPlugin}; - use bevy_asset::{AssetPlugin, Assets}; - use bevy_ecs::{ - component::Component, - entity::Entity, - prelude::{AppTypeRegistry, ReflectComponent, World}, - }; - use bevy_hierarchy::{Children, HierarchyPlugin}; - use bevy_reflect::Reflect; - - #[derive(Component, Reflect, Default)] - #[reflect(Component)] - struct ComponentA { - pub x: f32, - pub y: f32, - } - - #[test] - fn spawn_and_delete() { - let mut app = App::new(); - - app.add_plugins(ScheduleRunnerPlugin::default()) - .add_plugins(HierarchyPlugin) - .add_plugins(AssetPlugin::default()) - .add_plugins(ScenePlugin) - .register_type::(); - app.update(); - - let mut scene_world = World::new(); - - // create a new DynamicScene manually - let type_registry = app.world().resource::().clone(); - scene_world.insert_resource(type_registry); - scene_world.spawn(ComponentA { x: 3.0, y: 4.0 }); - let scene = DynamicScene::from_world(&scene_world); - let scene_handle = app - .world_mut() - .resource_mut::>() - .add(scene); - - // spawn the scene as a child of `entity` using `DynamicSceneRoot` - let entity = app - .world_mut() - .spawn(DynamicSceneRoot(scene_handle.clone())) - .id(); - - // run the app's schedule once, so that the scene gets spawned - app.update(); - - // make sure that the scene was added as a child of the root entity - let (scene_entity, scene_component_a) = app - .world_mut() - .query::<(Entity, &ComponentA)>() - .single(app.world()); - assert_eq!(scene_component_a.x, 3.0); - assert_eq!(scene_component_a.y, 4.0); - assert_eq!( - app.world().entity(entity).get::().unwrap().len(), - 1 - ); - - // let's try to delete the scene - let mut scene_spawner = app.world_mut().resource_mut::(); - scene_spawner.despawn(&scene_handle); - - // run the scene spawner system to despawn the scene - app.update(); - - // the scene entity does not exist anymore - assert!(app.world().get_entity(scene_entity).is_err()); - - // the root entity does not have any children anymore - assert!(app.world().entity(entity).get::().is_none()); - } -} diff --git a/crates/bevy_scene/src/lib.rs b/crates/bevy_scene/src/lib.rs index 8a21b2040d78e..76673219c5e7d 100644 --- a/crates/bevy_scene/src/lib.rs +++ b/crates/bevy_scene/src/lib.rs @@ -13,7 +13,6 @@ extern crate alloc; -mod bundle; mod components; mod dynamic_scene; mod dynamic_scene_builder; @@ -29,7 +28,6 @@ pub mod serde; pub use bevy_asset::ron; use bevy_ecs::schedule::IntoSystemConfigs; -pub use bundle::*; pub use components::*; pub use dynamic_scene::*; pub use dynamic_scene_builder::*; @@ -41,12 +39,11 @@ pub use scene_spawner::*; /// The scene prelude. /// /// This includes the most common types in this crate, re-exported for your convenience. -#[expect(deprecated)] pub mod prelude { #[doc(hidden)] pub use crate::{ - DynamicScene, DynamicSceneBuilder, DynamicSceneBundle, DynamicSceneRoot, Scene, - SceneBundle, SceneFilter, SceneRoot, SceneSpawner, + DynamicScene, DynamicSceneBuilder, DynamicSceneRoot, Scene, SceneFilter, SceneRoot, + SceneSpawner, }; } diff --git a/crates/bevy_scene/src/scene_spawner.rs b/crates/bevy_scene/src/scene_spawner.rs index 7117542d83100..7e18bdc001adc 100644 --- a/crates/bevy_scene/src/scene_spawner.rs +++ b/crates/bevy_scene/src/scene_spawner.rs @@ -13,6 +13,13 @@ use bevy_utils::{HashMap, HashSet}; use thiserror::Error; use uuid::Uuid; +use crate::{DynamicSceneRoot, SceneRoot}; +use bevy_derive::{Deref, DerefMut}; +use bevy_ecs::{ + change_detection::ResMut, + prelude::{Changed, Component, Without}, + system::{Commands, Query}, +}; /// Triggered on a scene's parent entity when [`crate::SceneInstance`] becomes ready to use. /// /// See also [`Trigger`], [`SceneSpawner::instance_is_ready`]. @@ -468,6 +475,44 @@ pub fn scene_spawner_system(world: &mut World) { }); } +/// [`InstanceId`] of a spawned scene. It can be used with the [`SceneSpawner`] to +/// interact with the spawned scene. +#[derive(Component, Deref, DerefMut)] +pub struct SceneInstance(pub(crate) InstanceId); + +/// System that will spawn scenes from the [`SceneRoot`] and [`DynamicSceneRoot`] components. +pub fn scene_spawner( + mut commands: Commands, + mut scene_to_spawn: Query< + (Entity, &SceneRoot, Option<&mut SceneInstance>), + (Changed, Without), + >, + mut dynamic_scene_to_spawn: Query< + (Entity, &DynamicSceneRoot, Option<&mut SceneInstance>), + (Changed, Without), + >, + mut scene_spawner: ResMut, +) { + for (entity, scene, instance) in &mut scene_to_spawn { + let new_instance = scene_spawner.spawn_as_child(scene.0.clone(), entity); + if let Some(mut old_instance) = instance { + scene_spawner.despawn_instance(**old_instance); + *old_instance = SceneInstance(new_instance); + } else { + commands.entity(entity).insert(SceneInstance(new_instance)); + } + } + for (entity, dynamic_scene, instance) in &mut dynamic_scene_to_spawn { + let new_instance = scene_spawner.spawn_dynamic_as_child(dynamic_scene.0.clone(), entity); + if let Some(mut old_instance) = instance { + scene_spawner.despawn_instance(**old_instance); + *old_instance = SceneInstance(new_instance); + } else { + commands.entity(entity).insert(SceneInstance(new_instance)); + } + } +} + #[cfg(test)] mod tests { use bevy_app::App; @@ -484,6 +529,79 @@ mod tests { use crate::{DynamicSceneBuilder, DynamicSceneRoot, ScenePlugin}; use super::*; + use crate::{DynamicScene, SceneSpawner}; + use bevy_app::ScheduleRunnerPlugin; + use bevy_asset::Assets; + use bevy_ecs::{ + entity::Entity, + prelude::{AppTypeRegistry, World}, + }; + use bevy_hierarchy::{Children, HierarchyPlugin}; + + #[derive(Component, Reflect, Default)] + #[reflect(Component)] + struct ComponentA { + pub x: f32, + pub y: f32, + } + + #[test] + fn spawn_and_delete() { + let mut app = App::new(); + + app.add_plugins(ScheduleRunnerPlugin::default()) + .add_plugins(HierarchyPlugin) + .add_plugins(AssetPlugin::default()) + .add_plugins(ScenePlugin) + .register_type::(); + app.update(); + + let mut scene_world = World::new(); + + // create a new DynamicScene manually + let type_registry = app.world().resource::().clone(); + scene_world.insert_resource(type_registry); + scene_world.spawn(ComponentA { x: 3.0, y: 4.0 }); + let scene = DynamicScene::from_world(&scene_world); + let scene_handle = app + .world_mut() + .resource_mut::>() + .add(scene); + + // spawn the scene as a child of `entity` using `DynamicSceneRoot` + let entity = app + .world_mut() + .spawn(DynamicSceneRoot(scene_handle.clone())) + .id(); + + // run the app's schedule once, so that the scene gets spawned + app.update(); + + // make sure that the scene was added as a child of the root entity + let (scene_entity, scene_component_a) = app + .world_mut() + .query::<(Entity, &ComponentA)>() + .single(app.world()); + assert_eq!(scene_component_a.x, 3.0); + assert_eq!(scene_component_a.y, 4.0); + assert_eq!( + app.world().entity(entity).get::().unwrap().len(), + 1 + ); + + // let's try to delete the scene + let mut scene_spawner = app.world_mut().resource_mut::(); + scene_spawner.despawn(&scene_handle); + + // run the scene spawner system to despawn the scene + app.update(); + + // the scene entity does not exist anymore + assert!(app.world().get_entity(scene_entity).is_err()); + + // the root entity does not have any children anymore + assert!(app.world().entity(entity).get::().is_none()); + } #[derive(Reflect, Component, Debug, PartialEq, Eq, Clone, Copy, Default)] #[reflect(Component)] @@ -538,7 +656,7 @@ mod tests { #[derive(Component, Reflect, Default)] #[reflect(Component)] - struct ComponentA; + struct ComponentF; #[derive(Resource, Default)] struct TriggerCount(u32); @@ -548,9 +666,9 @@ mod tests { app.add_plugins((AssetPlugin::default(), ScenePlugin)); app.init_resource::(); - app.register_type::(); - app.world_mut().spawn(ComponentA); - app.world_mut().spawn(ComponentA); + app.register_type::(); + app.world_mut().spawn(ComponentF); + app.world_mut().spawn(ComponentF); app } @@ -704,7 +822,7 @@ mod tests { fn despawn_scene() { let mut app = App::new(); app.add_plugins((AssetPlugin::default(), ScenePlugin)); - app.register_type::(); + app.register_type::(); let asset_server = app.world().resource::(); @@ -725,7 +843,7 @@ mod tests { // Spawn scene. for _ in 0..count { app.world_mut() - .spawn((ComponentA, DynamicSceneRoot(scene.clone()))); + .spawn((ComponentF, DynamicSceneRoot(scene.clone()))); } app.update(); @@ -734,7 +852,7 @@ mod tests { // Despawn scene. app.world_mut() .run_system_once( - |mut commands: Commands, query: Query>| { + |mut commands: Commands, query: Query>| { for entity in query.iter() { commands.entity(entity).despawn_recursive(); } diff --git a/crates/bevy_sprite/src/bundle.rs b/crates/bevy_sprite/src/bundle.rs deleted file mode 100644 index fdc2f8ed515d1..0000000000000 --- a/crates/bevy_sprite/src/bundle.rs +++ /dev/null @@ -1,31 +0,0 @@ -#![expect(deprecated)] -use crate::Sprite; -use bevy_ecs::bundle::Bundle; -use bevy_render::{ - sync_world::SyncToRenderWorld, - view::{InheritedVisibility, ViewVisibility, Visibility}, -}; -use bevy_transform::components::{GlobalTransform, Transform}; - -/// A [`Bundle`] of components for drawing a single sprite from an image. -#[derive(Bundle, Clone, Debug, Default)] -#[deprecated( - since = "0.15.0", - note = "Use the `Sprite` component instead. Inserting it will now also insert `Transform` and `Visibility` automatically." -)] -pub struct SpriteBundle { - /// Specifies the rendering properties of the sprite, such as color tint and flip. - pub sprite: Sprite, - /// The local transform of the sprite, relative to its parent. - pub transform: Transform, - /// The absolute transform of the sprite. This should generally not be written to directly. - pub global_transform: GlobalTransform, - /// User indication of whether an entity is visible - pub visibility: Visibility, - /// Inherited visibility of an entity. - pub inherited_visibility: InheritedVisibility, - /// Algorithmically-computed indication of whether an entity is visible and should be extracted for rendering - pub view_visibility: ViewVisibility, - /// Marker component that indicates that its entity needs to be synchronized to the render world - pub sync: SyncToRenderWorld, -} diff --git a/crates/bevy_sprite/src/lib.rs b/crates/bevy_sprite/src/lib.rs index ae68868ebbc35..543ca5c3dbc3a 100644 --- a/crates/bevy_sprite/src/lib.rs +++ b/crates/bevy_sprite/src/lib.rs @@ -10,7 +10,6 @@ extern crate alloc; -mod bundle; mod dynamic_texture_atlas_builder; mod mesh2d; #[cfg(feature = "bevy_sprite_picking_backend")] @@ -24,19 +23,16 @@ mod texture_slice; /// The sprite prelude. /// /// This includes the most common types in this crate, re-exported for your convenience. -#[expect(deprecated)] pub mod prelude { #[doc(hidden)] pub use crate::{ - bundle::SpriteBundle, sprite::{Sprite, SpriteImageMode}, texture_atlas::{TextureAtlas, TextureAtlasLayout, TextureAtlasSources}, texture_slice::{BorderRect, SliceScaleMode, TextureSlice, TextureSlicer}, - ColorMaterial, ColorMesh2dBundle, MeshMaterial2d, TextureAtlasBuilder, + ColorMaterial, MeshMaterial2d, TextureAtlasBuilder, }; } -pub use bundle::*; pub use dynamic_texture_atlas_builder::*; pub use mesh2d::*; #[cfg(feature = "bevy_sprite_picking_backend")] diff --git a/crates/bevy_sprite/src/mesh2d/color_material.rs b/crates/bevy_sprite/src/mesh2d/color_material.rs index 8c3267c40ba4d..e844fc3a997a8 100644 --- a/crates/bevy_sprite/src/mesh2d/color_material.rs +++ b/crates/bevy_sprite/src/mesh2d/color_material.rs @@ -1,6 +1,4 @@ -#![expect(deprecated)] - -use crate::{AlphaMode2d, Material2d, Material2dPlugin, MaterialMesh2dBundle}; +use crate::{AlphaMode2d, Material2d, Material2dPlugin}; use bevy_app::{App, Plugin}; use bevy_asset::{load_internal_asset, Asset, AssetApp, Assets, Handle}; use bevy_color::{Alpha, Color, ColorToComponents, LinearRgba}; @@ -157,10 +155,3 @@ impl Material2d for ColorMaterial { self.alpha_mode } } - -/// A component bundle for entities with a [`Mesh2d`](crate::Mesh2d) and a [`ColorMaterial`]. -#[deprecated( - since = "0.15.0", - note = "Use the `Mesh3d` and `MeshMaterial3d` components instead. Inserting them will now also insert the other components required by them automatically." -)] -pub type ColorMesh2dBundle = MaterialMesh2dBundle; diff --git a/crates/bevy_sprite/src/mesh2d/material.rs b/crates/bevy_sprite/src/mesh2d/material.rs index 58b68b84e6f14..a53b45cc26d21 100644 --- a/crates/bevy_sprite/src/mesh2d/material.rs +++ b/crates/bevy_sprite/src/mesh2d/material.rs @@ -1,5 +1,3 @@ -#![expect(deprecated)] - use crate::{ DrawMesh2d, Mesh2d, Mesh2dPipeline, Mesh2dPipelineKey, RenderMesh2dInstances, SetMesh2dBindGroup, SetMesh2dViewBindGroup, @@ -34,11 +32,10 @@ use bevy_render::{ SpecializedMeshPipelineError, SpecializedMeshPipelines, }, renderer::RenderDevice, - view::{ExtractedView, InheritedVisibility, Msaa, ViewVisibility, Visibility}, + view::{ExtractedView, Msaa, ViewVisibility}, Extract, ExtractSchedule, Render, RenderApp, RenderSet, }; use bevy_render::{render_resource::BindingResources, sync_world::MainEntityHashMap}; -use bevy_transform::components::{GlobalTransform, Transform}; use bevy_utils::tracing::error; use core::{hash::Hash, marker::PhantomData}; use derive_more::derive::From; @@ -674,36 +671,3 @@ impl RenderAsset for PreparedMaterial2d { } } } - -/// A component bundle for entities with a [`Mesh2d`] and a [`MeshMaterial2d`]. -#[derive(Bundle, Clone)] -#[deprecated( - since = "0.15.0", - note = "Use the `Mesh2d` and `MeshMaterial2d` components instead. Inserting them will now also insert the other components required by them automatically." -)] -pub struct MaterialMesh2dBundle { - pub mesh: Mesh2d, - pub material: MeshMaterial2d, - pub transform: Transform, - pub global_transform: GlobalTransform, - /// User indication of whether an entity is visible - pub visibility: Visibility, - // Inherited visibility of an entity. - pub inherited_visibility: InheritedVisibility, - // Indication of whether an entity is visible in any view. - pub view_visibility: ViewVisibility, -} - -impl Default for MaterialMesh2dBundle { - fn default() -> Self { - Self { - mesh: Default::default(), - material: Default::default(), - transform: Default::default(), - global_transform: Default::default(), - visibility: Default::default(), - inherited_visibility: Default::default(), - view_visibility: Default::default(), - } - } -} diff --git a/crates/bevy_sprite/src/texture_atlas_builder.rs b/crates/bevy_sprite/src/texture_atlas_builder.rs index 695e00f437e0d..50619e18f9692 100644 --- a/crates/bevy_sprite/src/texture_atlas_builder.rs +++ b/crates/bevy_sprite/src/texture_atlas_builder.rs @@ -155,16 +155,6 @@ impl<'a> TextureAtlasBuilder<'a> { } } - #[deprecated( - since = "0.14.0", - note = "TextureAtlasBuilder::finish() was not idiomatic. Use TextureAtlasBuilder::build() instead." - )] - pub fn finish( - &mut self, - ) -> Result<(TextureAtlasLayout, TextureAtlasSources, Image), TextureAtlasBuilderError> { - self.build() - } - /// Consumes the builder, and returns the newly created texture atlas and /// the associated atlas layout. /// diff --git a/crates/bevy_text/src/lib.rs b/crates/bevy_text/src/lib.rs index ad9feb7fdcf0f..d50cd45cf25e5 100644 --- a/crates/bevy_text/src/lib.rs +++ b/crates/bevy_text/src/lib.rs @@ -61,9 +61,6 @@ pub use text_access::*; /// /// This includes the most common types in this crate, re-exported for your convenience. pub mod prelude { - #[doc(hidden)] - #[allow(deprecated)] - pub use crate::Text2dBundle; #[doc(hidden)] pub use crate::{ Font, JustifyText, LineBreak, Text2d, Text2dReader, Text2dWriter, TextColor, TextError, diff --git a/crates/bevy_text/src/text2d.rs b/crates/bevy_text/src/text2d.rs index e3f65b43115db..970b226f81eae 100644 --- a/crates/bevy_text/src/text2d.rs +++ b/crates/bevy_text/src/text2d.rs @@ -31,18 +31,6 @@ use bevy_transform::components::Transform; use bevy_transform::prelude::GlobalTransform; use bevy_window::{PrimaryWindow, Window}; -/// [`Text2dBundle`] was removed in favor of required components. -/// The core component is now [`Text2d`] which can contain a single text segment. -/// Indexed access to segments can be done with the new [`Text2dReader`] and [`Text2dWriter`] system params. -/// Additional segments can be added through children with [`TextSpan`](crate::text::TextSpan). -/// Text configuration can be done with [`TextLayout`], [`TextFont`] and [`TextColor`], -/// while sprite-related configuration uses [`TextBounds`] and [`Anchor`] components. -#[deprecated( - since = "0.15.0", - note = "Text2dBundle has been migrated to required components. Follow the documentation for more information." -)] -pub struct Text2dBundle {} - /// The top-level 2D text component. /// /// Adding `Text2d` to an entity will pull in required components for setting up 2d text. diff --git a/crates/bevy_transform/src/bundles.rs b/crates/bevy_transform/src/bundles.rs deleted file mode 100644 index 16a36441a4396..0000000000000 --- a/crates/bevy_transform/src/bundles.rs +++ /dev/null @@ -1,66 +0,0 @@ -#![expect(deprecated)] -use bevy_ecs::bundle::Bundle; - -use crate::prelude::{GlobalTransform, Transform}; - -/// A [`Bundle`] of the [`Transform`] and [`GlobalTransform`] -/// [`Component`](bevy_ecs::component::Component)s, which describe the position of an entity. -/// -/// * To place or move an entity, you should set its [`Transform`]. -/// * To get the global transform of an entity, you should get its [`GlobalTransform`]. -/// * For transform hierarchies to work correctly, you must have both a [`Transform`] and a [`GlobalTransform`]. -/// * ~You may use the [`TransformBundle`] to guarantee this.~ -/// [`TransformBundle`] is now deprecated. -/// [`GlobalTransform`] is automatically inserted whenever [`Transform`] is inserted. -/// -/// ## [`Transform`] and [`GlobalTransform`] -/// -/// [`Transform`] is the position of an entity relative to its parent position, or the reference -/// frame if it doesn't have a parent. -/// -/// [`GlobalTransform`] is the position of an entity relative to the reference frame. -/// -/// [`GlobalTransform`] is updated from [`Transform`] by systems in the system set -/// [`TransformPropagate`](crate::TransformSystem::TransformPropagate). -/// -/// This system runs during [`PostUpdate`](bevy_app::PostUpdate). If you -/// update the [`Transform`] of an entity in this schedule or after, you will notice a 1 frame lag -/// before the [`GlobalTransform`] is updated. -#[derive(Clone, Copy, Debug, Default, Bundle)] -#[deprecated( - since = "0.15.0", - note = "Use the `Transform` component instead. Inserting `Transform` will now also insert a `GlobalTransform` automatically." -)] -pub struct TransformBundle { - /// The transform of the entity. - pub local: Transform, - /// The global transform of the entity. - pub global: GlobalTransform, -} - -impl TransformBundle { - /// An identity [`TransformBundle`] with no translation, rotation, and a scale of 1 on all axes. - pub const IDENTITY: Self = TransformBundle { - local: Transform::IDENTITY, - global: GlobalTransform::IDENTITY, - }; - - /// Creates a new [`TransformBundle`] from a [`Transform`]. - /// - /// This initializes [`GlobalTransform`] as identity, to be updated later by the - /// [`bevy_app::PostUpdate`] schedule. - #[inline] - pub const fn from_transform(transform: Transform) -> Self { - TransformBundle { - local: transform, - ..Self::IDENTITY - } - } -} - -impl From for TransformBundle { - #[inline] - fn from(transform: Transform) -> Self { - Self::from_transform(transform) - } -} diff --git a/crates/bevy_transform/src/components/global_transform.rs b/crates/bevy_transform/src/components/global_transform.rs index 0a04baadafd02..eb244cae8e911 100644 --- a/crates/bevy_transform/src/components/global_transform.rs +++ b/crates/bevy_transform/src/components/global_transform.rs @@ -23,8 +23,6 @@ use { /// /// * To get the global transform of an entity, you should get its [`GlobalTransform`]. /// * For transform hierarchies to work correctly, you must have both a [`Transform`] and a [`GlobalTransform`]. -/// * ~You may use the [`TransformBundle`](crate::bundles::TransformBundle) to guarantee this.~ -/// [`TransformBundle`](crate::bundles::TransformBundle) is now deprecated. /// [`GlobalTransform`] is automatically inserted whenever [`Transform`] is inserted. /// /// ## [`Transform`] and [`GlobalTransform`] diff --git a/crates/bevy_transform/src/components/transform.rs b/crates/bevy_transform/src/components/transform.rs index c07a8c52e5228..718ca118de4e5 100644 --- a/crates/bevy_transform/src/components/transform.rs +++ b/crates/bevy_transform/src/components/transform.rs @@ -14,8 +14,6 @@ use {bevy_ecs::reflect::ReflectComponent, bevy_reflect::prelude::*}; /// * To place or move an entity, you should set its [`Transform`]. /// * To get the global transform of an entity, you should get its [`GlobalTransform`]. /// * To be displayed, an entity must have both a [`Transform`] and a [`GlobalTransform`]. -/// * ~You may use the [`TransformBundle`](crate::bundles::TransformBundle) to guarantee this.~ -/// [`TransformBundle`](crate::bundles::TransformBundle) is now deprecated. /// [`GlobalTransform`] is automatically inserted whenever [`Transform`] is inserted. /// /// ## [`Transform`] and [`GlobalTransform`] diff --git a/crates/bevy_transform/src/lib.rs b/crates/bevy_transform/src/lib.rs index b8e08c2c1bacd..9a6538ed53d9f 100644 --- a/crates/bevy_transform/src/lib.rs +++ b/crates/bevy_transform/src/lib.rs @@ -17,10 +17,6 @@ pub mod commands; /// The basic components of the transform crate pub mod components; -/// Transform related bundles -#[cfg(feature = "bevy-support")] -pub mod bundles; - /// Transform related traits pub mod traits; @@ -40,7 +36,6 @@ pub mod systems; /// /// This includes the most common types in this crate, re-exported for your convenience. #[doc(hidden)] -#[expect(deprecated)] pub mod prelude { #[doc(hidden)] pub use crate::components::*; @@ -48,7 +43,6 @@ pub mod prelude { #[cfg(feature = "bevy-support")] #[doc(hidden)] pub use crate::{ - bundles::TransformBundle, commands::BuildChildrenTransformExt, helper::TransformHelper, plugins::{TransformPlugin, TransformSystem}, diff --git a/crates/bevy_ui/src/lib.rs b/crates/bevy_ui/src/lib.rs index 508f133b81cd9..d60e7f4febd60 100644 --- a/crates/bevy_ui/src/lib.rs +++ b/crates/bevy_ui/src/lib.rs @@ -11,7 +11,6 @@ //! This UI is laid out with the Flexbox and CSS Grid layout models (see ) pub mod measurement; -pub mod node_bundles; pub mod ui_material; pub mod update; pub mod widget; @@ -49,16 +48,12 @@ pub mod prelude { #[doc(hidden)] #[cfg(feature = "bevy_ui_debug")] pub use crate::render::UiDebugOptions; - #[allow(deprecated)] - #[doc(hidden)] - pub use crate::widget::TextBundle; #[doc(hidden)] pub use crate::widget::{Text, TextUiReader, TextUiWriter}; #[doc(hidden)] pub use { crate::{ geometry::*, - node_bundles::*, ui_material::*, ui_node::*, widget::{Button, ImageNode, Label}, diff --git a/crates/bevy_ui/src/node_bundles.rs b/crates/bevy_ui/src/node_bundles.rs deleted file mode 100644 index 4d318c78c46fa..0000000000000 --- a/crates/bevy_ui/src/node_bundles.rs +++ /dev/null @@ -1,227 +0,0 @@ -//! This module contains basic node bundles used to build UIs -#![expect(deprecated)] - -use crate::{ - widget::{Button, ImageNodeSize}, - BackgroundColor, BorderColor, BorderRadius, ComputedNode, ContentSize, FocusPolicy, ImageNode, - Interaction, MaterialNode, Node, ScrollPosition, UiMaterial, ZIndex, -}; -use bevy_ecs::bundle::Bundle; -use bevy_render::view::{InheritedVisibility, ViewVisibility, Visibility}; -use bevy_transform::prelude::{GlobalTransform, Transform}; - -/// The basic UI node. -/// -/// Contains the [`Node`] component and other components required to make a container. -/// -/// See [`node_bundles`](crate::node_bundles) for more specialized bundles like [`ImageBundle`]. -#[derive(Bundle, Clone, Debug, Default)] -#[deprecated( - since = "0.15.0", - note = "Use the `Node` component instead. Inserting `Node` will also insert the other components required automatically." -)] -pub struct NodeBundle { - /// Controls the layout (size and position) of the node and its children - /// This also affect how the node is drawn/painted. - pub node: Node, - /// Describes the logical size of the node - pub computed_node: ComputedNode, - /// The background color, which serves as a "fill" for this node - pub background_color: BackgroundColor, - /// The color of the Node's border - pub border_color: BorderColor, - /// The border radius of the node - pub border_radius: BorderRadius, - /// Whether this node should block interaction with lower nodes - pub focus_policy: FocusPolicy, - /// The scroll position of the node, - pub scroll_position: ScrollPosition, - /// The transform of the node - /// - /// This component is automatically managed by the UI layout system. - /// To alter the position of the `NodeBundle`, use the properties of the [`Node`] component. - pub transform: Transform, - /// The global transform of the node - /// - /// This component is automatically updated by the [`TransformPropagate`](`bevy_transform::TransformSystem::TransformPropagate`) systems. - /// To alter the position of the `NodeBundle`, use the properties of the [`Node`] component. - pub global_transform: GlobalTransform, - /// Describes the visibility properties of the node - pub visibility: Visibility, - /// Inherited visibility of an entity. - pub inherited_visibility: InheritedVisibility, - /// Algorithmically-computed indication of whether an entity is visible and should be extracted for rendering - pub view_visibility: ViewVisibility, - /// Indicates the depth at which the node should appear in the UI - pub z_index: ZIndex, -} - -/// A UI node that is an image -#[derive(Bundle, Debug, Default)] -#[deprecated( - since = "0.15.0", - note = "Use the `ImageNode` component instead. Inserting `ImageNode` will also insert the other components required automatically." -)] -pub struct ImageBundle { - /// Describes the logical size of the node - pub computed_node: ComputedNode, - /// Controls the layout (size and position) of the node and its children - /// This also affects how the node is drawn/painted. - pub node: Node, - /// The calculated size based on the given image - pub calculated_size: ContentSize, - /// The image of the node. - /// - /// To tint the image, change the `color` field of this component. - pub image: ImageNode, - /// The color of the background that will fill the containing node. - pub background_color: BackgroundColor, - /// The border radius of the node - pub border_radius: BorderRadius, - /// The size of the image in pixels - /// - /// This component is set automatically - pub image_size: ImageNodeSize, - /// Whether this node should block interaction with lower nodes - pub focus_policy: FocusPolicy, - /// The transform of the node - /// - /// This component is automatically managed by the UI layout system. - /// To alter the position of the `ImageBundle`, use the properties of the [`Node`] component. - pub transform: Transform, - /// The global transform of the node - /// - /// This component is automatically updated by the [`TransformPropagate`](`bevy_transform::TransformSystem::TransformPropagate`) systems. - pub global_transform: GlobalTransform, - /// Describes the visibility properties of the node - pub visibility: Visibility, - /// Inherited visibility of an entity. - pub inherited_visibility: InheritedVisibility, - /// Algorithmically-computed indication of whether an entity is visible and should be extracted for rendering - pub view_visibility: ViewVisibility, - /// Indicates the depth at which the node should appear in the UI - pub z_index: ZIndex, -} - -/// A UI node that is a button -#[derive(Bundle, Clone, Debug)] -#[deprecated( - since = "0.15.0", - note = "Use the `Button` component instead. Inserting `Button` will also insert the other components required automatically." -)] -pub struct ButtonBundle { - /// Describes the logical size of the node - pub computed_node: ComputedNode, - /// Marker component that signals this node is a button - pub button: Button, - /// Controls the layout (size and position) of the node and its children - /// Also affect how the node is drawn/painted. - pub node: Node, - /// Describes whether and how the button has been interacted with by the input - pub interaction: Interaction, - /// Whether this node should block interaction with lower nodes - pub focus_policy: FocusPolicy, - /// The color of the Node's border - pub border_color: BorderColor, - /// The border radius of the node - pub border_radius: BorderRadius, - /// The image of the node - pub image: ImageNode, - /// The background color that will fill the containing node - pub background_color: BackgroundColor, - /// The transform of the node - /// - /// This component is automatically managed by the UI layout system. - /// To alter the position of the `ButtonBundle`, use the properties of the [`Node`] component. - pub transform: Transform, - /// The global transform of the node - /// - /// This component is automatically updated by the [`TransformPropagate`](`bevy_transform::TransformSystem::TransformPropagate`) systems. - pub global_transform: GlobalTransform, - /// Describes the visibility properties of the node - pub visibility: Visibility, - /// Inherited visibility of an entity. - pub inherited_visibility: InheritedVisibility, - /// Algorithmically-computed indication of whether an entity is visible and should be extracted for rendering - pub view_visibility: ViewVisibility, - /// Indicates the depth at which the node should appear in the UI - pub z_index: ZIndex, -} - -impl Default for ButtonBundle { - fn default() -> Self { - Self { - node: Default::default(), - computed_node: Default::default(), - button: Default::default(), - interaction: Default::default(), - focus_policy: FocusPolicy::Block, - border_color: Default::default(), - border_radius: Default::default(), - image: Default::default(), - background_color: Default::default(), - transform: Default::default(), - global_transform: Default::default(), - visibility: Default::default(), - inherited_visibility: Default::default(), - view_visibility: Default::default(), - z_index: Default::default(), - } - } -} - -/// A UI node that is rendered using a [`UiMaterial`] -/// -/// Adding a `BackgroundColor` component to an entity with this bundle will ignore the custom -/// material and use the background color instead. -#[derive(Bundle, Clone, Debug)] -#[deprecated( - since = "0.15.0", - note = "Use the `MaterialNode` component instead. Inserting `MaterialNode` will also insert the other components required automatically." -)] -pub struct MaterialNodeBundle { - /// Describes the logical size of the node - pub computed_node: ComputedNode, - /// Controls the layout (size and position) of the node and its children - /// Also affects how the node is drawn/painted. - pub node: Node, - /// The [`UiMaterial`] used to render the node. - pub material: MaterialNode, - /// Whether this node should block interaction with lower nodes - pub focus_policy: FocusPolicy, - /// The transform of the node - /// - /// This field is automatically managed by the UI layout system. - /// To alter the position of the `NodeBundle`, use the properties of the [`Node`] component. - pub transform: Transform, - /// The global transform of the node - /// - /// This field is automatically managed by the UI layout system. - /// To alter the position of the `NodeBundle`, use the properties of the [`Node`] component. - pub global_transform: GlobalTransform, - /// Describes the visibility properties of the node - pub visibility: Visibility, - /// Inherited visibility of an entity. - pub inherited_visibility: InheritedVisibility, - /// Algorithmically-computed indication of whether an entity is visible and should be extracted for rendering - pub view_visibility: ViewVisibility, - /// Indicates the depth at which the node should appear in the UI - pub z_index: ZIndex, -} - -impl Default for MaterialNodeBundle { - fn default() -> Self { - Self { - node: Default::default(), - computed_node: Default::default(), - material: Default::default(), - focus_policy: Default::default(), - transform: Default::default(), - global_transform: Default::default(), - visibility: Default::default(), - inherited_visibility: Default::default(), - view_visibility: Default::default(), - z_index: Default::default(), - } - } -} diff --git a/crates/bevy_ui/src/widget/text.rs b/crates/bevy_ui/src/widget/text.rs index 1b365c07e6aed..7db65cd58cbb0 100644 --- a/crates/bevy_ui/src/widget/text.rs +++ b/crates/bevy_ui/src/widget/text.rs @@ -48,18 +48,6 @@ impl Default for TextNodeFlags { } } -/// [`TextBundle`] was removed in favor of required components. -/// The core component is now [`Text`] which can contain a single text segment. -/// Indexed access to segments can be done with the new [`TextUiReader`] and [`TextUiWriter`] system params. -/// Additional segments can be added through children with [`TextSpan`](bevy_text::TextSpan). -/// Text configuration can be done with [`TextLayout`], [`TextFont`] and [`TextColor`], -/// while node-related configuration uses [`TextNodeFlags`] component. -#[deprecated( - since = "0.15.0", - note = "TextBundle has been migrated to required components. Follow the documentation for more information." -)] -pub struct TextBundle {} - /// The top-level UI text component. /// /// Adding [`Text`] to an entity will pull in required components for setting up a UI text node. diff --git a/errors/B0004.md b/errors/B0004.md index 9344d47b633ad..190898ff17970 100644 --- a/errors/B0004.md +++ b/errors/B0004.md @@ -34,7 +34,7 @@ fn setup_cube( // cube parent.spawn(( Mesh3d(meshes.add(Cuboid::default())), - MeshMaterial3d(materials.add(Color::rgb(0.8, 0.7, 0.6))), + MeshMaterial3d(materials.add(Color::srgb(0.8, 0.7, 0.6))), Transform::from_xyz(0.0, 0.5, 0.0), )); }); @@ -77,7 +77,7 @@ fn setup_cube( // cube parent.spawn(( Mesh3d(meshes.add(Cuboid::default())), - MeshMaterial3d(materials.add(Color::rgb(0.8, 0.7, 0.6))), + MeshMaterial3d(materials.add(Color::srgb(0.8, 0.7, 0.6))), Transform::from_xyz(0.0, 0.5, 0.0), )); }); diff --git a/examples/ecs/dynamic.rs b/examples/ecs/dynamic.rs index 553b6d9f3eade..b2138be85a7f7 100644 --- a/examples/ecs/dynamic.rs +++ b/examples/ecs/dynamic.rs @@ -148,9 +148,7 @@ fn main() { let mut builder = QueryBuilder::::new(&mut world); parse_query(rest, &mut builder, &component_names); let mut query = builder.build(); - query.iter_mut(&mut world).for_each(|filtered_entity| { - #[allow(deprecated)] let terms = filtered_entity .access() .component_reads_and_writes() From 49aae890494f276237394faa8e5788050bdee4f8 Mon Sep 17 00:00:00 2001 From: ickshonpe Date: Sun, 5 Jan 2025 20:34:11 +0000 Subject: [PATCH 131/272] unmut extracted view queries (#17142) # Objective Noticed a lot of the extracted view queries are unnecessarily mutable. Fixed them. --- crates/bevy_gizmos/src/pipeline_2d.rs | 8 ++++---- crates/bevy_gizmos/src/pipeline_3d.rs | 8 ++++---- crates/bevy_sprite/src/render/mod.rs | 4 ++-- crates/bevy_ui/src/render/box_shadow.rs | 4 ++-- crates/bevy_ui/src/render/mod.rs | 4 ++-- crates/bevy_ui/src/render/ui_material_pipeline.rs | 4 ++-- crates/bevy_ui/src/render/ui_texture_slice_pipeline.rs | 4 ++-- 7 files changed, 18 insertions(+), 18 deletions(-) diff --git a/crates/bevy_gizmos/src/pipeline_2d.rs b/crates/bevy_gizmos/src/pipeline_2d.rs index 2a885a186642e..d2e17a680cac2 100644 --- a/crates/bevy_gizmos/src/pipeline_2d.rs +++ b/crates/bevy_gizmos/src/pipeline_2d.rs @@ -297,7 +297,7 @@ fn queue_line_gizmos_2d( line_gizmos: Query<(Entity, &MainEntity, &GizmoMeshConfig)>, line_gizmo_assets: Res>, mut transparent_render_phases: ResMut>, - mut views: Query<(Entity, &ExtractedView, &Msaa, Option<&RenderLayers>)>, + views: Query<(Entity, &ExtractedView, &Msaa, Option<&RenderLayers>)>, ) { let draw_function = draw_functions.read().get_id::().unwrap(); let draw_function_strip = draw_functions @@ -305,7 +305,7 @@ fn queue_line_gizmos_2d( .get_id::() .unwrap(); - for (view_entity, view, msaa, render_layers) in &mut views { + for (view_entity, view, msaa, render_layers) in &views { let Some(transparent_phase) = transparent_render_phases.get_mut(&view_entity) else { continue; }; @@ -375,14 +375,14 @@ fn queue_line_joint_gizmos_2d( line_gizmos: Query<(Entity, &MainEntity, &GizmoMeshConfig)>, line_gizmo_assets: Res>, mut transparent_render_phases: ResMut>, - mut views: Query<(Entity, &ExtractedView, &Msaa, Option<&RenderLayers>)>, + views: Query<(Entity, &ExtractedView, &Msaa, Option<&RenderLayers>)>, ) { let draw_function = draw_functions .read() .get_id::() .unwrap(); - for (view_entity, view, msaa, render_layers) in &mut views { + for (view_entity, view, msaa, render_layers) in &views { let Some(transparent_phase) = transparent_render_phases.get_mut(&view_entity) else { continue; }; diff --git a/crates/bevy_gizmos/src/pipeline_3d.rs b/crates/bevy_gizmos/src/pipeline_3d.rs index 025cc4c7c033b..49d00b3343cb8 100644 --- a/crates/bevy_gizmos/src/pipeline_3d.rs +++ b/crates/bevy_gizmos/src/pipeline_3d.rs @@ -292,7 +292,7 @@ fn queue_line_gizmos_3d( line_gizmos: Query<(Entity, &MainEntity, &GizmoMeshConfig)>, line_gizmo_assets: Res>, mut transparent_render_phases: ResMut>, - mut views: Query<( + views: Query<( Entity, &ExtractedView, &Msaa, @@ -317,7 +317,7 @@ fn queue_line_gizmos_3d( msaa, render_layers, (normal_prepass, depth_prepass, motion_vector_prepass, deferred_prepass), - ) in &mut views + ) in &views { let Some(transparent_phase) = transparent_render_phases.get_mut(&view_entity) else { continue; @@ -407,7 +407,7 @@ fn queue_line_joint_gizmos_3d( line_gizmos: Query<(Entity, &MainEntity, &GizmoMeshConfig)>, line_gizmo_assets: Res>, mut transparent_render_phases: ResMut>, - mut views: Query<( + views: Query<( Entity, &ExtractedView, &Msaa, @@ -431,7 +431,7 @@ fn queue_line_joint_gizmos_3d( msaa, render_layers, (normal_prepass, depth_prepass, motion_vector_prepass, deferred_prepass), - ) in &mut views + ) in &views { let Some(transparent_phase) = transparent_render_phases.get_mut(&view_entity) else { continue; diff --git a/crates/bevy_sprite/src/render/mod.rs b/crates/bevy_sprite/src/render/mod.rs index 3adbfc9417dde..1c91bdd603062 100644 --- a/crates/bevy_sprite/src/render/mod.rs +++ b/crates/bevy_sprite/src/render/mod.rs @@ -503,7 +503,7 @@ pub fn queue_sprites( pipeline_cache: Res, extracted_sprites: Res, mut transparent_render_phases: ResMut>, - mut views: Query<( + views: Query<( Entity, &RenderVisibleEntities, &ExtractedView, @@ -514,7 +514,7 @@ pub fn queue_sprites( ) { let draw_sprite_function = draw_functions.read().id::(); - for (view_entity, visible_entities, view, msaa, tonemapping, dither) in &mut views { + for (view_entity, visible_entities, view, msaa, tonemapping, dither) in &views { let Some(transparent_phase) = transparent_render_phases.get_mut(&view_entity) else { continue; }; diff --git a/crates/bevy_ui/src/render/box_shadow.rs b/crates/bevy_ui/src/render/box_shadow.rs index 40363bf386c48..4521f1854677a 100644 --- a/crates/bevy_ui/src/render/box_shadow.rs +++ b/crates/bevy_ui/src/render/box_shadow.rs @@ -341,13 +341,13 @@ pub fn queue_shadows( box_shadow_pipeline: Res, mut pipelines: ResMut>, mut transparent_render_phases: ResMut>, - mut views: Query<(Entity, &ExtractedView, Option<&BoxShadowSamples>)>, + views: Query<(Entity, &ExtractedView, Option<&BoxShadowSamples>)>, pipeline_cache: Res, draw_functions: Res>, ) { let draw_function = draw_functions.read().id::(); for (entity, extracted_shadow) in extracted_box_shadows.box_shadows.iter() { - let Ok((view_entity, view, shadow_samples)) = views.get_mut(extracted_shadow.camera_entity) + let Ok((view_entity, view, shadow_samples)) = views.get(extracted_shadow.camera_entity) else { continue; }; diff --git a/crates/bevy_ui/src/render/mod.rs b/crates/bevy_ui/src/render/mod.rs index 54795e885f629..e1528b02d23f0 100644 --- a/crates/bevy_ui/src/render/mod.rs +++ b/crates/bevy_ui/src/render/mod.rs @@ -796,13 +796,13 @@ pub fn queue_uinodes( ui_pipeline: Res, mut pipelines: ResMut>, mut transparent_render_phases: ResMut>, - mut views: Query<(Entity, &ExtractedView, Option<&UiAntiAlias>)>, + views: Query<(Entity, &ExtractedView, Option<&UiAntiAlias>)>, pipeline_cache: Res, draw_functions: Res>, ) { let draw_function = draw_functions.read().id::(); for (entity, extracted_uinode) in extracted_uinodes.uinodes.iter() { - let Ok((view_entity, view, ui_anti_alias)) = views.get_mut(extracted_uinode.camera_entity) + let Ok((view_entity, view, ui_anti_alias)) = views.get(extracted_uinode.camera_entity) else { continue; }; diff --git a/crates/bevy_ui/src/render/ui_material_pipeline.rs b/crates/bevy_ui/src/render/ui_material_pipeline.rs index 481dce07d37f1..831cc71cd4d0b 100644 --- a/crates/bevy_ui/src/render/ui_material_pipeline.rs +++ b/crates/bevy_ui/src/render/ui_material_pipeline.rs @@ -614,7 +614,7 @@ pub fn queue_ui_material_nodes( pipeline_cache: Res, render_materials: Res>>, mut transparent_render_phases: ResMut>, - mut views: Query<&ExtractedView>, + views: Query<&ExtractedView>, ) where M::Data: PartialEq + Eq + Hash + Clone, { @@ -624,7 +624,7 @@ pub fn queue_ui_material_nodes( let Some(material) = render_materials.get(extracted_uinode.material) else { continue; }; - let Ok(view) = views.get_mut(extracted_uinode.camera_entity) else { + let Ok(view) = views.get(extracted_uinode.camera_entity) else { continue; }; let Some(transparent_phase) = diff --git a/crates/bevy_ui/src/render/ui_texture_slice_pipeline.rs b/crates/bevy_ui/src/render/ui_texture_slice_pipeline.rs index 657e4a95aef9c..f4fc6d77e9a0b 100644 --- a/crates/bevy_ui/src/render/ui_texture_slice_pipeline.rs +++ b/crates/bevy_ui/src/render/ui_texture_slice_pipeline.rs @@ -344,13 +344,13 @@ pub fn queue_ui_slices( ui_slicer_pipeline: Res, mut pipelines: ResMut>, mut transparent_render_phases: ResMut>, - mut views: Query<(Entity, &ExtractedView)>, + views: Query<(Entity, &ExtractedView)>, pipeline_cache: Res, draw_functions: Res>, ) { let draw_function = draw_functions.read().id::(); for (entity, extracted_slicer) in extracted_ui_slicers.slices.iter() { - let Ok((view_entity, view)) = views.get_mut(extracted_slicer.camera_entity) else { + let Ok((view_entity, view)) = views.get(extracted_slicer.camera_entity) else { continue; }; From 3c829d7f6869813c0c80d427c66e298a455aa93f Mon Sep 17 00:00:00 2001 From: Zachary Harrold Date: Mon, 6 Jan 2025 07:36:08 +1100 Subject: [PATCH 132/272] Remove everything except `Instant` from `bevy_utils::time` (#17158) # Objective - Contributes to #11478 - Contributes to #16877 ## Solution - Removed everything except `Instant` from `bevy_utils::time` ## Testing - CI --- ## Migration Guide If you relied on any of the following from `bevy_utils::time`: - `Duration` - `TryFromFloatSecsError` Import these directly from `core::time` regardless of platform target (WASM, mobile, etc.) If you relied on any of the following from `bevy_utils::time`: - `SystemTime` - `SystemTimeError` Instead import these directly from either `std::time` or `web_time` as appropriate for your target platform. ## Notes `Duration` and `TryFromFloatSecsError` are both re-exports from `core::time` regardless of whether they are used from `web_time` or `std::time`, so there is no value gained from re-exporting them from `bevy_utils::time` as well. As for `SystemTime` and `SystemTimeError`, no Bevy internal crates or examples rely on these types. Since Bevy doesn't have a `Time` resource for interacting with wall-time (and likely shouldn't need one), I think removing these from `bevy_utils` entirely and waiting for a use-case to justify inclusion is a reasonable path forward. --- crates/bevy_animation/src/transition.rs | 2 +- crates/bevy_app/src/schedule_runner.rs | 2 +- .../bevy_asset/src/io/embedded/embedded_watcher.rs | 3 ++- crates/bevy_asset/src/io/file/file_watcher.rs | 3 ++- crates/bevy_asset/src/io/source.rs | 4 ++-- crates/bevy_asset/src/lib.rs | 3 ++- crates/bevy_diagnostic/src/diagnostic.rs | 7 +++++-- .../bevy_diagnostic/src/log_diagnostics_plugin.rs | 6 ++---- crates/bevy_gilrs/src/rumble.rs | 3 ++- crates/bevy_input/src/gamepad.rs | 6 +++--- crates/bevy_picking/src/events.rs | 4 ++-- crates/bevy_reflect/src/impls/std.rs | 9 ++++++--- crates/bevy_time/src/common_conditions.rs | 14 +++++++------- crates/bevy_time/src/fixed.rs | 2 +- crates/bevy_time/src/lib.rs | 5 +++-- crates/bevy_time/src/real.rs | 3 ++- crates/bevy_time/src/stopwatch.rs | 2 +- crates/bevy_time/src/time.rs | 2 +- crates/bevy_time/src/timer.rs | 2 +- crates/bevy_time/src/virt.rs | 3 ++- crates/bevy_utils/src/lib.rs | 1 + crates/bevy_utils/src/time.rs | 10 ++-------- crates/bevy_winit/src/winit_config.rs | 2 +- examples/app/headless.rs | 3 ++- examples/app/plugin.rs | 3 ++- examples/audio/decodable.rs | 2 +- examples/ecs/ecs_guide.rs | 2 +- examples/input/gamepad_rumble.rs | 2 +- examples/scene/scene.rs | 3 ++- examples/stress_tests/bevymark.rs | 2 +- examples/ui/ui_scaling.rs | 3 ++- examples/window/low_power.rs | 2 +- 32 files changed, 65 insertions(+), 55 deletions(-) diff --git a/crates/bevy_animation/src/transition.rs b/crates/bevy_animation/src/transition.rs index 679c63bec3ffb..c94378208b3a7 100644 --- a/crates/bevy_animation/src/transition.rs +++ b/crates/bevy_animation/src/transition.rs @@ -10,7 +10,7 @@ use bevy_ecs::{ }; use bevy_reflect::{std_traits::ReflectDefault, Reflect}; use bevy_time::Time; -use bevy_utils::Duration; +use core::time::Duration; use crate::{graph::AnimationNodeIndex, ActiveAnimation, AnimationPlayer}; diff --git a/crates/bevy_app/src/schedule_runner.rs b/crates/bevy_app/src/schedule_runner.rs index df3f5843fdcf5..21ff6669ef710 100644 --- a/crates/bevy_app/src/schedule_runner.rs +++ b/crates/bevy_app/src/schedule_runner.rs @@ -3,7 +3,7 @@ use crate::{ plugin::Plugin, PluginsState, }; -use bevy_utils::Duration; +use core::time::Duration; #[cfg(any(target_arch = "wasm32", feature = "std"))] use bevy_utils::Instant; diff --git a/crates/bevy_asset/src/io/embedded/embedded_watcher.rs b/crates/bevy_asset/src/io/embedded/embedded_watcher.rs index cc97eb3cda83c..2e62fb0813ac4 100644 --- a/crates/bevy_asset/src/io/embedded/embedded_watcher.rs +++ b/crates/bevy_asset/src/io/embedded/embedded_watcher.rs @@ -4,7 +4,8 @@ use crate::io::{ AssetSourceEvent, AssetWatcher, }; use alloc::sync::Arc; -use bevy_utils::{tracing::warn, Duration, HashMap}; +use bevy_utils::{tracing::warn, HashMap}; +use core::time::Duration; use notify_debouncer_full::{notify::RecommendedWatcher, Debouncer, RecommendedCache}; use parking_lot::RwLock; use std::{ diff --git a/crates/bevy_asset/src/io/file/file_watcher.rs b/crates/bevy_asset/src/io/file/file_watcher.rs index 9aee5717814b3..e416b99971d82 100644 --- a/crates/bevy_asset/src/io/file/file_watcher.rs +++ b/crates/bevy_asset/src/io/file/file_watcher.rs @@ -2,7 +2,8 @@ use crate::{ io::{AssetSourceEvent, AssetWatcher}, path::normalize_path, }; -use bevy_utils::{tracing::error, Duration}; +use bevy_utils::tracing::error; +use core::time::Duration; use crossbeam_channel::Sender; use notify_debouncer_full::{ new_debouncer, diff --git a/crates/bevy_asset/src/io/source.rs b/crates/bevy_asset/src/io/source.rs index f1b4455179710..da98deaa125de 100644 --- a/crates/bevy_asset/src/io/source.rs +++ b/crates/bevy_asset/src/io/source.rs @@ -7,9 +7,9 @@ use atomicow::CowArc; use bevy_ecs::system::Resource; use bevy_utils::{ tracing::{error, warn}, - Duration, HashMap, + HashMap, }; -use core::{fmt::Display, hash::Hash}; +use core::{fmt::Display, hash::Hash, time::Duration}; use thiserror::Error; use super::{ErasedAssetReader, ErasedAssetWriter}; diff --git a/crates/bevy_asset/src/lib.rs b/crates/bevy_asset/src/lib.rs index 2568badb7e4a5..7a3a06c389d4e 100644 --- a/crates/bevy_asset/src/lib.rs +++ b/crates/bevy_asset/src/lib.rs @@ -641,7 +641,8 @@ mod tests { }; use bevy_log::LogPlugin; use bevy_reflect::TypePath; - use bevy_utils::{Duration, HashMap}; + use bevy_utils::HashMap; + use core::time::Duration; use serde::{Deserialize, Serialize}; use std::path::Path; use thiserror::Error; diff --git a/crates/bevy_diagnostic/src/diagnostic.rs b/crates/bevy_diagnostic/src/diagnostic.rs index 56c1788661501..1c641c88a837a 100644 --- a/crates/bevy_diagnostic/src/diagnostic.rs +++ b/crates/bevy_diagnostic/src/diagnostic.rs @@ -1,9 +1,12 @@ use alloc::{borrow::Cow, collections::VecDeque}; -use core::hash::{Hash, Hasher}; +use core::{ + hash::{Hash, Hasher}, + time::Duration, +}; use bevy_app::{App, SubApp}; use bevy_ecs::system::{Deferred, Res, Resource, SystemBuffer, SystemParam}; -use bevy_utils::{Duration, HashMap, Instant, PassHash}; +use bevy_utils::{HashMap, Instant, PassHash}; use const_fnv1a_hash::fnv1a_hash_str_64; use crate::DEFAULT_MAX_HISTORY_LENGTH; diff --git a/crates/bevy_diagnostic/src/log_diagnostics_plugin.rs b/crates/bevy_diagnostic/src/log_diagnostics_plugin.rs index d6e7a2e0b6a75..6e53bf0a31625 100644 --- a/crates/bevy_diagnostic/src/log_diagnostics_plugin.rs +++ b/crates/bevy_diagnostic/src/log_diagnostics_plugin.rs @@ -2,10 +2,8 @@ use super::{Diagnostic, DiagnosticPath, DiagnosticsStore}; use bevy_app::prelude::*; use bevy_ecs::prelude::*; use bevy_time::{Real, Time, Timer, TimerMode}; -use bevy_utils::{ - tracing::{debug, info}, - Duration, -}; +use bevy_utils::tracing::{debug, info}; +use core::time::Duration; /// An App Plugin that logs diagnostics to the console. /// diff --git a/crates/bevy_gilrs/src/rumble.rs b/crates/bevy_gilrs/src/rumble.rs index 62c6b0dc7d639..da1ce2e2bbfbc 100644 --- a/crates/bevy_gilrs/src/rumble.rs +++ b/crates/bevy_gilrs/src/rumble.rs @@ -8,8 +8,9 @@ use bevy_time::{Real, Time}; use bevy_utils::{ synccell::SyncCell, tracing::{debug, warn}, - Duration, HashMap, + HashMap, }; +use core::time::Duration; use gilrs::{ ff::{self, BaseEffect, BaseEffectType, Repeat, Replay}, GamepadId, diff --git a/crates/bevy_input/src/gamepad.rs b/crates/bevy_input/src/gamepad.rs index be122d73200ad..d51a087eee1fa 100644 --- a/crates/bevy_input/src/gamepad.rs +++ b/crates/bevy_input/src/gamepad.rs @@ -1,6 +1,6 @@ //! The gamepad input functionality. -use core::ops::RangeInclusive; +use core::{ops::RangeInclusive, time::Duration}; use crate::{Axis, ButtonInput, ButtonState}; use alloc::string::String; @@ -21,7 +21,7 @@ use bevy_math::Vec2; use bevy_reflect::{std_traits::ReflectDefault, Reflect}; #[cfg(all(feature = "serialize", feature = "bevy_reflect"))] use bevy_reflect::{ReflectDeserialize, ReflectSerialize}; -use bevy_utils::{Duration, HashMap}; +use bevy_utils::HashMap; use derive_more::derive::From; use log::{info, warn}; use thiserror::Error; @@ -1693,7 +1693,7 @@ impl GamepadRumbleIntensity { /// ``` /// # use bevy_input::gamepad::{Gamepad, GamepadRumbleRequest, GamepadRumbleIntensity}; /// # use bevy_ecs::prelude::{EventWriter, Res, Query, Entity, With}; -/// # use bevy_utils::Duration; +/// # use core::time::Duration; /// fn rumble_gamepad_system( /// mut rumble_requests: EventWriter, /// gamepads: Query>, diff --git a/crates/bevy_picking/src/events.rs b/crates/bevy_picking/src/events.rs index 9d8bda32bda72..c9792ccb8cb37 100644 --- a/crates/bevy_picking/src/events.rs +++ b/crates/bevy_picking/src/events.rs @@ -37,14 +37,14 @@ //! When received by an observer, these events will always be wrapped by the [`Pointer`] type, which contains //! general metadata about the pointer event. -use core::fmt::Debug; +use core::{fmt::Debug, time::Duration}; use bevy_ecs::{prelude::*, query::QueryData, system::SystemParam, traversal::Traversal}; use bevy_hierarchy::Parent; use bevy_math::Vec2; use bevy_reflect::prelude::*; use bevy_render::camera::NormalizedRenderTarget; -use bevy_utils::{tracing::debug, Duration, HashMap, Instant}; +use bevy_utils::{tracing::debug, HashMap, Instant}; use bevy_window::Window; use crate::{ diff --git a/crates/bevy_reflect/src/impls/std.rs b/crates/bevy_reflect/src/impls/std.rs index 0626a76f02d6b..fe59024ce8904 100644 --- a/crates/bevy_reflect/src/impls/std.rs +++ b/crates/bevy_reflect/src/impls/std.rs @@ -118,7 +118,7 @@ impl_reflect_opaque!(::core::ops::RangeTo()); impl_reflect_opaque!(::core::ops::RangeToInclusive()); impl_reflect_opaque!(::core::ops::RangeFull()); impl_reflect_opaque!(::core::ops::Bound()); -impl_reflect_opaque!(::bevy_utils::Duration( +impl_reflect_opaque!(::core::time::Duration( Debug, Hash, PartialEq, @@ -2430,8 +2430,11 @@ mod tests { TypeInfo, TypeRegistry, Typed, VariantInfo, VariantType, }; use alloc::{collections::BTreeMap, string::String, vec}; - use bevy_utils::{Duration, HashMap, Instant}; - use core::f32::consts::{PI, TAU}; + use bevy_utils::{HashMap, Instant}; + use core::{ + f32::consts::{PI, TAU}, + time::Duration, + }; use static_assertions::assert_impl_all; use std::path::Path; diff --git a/crates/bevy_time/src/common_conditions.rs b/crates/bevy_time/src/common_conditions.rs index e00acf8cffb8b..bb9e666319a08 100644 --- a/crates/bevy_time/src/common_conditions.rs +++ b/crates/bevy_time/src/common_conditions.rs @@ -1,6 +1,6 @@ use crate::{Real, Time, Timer, TimerMode, Virtual}; use bevy_ecs::system::Res; -use bevy_utils::Duration; +use core::time::Duration; /// Run condition that is active on a regular time interval, using [`Time`] to advance /// the timer. The timer ticks at the rate of [`Time::relative_speed`]. @@ -8,7 +8,7 @@ use bevy_utils::Duration; /// ```no_run /// # use bevy_app::{App, NoopPluginGroup as DefaultPlugins, PluginGroup, Update}; /// # use bevy_ecs::schedule::IntoSystemConfigs; -/// # use bevy_utils::Duration; +/// # use core::time::Duration; /// # use bevy_time::common_conditions::on_timer; /// fn main() { /// App::new() @@ -48,7 +48,7 @@ pub fn on_timer(duration: Duration) -> impl FnMut(Res { #[inline] fn update_component_access(state: &Self::State, access: &mut FilteredAccess) { <&A>::update_component_access(&state.asset_id, access); - assert!( - !access.access().has_resource_write(state.resource_id), - "AssetChanged<{ty}> requires read-only access to AssetChanges<{ty}>", - ty = ShortName::of::() - ); access.add_resource_read(state.resource_id); } diff --git a/crates/bevy_ecs/src/query/mod.rs b/crates/bevy_ecs/src/query/mod.rs index 720489d6d15bf..6104d0a543abb 100644 --- a/crates/bevy_ecs/src/query/mod.rs +++ b/crates/bevy_ecs/src/query/mod.rs @@ -805,9 +805,6 @@ mod tests { /// `QueryData` that performs read access on R to test that resource access is tracked struct ReadsRData; - /// `QueryData` that performs write access on R to test that resource access is tracked - struct WritesRData; - /// SAFETY: /// `update_component_access` adds resource read access for `R`. /// `update_archetype_component_access` does nothing, as this accesses no components. @@ -890,85 +887,6 @@ mod tests { /// SAFETY: access is read only unsafe impl ReadOnlyQueryData for ReadsRData {} - /// SAFETY: - /// `update_component_access` adds resource read access for `R`. - /// `update_archetype_component_access` does nothing, as this accesses no components. - unsafe impl WorldQuery for WritesRData { - type Item<'w> = (); - type Fetch<'w> = (); - type State = ComponentId; - - fn shrink<'wlong: 'wshort, 'wshort>(_item: Self::Item<'wlong>) -> Self::Item<'wshort> {} - - fn shrink_fetch<'wlong: 'wshort, 'wshort>(_: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> {} - - unsafe fn init_fetch<'w>( - _world: UnsafeWorldCell<'w>, - _state: &Self::State, - _last_run: Tick, - _this_run: Tick, - ) -> Self::Fetch<'w> { - } - - const IS_DENSE: bool = true; - - #[inline] - unsafe fn set_archetype<'w>( - _fetch: &mut Self::Fetch<'w>, - _state: &Self::State, - _archetype: &'w Archetype, - _table: &Table, - ) { - } - - #[inline] - unsafe fn set_table<'w>( - _fetch: &mut Self::Fetch<'w>, - _state: &Self::State, - _table: &'w Table, - ) { - } - - #[inline(always)] - unsafe fn fetch<'w>( - _fetch: &mut Self::Fetch<'w>, - _entity: Entity, - _table_row: TableRow, - ) -> Self::Item<'w> { - } - - fn update_component_access( - &component_id: &Self::State, - access: &mut FilteredAccess, - ) { - assert!( - !access.access().has_resource_read(component_id), - "WritesRData conflicts with a previous access in this query. Shared access cannot coincide with exclusive access.", - ); - access.add_resource_write(component_id); - } - - fn init_state(world: &mut World) -> Self::State { - world.components.register_resource::() - } - - fn get_state(components: &Components) -> Option { - components.resource_id::() - } - - fn matches_component_set( - _state: &Self::State, - _set_contains_id: &impl Fn(ComponentId) -> bool, - ) -> bool { - true - } - } - - /// SAFETY: `Self` is the same as `Self::ReadOnly` - unsafe impl QueryData for WritesRData { - type ReadOnly = ReadsRData; - } - #[test] fn read_res_read_res_no_conflict() { fn system(_q1: Query>, _q2: Query>) {} @@ -976,38 +894,13 @@ mod tests { } #[test] - #[should_panic] - fn read_res_write_res_conflict() { - fn system(_q1: Query>, _q2: Query>) {} - assert_is_system(system); - } - - #[test] - #[should_panic] - fn write_res_read_res_conflict() { - fn system(_q1: Query>, _q2: Query>) {} - assert_is_system(system); - } - - #[test] - #[should_panic] - fn write_res_write_res_conflict() { - fn system(_q1: Query>, _q2: Query>) {} - assert_is_system(system); - } - - #[test] - fn read_write_res_sets_archetype_component_access() { + fn read_res_sets_archetype_component_access() { let mut world = World::new(); fn read_query(_q: Query>) {} let mut read_query = IntoSystem::into_system(read_query); read_query.initialize(&mut world); - fn write_query(_q: Query>) {} - let mut write_query = IntoSystem::into_system(write_query); - write_query.initialize(&mut world); - fn read_res(_r: Res) {} let mut read_res = IntoSystem::into_system(read_res); read_res.initialize(&mut world); @@ -1019,14 +912,8 @@ mod tests { assert!(read_query .archetype_component_access() .is_compatible(read_res.archetype_component_access())); - assert!(!write_query - .archetype_component_access() - .is_compatible(read_res.archetype_component_access())); assert!(!read_query .archetype_component_access() .is_compatible(write_res.archetype_component_access())); - assert!(!write_query - .archetype_component_access() - .is_compatible(write_res.archetype_component_access())); } } diff --git a/crates/bevy_ecs/src/query/state.rs b/crates/bevy_ecs/src/query/state.rs index 8eceb2ef9ce94..29f0829f44549 100644 --- a/crates/bevy_ecs/src/query/state.rs +++ b/crates/bevy_ecs/src/query/state.rs @@ -199,13 +199,10 @@ impl QueryState { } } - if state.component_access.access().has_write_all_resources() { - access.write_all_resources(); - } else { - for component_id in state.component_access.access().resource_writes() { - access.add_resource_write(world.initialize_resource_internal(component_id).id()); - } - } + debug_assert!( + !state.component_access.access().has_any_resource_write(), + "Mutable resource access in queries is not allowed" + ); state } diff --git a/crates/bevy_ecs/src/query/world_query.rs b/crates/bevy_ecs/src/query/world_query.rs index c805e8dec7213..422a1c8cd0d98 100644 --- a/crates/bevy_ecs/src/query/world_query.rs +++ b/crates/bevy_ecs/src/query/world_query.rs @@ -14,7 +14,7 @@ use variadics_please::all_tuples; /// # Safety /// /// Implementor must ensure that -/// [`update_component_access`], [`matches_component_set`], and [`fetch`] +/// [`update_component_access`], [`matches_component_set`], [`fetch`] and [`init_fetch`] /// obey the following: /// /// - For each component mutably accessed by [`fetch`], [`update_component_access`] should add write access unless read or write access has already been added, in which case it should panic. @@ -26,8 +26,8 @@ use variadics_please::all_tuples; /// - [`matches_component_set`] must be a disjunction of the element's implementations /// - [`update_component_access`] must replace the filters with a disjunction of filters /// - Each filter in that disjunction must be a conjunction of the corresponding element's filter with the previous `access` -/// - For each resource mutably accessed by [`init_fetch`], [`update_component_access`] should add write access unless read or write access has already been added, in which case it should panic. -/// - For each resource readonly accessed by [`init_fetch`], [`update_component_access`] should add read access unless write access has already been added, in which case it should panic. +/// - For each resource readonly accessed by [`init_fetch`], [`update_component_access`] should add read access. +/// - Mutable resource access is not allowed. /// /// When implementing [`update_component_access`], note that `add_read` and `add_write` both also add a `With` filter, whereas `extend_access` does not change the filters. /// @@ -60,11 +60,14 @@ pub unsafe trait WorldQuery { fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort>; /// Creates a new instance of this fetch. + /// Readonly accesses resources registered in [`WorldQuery::update_component_access`]. /// /// # Safety /// /// - `state` must have been initialized (via [`WorldQuery::init_state`]) using the same `world` passed /// in to this function. + /// - `world` must have the **right** to access any access registered in `update_component_access`. + /// - There must not be simultaneous resource access conflicting with readonly resource access registered in [`WorldQuery::update_component_access`]. unsafe fn init_fetch<'w>( world: UnsafeWorldCell<'w>, state: &Self::State, @@ -114,11 +117,13 @@ pub unsafe trait WorldQuery { /// or for the given `entity` in the current [`Archetype`]. This must always be called after /// [`WorldQuery::set_table`] with a `table_row` in the range of the current [`Table`] or after /// [`WorldQuery::set_archetype`] with a `entity` in the current archetype. + /// Accesses components registered in [`WorldQuery::update_component_access`]. /// /// # Safety /// - /// Must always be called _after_ [`WorldQuery::set_table`] or [`WorldQuery::set_archetype`]. `entity` and - /// `table_row` must be in the range of the current table and archetype. + /// - Must always be called _after_ [`WorldQuery::set_table`] or [`WorldQuery::set_archetype`]. `entity` and + /// `table_row` must be in the range of the current table and archetype. + /// - There must not be simultaneous conflicting component access registered in `update_component_access`. unsafe fn fetch<'w>( fetch: &mut Self::Fetch<'w>, entity: Entity, From f61de1101c529bd9d9dfe300c7a30f8ff189ec65 Mon Sep 17 00:00:00 2001 From: MichiRecRoom <1008889+LikeLakers2@users.noreply.github.com> Date: Mon, 6 Jan 2025 14:09:08 -0500 Subject: [PATCH 145/272] bevy_audio: Apply `#![deny(clippy::allow_attributes, clippy::allow_attributes_without_reason)]` (#17119) # Objective - https://github.com/bevyengine/bevy/issues/17111 ## Solution Set the `clippy::allow_attributes` and `clippy::allow_attributes_without_reason` lints to `deny`, and bring `bevy_audio` in line with the new restrictions. No code changes have been made - except if a lint that was previously `allow(...)`'d could be removed via small code changes. For example, `unused_variables` can be handled by adding a `_` to the beginning of a field's name. ## Testing `cargo clippy` and `cargo test --package bevy_audio` were run, and no errors were encountered. --- crates/bevy_audio/src/lib.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/crates/bevy_audio/src/lib.rs b/crates/bevy_audio/src/lib.rs index babae2f8a9be9..c5b777c442be1 100644 --- a/crates/bevy_audio/src/lib.rs +++ b/crates/bevy_audio/src/lib.rs @@ -1,4 +1,9 @@ #![forbid(unsafe_code)] +#![deny( + clippy::allow_attributes, + clippy::allow_attributes_without_reason, + reason = "See #17111; To be removed once all crates are in-line with these attributes" +)] #![cfg_attr(docsrs, feature(doc_auto_cfg))] #![doc( html_logo_url = "https://bevyengine.org/assets/icon.png", From d220eccbb14f8d65b54a1dfbc5b09205d43fcc21 Mon Sep 17 00:00:00 2001 From: ickshonpe Date: Mon, 6 Jan 2025 19:11:04 +0000 Subject: [PATCH 146/272] More DefaultUiCamera fixes (#17120) # Objective Found more excessive `DefaultUiCamera` queries outside of extraction. The default UI camera lookup only needs to be done once. Do it first, not per node. --------- Co-authored-by: MichiRecRoom <1008889+LikeLakers2@users.noreply.github.com> --- crates/bevy_ui/src/focus.rs | 4 +++- crates/bevy_ui/src/picking_backend.rs | 6 ++++-- crates/bevy_ui/src/widget/text.rs | 4 +++- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/crates/bevy_ui/src/focus.rs b/crates/bevy_ui/src/focus.rs index f5175c61a8594..577ac82c6bac2 100644 --- a/crates/bevy_ui/src/focus.rs +++ b/crates/bevy_ui/src/focus.rs @@ -213,6 +213,8 @@ pub fn ui_focus_system( }) .collect(); + let default_camera_entity = default_ui_camera.get(); + // prepare an iterator that contains all the nodes that have the cursor in their rect, // from the top node to the bottom one. this will also reset the interaction to `None` // for all nodes encountered that are no longer hovered. @@ -239,7 +241,7 @@ pub fn ui_focus_system( let camera_entity = node .target_camera .map(TargetCamera::entity) - .or(default_ui_camera.get())?; + .or(default_camera_entity)?; let node_rect = Rect::from_center_size( node.global_transform.translation().truncate(), diff --git a/crates/bevy_ui/src/picking_backend.rs b/crates/bevy_ui/src/picking_backend.rs index 279acdb880241..f1a7cdd4c84be 100644 --- a/crates/bevy_ui/src/picking_backend.rs +++ b/crates/bevy_ui/src/picking_backend.rs @@ -72,6 +72,8 @@ pub fn ui_picking( // For each camera, the pointer and its position let mut pointer_pos_by_camera = HashMap::>::default(); + let default_camera_entity = default_ui_camera.get(); + for (pointer_id, pointer_location) in pointers.iter().filter_map(|(pointer, pointer_location)| { Some(*pointer).zip(pointer_location.location().cloned()) @@ -133,7 +135,7 @@ pub fn ui_picking( let Some(camera_entity) = node .target_camera .map(TargetCamera::entity) - .or(default_ui_camera.get()) + .or(default_camera_entity) else { continue; }; @@ -189,7 +191,7 @@ pub fn ui_picking( let Some(camera_entity) = node .target_camera .map(TargetCamera::entity) - .or(default_ui_camera.get()) + .or(default_camera_entity) else { continue; }; diff --git a/crates/bevy_ui/src/widget/text.rs b/crates/bevy_ui/src/widget/text.rs index 6b0ee8c3cb75e..62cc3f649db82 100644 --- a/crates/bevy_ui/src/widget/text.rs +++ b/crates/bevy_ui/src/widget/text.rs @@ -268,10 +268,12 @@ pub fn measure_text_system( ) { scale_factors_buffer.clear(); + let default_camera_entity = default_ui_camera.get(); + for (entity, block, content_size, text_flags, computed, maybe_camera) in &mut text_query { let Some(camera_entity) = maybe_camera .map(TargetCamera::entity) - .or(default_ui_camera.get()) + .or(default_camera_entity) else { continue; }; From 1162e03cec916c77f4e194d96bb6cc768c78f994 Mon Sep 17 00:00:00 2001 From: Tim Overbeek <158390905+Bleachfuel@users.noreply.github.com> Date: Mon, 6 Jan 2025 20:15:19 +0100 Subject: [PATCH 147/272] Make the `get` function on `InstanceInputUniformBuffer` less error prone (#17131) # Objective the `get` function on [`InstanceInputUniformBuffer`] seems very error-prone. This PR hopes to fix this. ## Solution Do a few checks to ensure the index is in bounds and that the `BDI` is not removed. Return `Option` instead of `BDI`. ## Testing - Did you test these changes? If so, how? added a test to verify that the instance buffer works correctly ## Future Work Performance decreases when using .binary_search(). However this is likely due to the fact that [`InstanceInputUniformBuffer::get`] for now is never used, and only get_unchecked. ## Migration Guide `InstanceInputUniformBuffer::get` now returns `Option` instead of `BDI` to reduce panics. If you require the old functionality of `InstanceInputUniformBuffer::get` consider using `InstanceInputUniformBuffer::get_unchecked`. --------- Co-authored-by: Tim Overbeek --- crates/bevy_pbr/src/render/mesh.rs | 3 +- .../src/batching/gpu_preprocessing.rs | 40 ++++++++++++++++++- 2 files changed, 41 insertions(+), 2 deletions(-) diff --git a/crates/bevy_pbr/src/render/mesh.rs b/crates/bevy_pbr/src/render/mesh.rs index 17c4188b5f2ce..de29bd2127933 100644 --- a/crates/bevy_pbr/src/render/mesh.rs +++ b/crates/bevy_pbr/src/render/mesh.rs @@ -957,7 +957,8 @@ impl RenderMeshInstanceGpuBuilder { // Save the old mesh input uniform. The mesh preprocessing // shader will need it to compute motion vectors. - let previous_mesh_input_uniform = current_input_buffer.get(current_uniform_index); + let previous_mesh_input_uniform = + current_input_buffer.get_unchecked(current_uniform_index); let previous_input_index = previous_input_buffer.add(previous_mesh_input_uniform); mesh_input_uniform.previous_input_index = previous_input_index; diff --git a/crates/bevy_render/src/batching/gpu_preprocessing.rs b/crates/bevy_render/src/batching/gpu_preprocessing.rs index 659630b4d77fc..9f42d622fdf1a 100644 --- a/crates/bevy_render/src/batching/gpu_preprocessing.rs +++ b/crates/bevy_render/src/batching/gpu_preprocessing.rs @@ -217,11 +217,31 @@ where } /// Returns the piece of buffered data at the given index. - pub fn get(&self, uniform_index: u32) -> BDI { + /// + /// Returns [`None`] if the index is out of bounds or the data is removed. + pub fn get(&self, uniform_index: u32) -> Option { + if (uniform_index as usize) >= self.buffer.len() + || self.free_uniform_indices.contains(&uniform_index) + { + None + } else { + Some(self.get_unchecked(uniform_index)) + } + } + + /// Returns the piece of buffered data at the given index. + /// Can return data that has previously been removed. + /// + /// # Panics + /// if `uniform_index` is not in bounds of [`Self::buffer`]. + pub fn get_unchecked(&self, uniform_index: u32) -> BDI { self.buffer.values()[uniform_index as usize] } /// Stores a piece of buffered data at the given index. + /// + /// # Panics + /// if `uniform_index` is not in bounds of [`Self::buffer`]. pub fn set(&mut self, uniform_index: u32, element: BDI) { self.buffer.values_mut()[uniform_index as usize] = element; } @@ -990,3 +1010,21 @@ pub fn write_indirect_parameters_buffer( .write_buffer(&render_device, &render_queue); indirect_parameters_buffer.buffer.clear(); } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn instance_buffer_correct_behavior() { + let mut instance_buffer = InstanceInputUniformBuffer::new(); + + let index = instance_buffer.add(2); + instance_buffer.remove(index); + assert_eq!(instance_buffer.get_unchecked(index), 2); + assert_eq!(instance_buffer.get(index), None); + + instance_buffer.add(5); + assert_eq!(instance_buffer.buffer().len(), 1); + } +} From 94b9fe384fd6e62758decdf32e1a85e386cdd329 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Mockers?= Date: Mon, 6 Jan 2025 20:19:56 +0100 Subject: [PATCH 148/272] can hide status bar on iOS (#17179) # Objective - I want to hide the clock and the battery indicator on iOS ## Solution - Add the platform specific property `prefers_status_bar_hidden` on Window creation, and map it to `with_prefers_status_bar_hidden` in winit. ## Testing - Tested on iOS --- crates/bevy_window/src/window.rs | 11 +++++++++++ crates/bevy_winit/src/winit_windows.rs | 2 ++ examples/mobile/src/lib.rs | 2 ++ 3 files changed, 15 insertions(+) diff --git a/crates/bevy_window/src/window.rs b/crates/bevy_window/src/window.rs index b5418927e631e..ac442f05f5559 100644 --- a/crates/bevy_window/src/window.rs +++ b/crates/bevy_window/src/window.rs @@ -412,6 +412,16 @@ pub struct Window { /// /// [`WindowAttributesExtIOS::with_prefers_home_indicator_hidden`]: https://docs.rs/winit/latest/x86_64-apple-darwin/winit/platform/ios/trait.WindowAttributesExtIOS.html#tymethod.with_prefers_home_indicator_hidden pub prefers_home_indicator_hidden: bool, + /// Sets whether the Window prefers the status bar hidden. + /// + /// Corresponds to [`WindowAttributesExtIOS::with_prefers_status_bar_hidden`]. + /// + /// # Platform-specific + /// + /// - Only used on iOS. + /// + /// [`WindowAttributesExtIOS::with_prefers_status_bar_hidden`]: https://docs.rs/winit/latest/x86_64-apple-darwin/winit/platform/ios/trait.WindowAttributesExtIOS.html#tymethod.with_prefers_status_bar_hidden + pub prefers_status_bar_hidden: bool, } impl Default for Window { @@ -454,6 +464,7 @@ impl Default for Window { titlebar_show_title: true, titlebar_show_buttons: true, prefers_home_indicator_hidden: false, + prefers_status_bar_hidden: false, } } } diff --git a/crates/bevy_winit/src/winit_windows.rs b/crates/bevy_winit/src/winit_windows.rs index 45e78508075bf..475eba32b555a 100644 --- a/crates/bevy_winit/src/winit_windows.rs +++ b/crates/bevy_winit/src/winit_windows.rs @@ -147,6 +147,8 @@ impl WinitWindows { use winit::platform::ios::WindowAttributesExtIOS; winit_window_attributes = winit_window_attributes .with_prefers_home_indicator_hidden(window.prefers_home_indicator_hidden); + winit_window_attributes = winit_window_attributes + .with_prefers_status_bar_hidden(window.prefers_status_bar_hidden); } let display_info = DisplayInfo { diff --git a/examples/mobile/src/lib.rs b/examples/mobile/src/lib.rs index ee49c01c58a25..afed081056852 100644 --- a/examples/mobile/src/lib.rs +++ b/examples/mobile/src/lib.rs @@ -30,6 +30,8 @@ fn main() { recognize_rotation_gesture: true, // Only has an effect on iOS prefers_home_indicator_hidden: true, + // Only has an effect on iOS + prefers_status_bar_hidden: true, ..default() }), ..default() From 17e3b850bdc1905d2a5f8b73c0dbcd822d76cdde Mon Sep 17 00:00:00 2001 From: ickshonpe Date: Mon, 6 Jan 2025 19:22:00 +0000 Subject: [PATCH 149/272] Simplified UI tree navigation without `ghost_nodes` (#17143) # Objective There is a large performance regression in the UI systems in 0.15 because the `UiChildren` and `UiRootRootNodes` system params (even with `ghost_nodes` disabled) are really inefficient compared to regular queries and can trigger a heap allocation with large numbers of children. ## Solution Replace the `UiChildren` and `UiRootRootNodes` system params with simplified versions when the `ghost_nodes` feature is disabled. ## Testing yellow this PR, red main cargo run --example many_buttons --features "trace_tracy" --release `ui_stack_system` stack `ui_layout_system` unghosted `update_clipping_system` clipping --- .../src/experimental/ghost_hierarchy.rs | 60 ++++++++++++++++++- 1 file changed, 57 insertions(+), 3 deletions(-) diff --git a/crates/bevy_ui/src/experimental/ghost_hierarchy.rs b/crates/bevy_ui/src/experimental/ghost_hierarchy.rs index 42832c0483500..75444ce8e3049 100644 --- a/crates/bevy_ui/src/experimental/ghost_hierarchy.rs +++ b/crates/bevy_ui/src/experimental/ghost_hierarchy.rs @@ -1,14 +1,17 @@ //! This module contains [`GhostNode`] and utilities to flatten the UI hierarchy, traversing past ghost nodes. +use crate::Node; use bevy_ecs::{prelude::*, system::SystemParam}; -use bevy_hierarchy::{Children, HierarchyQueryExt, Parent}; +use bevy_hierarchy::{Children, Parent}; use bevy_reflect::prelude::*; use bevy_render::view::Visibility; use bevy_transform::prelude::Transform; use core::marker::PhantomData; -use smallvec::SmallVec; -use crate::Node; +#[cfg(feature = "ghost_nodes")] +use bevy_hierarchy::HierarchyQueryExt; +#[cfg(feature = "ghost_nodes")] +use smallvec::SmallVec; /// Marker component for entities that should be ignored within UI hierarchies. /// @@ -40,6 +43,7 @@ impl GhostNode { } } +#[cfg(feature = "ghost_nodes")] /// System param that allows iteration of all UI root nodes. /// /// A UI root node is either a [`Node`] without a [`Parent`], or with only [`GhostNode`] ancestors. @@ -51,6 +55,10 @@ pub struct UiRootNodes<'w, 's> { ui_children: UiChildren<'w, 's>, } +#[cfg(not(feature = "ghost_nodes"))] +pub type UiRootNodes<'w, 's> = Query<'w, 's, Entity, (With, Without)>; + +#[cfg(feature = "ghost_nodes")] impl<'w, 's> UiRootNodes<'w, 's> { pub fn iter(&'s self) -> impl Iterator + 's { self.root_node_query @@ -62,6 +70,7 @@ impl<'w, 's> UiRootNodes<'w, 's> { } } +#[cfg(feature = "ghost_nodes")] /// System param that gives access to UI children utilities, skipping over [`GhostNode`]. #[derive(SystemParam)] pub struct UiChildren<'w, 's> { @@ -77,6 +86,16 @@ pub struct UiChildren<'w, 's> { parents_query: Query<'w, 's, &'static Parent>, } +#[cfg(not(feature = "ghost_nodes"))] +/// System param that gives access to UI children utilities. +#[derive(SystemParam)] +pub struct UiChildren<'w, 's> { + ui_children_query: Query<'w, 's, Option<&'static Children>, With>, + changed_children_query: Query<'w, 's, Entity, Changed>, + parents_query: Query<'w, 's, &'static Parent>, +} + +#[cfg(feature = "ghost_nodes")] impl<'w, 's> UiChildren<'w, 's> { /// Iterates the children of `entity`, skipping over [`GhostNode`]. /// @@ -134,6 +153,40 @@ impl<'w, 's> UiChildren<'w, 's> { } } +#[cfg(not(feature = "ghost_nodes"))] +impl<'w, 's> UiChildren<'w, 's> { + /// Iterates the children of `entity`. + pub fn iter_ui_children(&'s self, entity: Entity) -> impl Iterator + 's { + self.ui_children_query + .get(entity) + .ok() + .flatten() + .map(|children| children.as_ref()) + .unwrap_or(&[]) + .iter() + .copied() + } + + /// Returns the UI parent of the provided entity. + pub fn get_parent(&'s self, entity: Entity) -> Option { + self.parents_query + .get(entity) + .ok() + .map(|parent| parent.entity()) + } + + /// Given an entity in the UI hierarchy, check if its set of children has changed, e.g if children has been added/removed or if the order has changed. + pub fn is_changed(&'s self, entity: Entity) -> bool { + self.changed_children_query.contains(entity) + } + + /// Returns `true` if the given entity is either a [`Node`] or a [`GhostNode`]. + pub fn is_ui_node(&'s self, entity: Entity) -> bool { + self.ui_children_query.contains(entity) + } +} + +#[cfg(feature = "ghost_nodes")] pub struct UiChildrenIter<'w, 's> { stack: SmallVec<[Entity; 8]>, query: &'s Query< @@ -144,6 +197,7 @@ pub struct UiChildrenIter<'w, 's> { >, } +#[cfg(feature = "ghost_nodes")] impl<'w, 's> Iterator for UiChildrenIter<'w, 's> { type Item = Entity; fn next(&mut self) -> Option { From f64f3ac99762f3ae6fef0e264539b775b8336cb0 Mon Sep 17 00:00:00 2001 From: Christian Hughes <9044780+ItsDoot@users.noreply.github.com> Date: Mon, 6 Jan 2025 13:25:06 -0600 Subject: [PATCH 150/272] Cleanup entity reference types (#17149) # Objective Cleanup `EntityRef`, `EntityMut`, and `EntityWorldMut` in preparation for my "Scoped Entity References" PR. ## Solution - Switched `EntityRef`/`EntityMut` from tuple structs to normal ones. - Ensured all conversion trait impls use the same `entity` argument name. - Replaced some `unsafe` with delegated calls from `EntityMut` to `EntityRef` - Added `EntityMut::into_readonly` to make the replacements clearer - Replaced some `unsafe` with delegated calls from `EntityWorldMut` to `EntityMut` and `EntityRef` - Added `EntityWorldMut::into_readonly`, `::as_readonly`, `::into_mutable`, `::as_mutable` to make the replacements clearer ## Testing Reusing current tests. --- crates/bevy_ecs/src/world/entity_ref.rs | 267 +++++++++++++----------- 1 file changed, 147 insertions(+), 120 deletions(-) diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index 9080be1b9bba8..abcabc3891e81 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -49,7 +49,9 @@ use super::{unsafe_world_cell::UnsafeEntityCell, Ref, ON_REMOVE, ON_REPLACE}; /// # bevy_ecs::system::assert_is_system(disjoint_system); /// ``` #[derive(Copy, Clone)] -pub struct EntityRef<'w>(UnsafeEntityCell<'w>); +pub struct EntityRef<'w> { + cell: UnsafeEntityCell<'w>, +} impl<'w> EntityRef<'w> { /// # Safety @@ -58,26 +60,26 @@ impl<'w> EntityRef<'w> { /// at the same time as the returned [`EntityRef`]. #[inline] pub(crate) unsafe fn new(cell: UnsafeEntityCell<'w>) -> Self { - Self(cell) + Self { cell } } /// Returns the [ID](Entity) of the current entity. #[inline] #[must_use = "Omit the .id() call if you do not need to store the `Entity` identifier."] pub fn id(&self) -> Entity { - self.0.id() + self.cell.id() } /// Gets metadata indicating the location where the current entity is stored. #[inline] pub fn location(&self) -> EntityLocation { - self.0.location() + self.cell.location() } /// Returns the archetype that the current entity belongs to. #[inline] pub fn archetype(&self) -> &Archetype { - self.0.archetype() + self.cell.archetype() } /// Returns `true` if the current entity has a component of type `T`. @@ -102,7 +104,7 @@ impl<'w> EntityRef<'w> { /// [`Self::contains_type_id`]. #[inline] pub fn contains_id(&self, component_id: ComponentId) -> bool { - self.0.contains_id(component_id) + self.cell.contains_id(component_id) } /// Returns `true` if the current entity has a component with the type identified by `type_id`. @@ -114,7 +116,7 @@ impl<'w> EntityRef<'w> { /// - If you have a [`ComponentId`] instead of a [`TypeId`], consider using [`Self::contains_id`]. #[inline] pub fn contains_type_id(&self, type_id: TypeId) -> bool { - self.0.contains_type_id(type_id) + self.cell.contains_type_id(type_id) } /// Gets access to the component of type `T` for the current entity. @@ -122,7 +124,7 @@ impl<'w> EntityRef<'w> { #[inline] pub fn get(&self) -> Option<&'w T> { // SAFETY: We have read-only access to all components of this entity. - unsafe { self.0.get::() } + unsafe { self.cell.get::() } } /// Gets access to the component of type `T` for the current entity, @@ -132,7 +134,7 @@ impl<'w> EntityRef<'w> { #[inline] pub fn get_ref(&self) -> Option> { // SAFETY: We have read-only access to all components of this entity. - unsafe { self.0.get_ref::() } + unsafe { self.cell.get_ref::() } } /// Retrieves the change ticks for the given component. This can be useful for implementing change @@ -140,7 +142,7 @@ impl<'w> EntityRef<'w> { #[inline] pub fn get_change_ticks(&self) -> Option { // SAFETY: We have read-only access to all components of this entity. - unsafe { self.0.get_change_ticks::() } + unsafe { self.cell.get_change_ticks::() } } /// Retrieves the change ticks for the given [`ComponentId`]. This can be useful for implementing change @@ -152,7 +154,7 @@ impl<'w> EntityRef<'w> { #[inline] pub fn get_change_ticks_by_id(&self, component_id: ComponentId) -> Option { // SAFETY: We have read-only access to all components of this entity. - unsafe { self.0.get_change_ticks_by_id(component_id) } + unsafe { self.cell.get_change_ticks_by_id(component_id) } } /// Returns [untyped read-only reference(s)](Ptr) to component(s) for the @@ -265,7 +267,7 @@ impl<'w> EntityRef<'w> { component_ids: F, ) -> Result, EntityComponentError> { // SAFETY: We have read-only access to all components of this entity. - unsafe { component_ids.fetch_ref(self.0) } + unsafe { component_ids.fetch_ref(self.cell) } } /// Returns read-only components for the current entity that match the query `Q`. @@ -274,66 +276,67 @@ impl<'w> EntityRef<'w> { /// /// If the entity does not have the components required by the query `Q`. pub fn components(&self) -> Q::Item<'w> { - self.get_components::().expect(QUERY_MISMATCH_ERROR) + self.get_components::() + .expect("Query does not match the current entity") } /// Returns read-only components for the current entity that match the query `Q`, /// or `None` if the entity does not have the components required by the query `Q`. pub fn get_components(&self) -> Option> { // SAFETY: We have read-only access to all components of this entity. - unsafe { self.0.get_components::() } + unsafe { self.cell.get_components::() } } /// Returns the source code location from which this entity has been spawned. #[cfg(feature = "track_location")] pub fn spawned_by(&self) -> &'static Location<'static> { - self.0.spawned_by() + self.cell.spawned_by() } } impl<'w> From> for EntityRef<'w> { - fn from(entity_mut: EntityWorldMut<'w>) -> EntityRef<'w> { + fn from(entity: EntityWorldMut<'w>) -> EntityRef<'w> { // SAFETY: // - `EntityWorldMut` guarantees exclusive access to the entire world. - unsafe { EntityRef::new(entity_mut.into_unsafe_entity_cell()) } + unsafe { EntityRef::new(entity.into_unsafe_entity_cell()) } } } impl<'a> From<&'a EntityWorldMut<'_>> for EntityRef<'a> { - fn from(value: &'a EntityWorldMut<'_>) -> Self { + fn from(entity: &'a EntityWorldMut<'_>) -> Self { // SAFETY: // - `EntityWorldMut` guarantees exclusive access to the entire world. - // - `&value` ensures no mutable accesses are active. - unsafe { EntityRef::new(value.as_unsafe_entity_cell_readonly()) } + // - `&entity` ensures no mutable accesses are active. + unsafe { EntityRef::new(entity.as_unsafe_entity_cell_readonly()) } } } impl<'w> From> for EntityRef<'w> { - fn from(value: EntityMut<'w>) -> Self { + fn from(entity: EntityMut<'w>) -> Self { // SAFETY: // - `EntityMut` guarantees exclusive access to all of the entity's components. - unsafe { EntityRef::new(value.0) } + unsafe { EntityRef::new(entity.cell) } } } impl<'a> From<&'a EntityMut<'_>> for EntityRef<'a> { - fn from(value: &'a EntityMut<'_>) -> Self { + fn from(entity: &'a EntityMut<'_>) -> Self { // SAFETY: // - `EntityMut` guarantees exclusive access to all of the entity's components. - // - `&value` ensures there are no mutable accesses. - unsafe { EntityRef::new(value.0) } + // - `&entity` ensures there are no mutable accesses. + unsafe { EntityRef::new(entity.cell) } } } impl<'a> TryFrom> for EntityRef<'a> { type Error = TryFromFilteredError; - fn try_from(value: FilteredEntityRef<'a>) -> Result { - if !value.access.has_read_all() { + fn try_from(entity: FilteredEntityRef<'a>) -> Result { + if !entity.access.has_read_all() { Err(TryFromFilteredError::MissingReadAllAccess) } else { // SAFETY: check above guarantees read-only access to all components of the entity. - Ok(unsafe { EntityRef::new(value.entity) }) + Ok(unsafe { EntityRef::new(entity.entity) }) } } } @@ -341,12 +344,12 @@ impl<'a> TryFrom> for EntityRef<'a> { impl<'a> TryFrom<&'a FilteredEntityRef<'_>> for EntityRef<'a> { type Error = TryFromFilteredError; - fn try_from(value: &'a FilteredEntityRef<'_>) -> Result { - if !value.access.has_read_all() { + fn try_from(entity: &'a FilteredEntityRef<'_>) -> Result { + if !entity.access.has_read_all() { Err(TryFromFilteredError::MissingReadAllAccess) } else { // SAFETY: check above guarantees read-only access to all components of the entity. - Ok(unsafe { EntityRef::new(value.entity) }) + Ok(unsafe { EntityRef::new(entity.entity) }) } } } @@ -354,12 +357,12 @@ impl<'a> TryFrom<&'a FilteredEntityRef<'_>> for EntityRef<'a> { impl<'a> TryFrom> for EntityRef<'a> { type Error = TryFromFilteredError; - fn try_from(value: FilteredEntityMut<'a>) -> Result { - if !value.access.has_read_all() { + fn try_from(entity: FilteredEntityMut<'a>) -> Result { + if !entity.access.has_read_all() { Err(TryFromFilteredError::MissingReadAllAccess) } else { // SAFETY: check above guarantees read-only access to all components of the entity. - Ok(unsafe { EntityRef::new(value.entity) }) + Ok(unsafe { EntityRef::new(entity.entity) }) } } } @@ -367,12 +370,12 @@ impl<'a> TryFrom> for EntityRef<'a> { impl<'a> TryFrom<&'a FilteredEntityMut<'_>> for EntityRef<'a> { type Error = TryFromFilteredError; - fn try_from(value: &'a FilteredEntityMut<'_>) -> Result { - if !value.access.has_read_all() { + fn try_from(entity: &'a FilteredEntityMut<'_>) -> Result { + if !entity.access.has_read_all() { Err(TryFromFilteredError::MissingReadAllAccess) } else { // SAFETY: check above guarantees read-only access to all components of the entity. - Ok(unsafe { EntityRef::new(value.entity) }) + Ok(unsafe { EntityRef::new(entity.entity) }) } } } @@ -436,7 +439,9 @@ unsafe impl TrustedEntityBorrow for EntityRef<'_> {} /// } /// # bevy_ecs::system::assert_is_system(disjoint_system); /// ``` -pub struct EntityMut<'w>(UnsafeEntityCell<'w>); +pub struct EntityMut<'w> { + cell: UnsafeEntityCell<'w>, +} impl<'w> EntityMut<'w> { /// # Safety @@ -444,14 +449,20 @@ impl<'w> EntityMut<'w> { /// - No accesses to any of the entity's components may exist /// at the same time as the returned [`EntityMut`]. pub(crate) unsafe fn new(cell: UnsafeEntityCell<'w>) -> Self { - Self(cell) + Self { cell } } /// Returns a new instance with a shorter lifetime. /// This is useful if you have `&mut EntityMut`, but you need `EntityMut`. pub fn reborrow(&mut self) -> EntityMut<'_> { // SAFETY: We have exclusive access to the entire entity and its components. - unsafe { Self::new(self.0) } + unsafe { Self::new(self.cell) } + } + + /// Consumes `self` and returns read-only access to all of the entity's + /// components, with the world `'w` lifetime. + pub fn into_readonly(self) -> EntityRef<'w> { + EntityRef::from(self) } /// Gets read-only access to all of the entity's components. @@ -463,19 +474,19 @@ impl<'w> EntityMut<'w> { #[inline] #[must_use = "Omit the .id() call if you do not need to store the `Entity` identifier."] pub fn id(&self) -> Entity { - self.0.id() + self.cell.id() } /// Gets metadata indicating the location where the current entity is stored. #[inline] pub fn location(&self) -> EntityLocation { - self.0.location() + self.cell.location() } /// Returns the archetype that the current entity belongs to. #[inline] pub fn archetype(&self) -> &Archetype { - self.0.archetype() + self.cell.archetype() } /// Returns `true` if the current entity has a component of type `T`. @@ -500,7 +511,7 @@ impl<'w> EntityMut<'w> { /// [`Self::contains_type_id`]. #[inline] pub fn contains_id(&self, component_id: ComponentId) -> bool { - self.0.contains_id(component_id) + self.cell.contains_id(component_id) } /// Returns `true` if the current entity has a component with the type identified by `type_id`. @@ -512,7 +523,7 @@ impl<'w> EntityMut<'w> { /// - If you have a [`ComponentId`] instead of a [`TypeId`], consider using [`Self::contains_id`]. #[inline] pub fn contains_type_id(&self, type_id: TypeId) -> bool { - self.0.contains_type_id(type_id) + self.cell.contains_type_id(type_id) } /// Gets access to the component of type `T` for the current entity. @@ -528,14 +539,13 @@ impl<'w> EntityMut<'w> { /// /// If the entity does not have the components required by the query `Q`. pub fn components(&self) -> Q::Item<'_> { - self.get_components::().expect(QUERY_MISMATCH_ERROR) + self.as_readonly().components::() } /// Returns read-only components for the current entity that match the query `Q`, /// or `None` if the entity does not have the components required by the query `Q`. pub fn get_components(&self) -> Option> { - // SAFETY: We have read-only access to all components of this entity. - unsafe { self.0.get_components::() } + self.as_readonly().get_components::() } /// Consumes `self` and gets access to the component of type `T` with the @@ -544,8 +554,7 @@ impl<'w> EntityMut<'w> { /// Returns `None` if the entity does not have a component of type `T`. #[inline] pub fn into_borrow(self) -> Option<&'w T> { - // SAFETY: consuming `self` implies exclusive access - unsafe { self.0.get() } + self.into_readonly().get() } /// Gets access to the component of type `T` for the current entity, @@ -564,8 +573,7 @@ impl<'w> EntityMut<'w> { /// Returns `None` if the entity does not have a component of type `T`. #[inline] pub fn into_ref(self) -> Option> { - // SAFETY: consuming `self` implies exclusive access - unsafe { self.0.get_ref() } + self.into_readonly().get_ref() } /// Gets mutable access to the component of type `T` for the current entity. @@ -573,7 +581,7 @@ impl<'w> EntityMut<'w> { #[inline] pub fn get_mut>(&mut self) -> Option> { // SAFETY: &mut self implies exclusive access for duration of returned value - unsafe { self.0.get_mut() } + unsafe { self.cell.get_mut() } } /// Gets mutable access to the component of type `T` for the current entity. @@ -584,8 +592,10 @@ impl<'w> EntityMut<'w> { /// - `T` must be a mutable component #[inline] pub unsafe fn get_mut_assume_mutable(&mut self) -> Option> { - // SAFETY: &mut self implies exclusive access for duration of returned value - unsafe { self.0.get_mut_assume_mutable() } + // SAFETY: + // - &mut self implies exclusive access for duration of returned value + // - Caller ensures `T` is a mutable component + unsafe { self.cell.get_mut_assume_mutable() } } /// Consumes self and gets mutable access to the component of type `T` @@ -594,7 +604,21 @@ impl<'w> EntityMut<'w> { #[inline] pub fn into_mut>(self) -> Option> { // SAFETY: consuming `self` implies exclusive access - unsafe { self.0.get_mut() } + unsafe { self.cell.get_mut() } + } + + /// Gets mutable access to the component of type `T` for the current entity. + /// Returns `None` if the entity does not have a component of type `T`. + /// + /// # Safety + /// + /// - `T` must be a mutable component + #[inline] + pub unsafe fn into_mut_assume_mutable(self) -> Option> { + // SAFETY: + // - Consuming `self` implies exclusive access + // - Caller ensures `T` is a mutable component + unsafe { self.cell.get_mut_assume_mutable() } } /// Retrieves the change ticks for the given component. This can be useful for implementing change @@ -667,10 +691,7 @@ impl<'w> EntityMut<'w> { self, component_ids: F, ) -> Result, EntityComponentError> { - // SAFETY: - // - We have read-only access to all components of this entity. - // - consuming `self` ensures that no references exist to this entity's components. - unsafe { component_ids.fetch_ref(self.0) } + self.into_readonly().get_by_id(component_ids) } /// Returns [untyped mutable reference(s)](MutUntyped) to component(s) for @@ -794,7 +815,7 @@ impl<'w> EntityMut<'w> { // SAFETY: // - `&mut self` ensures that no references exist to this entity's components. // - We have exclusive access to all components of this entity. - unsafe { component_ids.fetch_mut(self.0) } + unsafe { component_ids.fetch_mut(self.cell) } } /// Returns [untyped mutable reference](MutUntyped) to component for @@ -822,7 +843,7 @@ impl<'w> EntityMut<'w> { // SAFETY: // - The caller must ensure simultaneous access is limited // - to components that are mutually independent. - unsafe { component_ids.fetch_mut(self.0) } + unsafe { component_ids.fetch_mut(self.cell) } } /// Consumes `self` and returns [untyped mutable reference(s)](MutUntyped) @@ -855,47 +876,47 @@ impl<'w> EntityMut<'w> { // SAFETY: // - consuming `self` ensures that no references exist to this entity's components. // - We have exclusive access to all components of this entity. - unsafe { component_ids.fetch_mut(self.0) } + unsafe { component_ids.fetch_mut(self.cell) } } /// Returns the source code location from which this entity has been spawned. #[cfg(feature = "track_location")] pub fn spawned_by(&self) -> &'static Location<'static> { - self.0.spawned_by() + self.cell.spawned_by() } } impl<'w> From<&'w mut EntityMut<'_>> for EntityMut<'w> { - fn from(value: &'w mut EntityMut<'_>) -> Self { - value.reborrow() + fn from(entity: &'w mut EntityMut<'_>) -> Self { + entity.reborrow() } } impl<'w> From> for EntityMut<'w> { - fn from(value: EntityWorldMut<'w>) -> Self { + fn from(entity: EntityWorldMut<'w>) -> Self { // SAFETY: `EntityWorldMut` guarantees exclusive access to the entire world. - unsafe { EntityMut::new(value.into_unsafe_entity_cell()) } + unsafe { EntityMut::new(entity.into_unsafe_entity_cell()) } } } impl<'a> From<&'a mut EntityWorldMut<'_>> for EntityMut<'a> { - fn from(value: &'a mut EntityWorldMut<'_>) -> Self { + fn from(entity: &'a mut EntityWorldMut<'_>) -> Self { // SAFETY: `EntityWorldMut` guarantees exclusive access to the entire world. - unsafe { EntityMut::new(value.as_unsafe_entity_cell()) } + unsafe { EntityMut::new(entity.as_unsafe_entity_cell()) } } } impl<'a> TryFrom> for EntityMut<'a> { type Error = TryFromFilteredError; - fn try_from(value: FilteredEntityMut<'a>) -> Result { - if !value.access.has_read_all() { + fn try_from(entity: FilteredEntityMut<'a>) -> Result { + if !entity.access.has_read_all() { Err(TryFromFilteredError::MissingReadAllAccess) - } else if !value.access.has_write_all() { + } else if !entity.access.has_write_all() { Err(TryFromFilteredError::MissingWriteAllAccess) } else { // SAFETY: check above guarantees exclusive access to all components of the entity. - Ok(unsafe { EntityMut::new(value.entity) }) + Ok(unsafe { EntityMut::new(entity.entity) }) } } } @@ -903,14 +924,14 @@ impl<'a> TryFrom> for EntityMut<'a> { impl<'a> TryFrom<&'a mut FilteredEntityMut<'_>> for EntityMut<'a> { type Error = TryFromFilteredError; - fn try_from(value: &'a mut FilteredEntityMut<'_>) -> Result { - if !value.access.has_read_all() { + fn try_from(entity: &'a mut FilteredEntityMut<'_>) -> Result { + if !entity.access.has_read_all() { Err(TryFromFilteredError::MissingReadAllAccess) - } else if !value.access.has_write_all() { + } else if !entity.access.has_write_all() { Err(TryFromFilteredError::MissingWriteAllAccess) } else { // SAFETY: check above guarantees exclusive access to all components of the entity. - Ok(unsafe { EntityMut::new(value.entity) }) + Ok(unsafe { EntityMut::new(entity.entity) }) } } } @@ -1039,6 +1060,28 @@ impl<'w> EntityWorldMut<'w> { } } + /// Consumes `self` and returns read-only access to all of the entity's + /// components, with the world `'w` lifetime. + pub fn into_readonly(self) -> EntityRef<'w> { + EntityRef::from(self) + } + + /// Gets read-only access to all of the entity's components. + pub fn as_readonly(&self) -> EntityRef<'_> { + EntityRef::from(self) + } + + /// Consumes `self` and returns non-structural mutable access to all of the + /// entity's components, with the world `'w` lifetime. + pub fn into_mutable(self) -> EntityMut<'w> { + EntityMut::from(self) + } + + /// Gets non-structural mutable access to all of the entity's components. + pub fn as_mutable(&mut self) -> EntityMut<'_> { + EntityMut::from(self) + } + /// Returns the [ID](Entity) of the current entity. #[inline] #[must_use = "Omit the .id() call if you do not need to store the `Entity` identifier."] @@ -1127,7 +1170,7 @@ impl<'w> EntityWorldMut<'w> { /// If the entity has been despawned while this `EntityWorldMut` is still alive. #[inline] pub fn get(&self) -> Option<&'_ T> { - EntityRef::from(self).get() + self.as_readonly().get() } /// Returns read-only components for the current entity that match the query `Q`. @@ -1138,7 +1181,7 @@ impl<'w> EntityWorldMut<'w> { /// has been despawned while this `EntityWorldMut` is still alive. #[inline] pub fn components(&self) -> Q::Item<'_> { - EntityRef::from(self).components::() + self.as_readonly().components::() } /// Returns read-only components for the current entity that match the query `Q`, @@ -1149,7 +1192,7 @@ impl<'w> EntityWorldMut<'w> { /// If the entity has been despawned while this `EntityWorldMut` is still alive. #[inline] pub fn get_components(&self) -> Option> { - EntityRef::from(self).get_components::() + self.as_readonly().get_components::() } /// Consumes `self` and gets access to the component of type `T` with @@ -1161,8 +1204,7 @@ impl<'w> EntityWorldMut<'w> { /// If the entity has been despawned while this `EntityWorldMut` is still alive. #[inline] pub fn into_borrow(self) -> Option<&'w T> { - // SAFETY: consuming `self` implies exclusive access - unsafe { self.into_unsafe_entity_cell().get() } + self.into_readonly().get() } /// Gets access to the component of type `T` for the current entity, @@ -1175,7 +1217,7 @@ impl<'w> EntityWorldMut<'w> { /// If the entity has been despawned while this `EntityWorldMut` is still alive. #[inline] pub fn get_ref(&self) -> Option> { - EntityRef::from(self).get_ref() + self.as_readonly().get_ref() } /// Consumes `self` and gets access to the component of type `T` @@ -1189,7 +1231,7 @@ impl<'w> EntityWorldMut<'w> { /// If the entity has been despawned while this `EntityWorldMut` is still alive. #[inline] pub fn into_ref(self) -> Option> { - EntityRef::from(self).get_ref() + self.into_readonly().get_ref() } /// Gets mutable access to the component of type `T` for the current entity. @@ -1200,8 +1242,7 @@ impl<'w> EntityWorldMut<'w> { /// If the entity has been despawned while this `EntityWorldMut` is still alive. #[inline] pub fn get_mut>(&mut self) -> Option> { - // SAFETY: trait bound `Mutability = Mutable` ensures `T` is mutable - unsafe { self.get_mut_assume_mutable() } + self.as_mutable().into_mut() } /// Temporarily removes a [`Component`] `T` from this [`Entity`] and runs the @@ -1265,10 +1306,7 @@ impl<'w> EntityWorldMut<'w> { /// - `T` must be a mutable component #[inline] pub unsafe fn get_mut_assume_mutable(&mut self) -> Option> { - // SAFETY: - // - &mut self implies exclusive access for duration of returned value - // - caller ensures T is mutable - unsafe { self.as_unsafe_entity_cell().get_mut_assume_mutable() } + self.as_mutable().into_mut_assume_mutable() } /// Consumes `self` and gets mutable access to the component of type `T` @@ -1292,7 +1330,7 @@ impl<'w> EntityWorldMut<'w> { /// If the entity has been despawned while this `EntityWorldMut` is still alive. #[inline] pub fn get_change_ticks(&self) -> Option { - EntityRef::from(self).get_change_ticks::() + self.as_readonly().get_change_ticks::() } /// Retrieves the change ticks for the given [`ComponentId`]. This can be useful for implementing change @@ -1307,7 +1345,7 @@ impl<'w> EntityWorldMut<'w> { /// If the entity has been despawned while this `EntityWorldMut` is still alive. #[inline] pub fn get_change_ticks_by_id(&self, component_id: ComponentId) -> Option { - EntityRef::from(self).get_change_ticks_by_id(component_id) + self.as_readonly().get_change_ticks_by_id(component_id) } /// Returns [untyped read-only reference(s)](Ptr) to component(s) for the @@ -1338,7 +1376,7 @@ impl<'w> EntityWorldMut<'w> { &self, component_ids: F, ) -> Result, EntityComponentError> { - EntityRef::from(self).get_by_id(component_ids) + self.as_readonly().get_by_id(component_ids) } /// Consumes `self` and returns [untyped read-only reference(s)](Ptr) to @@ -1370,10 +1408,7 @@ impl<'w> EntityWorldMut<'w> { self, component_ids: F, ) -> Result, EntityComponentError> { - // SAFETY: - // - We have read-only access to all components of this entity. - // - consuming `self` ensures that no references exist to this entity's components. - unsafe { component_ids.fetch_ref(self.into_unsafe_entity_cell()) } + self.into_readonly().get_by_id(component_ids) } /// Returns [untyped mutable reference(s)](MutUntyped) to component(s) for @@ -1406,10 +1441,7 @@ impl<'w> EntityWorldMut<'w> { &mut self, component_ids: F, ) -> Result, EntityComponentError> { - // SAFETY: - // - `&mut self` ensures that no references exist to this entity's components. - // - We have exclusive access to all components of this entity. - unsafe { component_ids.fetch_mut(self.as_unsafe_entity_cell()) } + self.as_mutable().into_mut_by_id(component_ids) } /// Consumes `self` and returns [untyped mutable reference(s)](MutUntyped) @@ -1443,10 +1475,7 @@ impl<'w> EntityWorldMut<'w> { self, component_ids: F, ) -> Result, EntityComponentError> { - // SAFETY: - // - consuming `self` ensures that no references exist to this entity's components. - // - We have exclusive access to all components of this entity. - unsafe { component_ids.fetch_mut(self.into_unsafe_entity_cell()) } + self.into_mutable().into_mut_by_id(component_ids) } /// Adds a [`Bundle`] of components to the entity. @@ -2499,8 +2528,6 @@ unsafe fn trigger_on_replace_and_on_remove_hooks_and_observers( deferred_world.trigger_on_remove(archetype, entity, bundle_info.iter_explicit_components()); } -const QUERY_MISMATCH_ERROR: &str = "Query does not match the current entity"; - /// A view into a single entity and component in a world, which may either be vacant or occupied. /// /// This `enum` can only be constructed from the [`entry`] method on [`EntityWorldMut`]. @@ -3018,19 +3045,19 @@ impl<'w> FilteredEntityRef<'w> { impl<'w> From> for FilteredEntityRef<'w> { #[inline] - fn from(entity_mut: FilteredEntityMut<'w>) -> Self { + fn from(entity: FilteredEntityMut<'w>) -> Self { // SAFETY: // - `FilteredEntityMut` guarantees exclusive access to all components in the new `FilteredEntityRef`. - unsafe { FilteredEntityRef::new(entity_mut.entity, entity_mut.access) } + unsafe { FilteredEntityRef::new(entity.entity, entity.access) } } } impl<'a> From<&'a FilteredEntityMut<'_>> for FilteredEntityRef<'a> { #[inline] - fn from(entity_mut: &'a FilteredEntityMut<'_>) -> Self { + fn from(entity: &'a FilteredEntityMut<'_>) -> Self { // SAFETY: // - `FilteredEntityMut` guarantees exclusive access to all components in the new `FilteredEntityRef`. - unsafe { FilteredEntityRef::new(entity_mut.entity, entity_mut.access.clone()) } + unsafe { FilteredEntityRef::new(entity.entity, entity.access.clone()) } } } @@ -3041,7 +3068,7 @@ impl<'a> From> for FilteredEntityRef<'a> { unsafe { let mut access = Access::default(); access.read_all(); - FilteredEntityRef::new(entity.0, access) + FilteredEntityRef::new(entity.cell, access) } } } @@ -3053,7 +3080,7 @@ impl<'a> From<&'a EntityRef<'_>> for FilteredEntityRef<'a> { unsafe { let mut access = Access::default(); access.read_all(); - FilteredEntityRef::new(entity.0, access) + FilteredEntityRef::new(entity.cell, access) } } } @@ -3065,7 +3092,7 @@ impl<'a> From> for FilteredEntityRef<'a> { unsafe { let mut access = Access::default(); access.read_all(); - FilteredEntityRef::new(entity.0, access) + FilteredEntityRef::new(entity.cell, access) } } } @@ -3077,7 +3104,7 @@ impl<'a> From<&'a EntityMut<'_>> for FilteredEntityRef<'a> { unsafe { let mut access = Access::default(); access.read_all(); - FilteredEntityRef::new(entity.0, access) + FilteredEntityRef::new(entity.cell, access) } } } @@ -3389,7 +3416,7 @@ impl<'a> From> for FilteredEntityMut<'a> { let mut access = Access::default(); access.read_all(); access.write_all(); - FilteredEntityMut::new(entity.0, access) + FilteredEntityMut::new(entity.cell, access) } } } @@ -3402,7 +3429,7 @@ impl<'a> From<&'a mut EntityMut<'_>> for FilteredEntityMut<'a> { let mut access = Access::default(); access.read_all(); access.write_all(); - FilteredEntityMut::new(entity.0, access) + FilteredEntityMut::new(entity.cell, access) } } } @@ -3566,10 +3593,10 @@ impl<'a, B> From<&'a EntityMutExcept<'_, B>> for EntityRefExcept<'a, B> where B: Bundle, { - fn from(entity_mut: &'a EntityMutExcept<'_, B>) -> Self { + fn from(entity: &'a EntityMutExcept<'_, B>) -> Self { // SAFETY: All accesses that `EntityRefExcept` provides are also // accesses that `EntityMutExcept` provides. - unsafe { EntityRefExcept::new(entity_mut.entity) } + unsafe { EntityRefExcept::new(entity.entity) } } } From b386d08d0f905d07843ddfed936e404845a6a10b Mon Sep 17 00:00:00 2001 From: MichiRecRoom <1008889+LikeLakers2@users.noreply.github.com> Date: Mon, 6 Jan 2025 14:25:47 -0500 Subject: [PATCH 151/272] bevy_asset: Apply `#![deny(clippy::allow_attributes, clippy::allow_attributes_without_reason)]` (#17113) # Objective - https://github.com/bevyengine/bevy/issues/17111 ## Solution Set the `clippy::allow_attributes` and `clippy::allow_attributes_without_reason` lints to `deny`, and bring `bevy_asset` in line with the new restrictions. No code changes have been made - except if a lint that was previously `allow(...)`'d could be removed via small code changes. For example, `unused_variables` can be handled by adding a `_` to the beginning of a field's name. ## Testing `cargo clippy` and `cargo test --package bevy_asset --features multi_threaded` were run, and no errors were encountered. --- crates/bevy_asset/src/asset_changed.rs | 4 ++-- crates/bevy_asset/src/handle.rs | 5 ++++- crates/bevy_asset/src/lib.rs | 12 +++++++++--- crates/bevy_asset/src/processor/mod.rs | 1 - crates/bevy_asset/src/server/loaders.rs | 10 +++------- 5 files changed, 18 insertions(+), 14 deletions(-) diff --git a/crates/bevy_asset/src/asset_changed.rs b/crates/bevy_asset/src/asset_changed.rs index d41b7f3cf08f9..ca269ef52ed1a 100644 --- a/crates/bevy_asset/src/asset_changed.rs +++ b/crates/bevy_asset/src/asset_changed.rs @@ -148,7 +148,7 @@ pub struct AssetChangedState { _asset: PhantomData, } -#[allow(unsafe_code)] +#[expect(unsafe_code, reason = "WorldQuery is an unsafe trait.")] /// SAFETY: `ROQueryFetch` is the same as `QueryFetch` unsafe impl WorldQuery for AssetChanged { type Item<'w> = (); @@ -264,7 +264,7 @@ unsafe impl WorldQuery for AssetChanged { } } -#[allow(unsafe_code)] +#[expect(unsafe_code, reason = "QueryFilter is an unsafe trait.")] /// SAFETY: read-only access unsafe impl QueryFilter for AssetChanged { const IS_ARCHETYPAL: bool = false; diff --git a/crates/bevy_asset/src/handle.rs b/crates/bevy_asset/src/handle.rs index 9c61ff0f88c7b..19065ae6e84fa 100644 --- a/crates/bevy_asset/src/handle.rs +++ b/crates/bevy_asset/src/handle.rs @@ -551,8 +551,11 @@ mod tests { } /// Typed and Untyped `Handles` should be orderable amongst each other and themselves - #[allow(clippy::cmp_owned)] #[test] + #[expect( + clippy::cmp_owned, + reason = "This lints on the assertion that a typed handle converted to an untyped handle maintains its ordering compared to an untyped handle. While the conversion would normally be useless, we need to ensure that converted handles maintain their ordering, making the conversion necessary here." + )] fn ordering() { assert!(UUID_1 < UUID_2); diff --git a/crates/bevy_asset/src/lib.rs b/crates/bevy_asset/src/lib.rs index bb34eed98c162..3d58b701b0c87 100644 --- a/crates/bevy_asset/src/lib.rs +++ b/crates/bevy_asset/src/lib.rs @@ -139,6 +139,11 @@ //! This trait mirrors [`AssetLoader`] in structure, and works in tandem with [`AssetWriter`](io::AssetWriter), which mirrors [`AssetReader`](io::AssetReader). #![expect(missing_docs, reason = "Not all docs are written yet, see #3492.")] +#![deny( + clippy::allow_attributes, + clippy::allow_attributes_without_reason, + reason = "See #17111; To be removed once all crates are in-line with these attributes" +)] #![cfg_attr(docsrs, feature(doc_auto_cfg))] #![doc( html_logo_url = "https://bevyengine.org/assets/icon.png", @@ -1767,8 +1772,11 @@ mod tests { #[derive(Asset, TypePath)] pub struct TestAsset; - #[allow(dead_code)] #[derive(Asset, TypePath)] + #[expect( + dead_code, + reason = "This exists to ensure that `#[derive(Asset)]` works on enums. The inner variants are known not to be used." + )] pub enum EnumTestAsset { Unnamed(#[dependency] Handle), Named { @@ -1783,7 +1791,6 @@ mod tests { Empty, } - #[allow(dead_code)] #[derive(Asset, TypePath)] pub struct StructTestAsset { #[dependency] @@ -1792,7 +1799,6 @@ mod tests { embedded: TestAsset, } - #[allow(dead_code)] #[derive(Asset, TypePath)] pub struct TupleTestAsset(#[dependency] Handle); } diff --git a/crates/bevy_asset/src/processor/mod.rs b/crates/bevy_asset/src/processor/mod.rs index 665ff7dd9e9ef..d32b98dcbe46d 100644 --- a/crates/bevy_asset/src/processor/mod.rs +++ b/crates/bevy_asset/src/processor/mod.rs @@ -478,7 +478,6 @@ impl AssetProcessor { self.set_state(ProcessorState::Finished).await; } - #[allow(unused)] #[cfg(all(not(target_arch = "wasm32"), feature = "multi_threaded"))] async fn process_assets_internal<'scope>( &'scope self, diff --git a/crates/bevy_asset/src/server/loaders.rs b/crates/bevy_asset/src/server/loaders.rs index 44c43ad2b8f4c..6bedfe2446132 100644 --- a/crates/bevy_asset/src/server/loaders.rs +++ b/crates/bevy_asset/src/server/loaders.rs @@ -351,18 +351,14 @@ mod tests { use super::*; - // The compiler notices these fields are never read and raises a dead_code lint which kill CI. - #[allow(dead_code)] #[derive(Asset, TypePath, Debug)] - struct A(usize); + struct A; - #[allow(dead_code)] #[derive(Asset, TypePath, Debug)] - struct B(usize); + struct B; - #[allow(dead_code)] #[derive(Asset, TypePath, Debug)] - struct C(usize); + struct C; struct Loader { sender: Sender<()>, From 3d797d75136788dd4567c3ac0b6554f5ab9b758f Mon Sep 17 00:00:00 2001 From: MichiRecRoom <1008889+LikeLakers2@users.noreply.github.com> Date: Mon, 6 Jan 2025 14:26:44 -0500 Subject: [PATCH 152/272] bevy_sprite: Apply `#![deny(clippy::allow_attributes, clippy::allow_attributes_without_reason)]` (Attempt 2) (#17184) I broke the commit history on the other one, https://github.com/bevyengine/bevy/pull/17160. Woops. # Objective - https://github.com/bevyengine/bevy/issues/17111 ## Solution Set the `clippy::allow_attributes` and `clippy::allow_attributes_without_reason` lints to `deny`, and bring `bevy_sprite` in line with the new restrictions. ## Testing `cargo clippy` and `cargo test --package bevy_sprite` were run, and no errors were encountered. --- crates/bevy_sprite/src/lib.rs | 13 +++++++++++++ crates/bevy_sprite/src/mesh2d/material.rs | 10 ++++++++-- crates/bevy_sprite/src/mesh2d/mesh.rs | 5 ++++- crates/bevy_sprite/src/mesh2d/wireframe2d.rs | 5 ++++- crates/bevy_sprite/src/picking_backend.rs | 5 ++++- crates/bevy_sprite/src/render/mod.rs | 15 ++++++++++++--- 6 files changed, 45 insertions(+), 8 deletions(-) diff --git a/crates/bevy_sprite/src/lib.rs b/crates/bevy_sprite/src/lib.rs index 543ca5c3dbc3a..173ef17f7deec 100644 --- a/crates/bevy_sprite/src/lib.rs +++ b/crates/bevy_sprite/src/lib.rs @@ -1,6 +1,11 @@ #![expect(missing_docs, reason = "Not all docs are written yet, see #3492.")] #![cfg_attr(docsrs, feature(doc_auto_cfg))] #![forbid(unsafe_code)] +#![deny( + clippy::allow_attributes, + clippy::allow_attributes_without_reason, + reason = "See #17111; To be removed once all crates are in-line with these attributes" +)] #![doc( html_logo_url = "https://bevyengine.org/assets/icon.png", html_favicon_url = "https://bevyengine.org/assets/icon.png" @@ -64,6 +69,14 @@ pub struct SpritePlugin { pub add_picking: bool, } +#[expect( + clippy::allow_attributes, + reason = "clippy::derivable_impls is not always linted" +)] +#[allow( + clippy::derivable_impls, + reason = "Known false positive with clippy: " +)] impl Default for SpritePlugin { fn default() -> Self { Self { diff --git a/crates/bevy_sprite/src/mesh2d/material.rs b/crates/bevy_sprite/src/mesh2d/material.rs index 977e4a81bf129..b350136ef080f 100644 --- a/crates/bevy_sprite/src/mesh2d/material.rs +++ b/crates/bevy_sprite/src/mesh2d/material.rs @@ -135,7 +135,10 @@ pub trait Material2d: AsBindGroup + Asset + Clone + Sized { } /// Customizes the default [`RenderPipelineDescriptor`]. - #[allow(unused_variables)] + #[expect( + unused_variables, + reason = "The parameters here are intentionally unused by the default implementation; however, putting underscores here will result in the underscores being copied by rust-analyzer's tab completion." + )] #[inline] fn specialize( descriptor: &mut RenderPipelineDescriptor, @@ -464,7 +467,10 @@ pub const fn tonemapping_pipeline_key(tonemapping: Tonemapping) -> Mesh2dPipelin } } -#[allow(clippy::too_many_arguments)] +#[expect( + clippy::too_many_arguments, + reason = "Could be rewritten with less arguments using a QueryData-implementing struct, but doesn't need to be." +)] pub fn queue_material2d_meshes( opaque_draw_functions: Res>, alpha_mask_draw_functions: Res>, diff --git a/crates/bevy_sprite/src/mesh2d/mesh.rs b/crates/bevy_sprite/src/mesh2d/mesh.rs index 92abdeb68a557..bd12a0412f711 100644 --- a/crates/bevy_sprite/src/mesh2d/mesh.rs +++ b/crates/bevy_sprite/src/mesh2d/mesh.rs @@ -717,7 +717,10 @@ pub struct Mesh2dViewBindGroup { pub value: BindGroup, } -#[allow(clippy::too_many_arguments)] +#[expect( + clippy::too_many_arguments, + reason = "Could be rewritten with less arguments using a QueryData-implementing struct, but doesn't need to be." +)] pub fn prepare_mesh2d_view_bind_groups( mut commands: Commands, render_device: Res, diff --git a/crates/bevy_sprite/src/mesh2d/wireframe2d.rs b/crates/bevy_sprite/src/mesh2d/wireframe2d.rs index 6f2659fbaaf91..c36e945ec5a0b 100644 --- a/crates/bevy_sprite/src/mesh2d/wireframe2d.rs +++ b/crates/bevy_sprite/src/mesh2d/wireframe2d.rs @@ -124,7 +124,10 @@ fn global_color_changed( } /// Updates the wireframe material when the color in [`Wireframe2dColor`] changes -#[allow(clippy::type_complexity)] +#[expect( + clippy::type_complexity, + reason = "Can't be rewritten with less complex arguments." +)] fn wireframe_color_changed( mut materials: ResMut>, mut colors_changed: Query< diff --git a/crates/bevy_sprite/src/picking_backend.rs b/crates/bevy_sprite/src/picking_backend.rs index 5d1e652db3a7d..aef7bc5c2403c 100644 --- a/crates/bevy_sprite/src/picking_backend.rs +++ b/crates/bevy_sprite/src/picking_backend.rs @@ -54,7 +54,10 @@ impl Plugin for SpritePickingPlugin { } } -#[allow(clippy::too_many_arguments)] +#[expect( + clippy::too_many_arguments, + reason = "Could be rewritten with less arguments using a QueryData-implementing struct, but doesn't need to be." +)] fn sprite_picking( pointers: Query<(&PointerId, &PointerLocation)>, cameras: Query<(Entity, &Camera, &GlobalTransform, &Projection)>, diff --git a/crates/bevy_sprite/src/render/mod.rs b/crates/bevy_sprite/src/render/mod.rs index 1c91bdd603062..014fd779eed5d 100644 --- a/crates/bevy_sprite/src/render/mod.rs +++ b/crates/bevy_sprite/src/render/mod.rs @@ -494,7 +494,10 @@ pub struct ImageBindGroups { values: HashMap, BindGroup>, } -#[allow(clippy::too_many_arguments)] +#[expect( + clippy::too_many_arguments, + reason = "Could be rewritten with less arguments using a QueryData-implementing struct, but doesn't need to be." +)] pub fn queue_sprites( mut view_entities: Local, draw_functions: Res>, @@ -582,7 +585,10 @@ pub fn queue_sprites( } } -#[allow(clippy::too_many_arguments)] +#[expect( + clippy::too_many_arguments, + reason = "Could be rewritten with less arguments using a QueryData-implementing struct, but doesn't need to be." +)] pub fn prepare_sprite_view_bind_groups( mut commands: Commands, render_device: Res, @@ -616,7 +622,10 @@ pub fn prepare_sprite_view_bind_groups( } } -#[allow(clippy::too_many_arguments)] +#[expect( + clippy::too_many_arguments, + reason = "Could be rewritten with less arguments using a QueryData-implementing struct, but doesn't need to be." +)] pub fn prepare_sprite_image_bind_groups( mut commands: Commands, mut previous_len: Local, From 51075aab40fb6f5fc1745639fac601a16d2fe11b Mon Sep 17 00:00:00 2001 From: MichiRecRoom <1008889+LikeLakers2@users.noreply.github.com> Date: Mon, 6 Jan 2025 14:29:14 -0500 Subject: [PATCH 153/272] Remove `bevy_core_pipeline::core_2d::Camera2dBundle` (#17185) # Objective https://github.com/bevyengine/bevy/pull/16338 forgot to remove this previously-deprecated item. In fact, it only removed the `#[deprecated]` attribute attached to it. ## Solution Removes `bevy_core_pipeline::core_2d::Camera2dBundle`. ## Testing CI. --- .../src/core_2d/camera_2d.rs | 82 +------------------ 1 file changed, 1 insertion(+), 81 deletions(-) diff --git a/crates/bevy_core_pipeline/src/core_2d/camera_2d.rs b/crates/bevy_core_pipeline/src/core_2d/camera_2d.rs index 783ec4b75a414..9780ddd31f63e 100644 --- a/crates/bevy_core_pipeline/src/core_2d/camera_2d.rs +++ b/crates/bevy_core_pipeline/src/core_2d/camera_2d.rs @@ -5,14 +5,9 @@ use crate::{ use bevy_ecs::prelude::*; use bevy_reflect::{std_traits::ReflectDefault, Reflect}; use bevy_render::{ - camera::{ - Camera, CameraMainTextureUsages, CameraProjection, CameraRenderGraph, - OrthographicProjection, Projection, - }, + camera::{Camera, CameraProjection, CameraRenderGraph, OrthographicProjection, Projection}, extract_component::ExtractComponent, primitives::Frustum, - sync_world::SyncToRenderWorld, - view::{Msaa, VisibleEntities}, }; use bevy_transform::prelude::{GlobalTransform, Transform}; @@ -29,78 +24,3 @@ use bevy_transform::prelude::{GlobalTransform, Transform}; Tonemapping(|| Tonemapping::None), )] pub struct Camera2d; - -#[derive(Bundle, Clone)] -pub struct Camera2dBundle { - pub camera: Camera, - pub camera_render_graph: CameraRenderGraph, - pub projection: Projection, - pub visible_entities: VisibleEntities, - pub frustum: Frustum, - pub transform: Transform, - pub global_transform: GlobalTransform, - pub camera_2d: Camera2d, - pub tonemapping: Tonemapping, - pub deband_dither: DebandDither, - pub main_texture_usages: CameraMainTextureUsages, - pub msaa: Msaa, - /// Marker component that indicates that its entity needs to be synchronized to the render world - pub sync: SyncToRenderWorld, -} - -impl Default for Camera2dBundle { - fn default() -> Self { - let projection = Projection::Orthographic(OrthographicProjection::default_2d()); - let transform = Transform::default(); - let frustum = projection.compute_frustum(&GlobalTransform::from(transform)); - Self { - camera_render_graph: CameraRenderGraph::new(Core2d), - projection, - visible_entities: VisibleEntities::default(), - frustum, - transform, - global_transform: Default::default(), - camera: Camera::default(), - camera_2d: Camera2d, - tonemapping: Tonemapping::None, - deband_dither: DebandDither::Disabled, - main_texture_usages: Default::default(), - msaa: Default::default(), - sync: Default::default(), - } - } -} - -impl Camera2dBundle { - /// Create an orthographic projection camera with a custom `Z` position. - /// - /// The camera is placed at `Z=far-0.1`, looking toward the world origin `(0,0,0)`. - /// Its orthographic projection extends from `0.0` to `-far` in camera view space, - /// corresponding to `Z=far-0.1` (closest to camera) to `Z=-0.1` (furthest away from - /// camera) in world space. - pub fn new_with_far(far: f32) -> Self { - // we want 0 to be "closest" and +far to be "farthest" in 2d, so we offset - // the camera's translation by far and use a right handed coordinate system - let projection = Projection::Orthographic(OrthographicProjection { - far, - ..OrthographicProjection::default_2d() - }); - let transform = Transform::from_xyz(0.0, 0.0, far - 0.1); - let frustum = projection.compute_frustum(&GlobalTransform::from(transform)); - Self { - camera_render_graph: CameraRenderGraph::new(Core2d), - projection, - visible_entities: VisibleEntities::default(), - frustum, - transform, - global_transform: Default::default(), - camera: Camera::default(), - camera_2d: Camera2d, - tonemapping: Tonemapping::None, - deband_dither: DebandDither::Disabled, - main_texture_usages: Default::default(), - msaa: Default::default(), - sync: Default::default(), - } - } -} From 94596d2bbfb2aafcd8a856fbb0123d9952805aad Mon Sep 17 00:00:00 2001 From: MichiRecRoom <1008889+LikeLakers2@users.noreply.github.com> Date: Mon, 6 Jan 2025 14:29:59 -0500 Subject: [PATCH 154/272] bevy_diagnostic: Apply `#![deny(clippy::allow_attributes, clippy::allow_attributes_without_reason)]` (#17186) # Objective - https://github.com/bevyengine/bevy/issues/17111 ## Solution Set the `clippy::allow_attributes` and `clippy::allow_attributes_without_reason` lints to `deny`, and bring `bevy_diagnostic` in line with the new restrictions. ## Testing `cargo clippy` and `cargo test --package bevy_diagnostic` were run, and no errors were encountered. --- crates/bevy_diagnostic/src/lib.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/crates/bevy_diagnostic/src/lib.rs b/crates/bevy_diagnostic/src/lib.rs index 970fc5ba07ae3..cecc64f777e8f 100644 --- a/crates/bevy_diagnostic/src/lib.rs +++ b/crates/bevy_diagnostic/src/lib.rs @@ -1,6 +1,11 @@ #![expect(missing_docs, reason = "Not all docs are written yet, see #3492.")] #![cfg_attr(docsrs, feature(doc_auto_cfg))] #![forbid(unsafe_code)] +#![deny( + clippy::allow_attributes, + clippy::allow_attributes_without_reason, + reason = "See #17111; To be removed once all crates are in-line with these attributes" +)] #![doc( html_logo_url = "https://bevyengine.org/assets/icon.png", html_favicon_url = "https://bevyengine.org/assets/icon.png" From 6f68776eaca0ea7c5d690ca3c95701cd5b3c334b Mon Sep 17 00:00:00 2001 From: Rob Parrett Date: Mon, 6 Jan 2025 11:32:32 -0800 Subject: [PATCH 155/272] Split up `animated_fox` example (#17191) # Objective Our `animated_fox` example used to be a bare-bones example of how to spawn an animated gltf and play a single animation. I think that's a valuable example, and the current `animated_fox` example is doing way too much. Users who are trying to understand how our animation system are presented with an enormous amount of information that may not be immediately relevant. Over the past few releases, I've been migrating a simple app of mine where the only animation I need is a single gltf that starts playing a single animation when it is loaded. It has been a slight struggle to wade through changes to the animation system to figure out the minimal amount of things required to accomplish this. Somewhat motivated by this [recent reddit thread](https://www.reddit.com/r/rust/comments/1ht93vl/comment/m5c0nc9/?utm_source=share&utm_medium=mweb3x&utm_name=mweb3xcss&utm_term=1) where Bevy and animation got a mention. ## Solution - Split `animated_fox` into three separate examples - `animated_fox` - Loads and immediately plays a single animation - `animated_fox_control` - Shows how to control animations - `animated_fox_events` - Shows fancy particles when the fox's feet hit the ground - Some minor drive-by tidying of these examples I have created this PR after playing around with the idea and liking how it turned out, but the duplication isn't totally ideal and there's some slight overlap with other examples and inconsistencies: - `animation_events` is simplified and not specific to "loaded animated scenes" and seems valuable on its own - `animation_graph` also uses a fox I am happy to close this if there's no consensus that it's a good idea / step forward for these examples. ## Testing `cargo run --example animated_fox` `cargo run --example animated_fox_control` `cargo run --example animated_fox_events` --- Cargo.toml | 22 ++ examples/README.md | 2 + examples/animation/animated_fox.rs | 331 ++------------------- examples/animation/animated_fox_control.rs | 210 +++++++++++++ examples/animation/animated_fox_events.rs | 292 ++++++++++++++++++ 5 files changed, 543 insertions(+), 314 deletions(-) create mode 100644 examples/animation/animated_fox_control.rs create mode 100644 examples/animation/animated_fox_events.rs diff --git a/Cargo.toml b/Cargo.toml index 6613ec3c4e344..6be54d00eb451 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1291,6 +1291,28 @@ description = "Plays an animation from a skinned glTF" category = "Animation" wasm = true +[[example]] +name = "animated_fox_control" +path = "examples/animation/animated_fox_control.rs" +doc-scrape-examples = true + +[package.metadata.example.animated_fox_control] +name = "Animated Fox Control" +description = "Plays an animation from a skinned glTF with keyboard controls" +category = "Animation" +wasm = true + +[[example]] +name = "animated_fox_events" +path = "examples/animation/animated_fox_events.rs" +doc-scrape-examples = true + +[package.metadata.example.animated_fox_events] +name = "Animated Fox Events" +description = "Plays an animation from a skinned glTF with events" +category = "Animation" +wasm = true + [[example]] name = "animation_graph" path = "examples/animation/animation_graph.rs" diff --git a/examples/README.md b/examples/README.md index 2ff4e504e1672..5b1594b8e016f 100644 --- a/examples/README.md +++ b/examples/README.md @@ -195,6 +195,8 @@ Example | Description Example | Description --- | --- [Animated Fox](../examples/animation/animated_fox.rs) | Plays an animation from a skinned glTF +[Animated Fox Control](../examples/animation/animated_fox_control.rs) | Plays an animation from a skinned glTF with keyboard controls +[Animated Fox Events](../examples/animation/animated_fox_events.rs) | Plays an animation from a skinned glTF with events [Animated Transform](../examples/animation/animated_transform.rs) | Create and play an animation defined by code that operates on the `Transform` component [Animated UI](../examples/animation/animated_ui.rs) | Shows how to use animation clips to animate UI properties [Animation Events](../examples/animation/animation_events.rs) | Demonstrate how to use animation events diff --git a/examples/animation/animated_fox.rs b/examples/animation/animated_fox.rs index 912b2d082b0de..12e8ecc75c263 100644 --- a/examples/animation/animated_fox.rs +++ b/examples/animation/animated_fox.rs @@ -2,14 +2,7 @@ use std::{f32::consts::PI, time::Duration}; -use bevy::{ - animation::{AnimationTargetId, RepeatAnimation}, - color::palettes::css::WHITE, - pbr::CascadeShadowConfigBuilder, - prelude::*, -}; -use rand::{Rng, SeedableRng}; -use rand_chacha::ChaCha8Rng; +use bevy::{pbr::CascadeShadowConfigBuilder, prelude::*}; const FOX_PATH: &str = "models/animated/Fox.glb"; @@ -21,49 +14,15 @@ fn main() { ..default() }) .add_plugins(DefaultPlugins) - .init_resource::() - .init_resource::() .add_systems(Startup, setup) .add_systems(Update, setup_scene_once_loaded) - .add_systems(Update, (keyboard_animation_control, simulate_particles)) - .add_observer(observe_on_step) .run(); } -#[derive(Resource)] -struct SeededRng(ChaCha8Rng); - #[derive(Resource)] struct Animations { - animations: Vec, - graph: Handle, -} - -#[derive(Event, Reflect, Clone)] -struct OnStep; - -fn observe_on_step( - trigger: Trigger, - particle: Res, - mut commands: Commands, - transforms: Query<&GlobalTransform>, - mut seeded_rng: ResMut, -) { - let translation = transforms.get(trigger.target()).unwrap().translation(); - // Spawn a bunch of particles. - for _ in 0..14 { - let horizontal = seeded_rng.0.gen::() * seeded_rng.0.gen_range(8.0..12.0); - let vertical = seeded_rng.0.gen_range(0.0..4.0); - let size = seeded_rng.0.gen_range(0.2..1.0); - commands.queue(spawn_particle( - particle.mesh.clone(), - particle.material.clone(), - translation.reject_from_normalized(Vec3::Y), - seeded_rng.0.gen_range(0.2..0.6), - size, - Vec3::new(horizontal.x, vertical, horizontal.y) * 10.0, - )); - } + graph_handle: Handle, + index: AnimationNodeIndex, } fn setup( @@ -74,17 +33,17 @@ fn setup( mut graphs: ResMut>, ) { // Build the animation graph - let (graph, node_indices) = AnimationGraph::from_clips([ - asset_server.load(GltfAssetLabel::Animation(2).from_asset(FOX_PATH)), - asset_server.load(GltfAssetLabel::Animation(1).from_asset(FOX_PATH)), + let (graph, index) = AnimationGraph::from_clip( + // We specifically want the "walk" animation, which is the first one. asset_server.load(GltfAssetLabel::Animation(0).from_asset(FOX_PATH)), - ]); + ); - // Insert a resource with the current scene information + // Keep our animation graph in a Resource so that it can be inserted onto + // the correct entity once the scene actually loads. let graph_handle = graphs.add(graph); commands.insert_resource(Animations { - animations: node_indices, - graph: graph_handle, + graph_handle: graph_handle.clone(), + index, }); // Camera @@ -115,60 +74,19 @@ fn setup( )); // Fox - commands.spawn(SceneRoot( - asset_server.load(GltfAssetLabel::Scene(0).from_asset(FOX_PATH)), + commands.spawn(( + SceneRoot(asset_server.load(GltfAssetLabel::Scene(0).from_asset(FOX_PATH))), + AnimationGraphHandle(graph_handle), )); - - println!("Animation controls:"); - println!(" - spacebar: play / pause"); - println!(" - arrow up / down: speed up / slow down animation playback"); - println!(" - arrow left / right: seek backward / forward"); - println!(" - digit 1 / 3 / 5: play the animation times"); - println!(" - L: loop the animation forever"); - println!(" - return: change animation"); - - // We're seeding the PRNG here to make this example deterministic for testing purposes. - // This isn't strictly required in practical use unless you need your app to be deterministic. - let seeded_rng = ChaCha8Rng::seed_from_u64(19878367467712); - commands.insert_resource(SeededRng(seeded_rng)); } -// An `AnimationPlayer` is automatically added to the scene when it's ready. -// When the player is added, start the animation. +// Once the scene is loaded, start the animation fn setup_scene_once_loaded( mut commands: Commands, animations: Res, - feet: Res, - graphs: Res>, - mut clips: ResMut>, mut players: Query<(Entity, &mut AnimationPlayer), Added>, ) { - fn get_clip<'a>( - node: AnimationNodeIndex, - graph: &AnimationGraph, - clips: &'a mut Assets, - ) -> &'a mut AnimationClip { - let node = graph.get(node).unwrap(); - let clip = match &node.node_type { - AnimationNodeType::Clip(handle) => clips.get_mut(handle), - _ => unreachable!(), - }; - clip.unwrap() - } - for (entity, mut player) in &mut players { - let graph = graphs.get(&animations.graph).unwrap(); - - // Send `OnStep` events once the fox feet hits the ground in the running animation. - let running_animation = get_clip(animations.animations[0], graph, &mut clips); - // You can determine the time an event should trigger if you know witch frame it occurs and - // the frame rate of the animation. Let's say we want to trigger an event at frame 15, - // and the animation has a frame rate of 24 fps, then time = 15 / 24 = 0.625. - running_animation.add_event_to_target(feet.front_left, 0.625, OnStep); - running_animation.add_event_to_target(feet.front_right, 0.5, OnStep); - running_animation.add_event_to_target(feet.back_left, 0.0, OnStep); - running_animation.add_event_to_target(feet.back_right, 0.125, OnStep); - let mut transitions = AnimationTransitions::new(); // Make sure to start the animation via the `AnimationTransitions` @@ -176,227 +94,12 @@ fn setup_scene_once_loaded( // the animations and will get confused if the animations are started // directly via the `AnimationPlayer`. transitions - .play(&mut player, animations.animations[0], Duration::ZERO) + .play(&mut player, animations.index, Duration::ZERO) .repeat(); commands .entity(entity) - .insert(AnimationGraphHandle(animations.graph.clone())) - .insert(transitions); - } -} - -fn keyboard_animation_control( - keyboard_input: Res>, - mut animation_players: Query<(&mut AnimationPlayer, &mut AnimationTransitions)>, - animations: Res, - mut current_animation: Local, -) { - for (mut player, mut transitions) in &mut animation_players { - let Some((&playing_animation_index, _)) = player.playing_animations().next() else { - continue; - }; - - if keyboard_input.just_pressed(KeyCode::Space) { - let playing_animation = player.animation_mut(playing_animation_index).unwrap(); - if playing_animation.is_paused() { - playing_animation.resume(); - } else { - playing_animation.pause(); - } - } - - if keyboard_input.just_pressed(KeyCode::ArrowUp) { - let playing_animation = player.animation_mut(playing_animation_index).unwrap(); - let speed = playing_animation.speed(); - playing_animation.set_speed(speed * 1.2); - } - - if keyboard_input.just_pressed(KeyCode::ArrowDown) { - let playing_animation = player.animation_mut(playing_animation_index).unwrap(); - let speed = playing_animation.speed(); - playing_animation.set_speed(speed * 0.8); - } - - if keyboard_input.just_pressed(KeyCode::ArrowLeft) { - let playing_animation = player.animation_mut(playing_animation_index).unwrap(); - let elapsed = playing_animation.seek_time(); - playing_animation.seek_to(elapsed - 0.1); - } - - if keyboard_input.just_pressed(KeyCode::ArrowRight) { - let playing_animation = player.animation_mut(playing_animation_index).unwrap(); - let elapsed = playing_animation.seek_time(); - playing_animation.seek_to(elapsed + 0.1); - } - - if keyboard_input.just_pressed(KeyCode::Enter) { - *current_animation = (*current_animation + 1) % animations.animations.len(); - - transitions - .play( - &mut player, - animations.animations[*current_animation], - Duration::from_millis(250), - ) - .repeat(); - } - - if keyboard_input.just_pressed(KeyCode::Digit1) { - let playing_animation = player.animation_mut(playing_animation_index).unwrap(); - playing_animation - .set_repeat(RepeatAnimation::Count(1)) - .replay(); - } - - if keyboard_input.just_pressed(KeyCode::Digit3) { - let playing_animation = player.animation_mut(playing_animation_index).unwrap(); - playing_animation - .set_repeat(RepeatAnimation::Count(3)) - .replay(); - } - - if keyboard_input.just_pressed(KeyCode::Digit5) { - let playing_animation = player.animation_mut(playing_animation_index).unwrap(); - playing_animation - .set_repeat(RepeatAnimation::Count(5)) - .replay(); - } - - if keyboard_input.just_pressed(KeyCode::KeyL) { - let playing_animation = player.animation_mut(playing_animation_index).unwrap(); - playing_animation.set_repeat(RepeatAnimation::Forever); - } - } -} - -fn simulate_particles( - mut commands: Commands, - mut query: Query<(Entity, &mut Transform, &mut Particle)>, - time: Res, diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index abcabc3891e81..bd939d00432ce 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -1746,7 +1746,6 @@ impl<'w> EntityWorldMut<'w> { /// when DROP is true removed components will be dropped otherwise they will be forgotten // We use a const generic here so that we are less reliant on // inlining for rustc to optimize out the `match DROP` - #[allow(clippy::too_many_arguments)] unsafe fn move_entity_from_remove( entity: Entity, self_location: &mut EntityLocation, @@ -1826,7 +1825,6 @@ impl<'w> EntityWorldMut<'w> { /// /// # Safety /// - A `BundleInfo` with the corresponding `BundleId` must have been initialized. - #[allow(clippy::too_many_arguments)] unsafe fn remove_bundle(&mut self, bundle: BundleId) -> EntityLocation { let entity = self.entity; let world = &mut self.world; diff --git a/crates/bevy_gizmos/src/grid.rs b/crates/bevy_gizmos/src/grid.rs index 03ee5c665445c..42742e196c77c 100644 --- a/crates/bevy_gizmos/src/grid.rs +++ b/crates/bevy_gizmos/src/grid.rs @@ -347,7 +347,6 @@ where } } -#[allow(clippy::too_many_arguments)] fn draw_grid( gizmos: &mut GizmoBuffer, isometry: Isometry3d, diff --git a/crates/bevy_gizmos/src/pipeline_2d.rs b/crates/bevy_gizmos/src/pipeline_2d.rs index 1e85602d62913..55ce5322ff40b 100644 --- a/crates/bevy_gizmos/src/pipeline_2d.rs +++ b/crates/bevy_gizmos/src/pipeline_2d.rs @@ -288,7 +288,6 @@ type DrawLineJointGizmo2d = ( DrawLineJointGizmo, ); -#[allow(clippy::too_many_arguments)] fn queue_line_gizmos_2d( draw_functions: Res>, pipeline: Res, @@ -365,8 +364,6 @@ fn queue_line_gizmos_2d( } } } - -#[allow(clippy::too_many_arguments)] fn queue_line_joint_gizmos_2d( draw_functions: Res>, pipeline: Res, diff --git a/crates/bevy_gizmos/src/pipeline_3d.rs b/crates/bevy_gizmos/src/pipeline_3d.rs index a36e5c55a3d88..89814bcbffd83 100644 --- a/crates/bevy_gizmos/src/pipeline_3d.rs +++ b/crates/bevy_gizmos/src/pipeline_3d.rs @@ -283,7 +283,6 @@ type DrawLineJointGizmo3d = ( DrawLineJointGizmo, ); -#[allow(clippy::too_many_arguments)] fn queue_line_gizmos_3d( draw_functions: Res>, pipeline: Res, @@ -398,7 +397,6 @@ fn queue_line_gizmos_3d( } } -#[allow(clippy::too_many_arguments)] fn queue_line_joint_gizmos_3d( draw_functions: Res>, pipeline: Res, diff --git a/crates/bevy_gltf/src/loader.rs b/crates/bevy_gltf/src/loader.rs index 6eb3198c29e09..4edf2552e9506 100644 --- a/crates/bevy_gltf/src/loader.rs +++ b/crates/bevy_gltf/src/loader.rs @@ -1368,7 +1368,7 @@ fn warn_on_differing_texture_transforms( } /// Loads a glTF node. -#[allow(clippy::too_many_arguments, clippy::result_large_err)] +#[allow(clippy::result_large_err)] fn load_node( gltf_node: &Node, world_builder: &mut WorldChildBuilder, diff --git a/crates/bevy_pbr/src/cluster/assign.rs b/crates/bevy_pbr/src/cluster/assign.rs index 19d8fdb059361..297672a78f995 100644 --- a/crates/bevy_pbr/src/cluster/assign.rs +++ b/crates/bevy_pbr/src/cluster/assign.rs @@ -135,7 +135,6 @@ impl ClusterableObjectType { } // NOTE: Run this before update_point_light_frusta! -#[allow(clippy::too_many_arguments)] pub(crate) fn assign_objects_to_clusters( mut commands: Commands, mut global_clusterable_objects: ResMut, @@ -842,7 +841,6 @@ pub(crate) fn assign_objects_to_clusters( } } -#[allow(clippy::too_many_arguments)] fn compute_aabb_for_cluster( z_near: f32, z_far: f32, diff --git a/crates/bevy_pbr/src/light/mod.rs b/crates/bevy_pbr/src/light/mod.rs index f88512a19cabd..fe99f1bbdc6ae 100644 --- a/crates/bevy_pbr/src/light/mod.rs +++ b/crates/bevy_pbr/src/light/mod.rs @@ -824,7 +824,6 @@ pub fn check_dir_light_mesh_visibility( }); } -#[allow(clippy::too_many_arguments)] pub fn check_point_light_mesh_visibility( visible_point_lights: Query<&VisibleClusterableObjects>, mut point_lights: Query<( diff --git a/crates/bevy_pbr/src/material.rs b/crates/bevy_pbr/src/material.rs index 521ca5295389c..362b4b6d31020 100644 --- a/crates/bevy_pbr/src/material.rs +++ b/crates/bevy_pbr/src/material.rs @@ -610,7 +610,6 @@ pub fn extract_mesh_materials( /// For each view, iterates over all the meshes visible from that view and adds /// them to [`BinnedRenderPhase`]s or [`SortedRenderPhase`]s as appropriate. -#[allow(clippy::too_many_arguments)] pub fn queue_material_meshes( ( opaque_draw_functions, diff --git a/crates/bevy_pbr/src/meshlet/from_mesh.rs b/crates/bevy_pbr/src/meshlet/from_mesh.rs index 6cd868e49d8cf..8d4f1c7533ba2 100644 --- a/crates/bevy_pbr/src/meshlet/from_mesh.rs +++ b/crates/bevy_pbr/src/meshlet/from_mesh.rs @@ -601,7 +601,6 @@ fn split_simplified_group_into_new_meshlets( new_meshlets_count } -#[allow(clippy::too_many_arguments)] fn build_and_compress_per_meshlet_vertex_data( meshlet: &meshopt_Meshlet, meshlet_vertex_ids: &[u32], diff --git a/crates/bevy_pbr/src/meshlet/instance_manager.rs b/crates/bevy_pbr/src/meshlet/instance_manager.rs index c190d7ea367b0..7a78abe482104 100644 --- a/crates/bevy_pbr/src/meshlet/instance_manager.rs +++ b/crates/bevy_pbr/src/meshlet/instance_manager.rs @@ -81,7 +81,6 @@ impl InstanceManager { } } - #[allow(clippy::too_many_arguments)] pub fn add_instance( &mut self, instance: MainEntity, diff --git a/crates/bevy_pbr/src/meshlet/material_pipeline_prepare.rs b/crates/bevy_pbr/src/meshlet/material_pipeline_prepare.rs index a6053b412ce5f..2a44e5934c4cf 100644 --- a/crates/bevy_pbr/src/meshlet/material_pipeline_prepare.rs +++ b/crates/bevy_pbr/src/meshlet/material_pipeline_prepare.rs @@ -29,7 +29,6 @@ pub struct MeshletViewMaterialsMainOpaquePass(pub Vec<(u32, CachedRenderPipeline /// Prepare [`Material`] pipelines for [`super::MeshletMesh`] entities for use in [`super::MeshletMainOpaquePass3dNode`], /// and register the material with [`InstanceManager`]. -#[allow(clippy::too_many_arguments)] pub fn prepare_material_meshlet_meshes_main_opaque_pass( resource_manager: ResMut, mut instance_manager: ResMut, @@ -247,7 +246,6 @@ pub struct MeshletViewMaterialsDeferredGBufferPrepass( /// Prepare [`Material`] pipelines for [`super::MeshletMesh`] entities for use in [`super::MeshletPrepassNode`], /// and [`super::MeshletDeferredGBufferPrepassNode`] and register the material with [`InstanceManager`]. -#[allow(clippy::too_many_arguments)] pub fn prepare_material_meshlet_meshes_prepass( resource_manager: ResMut, mut instance_manager: ResMut, diff --git a/crates/bevy_pbr/src/meshlet/resource_manager.rs b/crates/bevy_pbr/src/meshlet/resource_manager.rs index 79473b2c36fe1..c918990869cec 100644 --- a/crates/bevy_pbr/src/meshlet/resource_manager.rs +++ b/crates/bevy_pbr/src/meshlet/resource_manager.rs @@ -574,7 +574,6 @@ pub fn prepare_meshlet_per_frame_resources( } } -#[allow(clippy::too_many_arguments)] pub fn prepare_meshlet_view_bind_groups( meshlet_mesh_manager: Res, resource_manager: Res, diff --git a/crates/bevy_pbr/src/meshlet/visibility_buffer_raster_node.rs b/crates/bevy_pbr/src/meshlet/visibility_buffer_raster_node.rs index aa549ae679924..9890deb4dbfc1 100644 --- a/crates/bevy_pbr/src/meshlet/visibility_buffer_raster_node.rs +++ b/crates/bevy_pbr/src/meshlet/visibility_buffer_raster_node.rs @@ -358,7 +358,6 @@ fn fill_cluster_buffers_pass( ); } -#[allow(clippy::too_many_arguments)] fn cull_pass( label: &'static str, render_context: &mut RenderContext, @@ -405,7 +404,6 @@ fn cull_pass( } } -#[allow(clippy::too_many_arguments)] fn raster_pass( first_pass: bool, render_context: &mut RenderContext, diff --git a/crates/bevy_pbr/src/prepass/mod.rs b/crates/bevy_pbr/src/prepass/mod.rs index 2e5a082de782d..ad3c366c7a62b 100644 --- a/crates/bevy_pbr/src/prepass/mod.rs +++ b/crates/bevy_pbr/src/prepass/mod.rs @@ -755,7 +755,6 @@ pub fn prepare_prepass_view_bind_group( } } -#[allow(clippy::too_many_arguments)] pub fn queue_prepass_material_meshes( ( opaque_draw_functions, diff --git a/crates/bevy_pbr/src/render/light.rs b/crates/bevy_pbr/src/render/light.rs index 7f7434a9f7113..9ccf2b6dd4d1f 100644 --- a/crates/bevy_pbr/src/render/light.rs +++ b/crates/bevy_pbr/src/render/light.rs @@ -206,7 +206,6 @@ impl FromWorld for ShadowSamplers { } } -#[allow(clippy::too_many_arguments)] pub fn extract_lights( mut commands: Commands, point_light_shadow_map: Extract>, @@ -684,7 +683,6 @@ pub(crate) fn spot_light_clip_from_view(angle: f32, near_z: f32) -> Mat4 { Mat4::perspective_infinite_reverse_rh(angle * 2.0, 1.0, near_z) } -#[allow(clippy::too_many_arguments)] pub fn prepare_lights( mut commands: Commands, mut texture_cache: ResMut, @@ -1532,7 +1530,6 @@ fn despawn_entities(commands: &mut Commands, entities: Vec) { /// For each shadow cascade, iterates over all the meshes "visible" from it and /// adds them to [`BinnedRenderPhase`]s or [`SortedRenderPhase`]s as /// appropriate. -#[allow(clippy::too_many_arguments)] pub fn queue_shadows( shadow_draw_functions: Res>, prepass_pipeline: Res>, diff --git a/crates/bevy_pbr/src/render/mesh.rs b/crates/bevy_pbr/src/render/mesh.rs index de29bd2127933..069763048082b 100644 --- a/crates/bevy_pbr/src/render/mesh.rs +++ b/crates/bevy_pbr/src/render/mesh.rs @@ -896,7 +896,6 @@ impl RenderMeshInstanceGpuQueue { impl RenderMeshInstanceGpuBuilder { /// Flushes this mesh instance to the [`RenderMeshInstanceGpu`] and /// [`MeshInputUniform`] tables, replacing the existing entry if applicable. - #[allow(clippy::too_many_arguments)] fn update( mut self, entity: MainEntity, @@ -1170,7 +1169,6 @@ pub fn extract_meshes_for_cpu_building( /// /// This is the variant of the system that runs when we're using GPU /// [`MeshUniform`] building. -#[allow(clippy::too_many_arguments)] pub fn extract_meshes_for_gpu_building( mut render_mesh_instances: ResMut, render_visibility_ranges: Res, @@ -1351,7 +1349,6 @@ fn set_mesh_motion_vector_flags( /// Creates the [`RenderMeshInstanceGpu`]s and [`MeshInputUniform`]s when GPU /// mesh uniforms are built. -#[allow(clippy::too_many_arguments)] pub fn collect_meshes_for_gpu_building( render_mesh_instances: ResMut, batched_instance_buffers: ResMut< @@ -2411,7 +2408,6 @@ impl MeshBindGroupPair { } } -#[allow(clippy::too_many_arguments)] pub fn prepare_mesh_bind_group( meshes: Res>, mut groups: ResMut, diff --git a/crates/bevy_pbr/src/render/mesh_bindings.rs b/crates/bevy_pbr/src/render/mesh_bindings.rs index e6d07cb4e2c6e..51b28389dcd0c 100644 --- a/crates/bevy_pbr/src/render/mesh_bindings.rs +++ b/crates/bevy_pbr/src/render/mesh_bindings.rs @@ -495,7 +495,6 @@ impl MeshLayouts { } /// Creates the bind group for meshes with skins and morph targets. - #[allow(clippy::too_many_arguments)] pub fn morphed_skinned( &self, render_device: &RenderDevice, @@ -523,7 +522,6 @@ impl MeshLayouts { /// [`MeshLayouts::morphed_motion`] above for more information about the /// `current_skin`, `prev_skin`, `current_weights`, and `prev_weights` /// buffers. - #[allow(clippy::too_many_arguments)] pub fn morphed_skinned_motion( &self, render_device: &RenderDevice, diff --git a/crates/bevy_pbr/src/render/mesh_view_bindings.rs b/crates/bevy_pbr/src/render/mesh_view_bindings.rs index 5c8823f2d3e19..385c942c46835 100644 --- a/crates/bevy_pbr/src/render/mesh_view_bindings.rs +++ b/crates/bevy_pbr/src/render/mesh_view_bindings.rs @@ -488,7 +488,6 @@ pub struct MeshViewBindGroup { pub value: BindGroup, } -#[allow(clippy::too_many_arguments)] pub fn prepare_mesh_view_bind_groups( mut commands: Commands, render_device: Res, diff --git a/crates/bevy_pbr/src/volumetric_fog/render.rs b/crates/bevy_pbr/src/volumetric_fog/render.rs index 76039bbbe448b..a9eedad1e8e70 100644 --- a/crates/bevy_pbr/src/volumetric_fog/render.rs +++ b/crates/bevy_pbr/src/volumetric_fog/render.rs @@ -607,7 +607,6 @@ impl SpecializedRenderPipeline for VolumetricFogPipeline { } /// Specializes volumetric fog pipelines for all views with that effect enabled. -#[allow(clippy::too_many_arguments)] pub fn prepare_volumetric_fog_pipelines( mut commands: Commands, pipeline_cache: Res, diff --git a/crates/bevy_picking/src/events.rs b/crates/bevy_picking/src/events.rs index c3d35e0bb98e9..ae3d879e9a47d 100644 --- a/crates/bevy_picking/src/events.rs +++ b/crates/bevy_picking/src/events.rs @@ -402,7 +402,6 @@ pub struct PickingEventWriters<'w> { /// determined only by the pointer's *final position*. Since the hover state /// ultimately determines which entities receive events, this may mean that an /// entity can receive events from before or after it was actually hovered. -#[allow(clippy::too_many_arguments)] pub fn pointer_events( // Input mut input_events: EventReader, diff --git a/crates/bevy_picking/src/mesh_picking/mod.rs b/crates/bevy_picking/src/mesh_picking/mod.rs index 42671beb7b958..f424a87a3f41c 100644 --- a/crates/bevy_picking/src/mesh_picking/mod.rs +++ b/crates/bevy_picking/src/mesh_picking/mod.rs @@ -68,7 +68,6 @@ impl Plugin for MeshPickingPlugin { } /// Casts rays into the scene using [`MeshPickingSettings`] and sends [`PointerHits`] events. -#[allow(clippy::too_many_arguments)] pub fn update_hits( backend_settings: Res, ray_map: Res, diff --git a/crates/bevy_render/src/camera/camera.rs b/crates/bevy_render/src/camera/camera.rs index d30426f44949c..65608d5d0672d 100644 --- a/crates/bevy_render/src/camera/camera.rs +++ b/crates/bevy_render/src/camera/camera.rs @@ -897,10 +897,6 @@ impl NormalizedRenderTarget { /// /// [`OrthographicProjection`]: crate::camera::OrthographicProjection /// [`PerspectiveProjection`]: crate::camera::PerspectiveProjection -#[expect( - clippy::too_many_arguments, - reason = "Could be rewritten with less arguments using a QueryData-implementing struct, but doesn't need to be." -)] pub fn camera_system( mut window_resized_events: EventReader, mut window_created_events: EventReader, diff --git a/crates/bevy_render/src/mesh/allocator.rs b/crates/bevy_render/src/mesh/allocator.rs index be282a2444185..f1c43944248a2 100644 --- a/crates/bevy_render/src/mesh/allocator.rs +++ b/crates/bevy_render/src/mesh/allocator.rs @@ -526,10 +526,6 @@ impl MeshAllocator { } /// A generic function that copies either vertex or index data into a slab. - #[expect( - clippy::too_many_arguments, - reason = "Used in systems to reduce the amount of code duplication" - )] fn copy_element_data( &mut self, mesh_id: &AssetId, diff --git a/crates/bevy_render/src/view/window/screenshot.rs b/crates/bevy_render/src/view/window/screenshot.rs index 87b6858558463..01460efcad59b 100644 --- a/crates/bevy_render/src/view/window/screenshot.rs +++ b/crates/bevy_render/src/view/window/screenshot.rs @@ -251,10 +251,6 @@ fn extract_screenshots( system_state.apply(&mut main_world); } -#[expect( - clippy::too_many_arguments, - reason = "Could be rewritten with less arguments using a QueryData-implementing struct, but doesn't need to be." -)] fn prepare_screenshots( targets: Res, mut prepared: ResMut, @@ -579,10 +575,6 @@ pub(crate) fn submit_screenshot_commands(world: &World, encoder: &mut CommandEnc } } -#[expect( - clippy::too_many_arguments, - reason = "Used in systems to reduce the amount of code duplication" -)] fn render_screenshot( encoder: &mut CommandEncoder, prepared: &RenderScreenshotsPrepared, diff --git a/crates/bevy_sprite/src/mesh2d/material.rs b/crates/bevy_sprite/src/mesh2d/material.rs index b350136ef080f..479cdec5ec024 100644 --- a/crates/bevy_sprite/src/mesh2d/material.rs +++ b/crates/bevy_sprite/src/mesh2d/material.rs @@ -467,10 +467,6 @@ pub const fn tonemapping_pipeline_key(tonemapping: Tonemapping) -> Mesh2dPipelin } } -#[expect( - clippy::too_many_arguments, - reason = "Could be rewritten with less arguments using a QueryData-implementing struct, but doesn't need to be." -)] pub fn queue_material2d_meshes( opaque_draw_functions: Res>, alpha_mask_draw_functions: Res>, diff --git a/crates/bevy_sprite/src/mesh2d/mesh.rs b/crates/bevy_sprite/src/mesh2d/mesh.rs index bd12a0412f711..aa7e41dbcbd4f 100644 --- a/crates/bevy_sprite/src/mesh2d/mesh.rs +++ b/crates/bevy_sprite/src/mesh2d/mesh.rs @@ -717,10 +717,6 @@ pub struct Mesh2dViewBindGroup { pub value: BindGroup, } -#[expect( - clippy::too_many_arguments, - reason = "Could be rewritten with less arguments using a QueryData-implementing struct, but doesn't need to be." -)] pub fn prepare_mesh2d_view_bind_groups( mut commands: Commands, render_device: Res, diff --git a/crates/bevy_sprite/src/picking_backend.rs b/crates/bevy_sprite/src/picking_backend.rs index 435137b22f91d..ab7fbcddef14b 100644 --- a/crates/bevy_sprite/src/picking_backend.rs +++ b/crates/bevy_sprite/src/picking_backend.rs @@ -54,10 +54,6 @@ impl Plugin for SpritePickingPlugin { } } -#[expect( - clippy::too_many_arguments, - reason = "Could be rewritten with less arguments using a QueryData-implementing struct, but doesn't need to be." -)] fn sprite_picking( pointers: Query<(&PointerId, &PointerLocation)>, cameras: Query<(Entity, &Camera, &GlobalTransform, &Projection)>, diff --git a/crates/bevy_sprite/src/render/mod.rs b/crates/bevy_sprite/src/render/mod.rs index 62a92b4896398..6d697dd64ab43 100644 --- a/crates/bevy_sprite/src/render/mod.rs +++ b/crates/bevy_sprite/src/render/mod.rs @@ -492,10 +492,6 @@ pub struct ImageBindGroups { values: HashMap, BindGroup>, } -#[expect( - clippy::too_many_arguments, - reason = "Could be rewritten with less arguments using a QueryData-implementing struct, but doesn't need to be." -)] pub fn queue_sprites( mut view_entities: Local, draw_functions: Res>, @@ -583,10 +579,6 @@ pub fn queue_sprites( } } -#[expect( - clippy::too_many_arguments, - reason = "Could be rewritten with less arguments using a QueryData-implementing struct, but doesn't need to be." -)] pub fn prepare_sprite_view_bind_groups( mut commands: Commands, render_device: Res, @@ -620,10 +612,6 @@ pub fn prepare_sprite_view_bind_groups( } } -#[expect( - clippy::too_many_arguments, - reason = "Could be rewritten with less arguments using a QueryData-implementing struct, but doesn't need to be." -)] pub fn prepare_sprite_image_bind_groups( mut commands: Commands, mut previous_len: Local, diff --git a/crates/bevy_text/src/pipeline.rs b/crates/bevy_text/src/pipeline.rs index 8b2e4e3eb364d..f16fbce0dce25 100644 --- a/crates/bevy_text/src/pipeline.rs +++ b/crates/bevy_text/src/pipeline.rs @@ -80,7 +80,6 @@ impl TextPipeline { /// Utilizes [`cosmic_text::Buffer`] to shape and layout text /// /// Negative or 0.0 font sizes will not be laid out. - #[allow(clippy::too_many_arguments)] pub fn update_buffer<'a>( &mut self, fonts: &Assets, @@ -207,7 +206,6 @@ impl TextPipeline { /// /// Produces a [`TextLayoutInfo`], containing [`PositionedGlyph`]s /// which contain information for rendering the text. - #[allow(clippy::too_many_arguments)] pub fn queue_text<'a>( &mut self, layout_info: &mut TextLayoutInfo, @@ -341,7 +339,6 @@ impl TextPipeline { /// /// Produces a [`TextMeasureInfo`] which can be used by a layout system /// to measure the text area on demand. - #[allow(clippy::too_many_arguments)] pub fn create_text_measure<'a>( &mut self, entity: Entity, diff --git a/crates/bevy_text/src/text2d.rs b/crates/bevy_text/src/text2d.rs index 893f127b8ba13..547ed6bfce745 100644 --- a/crates/bevy_text/src/text2d.rs +++ b/crates/bevy_text/src/text2d.rs @@ -221,7 +221,6 @@ pub fn extract_text2d_sprite( /// /// [`ResMut>`](Assets) -- This system only adds new [`Image`] assets. /// It does not modify or observe existing ones. -#[allow(clippy::too_many_arguments)] pub fn update_text2d_layout( mut last_scale_factor: Local, // Text items which should be reprocessed again, generally when the font hasn't loaded yet. diff --git a/crates/bevy_ui/src/focus.rs b/crates/bevy_ui/src/focus.rs index d53ba3d104fad..f85f153a40c5d 100644 --- a/crates/bevy_ui/src/focus.rs +++ b/crates/bevy_ui/src/focus.rs @@ -147,10 +147,6 @@ pub struct NodeQuery { /// The system that sets Interaction for all UI elements based on the mouse cursor activity /// /// Entities with a hidden [`ViewVisibility`] are always treated as released. -#[expect( - clippy::too_many_arguments, - reason = "Could be rewritten with less arguments using a QueryData-implementing struct, but doesn't need to be." -)] pub fn ui_focus_system( mut state: Local, camera_query: Query<(Entity, &Camera)>, diff --git a/crates/bevy_ui/src/layout/mod.rs b/crates/bevy_ui/src/layout/mod.rs index 4c213541c2b7f..1397b1c7553a2 100644 --- a/crates/bevy_ui/src/layout/mod.rs +++ b/crates/bevy_ui/src/layout/mod.rs @@ -96,10 +96,6 @@ struct CameraLayoutInfo { } /// Updates the UI's layout tree, computes the new layout geometry and then updates the sizes and transforms of all the UI nodes. -#[expect( - clippy::too_many_arguments, - reason = "Could be rewritten with less arguments using a QueryData-implementing struct, but doesn't need to be." -)] pub fn ui_layout_system( mut commands: Commands, mut buffers: Local, diff --git a/crates/bevy_ui/src/render/box_shadow.rs b/crates/bevy_ui/src/render/box_shadow.rs index 6ad450bcfbbbd..0d0aa7c9beb31 100644 --- a/crates/bevy_ui/src/render/box_shadow.rs +++ b/crates/bevy_ui/src/render/box_shadow.rs @@ -380,10 +380,6 @@ pub fn queue_shadows( } } -#[expect( - clippy::too_many_arguments, - reason = "Could be rewritten with less arguments using a QueryData-implementing struct, but doesn't need to be." -)] pub fn prepare_shadows( mut commands: Commands, render_device: Res, diff --git a/crates/bevy_ui/src/render/mod.rs b/crates/bevy_ui/src/render/mod.rs index d8af8300a912b..c087254ab8eef 100644 --- a/crates/bevy_ui/src/render/mod.rs +++ b/crates/bevy_ui/src/render/mod.rs @@ -835,10 +835,6 @@ pub struct ImageNodeBindGroups { pub values: HashMap, BindGroup>, } -#[expect( - clippy::too_many_arguments, - reason = "Could be rewritten with less arguments using a QueryData-implementing struct, but doesn't need to be." -)] pub fn prepare_uinodes( mut commands: Commands, render_device: Res, diff --git a/crates/bevy_ui/src/render/ui_material_pipeline.rs b/crates/bevy_ui/src/render/ui_material_pipeline.rs index 3176e06ed6b28..b8e1a7564a600 100644 --- a/crates/bevy_ui/src/render/ui_material_pipeline.rs +++ b/crates/bevy_ui/src/render/ui_material_pipeline.rs @@ -424,10 +424,6 @@ pub fn extract_ui_material_nodes( } } -#[expect( - clippy::too_many_arguments, - reason = "Could be rewritten with less arguments using a QueryData-implementing struct, but doesn't need to be." -)] pub fn prepare_uimaterial_nodes( mut commands: Commands, render_device: Res, @@ -608,10 +604,6 @@ impl RenderAsset for PreparedUiMaterial { } } -#[expect( - clippy::too_many_arguments, - reason = "Could be rewritten with less arguments using a QueryData-implementing struct, but doesn't need to be." -)] pub fn queue_ui_material_nodes( extracted_uinodes: Res>, draw_functions: Res>, diff --git a/crates/bevy_ui/src/render/ui_texture_slice_pipeline.rs b/crates/bevy_ui/src/render/ui_texture_slice_pipeline.rs index aa1a51b611094..f2d5eb243ed8d 100644 --- a/crates/bevy_ui/src/render/ui_texture_slice_pipeline.rs +++ b/crates/bevy_ui/src/render/ui_texture_slice_pipeline.rs @@ -377,10 +377,6 @@ pub fn queue_ui_slices( } } -#[expect( - clippy::too_many_arguments, - reason = "Could be rewritten with less arguments using a QueryData-implementing struct, but doesn't need to be." -)] pub fn prepare_ui_slices( mut commands: Commands, render_device: Res, diff --git a/crates/bevy_ui/src/stack.rs b/crates/bevy_ui/src/stack.rs index 40512edf881cf..b8031df371f69 100644 --- a/crates/bevy_ui/src/stack.rs +++ b/crates/bevy_ui/src/stack.rs @@ -38,10 +38,6 @@ impl ChildBufferCache { /// Create a list of root nodes from parentless entities and entities with a `GlobalZIndex` component. /// Then build the `UiStack` from a walk of the existing layout trees starting from each root node, /// filtering branches by `Without`so that we don't revisit nodes. -#[expect( - clippy::too_many_arguments, - reason = "Could be rewritten with less arguments using a QueryData-implementing struct, but doesn't need to be." -)] pub fn ui_stack_system( mut cache: Local, mut root_nodes: Local>, diff --git a/crates/bevy_ui/src/widget/text.rs b/crates/bevy_ui/src/widget/text.rs index ea114f8ef2f90..6706e25df8356 100644 --- a/crates/bevy_ui/src/widget/text.rs +++ b/crates/bevy_ui/src/widget/text.rs @@ -188,10 +188,6 @@ impl Measure for TextMeasure { } } -#[expect( - clippy::too_many_arguments, - reason = "Used in measure_text_system to make the function body easier to read" -)] #[inline] fn create_text_measure<'a>( entity: Entity, @@ -245,10 +241,6 @@ fn create_text_measure<'a>( /// is only able to detect that a `Text` component has changed and will regenerate the `Measure` on /// color changes. This can be expensive, particularly for large blocks of text, and the [`bypass_change_detection`](bevy_ecs::change_detection::DetectChangesMut::bypass_change_detection) /// method should be called when only changing the `Text`'s colors. -#[expect( - clippy::too_many_arguments, - reason = "Could be rewritten with less arguments using a QueryData-implementing struct, but doesn't need to be." -)] pub fn measure_text_system( mut scale_factors_buffer: Local>, mut last_scale_factors: Local>, @@ -316,10 +308,6 @@ pub fn measure_text_system( core::mem::swap(&mut *last_scale_factors, &mut *scale_factors_buffer); } -#[expect( - clippy::too_many_arguments, - reason = "Used in text_system to make the function body easier to read" -)] #[inline] fn queue_text( entity: Entity, @@ -391,10 +379,6 @@ fn queue_text( /// /// [`ResMut>`](Assets) -- This system only adds new [`Image`] assets. /// It does not modify or observe existing ones. The exception is when adding new glyphs to a [`bevy_text::FontAtlas`]. -#[expect( - clippy::too_many_arguments, - reason = "Could be rewritten with less arguments using a QueryData-implementing struct, but doesn't need to be." -)] pub fn text_system( mut textures: ResMut>, fonts: Res>, diff --git a/crates/bevy_winit/src/system.rs b/crates/bevy_winit/src/system.rs index 702abbf4e0ab2..2c481f960878c 100644 --- a/crates/bevy_winit/src/system.rs +++ b/crates/bevy_winit/src/system.rs @@ -202,10 +202,6 @@ pub fn create_monitors( }); } -#[expect( - clippy::too_many_arguments, - reason = "Could be rewritten with less arguments using a QueryData-implementing struct, but doesn't need to be." -)] pub(crate) fn despawn_windows( closing: Query>, mut closed: RemovedComponents, diff --git a/crates/bevy_winit/src/winit_windows.rs b/crates/bevy_winit/src/winit_windows.rs index 2ae9d610b9f8e..be4a7c21e5f28 100644 --- a/crates/bevy_winit/src/winit_windows.rs +++ b/crates/bevy_winit/src/winit_windows.rs @@ -43,10 +43,6 @@ pub struct WinitWindows { impl WinitWindows { /// Creates a `winit` window and associates it with our entity. - #[expect( - clippy::too_many_arguments, - reason = "Could be rewritten with less arguments using a QueryData-implementing struct, but doesn't need to be." - )] pub fn create_window( &mut self, event_loop: &ActiveEventLoop, diff --git a/examples/2d/mesh2d_manual.rs b/examples/2d/mesh2d_manual.rs index 9870d48e6589b..da35c2a53dbb3 100644 --- a/examples/2d/mesh2d_manual.rs +++ b/examples/2d/mesh2d_manual.rs @@ -360,7 +360,6 @@ pub fn extract_colored_mesh2d( } /// Queue the 2d meshes marked with [`ColoredMesh2d`] using our custom pipeline and draw function -#[allow(clippy::too_many_arguments)] pub fn queue_colored_mesh2d( transparent_draw_functions: Res>, colored_mesh2d_pipeline: Res, diff --git a/examples/3d/blend_modes.rs b/examples/3d/blend_modes.rs index b9f4343102898..830acfdb34160 100644 --- a/examples/3d/blend_modes.rs +++ b/examples/3d/blend_modes.rs @@ -246,7 +246,6 @@ impl Default for ExampleState { } } -#[allow(clippy::too_many_arguments)] fn example_control_system( mut materials: ResMut>, controllable: Query<(&MeshMaterial3d, &ExampleControls)>, diff --git a/examples/3d/deferred_rendering.rs b/examples/3d/deferred_rendering.rs index 2bca5a5be7b6b..0ac3c4db374b0 100644 --- a/examples/3d/deferred_rendering.rs +++ b/examples/3d/deferred_rendering.rs @@ -280,7 +280,6 @@ enum DefaultRenderMode { ForwardPrepass, } -#[allow(clippy::too_many_arguments)] fn switch_mode( mut text: Single<&mut Text>, mut commands: Commands, diff --git a/examples/3d/ssr.rs b/examples/3d/ssr.rs index 1cc3695354553..9d0026fbbcf98 100644 --- a/examples/3d/ssr.rs +++ b/examples/3d/ssr.rs @@ -338,7 +338,6 @@ fn move_camera( } // Adjusts app settings per user input. -#[allow(clippy::too_many_arguments)] fn adjust_app_settings( mut commands: Commands, keyboard_input: Res>, diff --git a/examples/3d/transmission.rs b/examples/3d/transmission.rs index b6a6425baf495..42f05ac2fac08 100644 --- a/examples/3d/transmission.rs +++ b/examples/3d/transmission.rs @@ -380,7 +380,6 @@ impl Default for ExampleState { } } -#[allow(clippy::too_many_arguments)] fn example_control_system( mut commands: Commands, mut materials: ResMut>, diff --git a/examples/helpers/camera_controller.rs b/examples/helpers/camera_controller.rs index 4a3e696db1c2e..5a862fb439304 100644 --- a/examples/helpers/camera_controller.rs +++ b/examples/helpers/camera_controller.rs @@ -99,7 +99,6 @@ Freecam Controls: } } -#[allow(clippy::too_many_arguments)] fn run_camera_controller( time: Res

for DrawMesh { batch_range.clone(), ); } - Some(( - indirect_parameters_offset, - indirect_parameters_count, - indirect_parameters_buffer, - )) => { - pass.multi_draw_indexed_indirect( - indirect_parameters_buffer, - indirect_parameters_offset, - indirect_parameters_count, - ); + PhaseItemExtraIndex::IndirectParametersIndex { + range: indirect_parameters_range, + batch_set_index, + } => { + // Look up the indirect parameters buffer, as well as + // the buffer we're going to use for + // `multi_draw_indexed_indirect_count` (if available). + let (Some(indirect_parameters_buffer), Some(batch_sets_buffer)) = ( + indirect_parameters_buffer.indexed_data_buffer(), + indirect_parameters_buffer.indexed_batch_sets_buffer(), + ) else { + warn!( + "Not rendering mesh because indexed indirect parameters buffer \ + wasn't present", + ); + return RenderCommandResult::Skip; + }; + + // Calculate the location of the indirect parameters + // within the buffer. + let indirect_parameters_offset = indirect_parameters_range.start as u64 + * size_of::() as u64; + let indirect_parameters_count = + indirect_parameters_range.end - indirect_parameters_range.start; + + // If we're using `multi_draw_indirect_count`, take the + // number of batches from the appropriate position in + // the batch sets buffer. Otherwise, supply the size of + // the batch set. + match batch_set_index { + Some(batch_set_index) => { + let count_offset = u32::from(batch_set_index) + * (size_of::() as u32); + pass.multi_draw_indexed_indirect_count( + indirect_parameters_buffer, + indirect_parameters_offset, + batch_sets_buffer, + count_offset as u64, + indirect_parameters_count, + ); + } + None => { + pass.multi_draw_indexed_indirect( + indirect_parameters_buffer, + indirect_parameters_offset, + indirect_parameters_count, + ); + } + } } } } - RenderMeshBufferInfo::NonIndexed => match indirect_parameters { - None => { + + RenderMeshBufferInfo::NonIndexed => match item.extra_index() { + PhaseItemExtraIndex::None | PhaseItemExtraIndex::DynamicOffset(_) => { pass.draw(vertex_buffer_slice.range, batch_range.clone()); } - Some(( - indirect_parameters_offset, - indirect_parameters_count, - indirect_parameters_buffer, - )) => { - pass.multi_draw_indirect( - indirect_parameters_buffer, - indirect_parameters_offset, - indirect_parameters_count, - ); + PhaseItemExtraIndex::IndirectParametersIndex { + range: indirect_parameters_range, + batch_set_index, + } => { + // Look up the indirect parameters buffer, as well as the + // buffer we're going to use for + // `multi_draw_indirect_count` (if available). + let (Some(indirect_parameters_buffer), Some(batch_sets_buffer)) = ( + indirect_parameters_buffer.non_indexed_data_buffer(), + indirect_parameters_buffer.non_indexed_batch_sets_buffer(), + ) else { + warn!( + "Not rendering mesh because non-indexed indirect parameters buffer \ + wasn't present" + ); + return RenderCommandResult::Skip; + }; + + // Calculate the location of the indirect parameters within + // the buffer. + let indirect_parameters_offset = indirect_parameters_range.start as u64 + * size_of::() as u64; + let indirect_parameters_count = + indirect_parameters_range.end - indirect_parameters_range.start; + + // If we're using `multi_draw_indirect_count`, take the + // number of batches from the appropriate position in the + // batch sets buffer. Otherwise, supply the size of the + // batch set. + match batch_set_index { + Some(batch_set_index) => { + let count_offset = + u32::from(batch_set_index) * (size_of::() as u32); + pass.multi_draw_indirect_count( + indirect_parameters_buffer, + indirect_parameters_offset, + batch_sets_buffer, + count_offset as u64, + indirect_parameters_count, + ); + } + None => { + pass.multi_draw_indirect( + indirect_parameters_buffer, + indirect_parameters_offset, + indirect_parameters_count, + ); + } + } } }, } diff --git a/crates/bevy_pbr/src/render/mesh_preprocess.wgsl b/crates/bevy_pbr/src/render/mesh_preprocess.wgsl index 74f527a374949..df73454a3e880 100644 --- a/crates/bevy_pbr/src/render/mesh_preprocess.wgsl +++ b/crates/bevy_pbr/src/render/mesh_preprocess.wgsl @@ -8,29 +8,10 @@ // so that TAA works. #import bevy_pbr::mesh_types::{Mesh, MESH_FLAGS_NO_FRUSTUM_CULLING_BIT} -#import bevy_pbr::mesh_preprocess_types::IndirectParameters +#import bevy_pbr::mesh_preprocess_types::{MeshInput, IndirectParametersMetadata} #import bevy_render::maths #import bevy_render::view::View -// Per-frame data that the CPU supplies to the GPU. -struct MeshInput { - // The model transform. - world_from_local: mat3x4, - // The lightmap UV rect, packed into 64 bits. - lightmap_uv_rect: vec2, - // Various flags. - flags: u32, - // The index of this mesh's `MeshInput` in the `previous_input` array, if - // applicable. If not present, this is `u32::MAX`. - previous_input_index: u32, - first_vertex_index: u32, - current_skin_index: u32, - previous_skin_index: u32, - // Low 16 bits: index of the material inside the bind group data. - // High 16 bits: index of the lightmap in the binding array. - material_and_lightmap_bind_group_slot: u32, -} - // Information about each mesh instance needed to cull it on GPU. // // At the moment, this just consists of its axis-aligned bounding box (AABB). @@ -68,7 +49,8 @@ struct PreprocessWorkItem { #ifdef INDIRECT // The array of indirect parameters for drawcalls. -@group(0) @binding(4) var indirect_parameters: array; +@group(0) @binding(4) var indirect_parameters_metadata: + array; #endif #ifdef FRUSTUM_CULLING @@ -167,28 +149,15 @@ fn main(@builtin(global_invocation_id) global_invocation_id: vec3) { } // Figure out the output index. In indirect mode, this involves bumping the - // instance index in the indirect parameters structure. Otherwise, this - // index was directly supplied to us. + // instance index in the indirect parameters metadata, which + // `build_indirect_params.wgsl` will use to generate the actual indirect + // parameters. Otherwise, this index was directly supplied to us. #ifdef INDIRECT let batch_output_index = - atomicAdd(&indirect_parameters[indirect_parameters_index].instance_count, 1u); - let mesh_output_index = output_index + batch_output_index; - - // If this is the first mesh in the batch, write the first instance index - // into the indirect parameters. - // - // We could have done this on CPU, but when we start retaining indirect - // parameters that will no longer be desirable, as the index of the first - // instance will change from frame to frame and we won't want the CPU to - // have to keep updating it. - if (batch_output_index == 0u) { - if (indirect_parameters[indirect_parameters_index].first_instance == 0xffffffffu) { - indirect_parameters[indirect_parameters_index].base_vertex_or_first_instance = - mesh_output_index; - } else { - indirect_parameters[indirect_parameters_index].first_instance = mesh_output_index; - } - } + atomicAdd(&indirect_parameters_metadata[indirect_parameters_index].instance_count, 1u); + let mesh_output_index = + indirect_parameters_metadata[indirect_parameters_index].base_output_index + + batch_output_index; #else // INDIRECT let mesh_output_index = output_index; #endif // INDIRECT diff --git a/crates/bevy_pbr/src/render/mesh_preprocess_types.wgsl b/crates/bevy_pbr/src/render/mesh_preprocess_types.wgsl index 5314e75ce34de..974a9d303aa6d 100644 --- a/crates/bevy_pbr/src/render/mesh_preprocess_types.wgsl +++ b/crates/bevy_pbr/src/render/mesh_preprocess_types.wgsl @@ -2,18 +2,97 @@ #define_import_path bevy_pbr::mesh_preprocess_types -// The `wgpu` indirect parameters structure. This is a union of two structures. -// For more information, see the corresponding comment in -// `gpu_preprocessing.rs`. -struct IndirectParameters { - // `vertex_count` or `index_count`. - vertex_count_or_index_count: u32, - // `instance_count` in both structures. - instance_count: atomic, - // `first_vertex` or `first_index`. - first_vertex_or_first_index: u32, - // `base_vertex` or `first_instance`. - base_vertex_or_first_instance: u32, - // A read-only copy of `instance_index`. +// Per-frame data that the CPU supplies to the GPU. +struct MeshInput { + // The model transform. + world_from_local: mat3x4, + // The lightmap UV rect, packed into 64 bits. + lightmap_uv_rect: vec2, + // A set of bitflags corresponding to `MeshFlags` on the Rust side. See the + // `MESH_FLAGS_` flags in `mesh_types.wgsl` for a list of these. + flags: u32, + // The index of this mesh's `MeshInput` in the `previous_input` array, if + // applicable. If not present, this is `u32::MAX`. + previous_input_index: u32, + // The index of the first vertex in the vertex slab. + first_vertex_index: u32, + // The index of the first vertex index in the index slab. + // + // If this mesh isn't indexed, this value is ignored. + first_index_index: u32, + // For indexed meshes, the number of indices that this mesh has; for + // non-indexed meshes, the number of vertices that this mesh consists of. + index_count: u32, + current_skin_index: u32, + previous_skin_index: u32, + // Low 16 bits: index of the material inside the bind group data. + // High 16 bits: index of the lightmap in the binding array. + material_and_lightmap_bind_group_slot: u32, +} + +// The `wgpu` indirect parameters structure for indexed meshes. +// +// The `build_indirect_params.wgsl` shader generates these. +struct IndirectParametersIndexed { + // The number of indices that this mesh has. + index_count: u32, + // The number of instances we are to draw. + instance_count: u32, + // The offset of the first index for this mesh in the index buffer slab. + first_index: u32, + // The offset of the first vertex for this mesh in the vertex buffer slab. + base_vertex: u32, + // The index of the first mesh instance in the `Mesh` buffer. + first_instance: u32, +} + +// The `wgpu` indirect parameters structure for non-indexed meshes. +// +// The `build_indirect_params.wgsl` shader generates these. +struct IndirectParametersNonIndexed { + // The number of vertices that this mesh has. + vertex_count: u32, + // The number of instances we are to draw. + instance_count: u32, + // The offset of the first vertex for this mesh in the vertex buffer slab. + base_vertex: u32, + // The index of the first mesh instance in the `Mesh` buffer. first_instance: u32, } + +// Information needed to generate the `IndirectParametersIndexed` and +// `IndirectParametersNonIndexed` draw commands. +struct IndirectParametersMetadata { + // The index of the mesh in the `MeshInput` buffer. + mesh_index: u32, + // The index of the first instance corresponding to this batch in the `Mesh` + // buffer. + base_output_index: u32, + // The index of the batch set in the `IndirectBatchSet` buffer. + batch_set_index: u32, + // The number of instances that are to be drawn. + // + // The `mesh_preprocess.wgsl` shader determines this, and the + // `build_indirect_params.wgsl` shader copies this value into the indirect + // draw command. + instance_count: atomic, +} + +// Information about each batch set. +// +// A *batch set* is a set of meshes that might be multi-drawn together. +// +// The CPU creates this structure, and the `build_indirect_params.wgsl` shader +// modifies it. If `multi_draw_indirect_count` is in use, the GPU reads this +// value when multi-drawing a batch set in order to determine how many commands +// make up the batch set. +struct IndirectBatchSet { + // The number of commands that make up this batch set. + // + // The CPU initializes this value to zero. The `build_indirect_params.wgsl` + // shader increments this value as it processes batches. + indirect_parameters_count: atomic, + // The offset of the first batch corresponding to this batch set within the + // `IndirectParametersIndexed` or `IndirectParametersNonIndexed` arrays. + indirect_parameters_base: u32, +} diff --git a/crates/bevy_render/src/batching/gpu_preprocessing.rs b/crates/bevy_render/src/batching/gpu_preprocessing.rs index a98afc02de48a..2e893616f9294 100644 --- a/crates/bevy_render/src/batching/gpu_preprocessing.rs +++ b/crates/bevy_render/src/batching/gpu_preprocessing.rs @@ -1,5 +1,7 @@ //! Batching functionality when GPU preprocessing is in use. +use core::any::TypeId; + use bevy_app::{App, Plugin}; use bevy_ecs::{ entity::{Entity, EntityHashMap}, @@ -9,7 +11,7 @@ use bevy_ecs::{ world::{FromWorld, World}, }; use bevy_encase_derive::ShaderType; -use bevy_utils::default; +use bevy_utils::{default, TypeIdMap}; use bytemuck::{Pod, Zeroable}; use nonmax::NonMaxU32; use tracing::error; @@ -18,9 +20,9 @@ use wgpu::{BindingResource, BufferUsages, DownlevelFlags, Features}; use crate::{ render_phase::{ BinnedPhaseItem, BinnedRenderPhaseBatch, BinnedRenderPhaseBatchSet, - BinnedRenderPhaseBatchSets, CachedRenderPipelinePhaseItem, PhaseItemExtraIndex, - SortedPhaseItem, SortedRenderPhase, UnbatchableBinnedEntityIndices, ViewBinnedRenderPhases, - ViewSortedRenderPhases, + BinnedRenderPhaseBatchSets, CachedRenderPipelinePhaseItem, PhaseItemBatchSetKey as _, + PhaseItemExtraIndex, SortedPhaseItem, SortedRenderPhase, UnbatchableBinnedEntityIndices, + ViewBinnedRenderPhases, ViewSortedRenderPhases, }, render_resource::{Buffer, BufferVec, GpuArrayBufferable, RawBufferVec, UninitBufferVec}, renderer::{RenderAdapter, RenderDevice, RenderQueue}, @@ -39,10 +41,14 @@ impl Plugin for BatchingPlugin { }; render_app - .insert_resource(IndirectParametersBuffer::new()) + .insert_resource(IndirectParametersBuffers::new()) + .add_systems( + Render, + write_indirect_parameters_buffers.in_set(RenderSet::PrepareResourcesFlush), + ) .add_systems( Render, - write_indirect_parameters_buffer.in_set(RenderSet::PrepareResourcesFlush), + clear_indirect_parameters_buffers.in_set(RenderSet::ManageViews), ); } @@ -137,7 +143,7 @@ where /// corresponds to each instance. /// /// This is keyed off each view. Each view has a separate buffer. - pub work_item_buffers: EntityHashMap, + pub work_item_buffers: EntityHashMap>, /// The uniform data inputs for the current frame. /// @@ -265,16 +271,68 @@ where } /// The buffer of GPU preprocessing work items for a single view. -pub struct PreprocessWorkItemBuffer { - /// The buffer of work items. - pub buffer: BufferVec, - /// True if we're drawing directly instead of indirectly. - pub no_indirect_drawing: bool, +pub enum PreprocessWorkItemBuffers { + /// The work items we use if we aren't using indirect drawing. + /// + /// Because we don't have to separate indexed from non-indexed meshes in + /// direct mode, we only have a single buffer here. + Direct(BufferVec), + + /// The buffer of work items we use if we are using indirect drawing. + /// + /// We need to separate out indexed meshes from non-indexed meshes in this + /// case because the indirect parameters for these two types of meshes have + /// different sizes. + Indirect { + /// The buffer of work items corresponding to indexed meshes. + indexed: BufferVec, + /// The buffer of work items corresponding to non-indexed meshes. + non_indexed: BufferVec, + }, +} + +impl PreprocessWorkItemBuffers { + /// Creates a new set of buffers. + /// + /// `no_indirect_drawing` specifies whether we're drawing directly or + /// indirectly. + pub fn new(no_indirect_drawing: bool) -> Self { + if no_indirect_drawing { + PreprocessWorkItemBuffers::Direct(BufferVec::new(BufferUsages::STORAGE)) + } else { + PreprocessWorkItemBuffers::Indirect { + indexed: BufferVec::new(BufferUsages::STORAGE), + non_indexed: BufferVec::new(BufferUsages::STORAGE), + } + } + } + + /// Adds a new work item to the appropriate buffer. + /// + /// `indexed` specifies whether the work item corresponds to an indexed + /// mesh. + pub fn push(&mut self, indexed: bool, preprocess_work_item: PreprocessWorkItem) { + match *self { + PreprocessWorkItemBuffers::Direct(ref mut buffer) => { + buffer.push(preprocess_work_item); + } + PreprocessWorkItemBuffers::Indirect { + indexed: ref mut indexed_buffer, + non_indexed: ref mut non_indexed_buffer, + } => { + if indexed { + indexed_buffer.push(preprocess_work_item); + } else { + non_indexed_buffer.push(preprocess_work_item); + } + } + } + } } /// One invocation of the preprocessing shader: i.e. one mesh instance in a /// view. -#[derive(Clone, Copy, Pod, Zeroable, ShaderType)] +#[derive(Clone, Copy, Default, Pod, Zeroable, ShaderType)] #[repr(C)] pub struct PreprocessWorkItem { /// The index of the batch input data in the input buffer that the shader @@ -284,112 +342,378 @@ pub struct PreprocessWorkItem { /// In direct mode, this is the index of the uniform. In indirect mode, this /// is the first index uniform in the batch set. pub output_index: u32, - /// The index of the [`IndirectParameters`] in the - /// [`IndirectParametersBuffer`]. + /// The index of the [`IndirectParametersMetadata`] in the + /// `IndirectParametersBuffers::indexed_metadata` or + /// `IndirectParametersBuffers::non_indexed_metadata`. pub indirect_parameters_index: u32, } -/// The `wgpu` indirect parameters structure. +/// The `wgpu` indirect parameters structure that specifies a GPU draw command. /// -/// This is actually a union of the two following structures: +/// This is the variant for indexed meshes. We generate the instances of this +/// structure in the `build_indirect_params.wgsl` compute shader. +#[derive(Clone, Copy, Pod, Zeroable, ShaderType)] +#[repr(C)] +pub struct IndirectParametersIndexed { + /// The number of indices that this mesh has. + pub index_count: u32, + /// The number of instances we are to draw. + pub instance_count: u32, + /// The offset of the first index for this mesh in the index buffer slab. + pub first_index: u32, + /// The offset of the first vertex for this mesh in the vertex buffer slab. + pub base_vertex: u32, + /// The index of the first mesh instance in the `MeshUniform` buffer. + pub first_instance: u32, +} + +/// The `wgpu` indirect parameters structure that specifies a GPU draw command. /// -/// ``` -/// #[repr(C)] -/// struct ArrayIndirectParameters { -/// vertex_count: u32, -/// instance_count: u32, -/// first_vertex: u32, -/// first_instance: u32, -/// } +/// This is the variant for non-indexed meshes. We generate the instances of +/// this structure in the `build_indirect_params.wgsl` compute shader. +#[derive(Clone, Copy, Pod, Zeroable, ShaderType)] +#[repr(C)] +pub struct IndirectParametersNonIndexed { + /// The number of vertices that this mesh has. + pub vertex_count: u32, + /// The number of instances we are to draw. + pub instance_count: u32, + /// The offset of the first vertex for this mesh in the vertex buffer slab. + pub base_vertex: u32, + /// The index of the first mesh instance in the `Mesh` buffer. + pub first_instance: u32, +} + +/// A structure, shared between CPU and GPU, that records how many instances of +/// each mesh are actually to be drawn. /// -/// #[repr(C)] -/// struct ElementIndirectParameters { -/// index_count: u32, -/// instance_count: u32, -/// first_vertex: u32, -/// base_vertex: u32, -/// first_instance: u32, -/// } -/// ``` +/// The CPU writes to this structure in order to initialize the fields other +/// than [`Self::instance_count`]. The GPU mesh preprocessing shader increments +/// the [`Self::instance_count`] as it determines that meshes are visible. The +/// indirect parameter building shader reads this metadata in order to construct +/// the indirect draw parameters. /// -/// We actually generally treat these two variants identically in code. To do -/// that, we make the following two observations: +/// Each batch will have one instance of this structure. +#[derive(Clone, Copy, Default, Pod, Zeroable, ShaderType)] +#[repr(C)] +pub struct IndirectParametersMetadata { + /// The index of the mesh in the array of `MeshInputUniform`s. + pub mesh_index: u32, + + /// The index of the first instance of this mesh in the array of + /// `MeshUniform`s. + /// + /// Note that this is the *first* output index in this batch. Since each + /// instance of this structure refers to arbitrarily many instances, the + /// `MeshUniform`s corresponding to this batch span the indices + /// `base_output_index..(base_output_index + instance_count)`. + pub base_output_index: u32, + + /// The index of the batch set that this batch belongs to in the + /// [`IndirectBatchSet`] buffer. + /// + /// A *batch set* is a set of meshes that may be multi-drawn together. + /// Multiple batches (and therefore multiple instances of + /// [`IndirectParametersMetadata`] structures) can be part of the same batch + /// set. + pub batch_set_index: u32, + + /// The number of instances that have been judged potentially visible. + /// + /// The CPU sets this value to 0, and the GPU mesh preprocessing shader + /// increments it as it culls mesh instances. + pub instance_count: u32, +} + +/// A structure, shared between CPU and GPU, that holds the number of on-GPU +/// indirect draw commands for each *batch set*. /// -/// 1. `instance_count` is in the same place in both structures. So we can -/// access it regardless of the structure we're looking at. +/// A *batch set* is a set of meshes that may be multi-drawn together. /// -/// 2. The second structure is one word larger than the first. Thus we need to -/// pad out the first structure by one word in order to place both structures in -/// an array. If we pad out `ArrayIndirectParameters` by copying the -/// `first_instance` field into the padding, then the resulting union structure -/// will always have a read-only copy of `first_instance` in the final word. We -/// take advantage of this in the shader to reduce branching. -#[derive(Clone, Copy, Pod, Zeroable, ShaderType)] +/// If the current hardware and driver support `multi_draw_indirect_count`, the +/// indirect parameters building shader increments +/// [`Self::indirect_parameters_count`] as it generates indirect parameters. The +/// `multi_draw_indirect_count` command reads +/// [`Self::indirect_parameters_count`] in order to determine how many commands +/// belong to each batch set. +#[derive(Clone, Copy, Default, Pod, Zeroable, ShaderType)] #[repr(C)] -pub struct IndirectParameters { - /// For `ArrayIndirectParameters`, `vertex_count`; for - /// `ElementIndirectParameters`, `index_count`. - pub vertex_or_index_count: u32, +pub struct IndirectBatchSet { + /// The number of indirect parameter commands (i.e. batches) in this batch + /// set. + /// + /// The CPU sets this value to 0 before uploading this structure to GPU. The + /// indirect parameters building shader increments this value as it creates + /// indirect parameters. Then the `multi_draw_indirect_count` command reads + /// this value in order to determine how many indirect draw commands to + /// process. + pub indirect_parameters_count: u32, + + /// The offset within the `IndirectParametersBuffers::indexed_data` or + /// `IndirectParametersBuffers::non_indexed_data` of the first indirect draw + /// command for this batch set. + /// + /// The CPU fills out this value. + pub indirect_parameters_base: u32, +} - /// The number of instances we're going to draw. +/// The buffers containing all the information that indirect draw commands +/// (`multi_draw_indirect`, `multi_draw_indirect_count`) use to draw the scene. +/// +/// In addition to the indirect draw buffers themselves, this structure contains +/// the buffers that store [`IndirectParametersMetadata`], which are the +/// structures that culling writes to so that the indirect parameter building +/// pass can determine how many meshes are actually to be drawn. +/// +/// These buffers will remain empty if indirect drawing isn't in use. +#[derive(Resource)] +pub struct IndirectParametersBuffers { + /// The GPU buffer that stores the indirect draw parameters for non-indexed + /// meshes. /// - /// This field is in the same place in both structures. - pub instance_count: u32, + /// The indirect parameters building shader writes to this buffer, while the + /// `multi_draw_indirect` or `multi_draw_indirect_count` commands read from + /// it to perform the draws. + non_indexed_data: UninitBufferVec, - /// For `ArrayIndirectParameters`, `first_vertex`; for - /// `ElementIndirectParameters`, `first_index`. - pub first_vertex_or_first_index: u32, + /// The GPU buffer that holds the data used to construct indirect draw + /// parameters for non-indexed meshes. + /// + /// The GPU mesh preprocessing shader writes to this buffer, and the + /// indirect parameters building shader reads this buffer to construct the + /// indirect draw parameters. + non_indexed_metadata: RawBufferVec, - /// For `ArrayIndirectParameters`, `first_instance`; for - /// `ElementIndirectParameters`, `base_vertex`. - pub base_vertex_or_first_instance: u32, + /// The GPU buffer that holds the number of indirect draw commands for each + /// phase of each view, for non-indexed meshes. + /// + /// The indirect parameters building shader writes to this buffer, and the + /// `multi_draw_indirect_count` command reads from it in order to know how + /// many indirect draw commands to process. + non_indexed_batch_sets: RawBufferVec, - /// For `ArrayIndirectParameters`, this is padding; for - /// `ElementIndirectParameters`, this is `first_instance`. + /// The GPU buffer that stores the indirect draw parameters for indexed + /// meshes. /// - /// Conventionally, we copy `first_instance` into this field when padding - /// out `ArrayIndirectParameters`. That way, shader code can read this value - /// at the same place, regardless of the specific structure this represents. - pub first_instance: u32, -} + /// The indirect parameters building shader writes to this buffer, while the + /// `multi_draw_indirect` or `multi_draw_indirect_count` commands read from + /// it to perform the draws. + indexed_data: UninitBufferVec, -/// The buffer containing the list of [`IndirectParameters`], for draw commands. -#[derive(Resource)] -pub struct IndirectParametersBuffer { - /// The actual buffer. - buffer: RawBufferVec, + /// The GPU buffer that holds the data used to construct indirect draw + /// parameters for indexed meshes. + /// + /// The GPU mesh preprocessing shader writes to this buffer, and the + /// indirect parameters building shader reads this buffer to construct the + /// indirect draw parameters. + indexed_metadata: RawBufferVec, + + /// The GPU buffer that holds the number of indirect draw commands for each + /// phase of each view, for indexed meshes. + /// + /// The indirect parameters building shader writes to this buffer, and the + /// `multi_draw_indirect_count` command reads from it in order to know how + /// many indirect draw commands to process. + indexed_batch_sets: RawBufferVec, } -impl IndirectParametersBuffer { - /// Creates the indirect parameters buffer. - pub fn new() -> IndirectParametersBuffer { - IndirectParametersBuffer { - buffer: RawBufferVec::new(BufferUsages::STORAGE | BufferUsages::INDIRECT), +impl IndirectParametersBuffers { + /// Creates the indirect parameters buffers. + pub fn new() -> IndirectParametersBuffers { + IndirectParametersBuffers { + non_indexed_data: UninitBufferVec::new(BufferUsages::STORAGE | BufferUsages::INDIRECT), + non_indexed_metadata: RawBufferVec::new(BufferUsages::STORAGE), + non_indexed_batch_sets: RawBufferVec::new( + BufferUsages::STORAGE | BufferUsages::INDIRECT, + ), + indexed_data: UninitBufferVec::new(BufferUsages::STORAGE | BufferUsages::INDIRECT), + indexed_metadata: RawBufferVec::new(BufferUsages::STORAGE), + indexed_batch_sets: RawBufferVec::new(BufferUsages::STORAGE | BufferUsages::INDIRECT), } } - /// Returns the underlying GPU buffer. + /// Returns the GPU buffer that stores the indirect draw parameters for + /// indexed meshes. + /// + /// The indirect parameters building shader writes to this buffer, while the + /// `multi_draw_indirect` or `multi_draw_indirect_count` commands read from + /// it to perform the draws. + #[inline] + pub fn indexed_data_buffer(&self) -> Option<&Buffer> { + self.indexed_data.buffer() + } + + /// Returns the GPU buffer that holds the data used to construct indirect + /// draw parameters for indexed meshes. + /// + /// The GPU mesh preprocessing shader writes to this buffer, and the + /// indirect parameters building shader reads this buffer to construct the + /// indirect draw parameters. + #[inline] + pub fn indexed_metadata_buffer(&self) -> Option<&Buffer> { + self.indexed_metadata.buffer() + } + + /// Returns the GPU buffer that holds the number of indirect draw commands + /// for each phase of each view, for indexed meshes. + /// + /// The indirect parameters building shader writes to this buffer, and the + /// `multi_draw_indirect_count` command reads from it in order to know how + /// many indirect draw commands to process. + #[inline] + pub fn indexed_batch_sets_buffer(&self) -> Option<&Buffer> { + self.indexed_batch_sets.buffer() + } + + /// Returns the GPU buffer that stores the indirect draw parameters for + /// non-indexed meshes. + /// + /// The indirect parameters building shader writes to this buffer, while the + /// `multi_draw_indirect` or `multi_draw_indirect_count` commands read from + /// it to perform the draws. + #[inline] + pub fn non_indexed_data_buffer(&self) -> Option<&Buffer> { + self.non_indexed_data.buffer() + } + + /// Returns the GPU buffer that holds the data used to construct indirect + /// draw parameters for non-indexed meshes. + /// + /// The GPU mesh preprocessing shader writes to this buffer, and the + /// indirect parameters building shader reads this buffer to construct the + /// indirect draw parameters. + #[inline] + pub fn non_indexed_metadata_buffer(&self) -> Option<&Buffer> { + self.non_indexed_metadata.buffer() + } + + /// Returns the GPU buffer that holds the number of indirect draw commands + /// for each phase of each view, for non-indexed meshes. + /// + /// The indirect parameters building shader writes to this buffer, and the + /// `multi_draw_indirect_count` command reads from it in order to know how + /// many indirect draw commands to process. #[inline] - pub fn buffer(&self) -> Option<&Buffer> { - self.buffer.buffer() + pub fn non_indexed_batch_sets_buffer(&self) -> Option<&Buffer> { + self.non_indexed_batch_sets.buffer() } - /// Adds a new set of indirect parameters to the buffer. - pub fn allocate(&mut self, count: u32) -> u32 { - let length = self.buffer.len(); - self.buffer.reserve_internal(count as usize); + /// Reserves space for `count` new batches corresponding to indexed meshes. + /// + /// This allocates in both the [`Self::indexed_metadata`] and + /// [`Self::indexed_data`] buffers. + fn allocate_indexed(&mut self, count: u32) -> u32 { + let length = self.indexed_data.len(); + self.indexed_metadata.reserve_internal(count as usize); for _ in 0..count { - self.buffer.push(Zeroable::zeroed()); + self.indexed_data.add(); + self.indexed_metadata + .push(IndirectParametersMetadata::default()); } length as u32 } - pub fn set(&mut self, index: u32, value: IndirectParameters) { - self.buffer.set(index, value); + /// Reserves space for `count` new batches corresponding to non-indexed + /// meshes. + /// + /// This allocates in both the [`Self::non_indexed_metadata`] and + /// [`Self::non_indexed_data`] buffers. + fn allocate_non_indexed(&mut self, count: u32) -> u32 { + let length = self.non_indexed_data.len(); + self.non_indexed_metadata.reserve_internal(count as usize); + for _ in 0..count { + self.non_indexed_data.add(); + self.non_indexed_metadata + .push(IndirectParametersMetadata::default()); + } + length as u32 + } + + /// Reserves space for `count` new batches. + /// + /// The `indexed` parameter specifies whether the meshes that these batches + /// correspond to are indexed or not. + pub fn allocate(&mut self, indexed: bool, count: u32) -> u32 { + if indexed { + self.allocate_indexed(count) + } else { + self.allocate_non_indexed(count) + } + } + + /// Initializes the batch corresponding to an indexed mesh at the given + /// index with the given [`IndirectParametersMetadata`]. + pub fn set_indexed(&mut self, index: u32, value: IndirectParametersMetadata) { + self.indexed_metadata.set(index, value); + } + + /// Initializes the batch corresponding to a non-indexed mesh at the given + /// index with the given [`IndirectParametersMetadata`]. + pub fn set_non_indexed(&mut self, index: u32, value: IndirectParametersMetadata) { + self.non_indexed_metadata.set(index, value); + } + + /// Returns the number of batches currently allocated. + /// + /// The `indexed` parameter specifies whether the meshes that these batches + /// correspond to are indexed or not. + fn batch_count(&self, indexed: bool) -> usize { + if indexed { + self.indexed_batch_count() + } else { + self.non_indexed_batch_count() + } + } + + /// Returns the number of batches corresponding to indexed meshes that are + /// currently allocated. + #[inline] + pub fn indexed_batch_count(&self) -> usize { + self.indexed_data.len() + } + + /// Returns the number of batches corresponding to non-indexed meshes that + /// are currently allocated. + #[inline] + pub fn non_indexed_batch_count(&self) -> usize { + self.non_indexed_data.len() + } + + /// Returns the number of batch sets currently allocated. + /// + /// The `indexed` parameter specifies whether the meshes that these batch + /// sets correspond to are indexed or not. + pub fn batch_set_count(&self, indexed: bool) -> usize { + if indexed { + self.indexed_batch_sets.len() + } else { + self.non_indexed_batch_sets.len() + } + } + + /// Adds a new batch set to `Self::indexed_batch_sets` or + /// `Self::non_indexed_batch_sets` as appropriate. + /// + /// `indexed` specifies whether the meshes that these batch sets correspond + /// to are indexed or not. `indirect_parameters_base` specifies the offset + /// within `Self::indexed_data` or `Self::non_indexed_data` of the first + /// batch in this batch set. + pub fn add_batch_set(&mut self, indexed: bool, indirect_parameters_base: u32) { + if indexed { + self.indexed_batch_sets.push(IndirectBatchSet { + indirect_parameters_base, + indirect_parameters_count: 0, + }); + } else { + self.non_indexed_batch_sets.push(IndirectBatchSet { + indirect_parameters_base, + indirect_parameters_count: 0, + }); + } } } -impl Default for IndirectParametersBuffer { +impl Default for IndirectParametersBuffers { fn default() -> Self { Self::new() } @@ -454,8 +778,20 @@ where /// Clears out the buffers in preparation for a new frame. pub fn clear(&mut self) { self.data_buffer.clear(); - for work_item_buffer in self.work_item_buffers.values_mut() { - work_item_buffer.buffer.clear(); + + for view_work_item_buffers in self.work_item_buffers.values_mut() { + for phase_work_item_buffers in view_work_item_buffers.values_mut() { + match *phase_work_item_buffers { + PreprocessWorkItemBuffers::Direct(ref mut buffer_vec) => buffer_vec.clear(), + PreprocessWorkItemBuffers::Indirect { + ref mut indexed, + ref mut non_indexed, + } => { + indexed.clear(); + non_indexed.clear(); + } + } + } } } } @@ -483,8 +819,11 @@ where /// The index of the first instance in this batch in the instance buffer. instance_start_index: u32, + /// True if the mesh in question has an index buffer; false otherwise. + indexed: bool, + /// The index of the indirect parameters for this batch in the - /// [`IndirectParametersBuffer`]. + /// [`IndirectParametersBuffers`]. /// /// If CPU culling is being used, then this will be `None`. indirect_parameters_index: Option, @@ -505,8 +844,12 @@ where /// /// `instance_end_index` is the index of the last instance in this batch /// plus one. - fn flush(self, instance_end_index: u32, phase: &mut SortedRenderPhase) - where + fn flush( + self, + instance_end_index: u32, + phase: &mut SortedRenderPhase, + indirect_parameters_buffers: &mut IndirectParametersBuffers, + ) where I: CachedRenderPipelinePhaseItem + SortedPhaseItem, { let (batch_range, batch_extra_index) = @@ -514,6 +857,11 @@ where *batch_range = self.instance_start_index..instance_end_index; *batch_extra_index = PhaseItemExtraIndex::maybe_indirect_parameters_index(self.indirect_parameters_index); + + if let Some(indirect_parameters_index) = self.indirect_parameters_index { + indirect_parameters_buffers + .add_batch_set(self.indexed, indirect_parameters_index.into()); + } } } @@ -559,7 +907,7 @@ pub fn delete_old_work_item_buffers( /// trying to combine the draws into a batch. pub fn batch_and_prepare_sorted_render_phase( gpu_array_buffer: ResMut>, - mut indirect_parameters_buffer: ResMut, + mut indirect_parameters_buffers: ResMut, mut sorted_render_phases: ResMut>, mut views: Query<(Entity, &ExtractedView, Has)>, system_param_item: StaticSystemParam, @@ -580,24 +928,15 @@ pub fn batch_and_prepare_sorted_render_phase( }; // Create the work item buffer if necessary. - let work_item_buffer = - work_item_buffers - .entry(view) - .or_insert_with(|| PreprocessWorkItemBuffer { - buffer: BufferVec::new(BufferUsages::STORAGE), - no_indirect_drawing, - }); + let work_item_buffer = work_item_buffers + .entry(view) + .or_insert_with(TypeIdMap::default) + .entry(TypeId::of::()) + .or_insert_with(|| PreprocessWorkItemBuffers::new(no_indirect_drawing)); // Walk through the list of phase items, building up batches as we go. let mut batch: Option> = None; - // Allocate the indirect parameters if necessary. - let mut indirect_parameters_offset = if no_indirect_drawing { - None - } else { - Some(indirect_parameters_buffer.allocate(phase.items.len() as u32)) - }; - let mut first_output_index = data_buffer.len() as u32; for current_index in 0..phase.items.len() { @@ -605,6 +944,7 @@ pub fn batch_and_prepare_sorted_render_phase( // this entity. let item = &phase.items[current_index]; let entity = item.main_entity(); + let item_is_indexed = item.indexed(); let current_batch_input_index = GFBD::get_index_and_compare_data(&system_param_item, entity); @@ -615,7 +955,11 @@ pub fn batch_and_prepare_sorted_render_phase( let Some((current_input_index, current_meta)) = current_batch_input_index else { // Break a batch if we need to. if let Some(batch) = batch.take() { - batch.flush(data_buffer.len() as u32, phase); + batch.flush( + data_buffer.len() as u32, + phase, + &mut indirect_parameters_buffers, + ); } continue; @@ -634,62 +978,74 @@ pub fn batch_and_prepare_sorted_render_phase( }); // Make space in the data buffer for this instance. - let item = &phase.items[current_index]; - let entity = item.main_entity(); let output_index = data_buffer.add() as u32; // If we can't batch, break the existing batch and make a new one. if !can_batch { // Break a batch if we need to. if let Some(batch) = batch.take() { - batch.flush(output_index, phase); + batch.flush(output_index, phase, &mut indirect_parameters_buffers); } + let indirect_parameters_index = if no_indirect_drawing { + None + } else if item_is_indexed { + Some(indirect_parameters_buffers.allocate_indexed(1)) + } else { + Some(indirect_parameters_buffers.allocate_non_indexed(1)) + }; + // Start a new batch. - if let Some(indirect_parameters_offset) = indirect_parameters_offset { - GFBD::write_batch_indirect_parameters( - &system_param_item, - &mut indirect_parameters_buffer, - indirect_parameters_offset, - entity, + if let Some(indirect_parameters_index) = indirect_parameters_index { + GFBD::write_batch_indirect_parameters_metadata( + current_input_index.into(), + item_is_indexed, + output_index, + None, + &mut indirect_parameters_buffers, + indirect_parameters_index, ); }; batch = Some(SortedRenderBatch { phase_item_start_index: current_index as u32, instance_start_index: output_index, - indirect_parameters_index: indirect_parameters_offset.and_then(NonMaxU32::new), + indexed: item_is_indexed, + indirect_parameters_index: indirect_parameters_index.and_then(NonMaxU32::new), meta: current_meta, }); - if let Some(ref mut indirect_parameters_offset) = indirect_parameters_offset { - *indirect_parameters_offset += 1; - } - first_output_index = output_index; } // Add a new preprocessing work item so that the preprocessing // shader will copy the per-instance data over. if let Some(batch) = batch.as_ref() { - work_item_buffer.buffer.push(PreprocessWorkItem { - input_index: current_input_index.into(), - output_index: if no_indirect_drawing { - output_index - } else { - first_output_index - }, - indirect_parameters_index: match batch.indirect_parameters_index { - Some(indirect_parameters_index) => indirect_parameters_index.into(), - None => 0, + work_item_buffer.push( + item_is_indexed, + PreprocessWorkItem { + input_index: current_input_index.into(), + output_index: if no_indirect_drawing { + output_index + } else { + first_output_index + }, + indirect_parameters_index: match batch.indirect_parameters_index { + Some(indirect_parameters_index) => indirect_parameters_index.into(), + None => 0, + }, }, - }); + ); } } // Flush the final batch if necessary. if let Some(batch) = batch.take() { - batch.flush(data_buffer.len() as u32, phase); + batch.flush( + data_buffer.len() as u32, + phase, + &mut indirect_parameters_buffers, + ); } } } @@ -697,7 +1053,7 @@ pub fn batch_and_prepare_sorted_render_phase( /// Creates batches for a render phase that uses bins. pub fn batch_and_prepare_binned_render_phase( gpu_array_buffer: ResMut>, - mut indirect_parameters_buffer: ResMut, + mut indirect_parameters_buffers: ResMut, mut binned_render_phases: ResMut>, mut views: Query<(Entity, &ExtractedView, Has)>, param: StaticSystemParam, @@ -720,18 +1076,18 @@ pub fn batch_and_prepare_binned_render_phase( // Create the work item buffer if necessary; otherwise, just mark it as // used this frame. - let work_item_buffer = - work_item_buffers - .entry(view) - .or_insert_with(|| PreprocessWorkItemBuffer { - buffer: BufferVec::new(BufferUsages::STORAGE), - no_indirect_drawing, - }); + let work_item_buffer = work_item_buffers + .entry(view) + .or_insert_with(TypeIdMap::default) + .entry(TypeId::of::()) + .or_insert_with(|| PreprocessWorkItemBuffers::new(no_indirect_drawing)); // Prepare multidrawables. for batch_set_key in &phase.multidrawable_mesh_keys { let mut batch_set = None; + let indirect_parameters_base = + indirect_parameters_buffers.batch_count(batch_set_key.indexed()) as u32; for (bin_key, bin) in &phase.multidrawable_mesh_values[batch_set_key] { let first_output_index = data_buffer.len() as u32; let mut batch: Option = None; @@ -747,33 +1103,47 @@ pub fn batch_and_prepare_binned_render_phase( Some(ref mut batch) => { // Append to the current batch. batch.instance_range.end = output_index + 1; - work_item_buffer.buffer.push(PreprocessWorkItem { - input_index: input_index.into(), - output_index: first_output_index, - indirect_parameters_index: match batch.extra_index { - PhaseItemExtraIndex::IndirectParametersIndex(ref range) => { - range.start - } - PhaseItemExtraIndex::DynamicOffset(_) - | PhaseItemExtraIndex::None => 0, + work_item_buffer.push( + batch_set_key.indexed(), + PreprocessWorkItem { + input_index: input_index.into(), + output_index: first_output_index, + indirect_parameters_index: match batch.extra_index { + PhaseItemExtraIndex::IndirectParametersIndex { + ref range, + .. + } => range.start, + PhaseItemExtraIndex::DynamicOffset(_) + | PhaseItemExtraIndex::None => 0, + }, }, - }); + ); } None => { // Start a new batch, in indirect mode. - let indirect_parameters_index = indirect_parameters_buffer.allocate(1); - GFBD::write_batch_indirect_parameters( - &system_param_item, - &mut indirect_parameters_buffer, - indirect_parameters_index, - main_entity, + let indirect_parameters_index = + indirect_parameters_buffers.allocate(batch_set_key.indexed(), 1); + let batch_set_index = NonMaxU32::new( + indirect_parameters_buffers.batch_set_count(batch_set_key.indexed()) + as u32, ); - work_item_buffer.buffer.push(PreprocessWorkItem { - input_index: input_index.into(), - output_index: first_output_index, + GFBD::write_batch_indirect_parameters_metadata( + input_index.into(), + batch_set_key.indexed(), + output_index, + batch_set_index, + &mut indirect_parameters_buffers, indirect_parameters_index, - }); + ); + work_item_buffer.push( + batch_set_key.indexed(), + PreprocessWorkItem { + input_index: input_index.into(), + output_index: first_output_index, + indirect_parameters_index, + }, + ); batch = Some(BinnedRenderPhaseBatch { representative_entity: (entity, main_entity), instance_range: output_index..output_index + 1, @@ -791,6 +1161,9 @@ pub fn batch_and_prepare_binned_render_phase( batch_set = Some(BinnedRenderPhaseBatchSet { batches: vec![batch], bin_key: bin_key.clone(), + index: indirect_parameters_buffers + .batch_set_count(batch_set_key.indexed()) + as u32, }); } Some(ref mut batch_set) => { @@ -805,6 +1178,8 @@ pub fn batch_and_prepare_binned_render_phase( { if let Some(batch_set) = batch_set { batch_sets.push(batch_set); + indirect_parameters_buffers + .add_batch_set(batch_set_key.indexed(), indirect_parameters_base); } } } @@ -833,37 +1208,50 @@ pub fn batch_and_prepare_binned_render_phase( // tightly-packed buffer if GPU culling discards some of // the instances. Otherwise, we can just write the // output index directly. - work_item_buffer.buffer.push(PreprocessWorkItem { - input_index: input_index.into(), - output_index: if no_indirect_drawing { - output_index - } else { - first_output_index - }, - indirect_parameters_index: match batch.extra_index { - PhaseItemExtraIndex::IndirectParametersIndex(ref range) => { - range.start - } - PhaseItemExtraIndex::DynamicOffset(_) - | PhaseItemExtraIndex::None => 0, + work_item_buffer.push( + key.0.indexed(), + PreprocessWorkItem { + input_index: input_index.into(), + output_index: if no_indirect_drawing { + output_index + } else { + first_output_index + }, + indirect_parameters_index: match batch.extra_index { + PhaseItemExtraIndex::IndirectParametersIndex { + range: ref indirect_parameters_range, + .. + } => indirect_parameters_range.start, + PhaseItemExtraIndex::DynamicOffset(_) + | PhaseItemExtraIndex::None => 0, + }, }, - }); + ); } None if !no_indirect_drawing => { // Start a new batch, in indirect mode. - let indirect_parameters_index = indirect_parameters_buffer.allocate(1); - GFBD::write_batch_indirect_parameters( - &system_param_item, - &mut indirect_parameters_buffer, - indirect_parameters_index, - main_entity, + let indirect_parameters_index = + indirect_parameters_buffers.allocate(key.0.indexed(), 1); + let batch_set_index = NonMaxU32::new( + indirect_parameters_buffers.batch_set_count(key.0.indexed()) as u32, ); - work_item_buffer.buffer.push(PreprocessWorkItem { - input_index: input_index.into(), - output_index: first_output_index, + GFBD::write_batch_indirect_parameters_metadata( + input_index.into(), + key.0.indexed(), + output_index, + batch_set_index, + &mut indirect_parameters_buffers, indirect_parameters_index, - }); + ); + work_item_buffer.push( + key.0.indexed(), + PreprocessWorkItem { + input_index: input_index.into(), + output_index: first_output_index, + indirect_parameters_index, + }, + ); batch = Some(BinnedRenderPhaseBatch { representative_entity: (entity, main_entity), instance_range: output_index..output_index + 1, @@ -875,11 +1263,14 @@ pub fn batch_and_prepare_binned_render_phase( None => { // Start a new batch, in direct mode. - work_item_buffer.buffer.push(PreprocessWorkItem { - input_index: input_index.into(), - output_index, - indirect_parameters_index: 0, - }); + work_item_buffer.push( + key.0.indexed(), + PreprocessWorkItem { + input_index: input_index.into(), + output_index, + indirect_parameters_index: 0, + }, + ); batch = Some(BinnedRenderPhaseBatch { representative_entity: (entity, main_entity), instance_range: output_index..output_index + 1, @@ -905,6 +1296,8 @@ pub fn batch_and_prepare_binned_render_phase( vec.push(BinnedRenderPhaseBatchSet { batches: vec![batch], bin_key: key.1.clone(), + index: indirect_parameters_buffers.batch_set_count(key.0.indexed()) + as u32, }); } } @@ -918,8 +1311,16 @@ pub fn batch_and_prepare_binned_render_phase( // Allocate the indirect parameters if necessary. let mut indirect_parameters_offset = if no_indirect_drawing { None + } else if key.0.indexed() { + Some( + indirect_parameters_buffers + .allocate_indexed(unbatchables.entities.len() as u32), + ) } else { - Some(indirect_parameters_buffer.allocate(unbatchables.entities.len() as u32)) + Some( + indirect_parameters_buffers + .allocate_non_indexed(unbatchables.entities.len() as u32), + ) }; for &(_, main_entity) in &unbatchables.entities { @@ -932,32 +1333,43 @@ pub fn batch_and_prepare_binned_render_phase( if let Some(ref mut indirect_parameters_index) = indirect_parameters_offset { // We're in indirect mode, so add an indirect parameters // index. - GFBD::write_batch_indirect_parameters( - &system_param_item, - &mut indirect_parameters_buffer, + GFBD::write_batch_indirect_parameters_metadata( + input_index.into(), + key.0.indexed(), + output_index, + None, + &mut indirect_parameters_buffers, *indirect_parameters_index, - main_entity, ); - work_item_buffer.buffer.push(PreprocessWorkItem { - input_index: input_index.into(), - output_index, - indirect_parameters_index: *indirect_parameters_index, - }); + work_item_buffer.push( + key.0.indexed(), + PreprocessWorkItem { + input_index: input_index.into(), + output_index, + indirect_parameters_index: *indirect_parameters_index, + }, + ); unbatchables .buffer_indices .add(UnbatchableBinnedEntityIndices { instance_index: *indirect_parameters_index, - extra_index: PhaseItemExtraIndex::IndirectParametersIndex( - *indirect_parameters_index..(*indirect_parameters_index + 1), - ), + extra_index: PhaseItemExtraIndex::IndirectParametersIndex { + range: *indirect_parameters_index..(*indirect_parameters_index + 1), + batch_set_index: None, + }, }); + indirect_parameters_buffers + .add_batch_set(key.0.indexed(), *indirect_parameters_index); *indirect_parameters_index += 1; } else { - work_item_buffer.buffer.push(PreprocessWorkItem { - input_index: input_index.into(), - output_index, - indirect_parameters_index: 0, - }); + work_item_buffer.push( + key.0.indexed(), + PreprocessWorkItem { + input_index: input_index.into(), + output_index, + indirect_parameters_index: 0, + }, + ); unbatchables .buffer_indices .add(UnbatchableBinnedEntityIndices { @@ -980,7 +1392,7 @@ pub fn write_batched_instance_buffers( { let BatchedInstanceBuffers { ref mut data_buffer, - work_item_buffers: ref mut index_buffers, + ref mut work_item_buffers, ref mut current_input_buffer, ref mut previous_input_buffer, } = gpu_array_buffer.into_inner(); @@ -993,22 +1405,60 @@ pub fn write_batched_instance_buffers( .buffer .write_buffer(&render_device, &render_queue); - for index_buffer in index_buffers.values_mut() { - index_buffer - .buffer - .write_buffer(&render_device, &render_queue); + for view_work_item_buffers in work_item_buffers.values_mut() { + for phase_work_item_buffers in view_work_item_buffers.values_mut() { + match *phase_work_item_buffers { + PreprocessWorkItemBuffers::Direct(ref mut buffer_vec) => { + buffer_vec.write_buffer(&render_device, &render_queue); + } + PreprocessWorkItemBuffers::Indirect { + ref mut indexed, + ref mut non_indexed, + } => { + indexed.write_buffer(&render_device, &render_queue); + non_indexed.write_buffer(&render_device, &render_queue); + } + } + } } } -pub fn write_indirect_parameters_buffer( +pub fn clear_indirect_parameters_buffers( + mut indirect_parameters_buffers: ResMut, +) { + indirect_parameters_buffers.indexed_data.clear(); + indirect_parameters_buffers.indexed_metadata.clear(); + indirect_parameters_buffers.indexed_batch_sets.clear(); + indirect_parameters_buffers.non_indexed_data.clear(); + indirect_parameters_buffers.non_indexed_metadata.clear(); + indirect_parameters_buffers.non_indexed_batch_sets.clear(); +} + +pub fn write_indirect_parameters_buffers( render_device: Res, render_queue: Res, - mut indirect_parameters_buffer: ResMut, + mut indirect_parameters_buffers: ResMut, ) { - indirect_parameters_buffer - .buffer + indirect_parameters_buffers + .indexed_data + .write_buffer(&render_device); + indirect_parameters_buffers + .non_indexed_data + .write_buffer(&render_device); + + indirect_parameters_buffers + .indexed_metadata + .write_buffer(&render_device, &render_queue); + indirect_parameters_buffers + .non_indexed_metadata + .write_buffer(&render_device, &render_queue); + + indirect_parameters_buffers + .indexed_batch_sets + .write_buffer(&render_device, &render_queue); + indirect_parameters_buffers + .non_indexed_batch_sets .write_buffer(&render_device, &render_queue); - indirect_parameters_buffer.buffer.clear(); } #[cfg(test)] diff --git a/crates/bevy_render/src/batching/mod.rs b/crates/bevy_render/src/batching/mod.rs index 590ed94293290..214fdda13644e 100644 --- a/crates/bevy_render/src/batching/mod.rs +++ b/crates/bevy_render/src/batching/mod.rs @@ -6,7 +6,7 @@ use bevy_ecs::{ use bytemuck::Pod; use nonmax::NonMaxU32; -use self::gpu_preprocessing::IndirectParametersBuffer; +use self::gpu_preprocessing::IndirectParametersBuffers; use crate::{render_phase::PhaseItemExtraIndex, sync_world::MainEntity}; use crate::{ render_phase::{ @@ -58,7 +58,9 @@ impl BatchMeta { PhaseItemExtraIndex::DynamicOffset(dynamic_offset) => { NonMaxU32::new(dynamic_offset) } - PhaseItemExtraIndex::None | PhaseItemExtraIndex::IndirectParametersIndex(_) => None, + PhaseItemExtraIndex::None | PhaseItemExtraIndex::IndirectParametersIndex { .. } => { + None + } }, user_data, } @@ -141,17 +143,36 @@ pub trait GetFullBatchData: GetBatchData { query_item: MainEntity, ) -> Option; - /// Writes the [`gpu_preprocessing::IndirectParameters`] necessary to draw - /// this batch into the given [`IndirectParametersBuffer`] at the given - /// index. + /// Writes the [`gpu_preprocessing::IndirectParametersMetadata`] necessary + /// to draw this batch into the given metadata buffer at the given index. /// /// This is only used if GPU culling is enabled (which requires GPU /// preprocessing). - fn write_batch_indirect_parameters( - param: &SystemParamItem, - indirect_parameters_buffer: &mut IndirectParametersBuffer, + /// + /// * `mesh_index` describes the index of the first mesh instance in this + /// batch in the `MeshInputUniform` buffer. + /// + /// * `indexed` is true if the mesh is indexed or false if it's non-indexed. + /// + /// * `base_output_index` is the index of the first mesh instance in this + /// batch in the `MeshUniform` output buffer. + /// + /// * `batch_set_index` is the index of the batch set in the + /// [`gpu_preprocessing::IndirectBatchSet`] buffer, if this batch belongs to + /// a batch set. + /// + /// * `indirect_parameters_buffers` is the buffer in which to write the + /// metadata. + /// + /// * `indirect_parameters_offset` is the index in that buffer at which to + /// write the metadata. + fn write_batch_indirect_parameters_metadata( + mesh_index: u32, + indexed: bool, + base_output_index: u32, + batch_set_index: Option, + indirect_parameters_buffers: &mut IndirectParametersBuffers, indirect_parameters_offset: u32, - entity: MainEntity, ); } diff --git a/crates/bevy_render/src/camera/camera.rs b/crates/bevy_render/src/camera/camera.rs index b3b6b6f7c70aa..1bc4b3737ad81 100644 --- a/crates/bevy_render/src/camera/camera.rs +++ b/crates/bevy_render/src/camera/camera.rs @@ -1153,7 +1153,7 @@ pub fn extract_cameras( hdr: camera.hdr, }, ExtractedView { - retained_view_entity: RetainedViewEntity::new(main_entity.into(), 0), + retained_view_entity: RetainedViewEntity::new(main_entity.into(), None, 0), clip_from_view: camera.clip_from_view(), world_from_view: *transform, clip_from_world: None, diff --git a/crates/bevy_render/src/mesh/components.rs b/crates/bevy_render/src/mesh/components.rs index 10229be41210d..2b887c65d32c5 100644 --- a/crates/bevy_render/src/mesh/components.rs +++ b/crates/bevy_render/src/mesh/components.rs @@ -2,11 +2,15 @@ use crate::{ mesh::Mesh, view::{self, Visibility, VisibilityClass}, }; -use bevy_asset::{AssetId, Handle}; +use bevy_asset::{AssetEvent, AssetId, Handle}; use bevy_derive::{Deref, DerefMut}; -use bevy_ecs::{component::Component, prelude::require, reflect::ReflectComponent}; +use bevy_ecs::{ + change_detection::DetectChangesMut, component::Component, event::EventReader, prelude::require, + reflect::ReflectComponent, system::Query, +}; use bevy_reflect::{std_traits::ReflectDefault, Reflect}; use bevy_transform::components::Transform; +use bevy_utils::{FixedHasher, HashSet}; use derive_more::derive::From; /// A component for 2D meshes. Requires a [`MeshMaterial2d`] to be rendered, commonly using a [`ColorMaterial`]. @@ -101,3 +105,32 @@ impl From<&Mesh3d> for AssetId { mesh.id() } } + +/// A system that marks a [`Mesh3d`] as changed if the associated [`Mesh`] asset +/// has changed. +/// +/// This is needed because the systems that extract meshes, such as +/// `extract_meshes_for_gpu_building`, write some metadata about the mesh (like +/// the location within each slab) into the GPU structures that they build that +/// needs to be kept up to date if the contents of the mesh change. +pub fn mark_3d_meshes_as_changed_if_their_assets_changed( + mut meshes_3d: Query<&mut Mesh3d>, + mut mesh_asset_events: EventReader>, +) { + let mut changed_meshes: HashSet, FixedHasher> = HashSet::default(); + for mesh_asset_event in mesh_asset_events.read() { + if let AssetEvent::Modified { id } = mesh_asset_event { + changed_meshes.insert(*id); + } + } + + if changed_meshes.is_empty() { + return; + } + + for mut mesh_3d in &mut meshes_3d { + if changed_meshes.contains(&mesh_3d.0.id()) { + mesh_3d.set_changed(); + } + } +} diff --git a/crates/bevy_render/src/mesh/mod.rs b/crates/bevy_render/src/mesh/mod.rs index 7a7829e0f4ef1..703333675da74 100644 --- a/crates/bevy_render/src/mesh/mod.rs +++ b/crates/bevy_render/src/mesh/mod.rs @@ -9,6 +9,7 @@ use crate::{ render_asset::{PrepareAssetError, RenderAsset, RenderAssetPlugin, RenderAssets}, render_resource::TextureView, texture::GpuImage, + view::VisibilitySystems, RenderApp, }; use allocator::MeshAllocatorPlugin; @@ -17,6 +18,7 @@ use bevy_asset::{AssetApp, AssetId, RenderAssetUsages}; use bevy_ecs::{ entity::Entity, query::{Changed, With}, + schedule::IntoSystemConfigs, system::Query, }; use bevy_ecs::{ @@ -42,7 +44,12 @@ impl Plugin for MeshPlugin { .register_type::>() // 'Mesh' must be prepared after 'Image' as meshes rely on the morph target image being ready .add_plugins(RenderAssetPlugin::::default()) - .add_plugins(MeshAllocatorPlugin); + .add_plugins(MeshAllocatorPlugin) + .add_systems( + PostUpdate, + components::mark_3d_meshes_as_changed_if_their_assets_changed + .ambiguous_with(VisibilitySystems::CalculateBounds), + ); let Some(render_app) = app.get_sub_app_mut(RenderApp) else { return; @@ -130,6 +137,12 @@ impl RenderMesh { pub fn primitive_topology(&self) -> PrimitiveTopology { self.key_bits.primitive_topology() } + + /// Returns true if this mesh uses an index buffer or false otherwise. + #[inline] + pub fn indexed(&self) -> bool { + matches!(self.buffer_info, RenderMeshBufferInfo::Indexed { .. }) + } } /// The index/vertex buffer info of a [`RenderMesh`]. diff --git a/crates/bevy_render/src/render_phase/mod.rs b/crates/bevy_render/src/render_phase/mod.rs index d65b46da7cdf6..2b8d0c9e8a362 100644 --- a/crates/bevy_render/src/render_phase/mod.rs +++ b/crates/bevy_render/src/render_phase/mod.rs @@ -36,8 +36,10 @@ pub use draw_state::*; use encase::{internal::WriteInto, ShaderSize}; use nonmax::NonMaxU32; pub use rangefinder::*; +use wgpu::Features; use crate::batching::gpu_preprocessing::{GpuPreprocessingMode, GpuPreprocessingSupport}; +use crate::renderer::RenderDevice; use crate::sync_world::MainEntity; use crate::view::RetainedViewEntity; use crate::{ @@ -189,6 +191,7 @@ pub enum BinnedRenderPhaseBatchSets { pub struct BinnedRenderPhaseBatchSet { pub(crate) batches: Vec, pub(crate) bin_key: BK, + pub(crate) index: u32, } impl BinnedRenderPhaseBatchSets { @@ -456,6 +459,11 @@ where let draw_functions = world.resource::>(); let mut draw_functions = draw_functions.write(); + let render_device = world.resource::(); + let multi_draw_indirect_count_supported = render_device + .features() + .contains(Features::MULTI_DRAW_INDIRECT_COUNT); + match self.batch_sets { BinnedRenderPhaseBatchSets::DynamicUniforms(ref batch_sets) => { debug_assert_eq!(self.batchable_mesh_keys.len(), batch_sets.len()); @@ -522,6 +530,12 @@ where continue; }; + let batch_set_index = if multi_draw_indirect_count_supported { + NonMaxU32::new(batch_set.index) + } else { + None + }; + let binned_phase_item = BPI::new( batch_set_key.clone(), batch_set.bin_key.clone(), @@ -532,10 +546,12 @@ where PhaseItemExtraIndex::DynamicOffset(ref dynamic_offset) => { PhaseItemExtraIndex::DynamicOffset(*dynamic_offset) } - PhaseItemExtraIndex::IndirectParametersIndex(ref range) => { - PhaseItemExtraIndex::IndirectParametersIndex( - range.start..(range.start + batch_set.batches.len() as u32), - ) + PhaseItemExtraIndex::IndirectParametersIndex { ref range, .. } => { + PhaseItemExtraIndex::IndirectParametersIndex { + range: range.start + ..(range.start + batch_set.batches.len() as u32), + batch_set_index, + } } }, ); @@ -585,10 +601,11 @@ where let first_indirect_parameters_index_for_entity = u32::from(*first_indirect_parameters_index) + entity_index as u32; - PhaseItemExtraIndex::IndirectParametersIndex( - first_indirect_parameters_index_for_entity + PhaseItemExtraIndex::IndirectParametersIndex { + range: first_indirect_parameters_index_for_entity ..(first_indirect_parameters_index_for_entity + 1), - ) + batch_set_index: None, + } } }, }, @@ -725,10 +742,11 @@ impl UnbatchableBinnedEntityIndexSet { u32::from(*first_indirect_parameters_index) + entity_index; Some(UnbatchableBinnedEntityIndices { instance_index: instance_range.start + entity_index, - extra_index: PhaseItemExtraIndex::IndirectParametersIndex( - first_indirect_parameters_index_for_this_batch + extra_index: PhaseItemExtraIndex::IndirectParametersIndex { + range: first_indirect_parameters_index_for_this_batch ..(first_indirect_parameters_index_for_this_batch + 1), - ), + batch_set_index: None, + }, }) } UnbatchableBinnedEntityIndexSet::Dense(ref indices) => { @@ -890,12 +908,17 @@ impl UnbatchableBinnedEntityIndexSet { first_indirect_parameters_index: None, } } - PhaseItemExtraIndex::IndirectParametersIndex(ref range) => { + PhaseItemExtraIndex::IndirectParametersIndex { + range: ref indirect_parameters_index, + .. + } => { // This is the first entity we've seen, and we have compute // shaders. Initialize the fast path. *self = UnbatchableBinnedEntityIndexSet::Sparse { instance_range: indices.instance_index..indices.instance_index + 1, - first_indirect_parameters_index: NonMaxU32::new(range.start), + first_indirect_parameters_index: NonMaxU32::new( + indirect_parameters_index.start, + ), } } } @@ -909,7 +932,10 @@ impl UnbatchableBinnedEntityIndexSet { && indices.extra_index == PhaseItemExtraIndex::None) || first_indirect_parameters_index.is_some_and( |first_indirect_parameters_index| match indices.extra_index { - PhaseItemExtraIndex::IndirectParametersIndex(ref this_range) => { + PhaseItemExtraIndex::IndirectParametersIndex { + range: ref this_range, + .. + } => { u32::from(first_indirect_parameters_index) + instance_range.end - instance_range.start == this_range.start @@ -1129,7 +1155,22 @@ pub enum PhaseItemExtraIndex { /// An index into the buffer that specifies the indirect parameters for this /// [`PhaseItem`]'s drawcall. This is used when indirect mode is on (as used /// for GPU culling). - IndirectParametersIndex(Range), + IndirectParametersIndex { + /// The range of indirect parameters within the indirect parameters array. + /// + /// If we're using `multi_draw_indirect_count`, this specifies the + /// maximum range of indirect parameters within that array. If batches + /// are ultimately culled out on the GPU, the actual number of draw + /// commands might be lower than the length of this range. + range: Range, + /// If `multi_draw_indirect_count` is in use, and this phase item is + /// part of a batch set, specifies the index of the batch set that this + /// phase item is a part of. + /// + /// If `multi_draw_indirect_count` isn't in use, or this phase item + /// isn't part of a batch set, this is `None`. + batch_set_index: Option, + }, } impl PhaseItemExtraIndex { @@ -1139,9 +1180,11 @@ impl PhaseItemExtraIndex { indirect_parameters_index: Option, ) -> PhaseItemExtraIndex { match indirect_parameters_index { - Some(indirect_parameters_index) => PhaseItemExtraIndex::IndirectParametersIndex( - u32::from(indirect_parameters_index)..(u32::from(indirect_parameters_index) + 1), - ), + Some(indirect_parameters_index) => PhaseItemExtraIndex::IndirectParametersIndex { + range: u32::from(indirect_parameters_index) + ..(u32::from(indirect_parameters_index) + 1), + batch_set_index: None, + }, None => PhaseItemExtraIndex::None, } } @@ -1172,7 +1215,11 @@ pub trait BinnedPhaseItem: PhaseItem { /// reduces the need for rebinding between bins and improves performance. type BinKey: Clone + Send + Sync + PartialEq + Eq + Ord + Hash; - type BatchSetKey: Clone + Send + Sync + PartialEq + Eq + Ord + Hash; + /// The key used to combine batches into batch sets. + /// + /// A *batch set* is a set of meshes that can potentially be multi-drawn + /// together. + type BatchSetKey: PhaseItemBatchSetKey; /// Creates a new binned phase item from the key and per-entity data. /// @@ -1188,6 +1235,19 @@ pub trait BinnedPhaseItem: PhaseItem { ) -> Self; } +/// A key used to combine batches into batch sets. +/// +/// A *batch set* is a set of meshes that can potentially be multi-drawn +/// together. +pub trait PhaseItemBatchSetKey: Clone + Send + Sync + PartialEq + Eq + Ord + Hash { + /// Returns true if this batch set key describes indexed meshes or false if + /// it describes non-indexed meshes. + /// + /// Bevy uses this in order to determine which kind of indirect draw + /// parameters to use, if indirect drawing is enabled. + fn indexed(&self) -> bool; +} + /// Represents phase items that must be sorted. The `SortKey` specifies the /// order that these items are drawn in. These are placed into a single array, /// and the array as a whole is then sorted. @@ -1219,6 +1279,17 @@ pub trait SortedPhaseItem: PhaseItem { fn sort(items: &mut [Self]) { items.sort_unstable_by_key(Self::sort_key); } + + /// Whether this phase item targets indexed meshes (those with both vertex + /// and index buffers as opposed to just vertex buffers). + /// + /// Bevy needs this information in order to properly group phase items + /// together for multi-draw indirect, because the GPU layout of indirect + /// commands differs between indexed and non-indexed meshes. + /// + /// If you're implementing a custom phase item that doesn't describe a mesh, + /// you can safely return false here. + fn indexed(&self) -> bool; } /// A [`PhaseItem`] item, that automatically sets the appropriate render pipeline, diff --git a/crates/bevy_render/src/view/mod.rs b/crates/bevy_render/src/view/mod.rs index 3eb0df75e2f2c..b5ad7a541e297 100644 --- a/crates/bevy_render/src/view/mod.rs +++ b/crates/bevy_render/src/view/mod.rs @@ -191,14 +191,25 @@ impl Msaa { /// stable, and we can't use just [`MainEntity`] because some main world views /// extract to multiple render world views. For example, a directional light /// extracts to one render world view per cascade, and a point light extracts to -/// one render world view per cubemap face. So we pair the main entity with a -/// *subview index*, which *together* uniquely identify a view in the render -/// world in a way that's stable from frame to frame. +/// one render world view per cubemap face. So we pair the main entity with an +/// *auxiliary entity* and a *subview index*, which *together* uniquely identify +/// a view in the render world in a way that's stable from frame to frame. #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub struct RetainedViewEntity { /// The main entity that this view corresponds to. pub main_entity: MainEntity, + /// Another entity associated with the view entity. + /// + /// This is currently used for shadow cascades. If there are multiple + /// cameras, each camera needs to have its own set of shadow cascades. Thus + /// the light and subview index aren't themselves enough to uniquely + /// identify a shadow cascade: we need the camera that the cascade is + /// associated with as well. This entity stores that camera. + /// + /// If not present, this will be `MainEntity(Entity::PLACEHOLDER)`. + pub auxiliary_entity: MainEntity, + /// The index of the view corresponding to the entity. /// /// For example, for point lights that cast shadows, this is the index of @@ -208,14 +219,19 @@ pub struct RetainedViewEntity { } impl RetainedViewEntity { - /// Creates a new [`RetainedViewEntity`] from the given main world entity - /// and subview index. + /// Creates a new [`RetainedViewEntity`] from the given main world entity, + /// auxiliary main world entity, and subview index. /// /// See [`RetainedViewEntity::subview_index`] for an explanation of what - /// `subview_index` is. - pub fn new(main_entity: MainEntity, subview_index: u32) -> Self { + /// `auxiliary_entity` and `subview_index` are. + pub fn new( + main_entity: MainEntity, + auxiliary_entity: Option, + subview_index: u32, + ) -> Self { Self { main_entity, + auxiliary_entity: auxiliary_entity.unwrap_or(Entity::PLACEHOLDER.into()), subview_index, } } diff --git a/crates/bevy_sprite/src/mesh2d/material.rs b/crates/bevy_sprite/src/mesh2d/material.rs index 39b6b15f1a6ea..a4dfc376682c4 100644 --- a/crates/bevy_sprite/src/mesh2d/material.rs +++ b/crates/bevy_sprite/src/mesh2d/material.rs @@ -5,7 +5,9 @@ use crate::{ use bevy_app::{App, Plugin}; use bevy_asset::{Asset, AssetApp, AssetId, AssetServer, Handle}; use bevy_core_pipeline::{ - core_2d::{AlphaMask2d, AlphaMask2dBinKey, Opaque2d, Opaque2dBinKey, Transparent2d}, + core_2d::{ + AlphaMask2d, AlphaMask2dBinKey, BatchSetKey2d, Opaque2d, Opaque2dBinKey, Transparent2d, + }, tonemapping::{DebandDither, Tonemapping}, }; use bevy_derive::{Deref, DerefMut}; @@ -584,7 +586,9 @@ pub fn queue_material2d_meshes( material_bind_group_id: material_2d.get_bind_group_id().0, }; opaque_phase.add( - (), + BatchSetKey2d { + indexed: mesh.indexed(), + }, bin_key, (*render_entity, *visible_entity), binned_render_phase_type, @@ -598,7 +602,9 @@ pub fn queue_material2d_meshes( material_bind_group_id: material_2d.get_bind_group_id().0, }; alpha_mask_phase.add( - (), + BatchSetKey2d { + indexed: mesh.indexed(), + }, bin_key, (*render_entity, *visible_entity), binned_render_phase_type, @@ -617,6 +623,7 @@ pub fn queue_material2d_meshes( // Batching is done in batch_and_prepare_render_phase batch_range: 0..1, extra_index: PhaseItemExtraIndex::None, + indexed: mesh.indexed(), }); } } diff --git a/crates/bevy_sprite/src/mesh2d/mesh.rs b/crates/bevy_sprite/src/mesh2d/mesh.rs index aa7e41dbcbd4f..52309fd492aab 100644 --- a/crates/bevy_sprite/src/mesh2d/mesh.rs +++ b/crates/bevy_sprite/src/mesh2d/mesh.rs @@ -18,7 +18,7 @@ use bevy_image::{BevyDefault, Image, ImageSampler, TextureFormatPixelInfo}; use bevy_math::{Affine3, Vec4}; use bevy_render::{ batching::{ - gpu_preprocessing::IndirectParameters, + gpu_preprocessing::IndirectParametersMetadata, no_gpu_preprocessing::{ self, batch_and_prepare_binned_render_phase, batch_and_prepare_sorted_render_phase, write_batched_instance_buffer, BatchedInstanceBuffer, @@ -403,56 +403,33 @@ impl GetFullBatchData for Mesh2dPipeline { None } - fn write_batch_indirect_parameters( - (mesh_instances, meshes, mesh_allocator): &SystemParamItem, - indirect_parameters_buffer: &mut bevy_render::batching::gpu_preprocessing::IndirectParametersBuffer, + fn write_batch_indirect_parameters_metadata( + input_index: u32, + indexed: bool, + base_output_index: u32, + batch_set_index: Option, + indirect_parameters_buffer: &mut bevy_render::batching::gpu_preprocessing::IndirectParametersBuffers, indirect_parameters_offset: u32, - main_entity: MainEntity, ) { - let Some(mesh_instance) = mesh_instances.get(&main_entity) else { - return; - }; - let Some(mesh) = meshes.get(mesh_instance.mesh_asset_id) else { - return; - }; - let Some(vertex_buffer_slice) = - mesh_allocator.mesh_vertex_slice(&mesh_instance.mesh_asset_id) - else { - return; - }; - // Note that `IndirectParameters` covers both of these structures, even // though they actually have distinct layouts. See the comment above that // type for more information. - let indirect_parameters = match mesh.buffer_info { - RenderMeshBufferInfo::Indexed { - count: index_count, .. - } => { - let Some(index_buffer_slice) = - mesh_allocator.mesh_index_slice(&mesh_instance.mesh_asset_id) - else { - return; - }; - IndirectParameters { - vertex_or_index_count: index_count, - instance_count: 0, - first_vertex_or_first_index: index_buffer_slice.range.start, - base_vertex_or_first_instance: vertex_buffer_slice.range.start, - first_instance: 0, - } - } - RenderMeshBufferInfo::NonIndexed => IndirectParameters { - vertex_or_index_count: mesh.vertex_count, - instance_count: 0, - first_vertex_or_first_index: vertex_buffer_slice.range.start, - base_vertex_or_first_instance: 0, - // Use `0xffffffff` as a placeholder to tell the mesh - // preprocessing shader that this is a non-indexed mesh. - first_instance: !0, + let indirect_parameters = IndirectParametersMetadata { + mesh_index: input_index, + base_output_index, + batch_set_index: match batch_set_index { + None => !0, + Some(batch_set_index) => u32::from(batch_set_index), }, + instance_count: 0, }; - indirect_parameters_buffer.set(indirect_parameters_offset, indirect_parameters); + if indexed { + indirect_parameters_buffer.set_indexed(indirect_parameters_offset, indirect_parameters); + } else { + indirect_parameters_buffer + .set_non_indexed(indirect_parameters_offset, indirect_parameters); + } } } diff --git a/crates/bevy_sprite/src/render/mod.rs b/crates/bevy_sprite/src/render/mod.rs index 3dae793d323dd..585229052e04f 100644 --- a/crates/bevy_sprite/src/render/mod.rs +++ b/crates/bevy_sprite/src/render/mod.rs @@ -574,6 +574,7 @@ pub fn queue_sprites( // batch_range and dynamic_offset will be calculated in prepare_sprites batch_range: 0..0, extra_index: PhaseItemExtraIndex::None, + indexed: true, }); } } diff --git a/crates/bevy_ui/src/render/box_shadow.rs b/crates/bevy_ui/src/render/box_shadow.rs index 7f18e935ab8eb..f33a4ed4ded2d 100644 --- a/crates/bevy_ui/src/render/box_shadow.rs +++ b/crates/bevy_ui/src/render/box_shadow.rs @@ -387,6 +387,7 @@ pub fn queue_shadows( ), batch_range: 0..0, extra_index: PhaseItemExtraIndex::None, + indexed: true, }); } } diff --git a/crates/bevy_ui/src/render/mod.rs b/crates/bevy_ui/src/render/mod.rs index bbadf8aef7601..27f94776deae2 100644 --- a/crates/bevy_ui/src/render/mod.rs +++ b/crates/bevy_ui/src/render/mod.rs @@ -626,7 +626,7 @@ pub fn extract_ui_camera_view( // We use `UI_CAMERA_SUBVIEW` here so as not to conflict with the // main 3D or 2D camera, which will have subview index 0. let retained_view_entity = - RetainedViewEntity::new(main_entity.into(), UI_CAMERA_SUBVIEW); + RetainedViewEntity::new(main_entity.into(), None, UI_CAMERA_SUBVIEW); // Creates the UI view. let ui_camera_view = commands .spawn(( @@ -894,6 +894,7 @@ pub fn queue_uinodes( // batch_range will be calculated in prepare_uinodes batch_range: 0..0, extra_index: PhaseItemExtraIndex::None, + indexed: true, }); } } diff --git a/crates/bevy_ui/src/render/render_pass.rs b/crates/bevy_ui/src/render/render_pass.rs index d26844fcb6161..c9f0d3d69c547 100644 --- a/crates/bevy_ui/src/render/render_pass.rs +++ b/crates/bevy_ui/src/render/render_pass.rs @@ -112,6 +112,7 @@ pub struct TransparentUi { pub draw_function: DrawFunctionId, pub batch_range: Range, pub extra_index: PhaseItemExtraIndex, + pub indexed: bool, } impl PhaseItem for TransparentUi { @@ -162,6 +163,11 @@ impl SortedPhaseItem for TransparentUi { fn sort(items: &mut [Self]) { items.sort_by_key(SortedPhaseItem::sort_key); } + + #[inline] + fn indexed(&self) -> bool { + self.indexed + } } impl CachedRenderPipelinePhaseItem for TransparentUi { diff --git a/crates/bevy_ui/src/render/ui_material_pipeline.rs b/crates/bevy_ui/src/render/ui_material_pipeline.rs index c47729105e79a..904a0bd225db4 100644 --- a/crates/bevy_ui/src/render/ui_material_pipeline.rs +++ b/crates/bevy_ui/src/render/ui_material_pipeline.rs @@ -662,6 +662,7 @@ pub fn queue_ui_material_nodes( ), batch_range: 0..0, extra_index: PhaseItemExtraIndex::None, + indexed: false, }); } } diff --git a/crates/bevy_ui/src/render/ui_texture_slice_pipeline.rs b/crates/bevy_ui/src/render/ui_texture_slice_pipeline.rs index 989207b3a2728..869e5f0226c8a 100644 --- a/crates/bevy_ui/src/render/ui_texture_slice_pipeline.rs +++ b/crates/bevy_ui/src/render/ui_texture_slice_pipeline.rs @@ -385,6 +385,7 @@ pub fn queue_ui_slices( ), batch_range: 0..0, extra_index: PhaseItemExtraIndex::None, + indexed: true, }); } } diff --git a/examples/2d/mesh2d_manual.rs b/examples/2d/mesh2d_manual.rs index 09acb6dbb99bc..15d611ce667cf 100644 --- a/examples/2d/mesh2d_manual.rs +++ b/examples/2d/mesh2d_manual.rs @@ -392,10 +392,10 @@ pub fn queue_colored_mesh2d( let mesh2d_transforms = &mesh_instance.transforms; // Get our specialized pipeline let mut mesh2d_key = mesh_key; - if let Some(mesh) = render_meshes.get(mesh2d_handle) { - mesh2d_key |= - Mesh2dPipelineKey::from_primitive_topology(mesh.primitive_topology()); - } + let Some(mesh) = render_meshes.get(mesh2d_handle) else { + continue; + }; + mesh2d_key |= Mesh2dPipelineKey::from_primitive_topology(mesh.primitive_topology()); let pipeline_id = pipelines.specialize(&pipeline_cache, &colored_mesh2d_pipeline, mesh2d_key); @@ -411,6 +411,7 @@ pub fn queue_colored_mesh2d( // This material is not batched batch_range: 0..1, extra_index: PhaseItemExtraIndex::None, + indexed: mesh.indexed(), }); } } diff --git a/examples/shader/custom_shader_instancing.rs b/examples/shader/custom_shader_instancing.rs index 3cdfad774dbae..cd5909c36fff1 100644 --- a/examples/shader/custom_shader_instancing.rs +++ b/examples/shader/custom_shader_instancing.rs @@ -166,6 +166,7 @@ fn queue_custom( distance: rangefinder.distance_translation(&mesh_instance.translation), batch_range: 0..1, extra_index: PhaseItemExtraIndex::None, + indexed: true, }); } } diff --git a/examples/shader/specialized_mesh_pipeline.rs b/examples/shader/specialized_mesh_pipeline.rs index ee5c68bc11a16..bc1ecf113c78b 100644 --- a/examples/shader/specialized_mesh_pipeline.rs +++ b/examples/shader/specialized_mesh_pipeline.rs @@ -6,8 +6,11 @@ //! //! [`SpecializedMeshPipeline`] let's you customize the entire pipeline used when rendering a mesh. +use std::any::TypeId; + use bevy::{ core_pipeline::core_3d::{Opaque3d, Opaque3dBatchSetKey, Opaque3dBinKey, CORE_3D_DEPTH_FORMAT}, + ecs::system::StaticSystemParam, math::{vec3, vec4}, pbr::{ DrawMesh, MeshPipeline, MeshPipelineKey, MeshPipelineViewLayoutKey, RenderMeshInstances, @@ -15,6 +18,14 @@ use bevy::{ }, prelude::*, render::{ + batching::GetFullBatchData, + batching::{ + gpu_preprocessing::{ + BatchedInstanceBuffers, IndirectParametersBuffers, PreprocessWorkItem, + PreprocessWorkItemBuffers, + }, + GetBatchData, + }, extract_component::{ExtractComponent, ExtractComponentPlugin}, mesh::{Indices, MeshVertexBufferLayoutRef, PrimitiveTopology, RenderMesh}, render_asset::{RenderAssetUsages, RenderAssets}, @@ -28,9 +39,11 @@ use bevy::{ RenderPipelineDescriptor, SpecializedMeshPipeline, SpecializedMeshPipelineError, SpecializedMeshPipelines, TextureFormat, VertexState, }, + view::NoIndirectDrawing, view::{self, ExtractedView, RenderVisibleEntities, ViewTarget, VisibilityClass}, Render, RenderApp, RenderSet, }, + utils::TypeIdMap, }; const SHADER_ASSET_PATH: &str = "shaders/specialized_mesh_pipeline.wgsl"; @@ -264,13 +277,39 @@ impl SpecializedMeshPipeline for CustomMeshPipeline { fn queue_custom_mesh_pipeline( pipeline_cache: Res, custom_mesh_pipeline: Res, - mut opaque_render_phases: ResMut>, - opaque_draw_functions: Res>, + (mut opaque_render_phases, opaque_draw_functions): ( + ResMut>, + Res>, + ), mut specialized_mesh_pipelines: ResMut>, - views: Query<(&RenderVisibleEntities, &ExtractedView, &Msaa), With>, - render_meshes: Res>, - render_mesh_instances: Res, + views: Query<( + Entity, + &RenderVisibleEntities, + &ExtractedView, + &Msaa, + Has, + )>, + (render_meshes, render_mesh_instances): ( + Res>, + Res, + ), + param: StaticSystemParam<::Param>, + gpu_array_buffer: ResMut< + BatchedInstanceBuffers< + ::BufferData, + ::BufferInputData, + >, + >, + mut indirect_parameters_buffers: ResMut, ) { + let system_param_item = param.into_inner(); + + let BatchedInstanceBuffers { + ref mut data_buffer, + ref mut work_item_buffers, + .. + } = gpu_array_buffer.into_inner(); + // Get the id for our custom draw function let draw_function_id = opaque_draw_functions .read() @@ -279,15 +318,29 @@ fn queue_custom_mesh_pipeline( // Render phases are per-view, so we need to iterate over all views so that // the entity appears in them. (In this example, we have only one view, but // it's good practice to loop over all views anyway.) - for (view_visible_entities, view, msaa) in views.iter() { + for (view_entity, view_visible_entities, view, msaa, no_indirect_drawing) in views.iter() { let Some(opaque_phase) = opaque_render_phases.get_mut(&view.retained_view_entity) else { continue; }; + // Create a *work item buffer* if necessary. Work item buffers store the + // indices of meshes that are to be rendered when indirect drawing is + // enabled. + let work_item_buffer = work_item_buffers + .entry(view_entity) + .or_insert_with(TypeIdMap::default) + .entry(TypeId::of::()) + .or_insert_with(|| PreprocessWorkItemBuffers::new(no_indirect_drawing)); + // Create the key based on the view. In this case we only care about MSAA and HDR let view_key = MeshPipelineKey::from_msaa_samples(msaa.samples()) | MeshPipelineKey::from_hdr(view.hdr); + // Set up a slot to hold information about the batch set we're going to + // create. If there are any of our custom meshes in the scene, we'll + // need this information in order for Bevy to kick off the rendering. + let mut mesh_batch_set_info = None; + // Find all the custom rendered entities that are visible from this // view. for &(render_entity, visible_entity) in @@ -310,6 +363,27 @@ fn queue_custom_mesh_pipeline( let mut mesh_key = view_key; mesh_key |= MeshPipelineKey::from_primitive_topology(mesh.primitive_topology()); + // Initialize the batch set information if this was the first custom + // mesh we saw. We'll need that information later to create the + // batch set. + if mesh_batch_set_info.is_none() { + mesh_batch_set_info = Some(MeshBatchSetInfo { + indirect_parameters_index: indirect_parameters_buffers + .allocate(mesh.indexed(), 1), + is_indexed: mesh.indexed(), + }); + } + let mesh_info = mesh_batch_set_info.unwrap(); + + // Allocate some input and output indices. We'll need these to + // create the *work item* below. + let Some(input_index) = + MeshPipeline::get_binned_index(&system_param_item, visible_entity) + else { + continue; + }; + let output_index = data_buffer.add() as u32; + // Finally, we can specialize the pipeline based on the key let pipeline_id = specialized_mesh_pipelines .specialize( @@ -343,6 +417,35 @@ fn queue_custom_mesh_pipeline( // support it you can use `BinnedRenderPhaseType::UnbatchableMesh` BinnedRenderPhaseType::BatchableMesh, ); + + // Create a *work item*. A work item tells the Bevy renderer to + // transform the mesh on GPU. + work_item_buffer.push( + mesh.indexed(), + PreprocessWorkItem { + input_index: input_index.into(), + output_index, + indirect_parameters_index: mesh_info.indirect_parameters_index, + }, + ); + } + + // Now if there were any meshes, we need to add a command to the + // indirect parameters buffer, so that the renderer will end up + // enqueuing a command to draw the mesh. + if let Some(mesh_info) = mesh_batch_set_info { + indirect_parameters_buffers + .add_batch_set(mesh_info.is_indexed, mesh_info.indirect_parameters_index); } } } + +// If we end up having any custom meshes to draw, this contains information +// needed to create the batch set. +#[derive(Clone, Copy)] +struct MeshBatchSetInfo { + /// The first index of the mesh batch in the indirect parameters buffer. + indirect_parameters_index: u32, + /// Whether the mesh is indexed (has an index buffer). + is_indexed: bool, +} From 0c6fd70834c19fd9f1525a23c7900658138e9e41 Mon Sep 17 00:00:00 2001 From: MichiRecRoom <1008889+LikeLakers2@users.noreply.github.com> Date: Tue, 14 Jan 2025 16:33:28 -0500 Subject: [PATCH 238/272] bevy_core_pipeline: Apply `#![warn(clippy::allow_attributes, clippy::allow_attributes_without_reason)]` (#17137) # Objective - https://github.com/bevyengine/bevy/issues/17111 ## Solution Set the `clippy::allow_attributes` and `clippy::allow_attributes_without_reason` lints to `warn`, and bring `bevy_core_pipeline` in line with the new restrictions. ## Testing `cargo clippy` and `cargo test --package bevy_core_pipeline` were run, and no warnings were encountered. --- .../src/auto_exposure/compensation_curve.rs | 5 ++++- crates/bevy_core_pipeline/src/lib.rs | 5 +++++ crates/bevy_core_pipeline/src/tonemapping/mod.rs | 7 +++++-- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/crates/bevy_core_pipeline/src/auto_exposure/compensation_curve.rs b/crates/bevy_core_pipeline/src/auto_exposure/compensation_curve.rs index 25ec27cee4df2..7a89de331c19c 100644 --- a/crates/bevy_core_pipeline/src/auto_exposure/compensation_curve.rs +++ b/crates/bevy_core_pipeline/src/auto_exposure/compensation_curve.rs @@ -136,7 +136,10 @@ impl AutoExposureCompensationCurve { let lut_inv_range = 1.0 / (lut_end - lut_begin); // Iterate over all LUT entries whose pixel centers fall within the current segment. - #[allow(clippy::needless_range_loop)] + #[expect( + clippy::needless_range_loop, + reason = "This for-loop also uses `i` to calculate a value `t`." + )] for i in lut_begin.ceil() as usize..=lut_end.floor() as usize { let t = (i as f32 - lut_begin) * lut_inv_range; lut[i] = previous.y.lerp(current.y, t); diff --git a/crates/bevy_core_pipeline/src/lib.rs b/crates/bevy_core_pipeline/src/lib.rs index 6c2ee5bec489b..b47f5513438ae 100644 --- a/crates/bevy_core_pipeline/src/lib.rs +++ b/crates/bevy_core_pipeline/src/lib.rs @@ -1,5 +1,10 @@ #![expect(missing_docs, reason = "Not all docs are written yet, see #3492.")] #![forbid(unsafe_code)] +#![warn( + clippy::allow_attributes, + clippy::allow_attributes_without_reason, + reason = "See #17111; To be removed once all crates are in-line with these attributes" +)] #![cfg_attr(docsrs, feature(doc_auto_cfg))] #![doc( html_logo_url = "https://bevyengine.org/assets/icon.png", diff --git a/crates/bevy_core_pipeline/src/tonemapping/mod.rs b/crates/bevy_core_pipeline/src/tonemapping/mod.rs index bfc89cc64568d..e932639b7f420 100644 --- a/crates/bevy_core_pipeline/src/tonemapping/mod.rs +++ b/crates/bevy_core_pipeline/src/tonemapping/mod.rs @@ -431,8 +431,11 @@ pub fn get_lut_bind_group_layout_entries() -> [BindGroupLayoutEntryBuilder; 2] { ] } -// allow(dead_code) so it doesn't complain when the tonemapping_luts feature is disabled -#[allow(dead_code)] +#[expect(clippy::allow_attributes, reason = "`dead_code` is not always linted.")] +#[allow( + dead_code, + reason = "There is unused code when the `tonemapping_luts` feature is disabled." +)] fn setup_tonemapping_lut_image(bytes: &[u8], image_type: ImageType) -> Image { let image_sampler = ImageSampler::Descriptor(bevy_image::ImageSamplerDescriptor { label: Some("Tonemapping LUT sampler".to_string()), From 61a226bd8b67d518e3a5d89c7765970b1cb9c147 Mon Sep 17 00:00:00 2001 From: MichiRecRoom <1008889+LikeLakers2@users.noreply.github.com> Date: Tue, 14 Jan 2025 16:33:41 -0500 Subject: [PATCH 239/272] bevy_dylib: Apply `#![warn(clippy::allow_attributes, clippy::allow_attributes_without_reason)]` (#17332) # Objective - https://github.com/bevyengine/bevy/issues/17111 ## Solution Set the `clippy::allow_attributes` and `clippy::allow_attributes_without_reason` lints to `warn`, and bring `bevy_dylib` in line with the new restrictions. ## Testing `cargo clippy --tests --all-features --package bevy_dylib` was run, and no warnings were encountered. I would've skipped over this crate if there weren't the two lint attributes in it - might as well handle it now, y'know? --- crates/bevy_dylib/src/lib.rs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/crates/bevy_dylib/src/lib.rs b/crates/bevy_dylib/src/lib.rs index c37cff70c36a1..93528c70c9a50 100644 --- a/crates/bevy_dylib/src/lib.rs +++ b/crates/bevy_dylib/src/lib.rs @@ -1,4 +1,9 @@ #![cfg_attr(docsrs, feature(doc_auto_cfg))] +#![warn( + clippy::allow_attributes, + clippy::allow_attributes_without_reason, + reason = "See #17111; To be removed once all crates are in-line with these attributes" +)] #![doc( html_logo_url = "https://bevyengine.org/assets/icon.png", html_favicon_url = "https://bevyengine.org/assets/icon.png" @@ -54,6 +59,9 @@ //! ``` // Force linking of the main bevy crate -#[allow(unused_imports)] -#[allow(clippy::single_component_path_imports)] +#[expect( + unused_imports, + clippy::single_component_path_imports, + reason = "This links the main bevy crate when using dynamic linking, and as such cannot be removed or changed without affecting dynamic linking." +)] use bevy_internal; From dfe8e6300aff84b54ad8a65f0d6f0da38cec4828 Mon Sep 17 00:00:00 2001 From: MichiRecRoom <1008889+LikeLakers2@users.noreply.github.com> Date: Tue, 14 Jan 2025 16:34:01 -0500 Subject: [PATCH 240/272] bevy_input_focus: Apply `#![warn(clippy::allow_attributes, clippy::allow_attributes_without_reason)]` (#17323) # Objective - https://github.com/bevyengine/bevy/issues/17111 ## Solution Set the `clippy::allow_attributes` and `clippy::allow_attributes_without_reason` lints to `warn`, and bring `bevy_input_focus` in line with the new restrictions. ## Testing `cargo clippy --tests --all-features --features bevy_math/std,bevy_input/smol_str --package bevy_input_focus` was run, and only an unrelated warning from `bevy_ecs` was encountered. I could not test without the `bevy_math/std` feature due to compilation errors with `glam`. Additionally, I had to use the `bevy_input/smol_str` feature, as it appears some of `bevy_input_focus`' tests rely on that feature. I will investigate these issues further, and make issues/PRs as necessary. --- crates/bevy_input_focus/src/lib.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/crates/bevy_input_focus/src/lib.rs b/crates/bevy_input_focus/src/lib.rs index 2e63ad339c830..c7feba9786d35 100644 --- a/crates/bevy_input_focus/src/lib.rs +++ b/crates/bevy_input_focus/src/lib.rs @@ -1,5 +1,10 @@ #![cfg_attr(docsrs, feature(doc_auto_cfg))] #![forbid(unsafe_code)] +#![warn( + clippy::allow_attributes, + clippy::allow_attributes_without_reason, + reason = "See #17111; To be removed once all crates are in-line with these attributes" +)] #![doc( html_logo_url = "https://bevyengine.org/assets/icon.png", html_favicon_url = "https://bevyengine.org/assets/icon.png" From 706cdd5ce2b13a235348517b82186563a2565dcd Mon Sep 17 00:00:00 2001 From: MichiRecRoom <1008889+LikeLakers2@users.noreply.github.com> Date: Tue, 14 Jan 2025 16:34:12 -0500 Subject: [PATCH 241/272] bevy_macro_utils: Apply `#![warn(clippy::allow_attributes, clippy::allow_attributes_without_reason)]` (#17304) # Objective - https://github.com/bevyengine/bevy/issues/17111 ## Solution Set the `clippy::allow_attributes` and `clippy::allow_attributes_without_reason` lints to `warn`, and bring `bevy_macro_utils` in line with the new restrictions. ## Testing `cargo clippy --tests --all-features --package bevy_macro_utils` was run, and no warnings were encountered. --- crates/bevy_macro_utils/src/lib.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/crates/bevy_macro_utils/src/lib.rs b/crates/bevy_macro_utils/src/lib.rs index 28de7e2227e26..7883ee2cf299a 100644 --- a/crates/bevy_macro_utils/src/lib.rs +++ b/crates/bevy_macro_utils/src/lib.rs @@ -1,4 +1,9 @@ #![forbid(unsafe_code)] +#![warn( + clippy::allow_attributes, + clippy::allow_attributes_without_reason, + reason = "See #17111; To be removed once all crates are in-line with these attributes" +)] #![cfg_attr(docsrs, feature(doc_auto_cfg))] #![doc( html_logo_url = "https://bevyengine.org/assets/icon.png", From 17c46f4add2c4cd29d25960e511c2e1644120db4 Mon Sep 17 00:00:00 2001 From: MichiRecRoom <1008889+LikeLakers2@users.noreply.github.com> Date: Tue, 14 Jan 2025 16:37:41 -0500 Subject: [PATCH 242/272] bevy_ecs: Apply `#![warn(clippy::allow_attributes, clippy::allow_attributes_without_reason)]` (#17335) # Objective - https://github.com/bevyengine/bevy/issues/17111 ## Solution Set the `clippy::allow_attributes` and `clippy::allow_attributes_without_reason` lints to `warn`, and bring `bevy_ecs` in line with the new restrictions. ## Testing This PR is a WIP; testing will happen after it's finished. --- crates/bevy_ecs/src/archetype.rs | 10 +- crates/bevy_ecs/src/bundle.rs | 45 +++++-- crates/bevy_ecs/src/change_detection.rs | 5 +- crates/bevy_ecs/src/entity/entity_set.rs | 11 +- crates/bevy_ecs/src/entity/mod.rs | 23 +++- crates/bevy_ecs/src/entity/visit_entities.rs | 1 - crates/bevy_ecs/src/event/event_cursor.rs | 1 - crates/bevy_ecs/src/event/mod.rs | 2 - crates/bevy_ecs/src/identifier/mod.rs | 5 +- crates/bevy_ecs/src/lib.rs | 61 +++++++--- crates/bevy_ecs/src/query/fetch.rs | 26 +++-- crates/bevy_ecs/src/query/filter.rs | 69 +++++++---- crates/bevy_ecs/src/query/iter.rs | 11 +- crates/bevy_ecs/src/query/world_query.rs | 66 ++++++----- crates/bevy_ecs/src/schedule/config.rs | 18 ++- crates/bevy_ecs/src/schedule/executor/mod.rs | 5 +- .../src/schedule/executor/multi_threaded.rs | 6 +- .../bevy_ecs/src/schedule/executor/simple.rs | 6 +- .../src/schedule/executor/single_threaded.rs | 6 +- crates/bevy_ecs/src/schedule/graph/mod.rs | 2 +- crates/bevy_ecs/src/schedule/mod.rs | 1 - crates/bevy_ecs/src/schedule/schedule.rs | 13 ++- crates/bevy_ecs/src/schedule/stepping.rs | 5 +- crates/bevy_ecs/src/system/builder.rs | 52 +++++++-- crates/bevy_ecs/src/system/combinator.rs | 2 +- crates/bevy_ecs/src/system/commands/mod.rs | 6 +- .../src/system/exclusive_function_system.rs | 18 ++- .../src/system/exclusive_system_param.rs | 24 +++- crates/bevy_ecs/src/system/function_system.rs | 18 ++- crates/bevy_ecs/src/system/input.rs | 14 ++- crates/bevy_ecs/src/system/mod.rs | 25 ++-- crates/bevy_ecs/src/system/system.rs | 6 +- crates/bevy_ecs/src/system/system_param.rs | 110 +++++++++++++----- crates/bevy_ecs/src/world/command_queue.rs | 13 ++- crates/bevy_ecs/src/world/entity_ref.rs | 23 ++-- .../bevy_ecs/src/world/unsafe_world_cell.rs | 5 - 36 files changed, 492 insertions(+), 222 deletions(-) diff --git a/crates/bevy_ecs/src/archetype.rs b/crates/bevy_ecs/src/archetype.rs index e0d85242f9206..e3b8b8dac545d 100644 --- a/crates/bevy_ecs/src/archetype.rs +++ b/crates/bevy_ecs/src/archetype.rs @@ -789,7 +789,10 @@ pub struct Archetypes { pub struct ArchetypeRecord { /// Index of the component in the archetype's [`Table`](crate::storage::Table), /// or None if the component is a sparse set component. - #[allow(dead_code)] + #[expect( + dead_code, + reason = "Currently unused, but planned to be used to implement a component index to improve performance of fragmenting relations." + )] pub(crate) column: Option, } @@ -827,7 +830,10 @@ impl Archetypes { /// Fetches the total number of [`Archetype`]s within the world. #[inline] - #[allow(clippy::len_without_is_empty)] // the internal vec is never empty. + #[expect( + clippy::len_without_is_empty, + reason = "The internal vec is never empty" + )] pub fn len(&self) -> usize { self.archetypes.len() } diff --git a/crates/bevy_ecs/src/bundle.rs b/crates/bevy_ecs/src/bundle.rs index 4dc96311bb25a..6b4d9da2811bb 100644 --- a/crates/bevy_ecs/src/bundle.rs +++ b/crates/bevy_ecs/src/bundle.rs @@ -246,6 +246,15 @@ impl DynamicBundle for C { macro_rules! tuple_impl { ($(#[$meta:meta])* $($name: ident),*) => { + #[expect( + clippy::allow_attributes, + reason = "This is a tuple-related macro; as such, the lints below may not always apply." + )] + #[allow( + unused_mut, + unused_variables, + reason = "Zero-length tuples won't use any of the parameters." + )] $(#[$meta])* // SAFETY: // - `Bundle::component_ids` calls `ids` for each component type in the @@ -254,43 +263,57 @@ macro_rules! tuple_impl { // - `Bundle::get_components` is called exactly once for each member. Relies on the above implementation to pass the correct // `StorageType` into the callback. unsafe impl<$($name: Bundle),*> Bundle for ($($name,)*) { - #[allow(unused_variables)] fn component_ids(components: &mut Components, storages: &mut Storages, ids: &mut impl FnMut(ComponentId)){ $(<$name as Bundle>::component_ids(components, storages, ids);)* } - #[allow(unused_variables)] fn get_component_ids(components: &Components, ids: &mut impl FnMut(Option)){ $(<$name as Bundle>::get_component_ids(components, ids);)* } - #[allow(unused_variables, unused_mut)] - #[allow(clippy::unused_unit)] + #[allow( + clippy::unused_unit, + reason = "Zero-length tuples will generate a function body equivalent to `()`; however, this macro is meant for all applicable tuples, and as such it makes no sense to rewrite it just for that case." + )] unsafe fn from_components(ctx: &mut T, func: &mut F) -> Self where F: FnMut(&mut T) -> OwningPtr<'_> { - #[allow(unused_unsafe)] + #[allow( + unused_unsafe, + reason = "Zero-length tuples will not run anything in the unsafe block. Additionally, rewriting this to move the () outside of the unsafe would require putting the safety comment inside the tuple, hurting readability of the code." + )] // SAFETY: Rust guarantees that tuple calls are evaluated 'left to right'. // https://doc.rust-lang.org/reference/expressions.html#evaluation-order-of-operands unsafe { ($(<$name as Bundle>::from_components(ctx, func),)*) } } fn register_required_components( - _components: &mut Components, - _storages: &mut Storages, - _required_components: &mut RequiredComponents, + components: &mut Components, + storages: &mut Storages, + required_components: &mut RequiredComponents, ) { - $(<$name as Bundle>::register_required_components(_components, _storages, _required_components);)* + $(<$name as Bundle>::register_required_components(components, storages, required_components);)* } } + #[expect( + clippy::allow_attributes, + reason = "This is a tuple-related macro; as such, the lints below may not always apply." + )] + #[allow( + unused_mut, + unused_variables, + reason = "Zero-length tuples won't use any of the parameters." + )] $(#[$meta])* impl<$($name: Bundle),*> DynamicBundle for ($($name,)*) { - #[allow(unused_variables, unused_mut)] #[inline(always)] fn get_components(self, func: &mut impl FnMut(StorageType, OwningPtr<'_>)) { - #[allow(non_snake_case)] + #[allow( + non_snake_case, + reason = "The names of these variables are provided by the caller, not by us." + )] let ($(mut $name,)*) = self; $( $name.get_components(&mut *func); diff --git a/crates/bevy_ecs/src/change_detection.rs b/crates/bevy_ecs/src/change_detection.rs index 6be63ba964b87..e238287a7ec3d 100644 --- a/crates/bevy_ecs/src/change_detection.rs +++ b/crates/bevy_ecs/src/change_detection.rs @@ -609,7 +609,10 @@ impl<'w, T: Resource> Res<'w, T> { /// /// Note that unless you actually need an instance of `Res`, you should /// prefer to just convert it to `&T` which can be freely copied. - #[allow(clippy::should_implement_trait)] + #[expect( + clippy::should_implement_trait, + reason = "As this struct derefs to the inner resource, a `Clone` trait implementation would interfere with the common case of cloning the inner content. (A similar case of this happening can be found with `std::cell::Ref::clone()`.)" + )] pub fn clone(this: &Self) -> Self { Self { value: this.value, diff --git a/crates/bevy_ecs/src/entity/entity_set.rs b/crates/bevy_ecs/src/entity/entity_set.rs index be8ac88d04595..e2d77a98fd46a 100644 --- a/crates/bevy_ecs/src/entity/entity_set.rs +++ b/crates/bevy_ecs/src/entity/entity_set.rs @@ -382,25 +382,24 @@ impl + Debug> Debug for UniqueEntityIter< mod tests { use alloc::{vec, vec::Vec}; - #[allow(unused_imports)] use crate::prelude::{Schedule, World}; - #[allow(unused_imports)] use crate::component::Component; + use crate::entity::Entity; use crate::query::{QueryState, With}; use crate::system::Query; use crate::world::Mut; - #[allow(unused_imports)] use crate::{self as bevy_ecs}; - #[allow(unused_imports)] - use crate::{entity::Entity, world::unsafe_world_cell}; use super::UniqueEntityIter; #[derive(Component, Clone)] pub struct Thing; - #[allow(clippy::iter_skip_zero)] + #[expect( + clippy::iter_skip_zero, + reason = "The `skip(0)` is used to ensure that the `Skip` iterator implements `EntitySet`, which is needed to pass the iterator as the `entities` parameter." + )] #[test] fn preserving_uniqueness() { let mut world = World::new(); diff --git a/crates/bevy_ecs/src/entity/mod.rs b/crates/bevy_ecs/src/entity/mod.rs index 07df26a09a78d..946b6821a4e40 100644 --- a/crates/bevy_ecs/src/entity/mod.rs +++ b/crates/bevy_ecs/src/entity/mod.rs @@ -592,7 +592,14 @@ impl Entities { /// Reserve entity IDs concurrently. /// /// Storage for entity generation and location is lazily allocated by calling [`flush`](Entities::flush). - #[allow(clippy::unnecessary_fallible_conversions)] // Because `IdCursor::try_from` may fail on 32-bit platforms. + #[expect( + clippy::allow_attributes, + reason = "`clippy::unnecessary_fallible_conversions` may not always lint." + )] + #[allow( + clippy::unnecessary_fallible_conversions, + reason = "`IdCursor::try_from` may fail on 32-bit platforms." + )] pub fn reserve_entities(&self, count: u32) -> ReserveEntitiesIterator { // Use one atomic subtract to grab a range of new IDs. The range might be // entirely nonnegative, meaning all IDs come from the freelist, or entirely @@ -786,7 +793,14 @@ impl Entities { } /// Ensure at least `n` allocations can succeed without reallocating. - #[allow(clippy::unnecessary_fallible_conversions)] // Because `IdCursor::try_from` may fail on 32-bit platforms. + #[expect( + clippy::allow_attributes, + reason = "`clippy::unnecessary_fallible_conversions` may not always lint." + )] + #[allow( + clippy::unnecessary_fallible_conversions, + reason = "`IdCursor::try_from` may fail on 32-bit platforms." + )] pub fn reserve(&mut self, additional: u32) { self.verify_flushed(); @@ -1178,7 +1192,10 @@ mod tests { } #[test] - #[allow(clippy::nonminimal_bool)] // This is intentionally testing `lt` and `ge` as separate functions. + #[expect( + clippy::nonminimal_bool, + reason = "This intentionally tests all possible comparison operators as separate functions; thus, we don't want to rewrite these comparisons to use different operators." + )] fn entity_comparison() { assert_eq!( Entity::from_raw_and_generation(123, NonZero::::new(456).unwrap()), diff --git a/crates/bevy_ecs/src/entity/visit_entities.rs b/crates/bevy_ecs/src/entity/visit_entities.rs index a9f5e8dcbd847..0896d7208100a 100644 --- a/crates/bevy_ecs/src/entity/visit_entities.rs +++ b/crates/bevy_ecs/src/entity/visit_entities.rs @@ -71,7 +71,6 @@ mod tests { ordered: Vec, unordered: HashSet, single: Entity, - #[allow(dead_code)] #[visit_entities(ignore)] not_an_entity: String, } diff --git a/crates/bevy_ecs/src/event/event_cursor.rs b/crates/bevy_ecs/src/event/event_cursor.rs index ca1be152e5caa..262d4e3636adc 100644 --- a/crates/bevy_ecs/src/event/event_cursor.rs +++ b/crates/bevy_ecs/src/event/event_cursor.rs @@ -74,7 +74,6 @@ impl Clone for EventCursor { } } -#[allow(clippy::len_without_is_empty)] // Check fails since the is_empty implementation has a signature other than `(&self) -> bool` impl EventCursor { /// See [`EventReader::read`](super::EventReader::read) pub fn read<'a>(&'a mut self, events: &'a Events) -> EventIterator<'a, E> { diff --git a/crates/bevy_ecs/src/event/mod.rs b/crates/bevy_ecs/src/event/mod.rs index 1dbcf1ba0cb14..c8bd24dcdde5f 100644 --- a/crates/bevy_ecs/src/event/mod.rs +++ b/crates/bevy_ecs/src/event/mod.rs @@ -568,7 +568,6 @@ mod tests { assert!(last.is_none(), "EventMutator should be empty"); } - #[allow(clippy::iter_nth_zero)] #[test] fn test_event_reader_iter_nth() { use bevy_ecs::prelude::*; @@ -595,7 +594,6 @@ mod tests { schedule.run(&mut world); } - #[allow(clippy::iter_nth_zero)] #[test] fn test_event_mutator_iter_nth() { use bevy_ecs::prelude::*; diff --git a/crates/bevy_ecs/src/identifier/mod.rs b/crates/bevy_ecs/src/identifier/mod.rs index 6134e472427e2..cf467da505dfc 100644 --- a/crates/bevy_ecs/src/identifier/mod.rs +++ b/crates/bevy_ecs/src/identifier/mod.rs @@ -216,7 +216,10 @@ mod tests { #[rustfmt::skip] #[test] - #[allow(clippy::nonminimal_bool)] // This is intentionally testing `lt` and `ge` as separate functions. + #[expect( + clippy::nonminimal_bool, + reason = "This intentionally tests all possible comparison operators as separate functions; thus, we don't want to rewrite these comparisons to use different operators." + )] fn id_comparison() { assert!(Identifier::new(123, 456, IdKind::Entity).unwrap() == Identifier::new(123, 456, IdKind::Entity).unwrap()); assert!(Identifier::new(123, 456, IdKind::Placeholder).unwrap() == Identifier::new(123, 456, IdKind::Placeholder).unwrap()); diff --git a/crates/bevy_ecs/src/lib.rs b/crates/bevy_ecs/src/lib.rs index 860916362fc99..5b2b2d7a891e6 100644 --- a/crates/bevy_ecs/src/lib.rs +++ b/crates/bevy_ecs/src/lib.rs @@ -1,7 +1,14 @@ -// FIXME(11590): remove this once the lint is fixed -#![allow(unsafe_op_in_unsafe_fn)] -// TODO: remove once Edition 2024 is released -#![allow(dependency_on_unit_never_type_fallback)] +#![expect( + unsafe_op_in_unsafe_fn, + reason = "See #11590. To be removed once all applicable unsafe code has an unsafe block with a safety comment." +)] +#![cfg_attr( + test, + expect( + dependency_on_unit_never_type_fallback, + reason = "See #17340. To be removed once Edition 2024 is released" + ) +)] #![doc = include_str!("../README.md")] #![cfg_attr( any(docsrs, docsrs_dep), @@ -11,7 +18,12 @@ ) )] #![cfg_attr(any(docsrs, docsrs_dep), feature(doc_auto_cfg, rustdoc_internals))] -#![allow(unsafe_code)] +#![expect(unsafe_code, reason = "Unsafe code is used to improve performance.")] +#![warn( + clippy::allow_attributes, + clippy::allow_attributes_without_reason, + reason = "See #17111; To be removed once all crates are in-line with these attributes" +)] #![doc( html_logo_url = "https://bevyengine.org/assets/icon.png", html_favicon_url = "https://bevyengine.org/assets/icon.png" @@ -55,7 +67,10 @@ pub use bevy_ptr as ptr; /// /// This includes the most common types in this crate, re-exported for your convenience. pub mod prelude { - #[expect(deprecated)] + #[expect( + deprecated, + reason = "`crate::schedule::apply_deferred` is considered deprecated; however, it may still be used by crates which consume `bevy_ecs`, so its removal here may cause confusion. It is intended to be removed in the Bevy 0.17 cycle." + )] #[doc(hidden)] pub use crate::{ bundle::Bundle, @@ -147,9 +162,8 @@ mod tests { #[derive(Component, Debug, PartialEq, Eq, Clone, Copy)] struct C; - #[allow(dead_code)] #[derive(Default)] - struct NonSendA(usize, PhantomData<*mut ()>); + struct NonSendA(PhantomData<*mut ()>); #[derive(Component, Clone, Debug)] struct DropCk(Arc); @@ -166,8 +180,10 @@ mod tests { } } - // TODO: The compiler says the Debug and Clone are removed during dead code analysis. Investigate. - #[allow(dead_code)] + #[expect( + dead_code, + reason = "This struct is used to test how `Drop` behavior works in regards to SparseSet storage, and as such is solely a wrapper around `DropCk` to make it use the SparseSet storage. Because of this, the inner field is intentionally never read." + )] #[derive(Component, Clone, Debug)] #[component(storage = "SparseSet")] struct DropCkSparse(DropCk); @@ -2641,42 +2657,49 @@ mod tests { World::new().register_component::(); } - // These structs are primarily compilation tests to test the derive macros. Because they are - // never constructed, we have to manually silence the `dead_code` lint. - #[allow(dead_code)] + #[expect( + dead_code, + reason = "This struct is used as a compilation test to test the derive macros, and as such is intentionally never constructed." + )] #[derive(Component)] struct ComponentA(u32); - #[allow(dead_code)] + #[expect( + dead_code, + reason = "This struct is used as a compilation test to test the derive macros, and as such is intentionally never constructed." + )] #[derive(Component)] struct ComponentB(u32); - #[allow(dead_code)] #[derive(Bundle)] struct Simple(ComponentA); - #[allow(dead_code)] #[derive(Bundle)] struct Tuple(Simple, ComponentB); - #[allow(dead_code)] #[derive(Bundle)] struct Record { field0: Simple, field1: ComponentB, } - #[allow(dead_code)] #[derive(Component, VisitEntities, VisitEntitiesMut)] struct MyEntities { entities: Vec, another_one: Entity, maybe_entity: Option, + #[expect( + dead_code, + reason = "This struct is used as a compilation test to test the derive macros, and as such this field is intentionally never used." + )] #[visit_entities(ignore)] something_else: String, } - #[allow(dead_code)] + #[expect( + dead_code, + reason = "This struct is used as a compilation test to test the derive macros, and as such is intentionally never constructed." + )] #[derive(Component, VisitEntities, VisitEntitiesMut)] struct MyEntitiesTuple(Vec, Entity, #[visit_entities(ignore)] usize); } diff --git a/crates/bevy_ecs/src/query/fetch.rs b/crates/bevy_ecs/src/query/fetch.rs index 6df9b8e2de745..afb56206454e4 100644 --- a/crates/bevy_ecs/src/query/fetch.rs +++ b/crates/bevy_ecs/src/query/fetch.rs @@ -2015,8 +2015,6 @@ pub struct AnyOf(PhantomData); macro_rules! impl_tuple_query_data { ($(#[$meta:meta])* $(($name: ident, $state: ident)),*) => { - #[allow(non_snake_case)] - #[allow(clippy::unused_unit)] $(#[$meta])* // SAFETY: defers to soundness `$name: WorldQuery` impl unsafe impl<$($name: QueryData),*> QueryData for ($($name,)*) { @@ -2033,8 +2031,22 @@ macro_rules! impl_tuple_query_data { macro_rules! impl_anytuple_fetch { ($(#[$meta:meta])* $(($name: ident, $state: ident)),*) => { $(#[$meta])* - #[allow(non_snake_case)] - #[allow(clippy::unused_unit)] + #[expect( + clippy::allow_attributes, + reason = "This is a tuple-related macro; as such the lints below may not always apply." + )] + #[allow( + non_snake_case, + reason = "The names of some variables are provided by the macro's caller, not by us." + )] + #[allow( + unused_variables, + reason = "Zero-length tuples won't use any of the parameters." + )] + #[allow( + clippy::unused_unit, + reason = "Zero-length tuples will generate some function bodies equivalent to `()`; however, this macro is meant for all applicable tuples, and as such it makes no sense to rewrite it just for that case." + )] /// SAFETY: /// `fetch` accesses are a subset of the subqueries' accesses /// This is sound because `update_component_access` and `update_archetype_component_access` adds accesses according to the implementations of all the subqueries. @@ -2059,7 +2071,6 @@ macro_rules! impl_anytuple_fetch { } #[inline] - #[allow(clippy::unused_unit)] unsafe fn init_fetch<'w>(_world: UnsafeWorldCell<'w>, state: &Self::State, _last_run: Tick, _this_run: Tick) -> Self::Fetch<'w> { let ($($name,)*) = state; // SAFETY: The invariants are uphold by the caller. @@ -2100,7 +2111,6 @@ macro_rules! impl_anytuple_fetch { } #[inline(always)] - #[allow(clippy::unused_unit)] unsafe fn fetch<'w>( _fetch: &mut Self::Fetch<'w>, _entity: Entity, @@ -2139,11 +2149,9 @@ macro_rules! impl_anytuple_fetch { <($(Option<$name>,)*)>::update_component_access(state, access); } - #[allow(unused_variables)] fn init_state(world: &mut World) -> Self::State { ($($name::init_state(world),)*) } - #[allow(unused_variables)] fn get_state(components: &Components) -> Option { Some(($($name::get_state(components)?,)*)) } @@ -2155,8 +2163,6 @@ macro_rules! impl_anytuple_fetch { } $(#[$meta])* - #[allow(non_snake_case)] - #[allow(clippy::unused_unit)] // SAFETY: defers to soundness of `$name: WorldQuery` impl unsafe impl<$($name: QueryData),*> QueryData for AnyOf<($($name,)*)> { type ReadOnly = AnyOf<($($name::ReadOnly,)*)>; diff --git a/crates/bevy_ecs/src/query/filter.rs b/crates/bevy_ecs/src/query/filter.rs index 2540e7cc7abe3..3a3d9a0162769 100644 --- a/crates/bevy_ecs/src/query/filter.rs +++ b/crates/bevy_ecs/src/query/filter.rs @@ -98,7 +98,6 @@ pub unsafe trait QueryFilter: WorldQuery { /// /// Must always be called _after_ [`WorldQuery::set_table`] or [`WorldQuery::set_archetype`]. `entity` and /// `table_row` must be in the range of the current table and archetype. - #[allow(unused_variables)] unsafe fn filter_fetch( fetch: &mut Self::Fetch<'_>, entity: Entity, @@ -381,9 +380,22 @@ impl Clone for OrFetch<'_, T> { macro_rules! impl_or_query_filter { ($(#[$meta:meta])* $(($filter: ident, $state: ident)),*) => { $(#[$meta])* - #[allow(unused_variables)] - #[allow(non_snake_case)] - #[allow(clippy::unused_unit)] + #[expect( + clippy::allow_attributes, + reason = "This is a tuple-related macro; as such the lints below may not always apply." + )] + #[allow( + non_snake_case, + reason = "The names of some variables are provided by the macro's caller, not by us." + )] + #[allow( + unused_variables, + reason = "Zero-length tuples won't use any of the parameters." + )] + #[allow( + clippy::unused_unit, + reason = "Zero-length tuples will generate some function bodies equivalent to `()`; however, this macro is meant for all applicable tuples, and as such it makes no sense to rewrite it just for that case." + )] /// SAFETY: /// `fetch` accesses are a subset of the subqueries' accesses /// This is sound because `update_component_access` adds accesses according to the implementations of all the subqueries. @@ -454,34 +466,34 @@ macro_rules! impl_or_query_filter { #[inline(always)] unsafe fn fetch<'w>( fetch: &mut Self::Fetch<'w>, - _entity: Entity, - _table_row: TableRow + entity: Entity, + table_row: TableRow ) -> Self::Item<'w> { let ($($filter,)*) = fetch; // SAFETY: The invariants are uphold by the caller. - false $(|| ($filter.matches && unsafe { $filter::filter_fetch(&mut $filter.fetch, _entity, _table_row) }))* + false $(|| ($filter.matches && unsafe { $filter::filter_fetch(&mut $filter.fetch, entity, table_row) }))* } fn update_component_access(state: &Self::State, access: &mut FilteredAccess) { let ($($filter,)*) = state; - let mut _new_access = FilteredAccess::matches_nothing(); + let mut new_access = FilteredAccess::matches_nothing(); $( // Create an intermediate because `access`'s value needs to be preserved // for the next filter, and `_new_access` has to be modified only by `append_or` to it. let mut intermediate = access.clone(); $filter::update_component_access($filter, &mut intermediate); - _new_access.append_or(&intermediate); + new_access.append_or(&intermediate); // Also extend the accesses required to compute the filter. This is required because // otherwise a `Query<(), Or<(Changed,)>` won't conflict with `Query<&mut Foo>`. - _new_access.extend_access(&intermediate); + new_access.extend_access(&intermediate); )* // The required components remain the same as the original `access`. - _new_access.required = core::mem::take(&mut access.required); + new_access.required = core::mem::take(&mut access.required); - *access = _new_access; + *access = new_access; } fn init_state(world: &mut World) -> Self::State { @@ -492,15 +504,15 @@ macro_rules! impl_or_query_filter { Some(($($filter::get_state(components)?,)*)) } - fn matches_component_set(_state: &Self::State, _set_contains_id: &impl Fn(ComponentId) -> bool) -> bool { - let ($($filter,)*) = _state; - false $(|| $filter::matches_component_set($filter, _set_contains_id))* + fn matches_component_set(state: &Self::State, set_contains_id: &impl Fn(ComponentId) -> bool) -> bool { + let ($($filter,)*) = state; + false $(|| $filter::matches_component_set($filter, set_contains_id))* } } - $(#[$meta])* - // SAFETY: This only performs access that subqueries perform, and they impl `QueryFilter` and so perform no mutable access. - unsafe impl<$($filter: QueryFilter),*> QueryFilter for Or<($($filter,)*)> { + $(#[$meta])* + // SAFETY: This only performs access that subqueries perform, and they impl `QueryFilter` and so perform no mutable access. + unsafe impl<$($filter: QueryFilter),*> QueryFilter for Or<($($filter,)*)> { const IS_ARCHETYPAL: bool = true $(&& $filter::IS_ARCHETYPAL)*; #[inline(always)] @@ -518,9 +530,18 @@ macro_rules! impl_or_query_filter { macro_rules! impl_tuple_query_filter { ($(#[$meta:meta])* $($name: ident),*) => { - #[allow(unused_variables)] - #[allow(non_snake_case)] - #[allow(clippy::unused_unit)] + #[expect( + clippy::allow_attributes, + reason = "This is a tuple-related macro; as such the lints below may not always apply." + )] + #[allow( + non_snake_case, + reason = "The names of some variables are provided by the macro's caller, not by us." + )] + #[allow( + unused_variables, + reason = "Zero-length tuples won't use any of the parameters." + )] $(#[$meta])* // SAFETY: This only performs access that subqueries perform, and they impl `QueryFilter` and so perform no mutable access. unsafe impl<$($name: QueryFilter),*> QueryFilter for ($($name,)*) { @@ -529,12 +550,12 @@ macro_rules! impl_tuple_query_filter { #[inline(always)] unsafe fn filter_fetch( fetch: &mut Self::Fetch<'_>, - _entity: Entity, - _table_row: TableRow + entity: Entity, + table_row: TableRow ) -> bool { let ($($name,)*) = fetch; // SAFETY: The invariants are uphold by the caller. - true $(&& unsafe { $name::filter_fetch($name, _entity, _table_row) })* + true $(&& unsafe { $name::filter_fetch($name, entity, table_row) })* } } diff --git a/crates/bevy_ecs/src/query/iter.rs b/crates/bevy_ecs/src/query/iter.rs index 13eaf3f08c0a4..2fa595b925a28 100644 --- a/crates/bevy_ecs/src/query/iter.rs +++ b/crates/bevy_ecs/src/query/iter.rs @@ -2935,7 +2935,10 @@ impl PartialEq for NeutralOrd { impl Eq for NeutralOrd {} -#[allow(clippy::non_canonical_partial_ord_impl)] +#[expect( + clippy::non_canonical_partial_ord_impl, + reason = "`PartialOrd` and `Ord` on this struct must only ever return `Ordering::Equal`, so we prefer clarity" +)] impl PartialOrd for NeutralOrd { fn partial_cmp(&self, _other: &Self) -> Option { Some(Ordering::Equal) @@ -2953,13 +2956,9 @@ mod tests { use alloc::vec::Vec; use std::println; - #[allow(unused_imports)] use crate::component::Component; - #[allow(unused_imports)] use crate::entity::Entity; - #[allow(unused_imports)] use crate::prelude::World; - #[allow(unused_imports)] use crate::{self as bevy_ecs}; #[derive(Component, Debug, PartialEq, PartialOrd, Clone, Copy)] @@ -2968,7 +2967,6 @@ mod tests { #[component(storage = "SparseSet")] struct Sparse(usize); - #[allow(clippy::unnecessary_sort_by)] #[test] fn query_iter_sorts() { let mut world = World::new(); @@ -3156,7 +3154,6 @@ mod tests { } } - #[allow(clippy::unnecessary_sort_by)] #[test] fn query_iter_many_sorts() { let mut world = World::new(); diff --git a/crates/bevy_ecs/src/query/world_query.rs b/crates/bevy_ecs/src/query/world_query.rs index 3f9111ff3c566..88164e3398634 100644 --- a/crates/bevy_ecs/src/query/world_query.rs +++ b/crates/bevy_ecs/src/query/world_query.rs @@ -157,8 +157,22 @@ pub unsafe trait WorldQuery { macro_rules! impl_tuple_world_query { ($(#[$meta:meta])* $(($name: ident, $state: ident)),*) => { - #[allow(non_snake_case)] - #[allow(clippy::unused_unit)] + #[expect( + clippy::allow_attributes, + reason = "This is a tuple-related macro; as such the lints below may not always apply." + )] + #[allow( + non_snake_case, + reason = "The names of some variables are provided by the macro's caller, not by us." + )] + #[allow( + unused_variables, + reason = "Zero-length tuples won't use any of the parameters." + )] + #[allow( + clippy::unused_unit, + reason = "Zero-length tuples will generate some function bodies equivalent to `()`; however, this macro is meant for all applicable tuples, and as such it makes no sense to rewrite it just for that case." + )] $(#[$meta])* /// SAFETY: /// `fetch` accesses are the conjunction of the subqueries' accesses @@ -185,64 +199,60 @@ macro_rules! impl_tuple_world_query { } #[inline] - #[allow(clippy::unused_unit)] - unsafe fn init_fetch<'w>(_world: UnsafeWorldCell<'w>, state: &Self::State, _last_run: Tick, _this_run: Tick) -> Self::Fetch<'w> { + unsafe fn init_fetch<'w>(world: UnsafeWorldCell<'w>, state: &Self::State, last_run: Tick, this_run: Tick) -> Self::Fetch<'w> { let ($($name,)*) = state; // SAFETY: The invariants are uphold by the caller. - ($(unsafe { $name::init_fetch(_world, $name, _last_run, _this_run) },)*) + ($(unsafe { $name::init_fetch(world, $name, last_run, this_run) },)*) } const IS_DENSE: bool = true $(&& $name::IS_DENSE)*; #[inline] unsafe fn set_archetype<'w>( - _fetch: &mut Self::Fetch<'w>, - _state: &Self::State, - _archetype: &'w Archetype, - _table: &'w Table + fetch: &mut Self::Fetch<'w>, + state: &Self::State, + archetype: &'w Archetype, + table: &'w Table ) { - let ($($name,)*) = _fetch; - let ($($state,)*) = _state; + let ($($name,)*) = fetch; + let ($($state,)*) = state; // SAFETY: The invariants are uphold by the caller. - $(unsafe { $name::set_archetype($name, $state, _archetype, _table); })* + $(unsafe { $name::set_archetype($name, $state, archetype, table); })* } #[inline] - unsafe fn set_table<'w>(_fetch: &mut Self::Fetch<'w>, _state: &Self::State, _table: &'w Table) { - let ($($name,)*) = _fetch; - let ($($state,)*) = _state; + unsafe fn set_table<'w>(fetch: &mut Self::Fetch<'w>, state: &Self::State, table: &'w Table) { + let ($($name,)*) = fetch; + let ($($state,)*) = state; // SAFETY: The invariants are uphold by the caller. - $(unsafe { $name::set_table($name, $state, _table); })* + $(unsafe { $name::set_table($name, $state, table); })* } #[inline(always)] - #[allow(clippy::unused_unit)] unsafe fn fetch<'w>( - _fetch: &mut Self::Fetch<'w>, - _entity: Entity, - _table_row: TableRow + fetch: &mut Self::Fetch<'w>, + entity: Entity, + table_row: TableRow ) -> Self::Item<'w> { - let ($($name,)*) = _fetch; + let ($($name,)*) = fetch; // SAFETY: The invariants are uphold by the caller. - ($(unsafe { $name::fetch($name, _entity, _table_row) },)*) + ($(unsafe { $name::fetch($name, entity, table_row) },)*) } - fn update_component_access(state: &Self::State, _access: &mut FilteredAccess) { + fn update_component_access(state: &Self::State, access: &mut FilteredAccess) { let ($($name,)*) = state; - $($name::update_component_access($name, _access);)* + $($name::update_component_access($name, access);)* } - #[allow(unused_variables)] fn init_state(world: &mut World) -> Self::State { ($($name::init_state(world),)*) } - #[allow(unused_variables)] fn get_state(components: &Components) -> Option { Some(($($name::get_state(components)?,)*)) } - fn matches_component_set(state: &Self::State, _set_contains_id: &impl Fn(ComponentId) -> bool) -> bool { + fn matches_component_set(state: &Self::State, set_contains_id: &impl Fn(ComponentId) -> bool) -> bool { let ($($name,)*) = state; - true $(&& $name::matches_component_set($name, _set_contains_id))* + true $(&& $name::matches_component_set($name, set_contains_id))* } } }; diff --git a/crates/bevy_ecs/src/schedule/config.rs b/crates/bevy_ecs/src/schedule/config.rs index 909aca8775ba8..20308578a4726 100644 --- a/crates/bevy_ecs/src/schedule/config.rs +++ b/crates/bevy_ecs/src/schedule/config.rs @@ -563,7 +563,14 @@ macro_rules! impl_system_collection { where $($sys: IntoSystemConfigs<$param>),* { - #[allow(non_snake_case)] + #[expect( + clippy::allow_attributes, + reason = "We are inside a macro, and as such, `non_snake_case` is not guaranteed to apply." + )] + #[allow( + non_snake_case, + reason = "Variable names are provided by the macro caller, not by us." + )] fn into_configs(self) -> SystemConfigs { let ($($sys,)*) = self; SystemConfigs::Configs { @@ -788,7 +795,14 @@ macro_rules! impl_system_set_collection { $(#[$meta])* impl<$($set: IntoSystemSetConfigs),*> IntoSystemSetConfigs for ($($set,)*) { - #[allow(non_snake_case)] + #[expect( + clippy::allow_attributes, + reason = "We are inside a macro, and as such, `non_snake_case` is not guaranteed to apply." + )] + #[allow( + non_snake_case, + reason = "Variable names are provided by the macro caller, not by us." + )] fn into_configs(self) -> SystemSetConfigs { let ($($set,)*) = self; SystemSetConfigs::Configs { diff --git a/crates/bevy_ecs/src/schedule/executor/mod.rs b/crates/bevy_ecs/src/schedule/executor/mod.rs index 79c31aac0e2e6..8b549a15ea6d3 100644 --- a/crates/bevy_ecs/src/schedule/executor/mod.rs +++ b/crates/bevy_ecs/src/schedule/executor/mod.rs @@ -122,7 +122,10 @@ impl SystemSchedule { since = "0.16.0", note = "Use `ApplyDeferred` instead. This was previously a function but is now a marker struct System." )] -#[expect(non_upper_case_globals)] +#[expect( + non_upper_case_globals, + reason = "This item is deprecated; as such, its previous name needs to stay." +)] pub const apply_deferred: ApplyDeferred = ApplyDeferred; /// A special [`System`] that instructs the executor to call diff --git a/crates/bevy_ecs/src/schedule/executor/multi_threaded.rs b/crates/bevy_ecs/src/schedule/executor/multi_threaded.rs index 1185931fd64ac..a9eaea7311789 100644 --- a/crates/bevy_ecs/src/schedule/executor/multi_threaded.rs +++ b/crates/bevy_ecs/src/schedule/executor/multi_threaded.rs @@ -758,8 +758,10 @@ unsafe fn evaluate_and_fold_conditions( conditions: &mut [BoxedCondition], world: UnsafeWorldCell, ) -> bool { - // not short-circuiting is intentional - #[allow(clippy::unnecessary_fold)] + #[expect( + clippy::unnecessary_fold, + reason = "Short-circuiting here would prevent conditions from mutating their own state as needed." + )] conditions .iter_mut() .map(|condition| { diff --git a/crates/bevy_ecs/src/schedule/executor/simple.rs b/crates/bevy_ecs/src/schedule/executor/simple.rs index 122414f45e07b..81f7deab3a302 100644 --- a/crates/bevy_ecs/src/schedule/executor/simple.rs +++ b/crates/bevy_ecs/src/schedule/executor/simple.rs @@ -149,8 +149,10 @@ impl SimpleExecutor { } fn evaluate_and_fold_conditions(conditions: &mut [BoxedCondition], world: &mut World) -> bool { - // not short-circuiting is intentional - #[allow(clippy::unnecessary_fold)] + #[expect( + clippy::unnecessary_fold, + reason = "Short-circuiting here would prevent conditions from mutating their own state as needed." + )] conditions .iter_mut() .map(|condition| { diff --git a/crates/bevy_ecs/src/schedule/executor/single_threaded.rs b/crates/bevy_ecs/src/schedule/executor/single_threaded.rs index b42b419e9fe37..8c5a7e0261bbe 100644 --- a/crates/bevy_ecs/src/schedule/executor/single_threaded.rs +++ b/crates/bevy_ecs/src/schedule/executor/single_threaded.rs @@ -191,8 +191,10 @@ impl SingleThreadedExecutor { } fn evaluate_and_fold_conditions(conditions: &mut [BoxedCondition], world: &mut World) -> bool { - // not short-circuiting is intentional - #[allow(clippy::unnecessary_fold)] + #[expect( + clippy::unnecessary_fold, + reason = "Short-circuiting here would prevent conditions from mutating their own state as needed." + )] conditions .iter_mut() .map(|condition| { diff --git a/crates/bevy_ecs/src/schedule/graph/mod.rs b/crates/bevy_ecs/src/schedule/graph/mod.rs index 1532184f1761e..eb55d7db0d86c 100644 --- a/crates/bevy_ecs/src/schedule/graph/mod.rs +++ b/crates/bevy_ecs/src/schedule/graph/mod.rs @@ -86,7 +86,7 @@ pub(crate) struct CheckGraphResults { pub(crate) transitive_reduction: DiGraph, /// Variant of the graph with all possible transitive edges. // TODO: this will very likely be used by "if-needed" ordering - #[allow(dead_code)] + #[expect(dead_code, reason = "See the TODO above this attribute.")] pub(crate) transitive_closure: DiGraph, } diff --git a/crates/bevy_ecs/src/schedule/mod.rs b/crates/bevy_ecs/src/schedule/mod.rs index 4a5b5a09912b6..59340f0d9228d 100644 --- a/crates/bevy_ecs/src/schedule/mod.rs +++ b/crates/bevy_ecs/src/schedule/mod.rs @@ -4,7 +4,6 @@ mod condition; mod config; mod executor; mod graph; -#[allow(clippy::module_inception)] mod schedule; mod set; mod stepping; diff --git a/crates/bevy_ecs/src/schedule/schedule.rs b/crates/bevy_ecs/src/schedule/schedule.rs index e406d0555ef11..33568ec09f5f5 100644 --- a/crates/bevy_ecs/src/schedule/schedule.rs +++ b/crates/bevy_ecs/src/schedule/schedule.rs @@ -1,3 +1,7 @@ +#![expect( + clippy::module_inception, + reason = "This instance of module inception is being discussed; see #17344." +)] use alloc::{ boxed::Box, collections::BTreeSet, @@ -108,8 +112,13 @@ impl Schedules { pub(crate) fn check_change_ticks(&mut self, change_tick: Tick) { #[cfg(feature = "trace")] let _all_span = info_span!("check stored schedule ticks").entered(); - // label used when trace feature is enabled - #[allow(unused_variables)] + #[cfg_attr( + not(feature = "trace"), + expect( + unused_variables, + reason = "The `label` variable goes unused if the `trace` feature isn't active" + ) + )] for (label, schedule) in &mut self.inner { #[cfg(feature = "trace")] let name = format!("{label:?}"); diff --git a/crates/bevy_ecs/src/schedule/stepping.rs b/crates/bevy_ecs/src/schedule/stepping.rs index 867fb5f9870eb..7855053de6fb7 100644 --- a/crates/bevy_ecs/src/schedule/stepping.rs +++ b/crates/bevy_ecs/src/schedule/stepping.rs @@ -414,7 +414,10 @@ impl Stepping { // transitions, and add debugging messages for permitted // transitions. Any action transition that falls through // this match block will be performed. - #[expect(clippy::match_same_arms)] + #[expect( + clippy::match_same_arms, + reason = "Readability would be negatively impacted by combining the `(Waiting, RunAll)` and `(Continue, RunAll)` match arms." + )] match (self.action, action) { // ignore non-transition updates, and prevent a call to // enable() from overwriting a step or continue call diff --git a/crates/bevy_ecs/src/system/builder.rs b/crates/bevy_ecs/src/system/builder.rs index 7b7df57482a20..89ec9f25189fc 100644 --- a/crates/bevy_ecs/src/system/builder.rs +++ b/crates/bevy_ecs/src/system/builder.rs @@ -300,14 +300,28 @@ unsafe impl< macro_rules! impl_system_param_builder_tuple { ($(#[$meta:meta])* $(($param: ident, $builder: ident)),*) => { + #[expect( + clippy::allow_attributes, + reason = "This is in a macro; as such, the below lints may not always apply." + )] + #[allow( + unused_variables, + reason = "Zero-length tuples won't use any of the parameters." + )] + #[allow( + non_snake_case, + reason = "The variable names are provided by the macro caller, not by us." + )] $(#[$meta])* // SAFETY: implementors of each `SystemParamBuilder` in the tuple have validated their impls unsafe impl<$($param: SystemParam,)* $($builder: SystemParamBuilder<$param>,)*> SystemParamBuilder<($($param,)*)> for ($($builder,)*) { - fn build(self, _world: &mut World, _meta: &mut SystemMeta) -> <($($param,)*) as SystemParam>::State { - #[allow(non_snake_case)] + fn build(self, world: &mut World, meta: &mut SystemMeta) -> <($($param,)*) as SystemParam>::State { let ($($builder,)*) = self; - #[allow(clippy::unused_unit)] - ($($builder.build(_world, _meta),)*) + #[allow( + clippy::unused_unit, + reason = "Zero-length tuples won't generate any calls to the system parameter builders." + )] + ($($builder.build(world, meta),)*) } } }; @@ -407,10 +421,21 @@ pub struct ParamSetBuilder(pub T); macro_rules! impl_param_set_builder_tuple { ($(($param: ident, $builder: ident, $meta: ident)),*) => { + #[expect( + clippy::allow_attributes, + reason = "This is in a macro; as such, the below lints may not always apply." + )] + #[allow( + unused_variables, + reason = "Zero-length tuples won't use any of the parameters." + )] + #[allow( + non_snake_case, + reason = "The variable names are provided by the macro caller, not by us." + )] // SAFETY: implementors of each `SystemParamBuilder` in the tuple have validated their impls unsafe impl<'w, 's, $($param: SystemParam,)* $($builder: SystemParamBuilder<$param>,)*> SystemParamBuilder> for ParamSetBuilder<($($builder,)*)> { - #[allow(non_snake_case)] - fn build(self, _world: &mut World, _system_meta: &mut SystemMeta) -> <($($param,)*) as SystemParam>::State { + fn build(self, world: &mut World, system_meta: &mut SystemMeta) -> <($($param,)*) as SystemParam>::State { let ParamSetBuilder(($($builder,)*)) = self; // Note that this is slightly different from `init_state`, which calls `init_state` on each param twice. // One call populates an empty `SystemMeta` with the new access, while the other runs against a cloned `SystemMeta` to check for conflicts. @@ -418,22 +443,25 @@ macro_rules! impl_param_set_builder_tuple { // That means that any `filtered_accesses` in the `component_access_set` will get copied to every `$meta` // and will appear multiple times in the final `SystemMeta`. $( - let mut $meta = _system_meta.clone(); - let $param = $builder.build(_world, &mut $meta); + let mut $meta = system_meta.clone(); + let $param = $builder.build(world, &mut $meta); )* // Make the ParamSet non-send if any of its parameters are non-send. if false $(|| !$meta.is_send())* { - _system_meta.set_non_send(); + system_meta.set_non_send(); } $( - _system_meta + system_meta .component_access_set .extend($meta.component_access_set); - _system_meta + system_meta .archetype_component_access .extend(&$meta.archetype_component_access); )* - #[allow(clippy::unused_unit)] + #[allow( + clippy::unused_unit, + reason = "Zero-length tuples won't generate any calls to the system parameter builders." + )] ($($param,)*) } } diff --git a/crates/bevy_ecs/src/system/combinator.rs b/crates/bevy_ecs/src/system/combinator.rs index 1f956c5d4c00e..f6e696a106a96 100644 --- a/crates/bevy_ecs/src/system/combinator.rs +++ b/crates/bevy_ecs/src/system/combinator.rs @@ -194,7 +194,7 @@ where // be called in parallel. Since mutable access to `world` only exists within // the scope of either closure, we can be sure they will never alias one another. |input| self.a.run(input, unsafe { world.world_mut() }), - #[allow(clippy::undocumented_unsafe_blocks)] + // SAFETY: See the above safety comment. |input| self.b.run(input, unsafe { world.world_mut() }), ) } diff --git a/crates/bevy_ecs/src/system/commands/mod.rs b/crates/bevy_ecs/src/system/commands/mod.rs index 95a8711520335..52f0417ee2ec1 100644 --- a/crates/bevy_ecs/src/system/commands/mod.rs +++ b/crates/bevy_ecs/src/system/commands/mod.rs @@ -2149,7 +2149,6 @@ impl<'a, T: Component> EntityEntryCommands<'a, T> { } #[cfg(test)] -#[allow(clippy::float_cmp, clippy::approx_constant)] mod tests { use crate::{ self as bevy_ecs, @@ -2163,7 +2162,10 @@ mod tests { sync::atomic::{AtomicUsize, Ordering}, }; - #[allow(dead_code)] + #[expect( + dead_code, + reason = "This struct is used to test how `Drop` behavior works in regards to SparseSet storage, and as such is solely a wrapper around `DropCk` to make it use the SparseSet storage. Because of this, the inner field is intentionally never read." + )] #[derive(Component)] #[component(storage = "SparseSet")] struct SparseDropCk(DropCk); diff --git a/crates/bevy_ecs/src/system/exclusive_function_system.rs b/crates/bevy_ecs/src/system/exclusive_function_system.rs index 81ac03bfb8502..2b1c081d51db0 100644 --- a/crates/bevy_ecs/src/system/exclusive_function_system.rs +++ b/crates/bevy_ecs/src/system/exclusive_function_system.rs @@ -219,7 +219,14 @@ pub struct HasExclusiveSystemInput; macro_rules! impl_exclusive_system_function { ($($param: ident),*) => { - #[allow(non_snake_case)] + #[expect( + clippy::allow_attributes, + reason = "This is within a macro, and as such, the below lints may not always apply." + )] + #[allow( + non_snake_case, + reason = "Certain variable names are provided by the caller, not by us." + )] impl ExclusiveSystemParamFunction Out> for Func where Func: Send + Sync + 'static, @@ -248,7 +255,14 @@ macro_rules! impl_exclusive_system_function { } } - #[allow(non_snake_case)] + #[expect( + clippy::allow_attributes, + reason = "This is within a macro, and as such, the below lints may not always apply." + )] + #[allow( + non_snake_case, + reason = "Certain variable names are provided by the caller, not by us." + )] impl ExclusiveSystemParamFunction<(HasExclusiveSystemInput, fn(In, $($param,)*) -> Out)> for Func where Func: Send + Sync + 'static, diff --git a/crates/bevy_ecs/src/system/exclusive_system_param.rs b/crates/bevy_ecs/src/system/exclusive_system_param.rs index 678d4452331b2..e36b7ea759bdd 100644 --- a/crates/bevy_ecs/src/system/exclusive_system_param.rs +++ b/crates/bevy_ecs/src/system/exclusive_system_param.rs @@ -88,26 +88,38 @@ impl ExclusiveSystemParam for PhantomData { macro_rules! impl_exclusive_system_param_tuple { ($(#[$meta:meta])* $($param: ident),*) => { - #[allow(unused_variables)] - #[allow(non_snake_case)] + #[expect( + clippy::allow_attributes, + reason = "This is within a macro, and as such, the below lints may not always apply." + )] + #[allow( + non_snake_case, + reason = "Certain variable names are provided by the caller, not by us." + )] + #[allow( + unused_variables, + reason = "Zero-length tuples won't use any of the parameters." + )] $(#[$meta])* impl<$($param: ExclusiveSystemParam),*> ExclusiveSystemParam for ($($param,)*) { type State = ($($param::State,)*); type Item<'s> = ($($param::Item<'s>,)*); #[inline] - fn init(_world: &mut World, _system_meta: &mut SystemMeta) -> Self::State { - (($($param::init(_world, _system_meta),)*)) + fn init(world: &mut World, system_meta: &mut SystemMeta) -> Self::State { + (($($param::init(world, system_meta),)*)) } #[inline] - #[allow(clippy::unused_unit)] fn get_param<'s>( state: &'s mut Self::State, system_meta: &SystemMeta, ) -> Self::Item<'s> { - let ($($param,)*) = state; + #[allow( + clippy::unused_unit, + reason = "Zero-length tuples won't have any params to get." + )] ($($param::get_param($param, system_meta),)*) } } diff --git a/crates/bevy_ecs/src/system/function_system.rs b/crates/bevy_ecs/src/system/function_system.rs index fdf26a98c948d..593d94e5a0917 100644 --- a/crates/bevy_ecs/src/system/function_system.rs +++ b/crates/bevy_ecs/src/system/function_system.rs @@ -1012,7 +1012,14 @@ pub struct HasSystemInput; macro_rules! impl_system_function { ($($param: ident),*) => { - #[allow(non_snake_case)] + #[expect( + clippy::allow_attributes, + reason = "This is within a macro, and as such, the below lints may not always apply." + )] + #[allow( + non_snake_case, + reason = "Certain variable names are provided by the caller, not by us." + )] impl SystemParamFunction Out> for Func where Func: Send + Sync + 'static, @@ -1040,7 +1047,14 @@ macro_rules! impl_system_function { } } - #[allow(non_snake_case)] + #[expect( + clippy::allow_attributes, + reason = "This is within a macro, and as such, the below lints may not always apply." + )] + #[allow( + non_snake_case, + reason = "Certain variable names are provided by the caller, not by us." + )] impl SystemParamFunction<(HasSystemInput, fn(In, $($param,)*) -> Out)> for Func where Func: Send + Sync + 'static, diff --git a/crates/bevy_ecs/src/system/input.rs b/crates/bevy_ecs/src/system/input.rs index 469403bc7345c..12087fdf6a64a 100644 --- a/crates/bevy_ecs/src/system/input.rs +++ b/crates/bevy_ecs/src/system/input.rs @@ -259,8 +259,18 @@ macro_rules! impl_system_input_tuple { type Param<'i> = ($($name::Param<'i>,)*); type Inner<'i> = ($($name::Inner<'i>,)*); - #[allow(non_snake_case)] - #[allow(clippy::unused_unit)] + #[expect( + clippy::allow_attributes, + reason = "This is in a macro; as such, the below lints may not always apply." + )] + #[allow( + non_snake_case, + reason = "Certain variable names are provided by the caller, not by us." + )] + #[allow( + clippy::unused_unit, + reason = "Zero-length tuples won't have anything to wrap." + )] fn wrap(this: Self::Inner<'_>) -> Self::Param<'_> { let ($($name,)*) = this; ($($name::wrap($name),)*) diff --git a/crates/bevy_ecs/src/system/mod.rs b/crates/bevy_ecs/src/system/mod.rs index 75952e04227fa..360ed03c9b1b8 100644 --- a/crates/bevy_ecs/src/system/mod.rs +++ b/crates/bevy_ecs/src/system/mod.rs @@ -130,7 +130,6 @@ mod input; mod observer_system; mod query; mod schedule_system; -#[allow(clippy::module_inception)] mod system; mod system_name; mod system_param; @@ -421,8 +420,7 @@ mod tests { let entities_array: [Entity; ENTITIES_COUNT] = entities_array.0.clone().try_into().unwrap(); - #[allow(unused_mut)] - for (i, mut w) in (0..ENTITIES_COUNT).zip(q.get_many_mut(entities_array).unwrap()) { + for (i, w) in (0..ENTITIES_COUNT).zip(q.get_many_mut(entities_array).unwrap()) { assert_eq!(i, w.0); } @@ -898,13 +896,18 @@ mod tests { } #[test] + #[expect( + dead_code, + reason = "The `NotSend1` and `NotSend2` structs is used to verify that a system will run, even if the system params include a non-Send resource. As such, the inner value doesn't matter." + )] fn non_send_option_system() { let mut world = World::default(); world.insert_resource(SystemRan::No); - #[allow(dead_code)] + // Two structs are used, one which is inserted and one which is not, to verify that wrapping + // non-Send resources in an `Option` will allow the system to run regardless of their + // existence. struct NotSend1(alloc::rc::Rc); - #[allow(dead_code)] struct NotSend2(alloc::rc::Rc); world.insert_non_send_resource(NotSend1(alloc::rc::Rc::new(0))); @@ -923,13 +926,15 @@ mod tests { } #[test] + #[expect( + dead_code, + reason = "The `NotSend1` and `NotSend2` structs are used to verify that a system will run, even if the system params include a non-Send resource. As such, the inner value doesn't matter." + )] fn non_send_system() { let mut world = World::default(); world.insert_resource(SystemRan::No); - #[allow(dead_code)] struct NotSend1(alloc::rc::Rc); - #[allow(dead_code)] struct NotSend2(alloc::rc::Rc); world.insert_non_send_resource(NotSend1(alloc::rc::Rc::new(1))); @@ -1278,9 +1283,11 @@ mod tests { } } - /// this test exists to show that read-only world-only queries can return data that lives as long as 'world #[test] - #[allow(unused)] + #[expect( + dead_code, + reason = "This test exists to show that read-only world-only queries can return data that lives as long as `'world`." + )] fn long_life_test() { struct Holder<'w> { value: &'w A, diff --git a/crates/bevy_ecs/src/system/system.rs b/crates/bevy_ecs/src/system/system.rs index f976363aded11..4f22340bff8cc 100644 --- a/crates/bevy_ecs/src/system/system.rs +++ b/crates/bevy_ecs/src/system/system.rs @@ -1,3 +1,7 @@ +#![expect( + clippy::module_inception, + reason = "This instance of module inception is being discussed; see #17353." +)] use core::fmt::Debug; use log::warn; use thiserror::Error; @@ -400,7 +404,6 @@ mod tests { #[derive(Resource, Default, PartialEq, Debug)] struct Counter(u8); - #[allow(dead_code)] fn count_up(mut counter: ResMut) { counter.0 += 1; } @@ -416,7 +419,6 @@ mod tests { assert_eq!(*world.resource::(), Counter(2)); } - #[allow(dead_code)] fn spawn_entity(mut commands: Commands) { commands.spawn_empty(); } diff --git a/crates/bevy_ecs/src/system/system_param.rs b/crates/bevy_ecs/src/system/system_param.rs index 04292231297fd..8358bc66af827 100644 --- a/crates/bevy_ecs/src/system/system_param.rs +++ b/crates/bevy_ecs/src/system/system_param.rs @@ -201,7 +201,10 @@ pub unsafe trait SystemParam: Sized { /// # Safety /// `archetype` must be from the [`World`] used to initialize `state` in [`SystemParam::init_state`]. #[inline] - #[allow(unused_variables)] + #[expect( + unused_variables, + reason = "The parameters here are intentionally unused by the default implementation; however, putting underscores here will result in the underscores being copied by rust-analyzer's tab completion." + )] unsafe fn new_archetype( state: &mut Self::State, archetype: &Archetype, @@ -214,12 +217,18 @@ pub unsafe trait SystemParam: Sized { /// /// [`Commands`]: crate::prelude::Commands #[inline] - #[allow(unused_variables)] + #[expect( + unused_variables, + reason = "The parameters here are intentionally unused by the default implementation; however, putting underscores here will result in the underscores being copied by rust-analyzer's tab completion." + )] fn apply(state: &mut Self::State, system_meta: &SystemMeta, world: &mut World) {} /// Queues any deferred mutations to be applied at the next [`ApplyDeferred`](crate::prelude::ApplyDeferred). #[inline] - #[allow(unused_variables)] + #[expect( + unused_variables, + reason = "The parameters here are intentionally unused by the default implementation; however, putting underscores here will result in the underscores being copied by rust-analyzer's tab completion." + )] fn queue(state: &mut Self::State, system_meta: &SystemMeta, world: DeferredWorld) {} /// Validates that the param can be acquired by the [`get_param`](SystemParam::get_param). @@ -249,10 +258,14 @@ pub unsafe trait SystemParam: Sized { /// registered in [`init_state`](SystemParam::init_state). /// - `world` must be the same [`World`] that was used to initialize [`state`](SystemParam::init_state). /// - All `world`'s archetypes have been processed by [`new_archetype`](SystemParam::new_archetype). + #[expect( + unused_variables, + reason = "The parameters here are intentionally unused by the default implementation; however, putting underscores here will result in the underscores being copied by rust-analyzer's tab completion." + )] unsafe fn validate_param( - _state: &Self::State, - _system_meta: &SystemMeta, - _world: UnsafeWorldCell, + state: &Self::State, + system_meta: &SystemMeta, + world: UnsafeWorldCell, ) -> bool { // By default we allow panics in [`SystemParam::get_param`] and return `true`. // Preventing panics is an optional feature. @@ -691,8 +704,14 @@ macro_rules! impl_param_set { type State = ($($param::State,)*); type Item<'w, 's> = ParamSet<'w, 's, ($($param,)*)>; - // Note: We allow non snake case so the compiler don't complain about the creation of non_snake_case variables - #[allow(non_snake_case)] + #[expect( + clippy::allow_attributes, + reason = "This is inside a macro meant for tuples; as such, `non_snake_case` won't always lint." + )] + #[allow( + non_snake_case, + reason = "Certain variable names are provided by the caller, not by us." + )] fn init_state(world: &mut World, system_meta: &mut SystemMeta) -> Self::State { $( // Pretend to add each param to the system alone, see if it conflicts @@ -2009,56 +2028,76 @@ macro_rules! impl_system_param_tuple { // SAFETY: tuple consists only of ReadOnlySystemParams unsafe impl<$($param: ReadOnlySystemParam),*> ReadOnlySystemParam for ($($param,)*) {} - // SAFETY: implementors of each `SystemParam` in the tuple have validated their impls - #[allow(clippy::undocumented_unsafe_blocks)] // false positive by clippy - #[allow(non_snake_case)] + #[expect( + clippy::allow_attributes, + reason = "This is in a macro, and as such, the below lints may not always apply." + )] + #[allow( + non_snake_case, + reason = "Certain variable names are provided by the caller, not by us." + )] + #[allow( + unused_variables, + reason = "Zero-length tuples won't use some of the parameters." + )] $(#[$meta])* + // SAFETY: implementors of each `SystemParam` in the tuple have validated their impls unsafe impl<$($param: SystemParam),*> SystemParam for ($($param,)*) { type State = ($($param::State,)*); type Item<'w, 's> = ($($param::Item::<'w, 's>,)*); #[inline] - fn init_state(_world: &mut World, _system_meta: &mut SystemMeta) -> Self::State { - (($($param::init_state(_world, _system_meta),)*)) + fn init_state(world: &mut World, system_meta: &mut SystemMeta) -> Self::State { + (($($param::init_state(world, system_meta),)*)) } #[inline] - #[allow(unused_unsafe)] - unsafe fn new_archetype(($($param,)*): &mut Self::State, _archetype: &Archetype, _system_meta: &mut SystemMeta) { + unsafe fn new_archetype(($($param,)*): &mut Self::State, archetype: &Archetype, system_meta: &mut SystemMeta) { + #[allow( + unused_unsafe, + reason = "Zero-length tuples will not run anything in the unsafe block." + )] // SAFETY: The caller ensures that `archetype` is from the World the state was initialized from in `init_state`. - unsafe { $($param::new_archetype($param, _archetype, _system_meta);)* } + unsafe { $($param::new_archetype($param, archetype, system_meta);)* } } #[inline] - fn apply(($($param,)*): &mut Self::State, _system_meta: &SystemMeta, _world: &mut World) { - $($param::apply($param, _system_meta, _world);)* + fn apply(($($param,)*): &mut Self::State, system_meta: &SystemMeta, world: &mut World) { + $($param::apply($param, system_meta, world);)* } #[inline] - fn queue(($($param,)*): &mut Self::State, _system_meta: &SystemMeta, mut _world: DeferredWorld) { - $($param::queue($param, _system_meta, _world.reborrow());)* + #[allow( + unused_mut, + reason = "The `world` parameter is unused for zero-length tuples; however, it must be mutable for other lengths of tuples." + )] + fn queue(($($param,)*): &mut Self::State, system_meta: &SystemMeta, mut world: DeferredWorld) { + $($param::queue($param, system_meta, world.reborrow());)* } #[inline] unsafe fn validate_param( state: &Self::State, - _system_meta: &SystemMeta, - _world: UnsafeWorldCell, + system_meta: &SystemMeta, + world: UnsafeWorldCell, ) -> bool { let ($($param,)*) = state; - $($param::validate_param($param, _system_meta, _world)&&)* true + $($param::validate_param($param, system_meta, world)&&)* true } #[inline] - #[allow(clippy::unused_unit)] unsafe fn get_param<'w, 's>( state: &'s mut Self::State, - _system_meta: &SystemMeta, - _world: UnsafeWorldCell<'w>, - _change_tick: Tick, + system_meta: &SystemMeta, + world: UnsafeWorldCell<'w>, + change_tick: Tick, ) -> Self::Item<'w, 's> { let ($($param,)*) = state; - ($($param::get_param($param, _system_meta, _world, _change_tick),)*) + #[allow( + clippy::unused_unit, + reason = "Zero-length tuples won't have any params to get." + )] + ($($param::get_param($param, system_meta, world, change_tick),)*) } } }; @@ -2647,7 +2686,10 @@ mod tests { // Compile test for https://github.com/bevyengine/bevy/pull/7001. #[test] fn system_param_const_generics() { - #[allow(dead_code)] + #[expect( + dead_code, + reason = "This struct is used to ensure that const generics are supported as a SystemParam; thus, the inner value never needs to be read." + )] #[derive(SystemParam)] pub struct ConstGenericParam<'w, const I: usize>(Res<'w, R>); @@ -2705,7 +2747,10 @@ mod tests { #[derive(SystemParam)] pub struct UnitParam; - #[allow(dead_code)] + #[expect( + dead_code, + reason = "This struct is used to ensure that tuple structs are supported as a SystemParam; thus, the inner values never need to be read." + )] #[derive(SystemParam)] pub struct TupleParam<'w, 's, R: Resource, L: FromWorld + Send + 'static>( Res<'w, R>, @@ -2722,7 +2767,10 @@ mod tests { #[derive(Resource)] struct PrivateResource; - #[allow(dead_code)] + #[expect( + dead_code, + reason = "This struct is used to ensure that SystemParam's derive can't leak private fields; thus, the inner values never need to be read." + )] #[derive(SystemParam)] pub struct EncapsulatedParam<'w>(Res<'w, PrivateResource>); diff --git a/crates/bevy_ecs/src/world/command_queue.rs b/crates/bevy_ecs/src/world/command_queue.rs index 76b9b6eddb9ec..5b78b7a974e3f 100644 --- a/crates/bevy_ecs/src/world/command_queue.rs +++ b/crates/bevy_ecs/src/world/command_queue.rs @@ -431,10 +431,10 @@ mod test { assert_eq!(world.entities().len(), 2); } - // This has an arbitrary value `String` stored to ensure - // when then command gets pushed, the `bytes` vector gets - // some data added to it. - #[allow(dead_code)] + #[expect( + dead_code, + reason = "The inner string is used to ensure that, when the PanicCommand gets pushed to the queue, some data is written to the `bytes` vector." + )] struct PanicCommand(String); impl Command for PanicCommand { fn apply(self, _: &mut World) { @@ -510,7 +510,10 @@ mod test { assert_is_send(SpawnCommand); } - #[allow(dead_code)] + #[expect( + dead_code, + reason = "This struct is used to test how the CommandQueue reacts to padding added by rust's compiler." + )] struct CommandWithPadding(u8, u16); impl Command for CommandWithPadding { fn apply(self, _: &mut World) {} diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index 6b3491e877457..ddbfa81e627a6 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -388,12 +388,11 @@ impl PartialEq for EntityRef<'_> { impl Eq for EntityRef<'_> {} -#[expect(clippy::non_canonical_partial_ord_impl)] impl PartialOrd for EntityRef<'_> { /// [`EntityRef`]'s comparison trait implementations match the underlying [`Entity`], /// and cannot discern between different worlds. fn partial_cmp(&self, other: &Self) -> Option { - self.entity().partial_cmp(&other.entity()) + Some(self.cmp(other)) } } @@ -944,12 +943,11 @@ impl PartialEq for EntityMut<'_> { impl Eq for EntityMut<'_> {} -#[expect(clippy::non_canonical_partial_ord_impl)] impl PartialOrd for EntityMut<'_> { /// [`EntityMut`]'s comparison trait implementations match the underlying [`Entity`], /// and cannot discern between different worlds. fn partial_cmp(&self, other: &Self) -> Option { - self.entity().partial_cmp(&other.entity()) + Some(self.cmp(other)) } } @@ -1758,7 +1756,10 @@ impl<'w> EntityWorldMut<'w> { }) }; - #[allow(clippy::undocumented_unsafe_blocks)] // TODO: document why this is safe + #[expect( + clippy::undocumented_unsafe_blocks, + reason = "Needs to be documented; see #17345." + )] unsafe { Self::move_entity_from_remove::( entity, @@ -3178,12 +3179,11 @@ impl PartialEq for FilteredEntityRef<'_> { impl Eq for FilteredEntityRef<'_> {} -#[expect(clippy::non_canonical_partial_ord_impl)] impl PartialOrd for FilteredEntityRef<'_> { /// [`FilteredEntityRef`]'s comparison trait implementations match the underlying [`Entity`], /// and cannot discern between different worlds. fn partial_cmp(&self, other: &Self) -> Option { - self.entity().partial_cmp(&other.entity()) + Some(self.cmp(other)) } } @@ -3505,12 +3505,11 @@ impl PartialEq for FilteredEntityMut<'_> { impl Eq for FilteredEntityMut<'_> {} -#[expect(clippy::non_canonical_partial_ord_impl)] impl PartialOrd for FilteredEntityMut<'_> { /// [`FilteredEntityMut`]'s comparison trait implementations match the underlying [`Entity`], /// and cannot discern between different worlds. fn partial_cmp(&self, other: &Self) -> Option { - self.entity().partial_cmp(&other.entity()) + Some(self.cmp(other)) } } @@ -3653,12 +3652,11 @@ impl PartialEq for EntityRefExcept<'_, B> { impl Eq for EntityRefExcept<'_, B> {} -#[expect(clippy::non_canonical_partial_ord_impl)] impl PartialOrd for EntityRefExcept<'_, B> { /// [`EntityRefExcept`]'s comparison trait implementations match the underlying [`Entity`], /// and cannot discern between different worlds. fn partial_cmp(&self, other: &Self) -> Option { - self.entity().partial_cmp(&other.entity()) + Some(self.cmp(other)) } } @@ -3793,12 +3791,11 @@ impl PartialEq for EntityMutExcept<'_, B> { impl Eq for EntityMutExcept<'_, B> {} -#[expect(clippy::non_canonical_partial_ord_impl)] impl PartialOrd for EntityMutExcept<'_, B> { /// [`EntityMutExcept`]'s comparison trait implementations match the underlying [`Entity`], /// and cannot discern between different worlds. fn partial_cmp(&self, other: &Self) -> Option { - self.entity().partial_cmp(&other.entity()) + Some(self.cmp(other)) } } diff --git a/crates/bevy_ecs/src/world/unsafe_world_cell.rs b/crates/bevy_ecs/src/world/unsafe_world_cell.rs index bf8c4a8c6b235..94d36a3dc17fd 100644 --- a/crates/bevy_ecs/src/world/unsafe_world_cell.rs +++ b/crates/bevy_ecs/src/world/unsafe_world_cell.rs @@ -1,7 +1,5 @@ //! Contains types that allow disjoint mutable access to a [`World`]. -#![warn(unsafe_op_in_unsafe_fn)] - use super::{Mut, Ref, World, WorldId}; use crate::{ archetype::{Archetype, Archetypes}, @@ -1095,7 +1093,6 @@ impl<'w> UnsafeWorldCell<'w> { /// - `storage_type` must accurately reflect where the components for `component_id` are stored. /// - the caller must ensure that no aliasing rules are violated #[inline] -#[allow(unsafe_op_in_unsafe_fn)] unsafe fn get_component( world: UnsafeWorldCell<'_>, component_id: ComponentId, @@ -1122,7 +1119,6 @@ unsafe fn get_component( /// - `storage_type` must accurately reflect where the components for `component_id` are stored. /// - the caller must ensure that no aliasing rules are violated #[inline] -#[allow(unsafe_op_in_unsafe_fn)] unsafe fn get_component_and_ticks( world: UnsafeWorldCell<'_>, component_id: ComponentId, @@ -1166,7 +1162,6 @@ unsafe fn get_component_and_ticks( /// - `storage_type` must accurately reflect where the components for `component_id` are stored. /// - the caller must ensure that no aliasing rules are violated #[inline] -#[allow(unsafe_op_in_unsafe_fn)] unsafe fn get_ticks( world: UnsafeWorldCell<'_>, component_id: ComponentId, From eec5915da85a6614a7cbc9d0cc5a10c2a6da5945 Mon Sep 17 00:00:00 2001 From: Rich Churcher Date: Wed, 15 Jan 2025 10:51:12 +1300 Subject: [PATCH 243/272] Fix some punctuation (#17368) Found a few missing full-stops, etc. --- crates/bevy_pbr/src/pbr_material.rs | 10 +++++----- crates/bevy_render/src/render_resource/bind_group.rs | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/crates/bevy_pbr/src/pbr_material.rs b/crates/bevy_pbr/src/pbr_material.rs index f49d8a38bc261..150f58acf9664 100644 --- a/crates/bevy_pbr/src/pbr_material.rs +++ b/crates/bevy_pbr/src/pbr_material.rs @@ -23,8 +23,8 @@ pub enum UvChannel { Uv1, } -/// A material with "standard" properties used in PBR lighting -/// Standard property values with pictures here +/// A material with "standard" properties used in PBR lighting. +/// Standard property values with pictures here: /// . /// /// May be created directly from a [`Color`] or an [`Image`]. @@ -183,7 +183,7 @@ pub struct StandardMaterial { #[doc(alias = "specular_intensity")] pub reflectance: f32, - /// The amount of light transmitted _diffusely_ through the material (i.e. “translucency”) + /// The amount of light transmitted _diffusely_ through the material (i.e. “translucency”). /// /// Implemented as a second, flipped [Lambertian diffuse](https://en.wikipedia.org/wiki/Lambertian_reflectance) lobe, /// which provides an inexpensive but plausible approximation of translucency for thin dielectric objects (e.g. paper, @@ -221,7 +221,7 @@ pub struct StandardMaterial { #[cfg(feature = "pbr_transmission_textures")] pub diffuse_transmission_texture: Option>, - /// The amount of light transmitted _specularly_ through the material (i.e. via refraction) + /// The amount of light transmitted _specularly_ through the material (i.e. via refraction). /// /// - When set to `0.0` (the default) no light is transmitted. /// - When set to `1.0` all light is transmitted through the material. @@ -522,7 +522,7 @@ pub struct StandardMaterial { /// [`StandardMaterial::anisotropy_rotation`] to vary across the mesh. /// /// The [`KHR_materials_anisotropy` specification] defines the format that - /// this texture must take. To summarize: The direction vector is encoded in + /// this texture must take. To summarize: the direction vector is encoded in /// the red and green channels, while the strength is encoded in the blue /// channels. For the direction vector, the red and green channels map the /// color range [0, 1] to the vector range [-1, 1]. The direction vector diff --git a/crates/bevy_render/src/render_resource/bind_group.rs b/crates/bevy_render/src/render_resource/bind_group.rs index 568a2dc811290..2fe2401c56411 100644 --- a/crates/bevy_render/src/render_resource/bind_group.rs +++ b/crates/bevy_render/src/render_resource/bind_group.rs @@ -414,7 +414,7 @@ pub trait AsBindGroup { ) } - /// Returns a vec of bind group layout entries + /// Returns a vec of bind group layout entries. /// /// Set `force_no_bindless` to true to require that bindless textures *not* /// be used. `ExtendedMaterial` uses this in order to ensure that the base From ec611976eff81ab91294bddfdccb22c1afb980fb Mon Sep 17 00:00:00 2001 From: Matty Weatherley Date: Tue, 14 Jan 2025 16:52:24 -0500 Subject: [PATCH 244/272] Fix a missing `.entity() -> .target()` conversion in observers example (#17363) See title :) --- examples/ecs/observers.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/ecs/observers.rs b/examples/ecs/observers.rs index d8385380ce6c8..2ed3c785c0f1b 100644 --- a/examples/ecs/observers.rs +++ b/examples/ecs/observers.rs @@ -142,7 +142,7 @@ fn on_remove_mine( } fn explode_mine(trigger: Trigger, query: Query<&Mine>, mut commands: Commands) { - // If a triggered event is targeting a specific entity you can access it with `.entity()` + // If a triggered event is targeting a specific entity you can access it with `.target()` let id = trigger.target(); let Some(mut entity) = commands.get_entity(id) else { return; From 276d6e801444aeb1558b19df09ef517b44f75079 Mon Sep 17 00:00:00 2001 From: Younes <3153107+younes-io@users.noreply.github.com> Date: Tue, 14 Jan 2025 22:53:19 +0100 Subject: [PATCH 245/272] fix typo query.rs (#17366) fix typo query.rs --- crates/bevy_ecs/src/system/query.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_ecs/src/system/query.rs b/crates/bevy_ecs/src/system/query.rs index 75893a1afa295..1680eea8497cd 100644 --- a/crates/bevy_ecs/src/system/query.rs +++ b/crates/bevy_ecs/src/system/query.rs @@ -202,7 +202,7 @@ use core::{ /// ## Whole Entity Access /// /// [`EntityRef`]s can be fetched from a query. This will give read-only access to any component on the entity, -/// and can be use to dynamically fetch any component without baking it into the query type. Due to this global +/// and can be used to dynamically fetch any component without baking it into the query type. Due to this global /// access to the entity, this will block any other system from parallelizing with it. As such these queries /// should be sparingly used. /// From 9ef1964d34bfcca757e8f0d50184592fc4b038e4 Mon Sep 17 00:00:00 2001 From: Jaso333 Date: Tue, 14 Jan 2025 21:53:35 +0000 Subject: [PATCH 246/272] added `any_match_filter` common condition (#17327) # Objective resolves #17326. ## Solution Simply added the suggested run condition. ## Testing A self-explanatory run condition. Fully verified by the operation of `QueryFilter` in a system. --- crates/bevy_ecs/src/schedule/condition.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/crates/bevy_ecs/src/schedule/condition.rs b/crates/bevy_ecs/src/schedule/condition.rs index 4cfdd1cc81f0f..112d6d9481f4e 100644 --- a/crates/bevy_ecs/src/schedule/condition.rs +++ b/crates/bevy_ecs/src/schedule/condition.rs @@ -398,6 +398,7 @@ pub mod common_conditions { change_detection::DetectChanges, event::{Event, EventReader}, prelude::{Component, Query, With}, + query::QueryFilter, removal_detection::RemovedComponents, system::{In, IntoSystem, Local, Res, Resource, System, SystemInput}, }; @@ -937,6 +938,12 @@ pub mod common_conditions { removals.read().count() > 0 } + /// A [`Condition`]-satisfying system that returns `true` + /// if there are any entities that match the given [`QueryFilter`]. + pub fn any_match_filter(query: Query<(), F>) -> bool { + !query.is_empty() + } + /// Generates a [`Condition`] that inverses the result of passed one. /// /// # Example @@ -1256,6 +1263,7 @@ where mod tests { use super::{common_conditions::*, Condition}; use crate as bevy_ecs; + use crate::query::With; use crate::{ change_detection::ResMut, component::Component, @@ -1396,6 +1404,7 @@ mod tests { .distributive_run_if(resource_removed::) .distributive_run_if(on_event::) .distributive_run_if(any_with_component::) + .distributive_run_if(any_match_filter::>) .distributive_run_if(not(run_once)), ); } From dcff8f3ecb10520012672db932926d6963acb3ec Mon Sep 17 00:00:00 2001 From: Adrian Kortyczko Date: Tue, 14 Jan 2025 22:53:42 +0100 Subject: [PATCH 247/272] Make `ObservedBy` public (#17297) # Objective - Currently, the `ObservedBy`-component is only public within the `bevy_ecs` crate. Sometimes it is desirable to refer to this component in the "game-code". Two examples that come in mind: - Clearing all components in an entity, but intending to keep the existing observers: Making `ObservedBy` public allows us to use `commands.entity(entity).retain::();`, which clears all other components, but keeps `ObservedBy`, which prevents the Observers from despawning. - The opposite of the above, clearing all of entities' Observers: `commands.entity(entity).remove::` will despawn all associated Observers. Admittedly, a cleaner solution would be something like `commands.entity(entity).clear_observers()`, but this is sufficient. ## Solution - Removed `(crate)` "rule" and added `ObservedBy` to the prelude-module ## Testing - Linked `bevy_ecs` locally with another project to see if `ObservedBy` could be referenced. --- crates/bevy_ecs/src/observer/entity_observer.rs | 2 +- crates/bevy_ecs/src/observer/mod.rs | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/crates/bevy_ecs/src/observer/entity_observer.rs b/crates/bevy_ecs/src/observer/entity_observer.rs index e86f6814a8ff6..9fb6beb2a206e 100644 --- a/crates/bevy_ecs/src/observer/entity_observer.rs +++ b/crates/bevy_ecs/src/observer/entity_observer.rs @@ -8,7 +8,7 @@ use alloc::vec::Vec; /// Tracks a list of entity observers for the [`Entity`] [`ObservedBy`] is added to. #[derive(Default)] -pub(crate) struct ObservedBy(pub(crate) Vec); +pub struct ObservedBy(pub(crate) Vec); impl Component for ObservedBy { const STORAGE_TYPE: StorageType = StorageType::SparseSet; diff --git a/crates/bevy_ecs/src/observer/mod.rs b/crates/bevy_ecs/src/observer/mod.rs index 8194c1e04ea65..9a19c494e7a2a 100644 --- a/crates/bevy_ecs/src/observer/mod.rs +++ b/crates/bevy_ecs/src/observer/mod.rs @@ -3,14 +3,13 @@ mod entity_observer; mod runner; -pub use entity_observer::CloneEntityWithObserversExt; +pub use entity_observer::{CloneEntityWithObserversExt, ObservedBy}; pub use runner::*; use crate::{ archetype::ArchetypeFlags, component::ComponentId, entity::EntityHashMap, - observer::entity_observer::ObservedBy, prelude::*, system::IntoObserverSystem, world::{DeferredWorld, *}, From f2e00c8ed5b8eddf9c38fdc7cf6f0056f4ee00c2 Mon Sep 17 00:00:00 2001 From: Nicholas Charbonneau Date: Tue, 14 Jan 2025 17:22:20 -0500 Subject: [PATCH 248/272] feat: support for clip children on windows (#16545) # Objective Support the parametrization of the WS_CLIPCHILDREN style on Windows. Fixes #16544 ## Solution Added a window configuration in bevy_winit to control the usage of the WS_CLIPCHILDREN style. ## Testing - Did you test these changes? If so, how? I did. I was able to create a Wry Webview with a transparent HTML document and was also able to see my Bevy scene behind the webview elements. - Are there any parts that need more testing? I don't believe so. I assume the option is extensively tested within winit itself. - How can other people (reviewers) test your changes? Is there anything specific they need to know? Test repositiory [here](https://github.com/nicholasc/bevy_wry_test). Bevy's path will need to be updated in the Cargo.toml - If relevant, what platforms did you test these changes on, and are there any important ones you can't test? This is a Windows specific issue. Should be tested accordingly. --------- Co-authored-by: jf908 --- crates/bevy_window/src/window.rs | 10 ++++++++++ crates/bevy_winit/src/winit_windows.rs | 2 ++ 2 files changed, 12 insertions(+) diff --git a/crates/bevy_window/src/window.rs b/crates/bevy_window/src/window.rs index ac442f05f5559..1acf96ee416ac 100644 --- a/crates/bevy_window/src/window.rs +++ b/crates/bevy_window/src/window.rs @@ -291,6 +291,15 @@ pub struct Window { /// /// - Only supported on Windows. pub skip_taskbar: bool, + /// Sets whether the window should draw over its child windows. + /// + /// If `true`, the window excludes drawing over areas obscured by child windows. + /// If `false`, the window can draw over child windows. + /// + /// ## Platform-specific + /// + /// - Only supported on Windows. + pub clip_children: bool, /// Optional hint given to the rendering API regarding the maximum number of queued frames admissible on the GPU. /// /// Given values are usually within the 1-3 range. If not provided, this will default to 2. @@ -451,6 +460,7 @@ impl Default for Window { window_theme: None, visible: true, skip_taskbar: false, + clip_children: true, desired_maximum_frame_latency: None, recognize_pinch_gesture: false, recognize_rotation_gesture: false, diff --git a/crates/bevy_winit/src/winit_windows.rs b/crates/bevy_winit/src/winit_windows.rs index be4a7c21e5f28..f7b948a6bd530 100644 --- a/crates/bevy_winit/src/winit_windows.rs +++ b/crates/bevy_winit/src/winit_windows.rs @@ -126,6 +126,8 @@ impl WinitWindows { use winit::platform::windows::WindowAttributesExtWindows; winit_window_attributes = winit_window_attributes.with_skip_taskbar(window.skip_taskbar); + winit_window_attributes = + winit_window_attributes.with_clip_children(window.clip_children); } #[cfg(target_os = "macos")] From 0756a19f28d70a5af222243d13988852f5ba4409 Mon Sep 17 00:00:00 2001 From: mgi388 <135186256+mgi388@users.noreply.github.com> Date: Wed, 15 Jan 2025 09:27:24 +1100 Subject: [PATCH 249/272] Support texture atlases in CustomCursor::Image (#17121) # Objective - Bevy 0.15 added support for custom cursor images in https://github.com/bevyengine/bevy/pull/14284. - However, to do animated cursors using the initial support shipped in 0.15 means you'd have to animate the `Handle`: You can't use a `TextureAtlas` like you can with sprites and UI images. - For my use case, my cursors are spritesheets. To animate them, I'd have to break them down into multiple `Image` assets, but that seems less than ideal. ## Solution - Allow users to specify a `TextureAtlas` field when creating a custom cursor image. - To create parity with Bevy's `TextureAtlas` support on `Sprite`s and `ImageNode`s, this also allows users to specify `rect`, `flip_x` and `flip_y`. In fact, for my own use case, I need to `flip_y`. ## Testing - I added unit tests for `calculate_effective_rect` and `extract_and_transform_rgba_pixels`. - I added a brand new example for custom cursor images. It has controls to toggle fields on and off. I opted to add a new example because the existing cursor example (`window_settings`) would be far too messy for showcasing these custom cursor features (I did start down that path but decided to stop and make a brand new example). - The new example uses a [Kenny cursor icon] sprite sheet. I included the licence even though it's not required (and it's CC0). - I decided to make the example just loop through all cursor icons for its animation even though it's not a _realistic_ in-game animation sequence. - I ran the PNG through https://tinypng.com. Looks like it's about 35KB. - I'm open to adjusting the example spritesheet if required, but if it's fine as is, great. [Kenny cursor icon]: https://kenney-assets.itch.io/crosshair-pack --- ## Showcase https://github.com/user-attachments/assets/8f6be8d7-d1d4-42f9-b769-ef8532367749 ## Migration Guide The `CustomCursor::Image` enum variant has some new fields. Update your code to set them. Before: ```rust CustomCursor::Image { handle: asset_server.load("branding/icon.png"), hotspot: (128, 128), } ``` After: ```rust CustomCursor::Image { handle: asset_server.load("branding/icon.png"), texture_atlas: None, flip_x: false, flip_y: false, rect: None, hotspot: (128, 128), } ``` ## References - Feature request [originally raised in Discord]. [originally raised in Discord]: https://discord.com/channels/691052431525675048/692572690833473578/1319836362219847681 --- Cargo.toml | 12 + .../cursors/kenney_crosshairPack/License.txt | 19 + .../Tilesheet/crosshairs_tilesheet_white.png | Bin 0 -> 35335 bytes crates/bevy_image/src/texture_atlas.rs | 8 +- crates/bevy_winit/src/cursor.rs | 87 ++-- crates/bevy_winit/src/custom_cursor.rs | 490 ++++++++++++++++++ crates/bevy_winit/src/lib.rs | 2 + crates/bevy_winit/src/state.rs | 17 +- examples/README.md | 1 + examples/window/custom_cursor_image.rs | 228 ++++++++ examples/window/window_settings.rs | 4 + 11 files changed, 826 insertions(+), 42 deletions(-) create mode 100644 assets/cursors/kenney_crosshairPack/License.txt create mode 100644 assets/cursors/kenney_crosshairPack/Tilesheet/crosshairs_tilesheet_white.png create mode 100644 crates/bevy_winit/src/custom_cursor.rs create mode 100644 examples/window/custom_cursor_image.rs diff --git a/Cargo.toml b/Cargo.toml index 729279dc9ebb4..b528e5b7d56c8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3311,6 +3311,18 @@ description = "Creates a solid color window" category = "Window" wasm = true +[[example]] +name = "custom_cursor_image" +path = "examples/window/custom_cursor_image.rs" +doc-scrape-examples = true +required-features = ["custom_cursor"] + +[package.metadata.example.custom_cursor_image] +name = "Custom Cursor Image" +description = "Demonstrates creating an animated custom cursor from an image" +category = "Window" +wasm = true + [[example]] name = "custom_user_event" path = "examples/window/custom_user_event.rs" diff --git a/assets/cursors/kenney_crosshairPack/License.txt b/assets/cursors/kenney_crosshairPack/License.txt new file mode 100644 index 0000000000000..d6eaa6cb6b7d6 --- /dev/null +++ b/assets/cursors/kenney_crosshairPack/License.txt @@ -0,0 +1,19 @@ + + + Crosshair Pack + + by Kenney Vleugels (Kenney.nl) + + ------------------------------ + + License (Creative Commons Zero, CC0) + http://creativecommons.org/publicdomain/zero/1.0/ + + You may use these assets in personal and commercial projects. + Credit (Kenney or www.kenney.nl) would be nice but is not mandatory. + + ------------------------------ + + Donate: http://support.kenney.nl + + Follow on Twitter for updates: @KenneyNL (www.twitter.com/kenneynl) diff --git a/assets/cursors/kenney_crosshairPack/Tilesheet/crosshairs_tilesheet_white.png b/assets/cursors/kenney_crosshairPack/Tilesheet/crosshairs_tilesheet_white.png new file mode 100644 index 0000000000000000000000000000000000000000..76c8b2f85141447948d1712e046ec83ddcf7b72d GIT binary patch literal 35335 zcmb4~bx<6^x9@@AF2QwSS==3hySvNc5Hz^EF7Cle&=3fVOK=bFP9V6u`{Q@-yZ^lV z*PH69u9-Uh=`-KyGhKD+G(=5B76X+86$SQ_#( zp=W)3!RN^Xqlh8LMJBB%iqS&A>hdp2vy;%X&fBmarN&x=yu?mbDqHb3(P9_CZ|BrZ z*r=Rt|CQpWE^JA;tlv9^c$|M`!)^8bwdm~QKI)Y1Z*!9~+L*VGw|ADb_bW9{jVVpz zAU;hYKgRPDQCs01zx9fF4Vb*OJh+Qa=WWmemahONoyB+l}$<(Oi$^S!{#du zZuSn0u0oq)s|dGjwn9#^W1Jf4g|DNe#p9d~J#ag@jwFx?1bHJ%1XFON8rxQ?2opXa z=YPZB6pKq6glB?m-z^Lz(*3lInpUdq)&3*|oQ({hS*h{fF!mjCqB*nG!Fo})bhtO%o;npzpsMc} zmZ1U=+JBaRE4_C{DHUN~d0*OR(sgaSJ0$9?0U}SJ3??fG@zCCO}X@n&kL*J>uC*7lw zIMuk~BLF8sWN7haI7<|YcbBJXi+RWt1pv|!S!6Z%l&<8RuN%_y3I0lW4jp@q57D`7y!RKga8~QP+2m=5@MAmWN0#6`*XHTpwd~7CY5i#>J zC&cg=z%ZmXmmh-vXXYCZ*t?o*Zkl0$Z!vmN75av;Yv7-YEcbis7YX+vc~@B>&6Hds zK?Q0mi_L>xh5f*2-GBWwq0;XnZ4t+g6u!=9vqZv6qj{clZy_n7F=~aMvo7J_mif_Y zR$_8AVPq;rui$OksfJ{t1lcx1T%v9c(IE>bCsbRUsTCjCC~%m~vrS1SqQ~xrs9C@I zM>q0+oI+1f)Tsm9u$E)t@V4NLMvxWAM{U`{?(z|Qh-BeHIWROb|JCwudd|gcXS-J> zT2!3re^f{&n|r>+T3?j-v$b_T2_y149y$BFSfse_8mZAjvRe0+@({zRY@QPor5&Mg zpY;t_Tg$4xWv+%JnG;Ji)&8xF#QP%J6lVs0MQ%dru@NQSO$RqLvA}#CNXa`E2_HV| zAQB$#DwL1wFD-P@2x-QI(ErM=4m1x*I?M{cF*((_L#rQwCOloKqn;eYRJd8auZ%X+ z2@!a-saLB}w%c*XC1s+>!W#s#D45qb=!w#ddm?*HwUjTqIGg-#k}+79d?{tZhG4Y9 z&}PYt8Y_-xl+?8^m2iWOl1<_RWosGKMPkuHzZfsKhl;vR%hJU1*C}nMoNZm=HKorU zsJNdz^{>U3rN{?V)?rvQVxFQg53A9GN~Fuf#*rs{oZ~;0HeUwT&}e#8MO4B;rZum$ zoJw-b8n{^q;v`wo$x8Qu-3#g@3W+IFwfVAV*jMDn3xm1Gg)Y;q;BILIQ$UeTy}&^_ z$3)f3Kt&8qu1A}ClW8&aXaag5*ZRCPqK|xJ8*rBJxeGlol>J7@W14Dt402sW_)pDHgo-J*Z#bs%Bkuw%JA#$*!LP#57 znHk3G%_X?yKc95R6aQ_GbYcpa0P*1X}0DrCW zUd)1e_Nglr9c+sNrWc$uB9uJsSrZ<#Q%$e+^LC7g?Sk3ZALHoah39)|rk_OR%(B+H z-Ox>Z=wYkevjvgR-G^KOY;cG!fx;)#@8gi| zi9>1crk#G>-@PU6B$$dZvC#O-xsq+n0o^uZV!L^QC_^`Y1dtDXjcKGHU5aQ0;eTPd z+74l2Y5V&ZcpvO?W9c?3x{cp<+5HhriRmxArCr=`5WoP}6KC}x2nBTfGwhR%yvvF6 z=i8>XZ|$5^D36nTzdIuiEsJZtjhhhfnqU?mgTp_ZIOoMdQN1|7{4<|jtGqDKa8MG( zwlZrF#xEBwkd_wokry;bS-#SzN0I4n53KH&nXtLJywVpA5?oqdIC}q+L`9QTe=>ny z>5Jl1R(R3Lel_xMPpdq+53S0&4}A)XV{4`ac6>|iBix?*>`Wk83|D>lWf_$??0uPZ ze1)EWm1DWZVWZ>=(6VE_q#qm<9Mz0ICR7uw>K{xXqVr~aNIwjQ>R@-y+4>(Ni{ny` zL{hJ2u6=g=AJPdY;+7=9wXeODsdFUo!I2Phwi$k^rT?d=>TNjfhJT6JH#nRp74c7z8-yGU$n0twB3)*gNQ$+t4A*npJSRj;n zh|18qa+@x0T9fC<$LOrsOM#EzTE91c1bKGWK}$|-JJ7I( zFBy6qu@IIe9VOoxK5;KE(%DYAI}kmqEMl@fq{%13W<&lrwW;z?v16*b@5OlYKYQ%x zw9}{yz}Qh$;lIzl7wv8i9Y1yBwPaXpUcz(Q&wZKwOy#qPn-tSyyI%NGe8POzIjHed z6LDbqICFHRw(m$KfYx64!>3~Z*idD2&KJx@2}D-+=Y4IA^7Ri|B85f3bBd~rH0eJC zHmup&C`LTn3rJ1oxqSqk&E?KVUl*QQC|Pmb5?QEqRzTtz-x>U015b%+*Df?slc_`O z7rD;0+pmi`zph-@t{>-}_^wc)NA&U`TDZOGwz8BL(VD``mzNh^+1uA@%6AD-gaKNM z*>cM4JSX5W%YBTrL&NOZ@%+zuvgIgfqDN%#jH*i-+3)&xX2k8f;VV!wix2ik@Jwgs zTQ=~EuXqG<3`+BxF*efEXe4d_axrZupgi-)IKDW-`&H0XF4KVc6|yK++2;$T zP^!Pb%j84k0l3AWPdS*19XGRrpUfnxK)0@f4NAf;q&-Y1;YRlc-dM94%Ed9n4Em0t zb;X?!`;|}F`c6h;@kg)d5O~GGotdf22R|&9v|L%H8xu3tvW#*%CrNKwTFLUygikj| zxLo?u{waERsg*oExW`Ae^YDpMyy=tMiK##MhA4%;cNbHWGkDtJYT#a-uzTfS3LVe( z<({F4&&=XA0oqb1c2J#PPuyvua^#pO_U&k$IGbcS?^KZpCsa?`YmHX!QyZC<3MGp5iUG+#^Yv*`(=+qly`u}uGSg;!LMLk8ow+f17+4cn(~2) z;l$=fKK84vNv(CmVFexzTv*&ot7uC-fd7fm4g9-m2jUw4=#V6laBB-73oM{zFC|%B z6%iJ8LCK$d%2X-AWq4IQOOE6;N5R0cz@xZ$v~Xx%S-$DJWL8z$TCSZ>3l+z$JBoz1zxGyuuOU#O`I3fA&H#o2Wz$NNZArf{FmtSaD7wml@ldfjvmUP z%&_v2%RPrUcQ|5#>u`Fnkr;l2&=sKfK7zDHSWly-hwtgHnZ~a?&(RPgmsIUnIYCcS zJGWZWw5`25xX}jYjyx(oN3$(X(K0T!n(rRl;nEl$94K! z)ADv)K;j2uk`-T!fM1thRk?-0m>LOC*+$F@9;R_w4gki_dtHmnxa4usur1BuiyQb~ zlGFj2(a=6!T!<8PvK;V#b&uTfbvh#BDX09&ajD}&c9)I&Ec(-DB>r31vo6%cba|Yiz!>|ke(#mO-NDqWJQiuJz6w!8Ud zRDbw3&|W*%nBAOIif0rBI_+AxohdvBKS;GD8rZG!6M^&E^qI{pK%?-wu~)LVoD=gvY>o-8GG^V8bt zb{dK(c}iEy%ELULZOw6}kBCBpL8r48`fV{ho9Qr5r23qUB%Ng-HlJo8crDZM{!Z5f z1P`Q;Cwx#`cS9u~afcI#DXQRi%${HVdS_L$@n7xHOFw_jxfcYlRG0~HSUD$bxrVZq z2i{TpQ6KlmM#aRP8sZG*X@(iX%_Cb7GPs5`f7=3B`x;57Z$aIf)paE(H^}~GyP375 zG`Z)ec+J>!{ry;(R0WM#G4^j=kp(IpGmV6&{d-H2`1K9I`i3z4(C?<+GB z#7k_eg%Ik;d4zyYCr@Cj%X*kB!kog1kmM*QblIHvmpjeg21wLN0yKXO%?`pNULbCK zp0He|G9l`i76X=3;Tl&$RQ9uJEAtsiK8uor=laa6nqk!Mo&Hk5_<`C6{tT=lb6_Tk zz&Gef!@MrV=MaYn&(rOR5R}`tsUna!aB6P`2QP|Z z;(AL+cXBVDFT!1EWD6hA*$sRY$s6m9t5;x}E&f(qJtpN#3FWBmT{t>BK#yEV4tD2k znAL_QXEcyC`5c!?1((X2ylS2LTp>DfpbxrQ_@TEQKftJ}v|j&Y{(y<+c0ir()5x?` zSXIEHWc?Z6v#(>MMA^)w$sL}VNZtJd1;kG>nSU{b?*^oD z<9?uP(z3(Pl5?;YP>woMb>=~tV8xcFF0OWlBk-PGf_FgGoIs3iH(3tV-8?!|i&LJ_ zU7r@D`0YMrxr+1G~1M#PLDNeF~9Ib}Rla^sQS89>>J;ShyLwc*>NDgc@ zf6lc{H7UVS*pXH8&HA#hr=D3-6$Yiax*x-a&TWB`$?o(fSHUNcA)*&yJKUkg#n0hG-^2aQ zriSa(IUIMCVHP77O6gF*poZtu_=JweadtB)rQN+8_z&2C;jmp~r6}jwH4y^HO%zsA zPPnDub?e!rYLLfY`nHL+pF~@hyGF{p{NFin$XS>v^pkH_zu~3an@VWj0vmBjC1iQ2$>0wq? zwVv8FKW2IbB*tQC#s(fWcD}jV)~fj9iq3R6oKD}zFf7NsWZtK|?P0u(zuQNOQ27QodI2fXC#+Y-5J>w_=s z>IZMnQ|PH7naKAF%0|JEk~~pVKxXqP60k1;K~VFG=DajrjL3OfUEi?-#LM_g<^Q^O zw|!*s_#Yh=#>IBu60*?`I#%F6a)OM~;!sRfN5i$%iHEP!Css?)BJweSUMi6jT|hz@ty(6{%_03cgul=frYz`3bbyXj(GqK?|U+*fgfCB z7u(^B0ls)Rc@=9FJ+He%vW|qVYR}#qKt966);4|r)ufYr!Ll;Yq>$ju;xaQm&?F)S z4tC^15|PH_3N9Y8BhQja%Qnbfy?_U0Wd6BF#Ve*MDB$Usd#+R#6t(wv56rN?s`E~V zap*mY`tj#6RHQvUcp&RTDsD22^lixz8i4?quoQ%+uUrlPSbF>j_Y`-7a7(j5c#WJc zR`u;ZzEi9B$WNi3F&#VrNjG3J?rhjxGuxba-b%i=6jTfhs6>QOtW3NKy{YjRoD7zQ z|0lrmfk(gO$fD*WEU!Jat$|)~Xio<%_$gSe7Y()o_XGp#T+aE_`h&#>VpCU@HC}gX(bx>3{lnVzb z(*Hh)35MR&tQC7zw5 z<@QUVyhdst@UZC@X9ju3=RU2oJJzDpA6Hj{Mm8RaJbZ6+DXgKO`hCH>Dx{< zE%uLSh)9wvSrt#nC_!X$kVsQywJWCvZwB9Y?}+$6o4HW9oX7j+YnBdQchZNVFqNV` zWgU#R!*mWG9iZRr7AYIi;Tzupo1=wgZK^#&GJB}JDs^)9q+DP7KLH?h&0m~q!B>f2 zQ11vbRK-3*9Tl130S(Y66Em9X_bm$UJ$}8^kPBmd)5-Gi|NY6y{2Lim+bW;)F~%}B z2)+B^!JO02YomS0{4W4kCAyCmwE_rYOin;EOTF+w>rT#Ax-QD?d>mh!67!Ri==-P> zqYEJ20lBvI$RIzm0&osV!Rr#Czc)XBJD%zm7N5v`cP~pW1Imyz5E)+C^3strPT_(9 zy2#cbOd`xKrg7;DxpCpfwMlTUePe^Ep1~=2IJGaWc5M?Gm+8-;zsS{mY~4H%i-(>8 zN~eWU5^_6H29UwA>bE91*u4eMws5ke)`D$0LP{oFO(=W{#I~+WbxJ zYF0v|^Vp;Ni@s;!Uvt+?1pd~|Q?PdE`p{kmBe6*ZoP)4GMv!)tJWj{u3&_J+knt~* zp=k=C5`i6cqNRM9#n=yLoq;1{xV3*3l*&;e8R-id6!TfzJXyoJ4n^miCW~^@5$Rj$ z0F9w4IyN6c57*>l)W5f{Lu}MNpa9~;)i@osxksyizY`VT(TO4wrp{Q2r$n#_`2f3mmFQk zUyzYY>XT3=HVz&i7YK+O0vY%;S}E_;QA&JztX5_EAa5q@ys56YG^7)pWlfKVvZEd# z_4`nAx*SQaGY7hG5H~;RzMoV>QNeeS@>($=Q%j5}Lz)!}Eedn5;q-Gx=Ob5k3imw04E*ne9V+GK7`} zV9hMU81PW~kLX}CiZ%NQs%~HonyzjhJ@_kknaS%+gd*m0o>$~=&1=b(Y$?4)jJgH@ zxQtSp=ue`$=X)TL)mUAw{1UG0r;a-|@*?S?>XaFigauVNhqUpLJuWTeV(`QQ>HPK5 zR>%#$-CT~5&ep0@n|xO0%ZSttjD6jVAQY4#KS-VO(b%kKILbq%gbUie?h~z>8Za2s z6QklMU?PfAtuDw`Frh}1bhyE!G<=zE*Y1eVPC-NLY=}~s8@1$<`u8K3I3Mi64~A6C zwF2oMw_LkUM?Mk=U*O>^K;Ms7d^N91Ak(qL?DamA_Z;}6vnOa+GXPt5Eeyy+Ma~4`l9S@c( zg$^JjE8#BlH_+t0SQnm(^hpLubizjI`3@*tEWUH0}Y?ABj9ow{8J;H|gk z$e)cXS5LpaHWc$uZfckC<__P6N*?r38^h~t9m|rt7Jo*}?1bPcne%9K> z9(iD-lz_S~wwE7~fD?b#fo}GD6I94ibsK(jw-5H~PGlLV0&j}1%y>TgJKEVj_O7)! zGN67?=axQ2`TF<4mIxTA{0{-I4&p@*+~+~*aPCVV#Ml&tY72Yp!8I=SIj+-9sudj7EpnB}M; zb5@E^@z`9B!cm^bs*&FA*b><0udq&)fy%Dy=+P5d*mN$a<1^tcM6)+wfnt96{PdZe zJZ!a!J+ggW1P}{)@{qkCky>G7w5rqvhdT8zy-L@Ae4+US{Dy=KYa6@dZjuAkbDH3B}if_rwBf^ohBkq?C zc%+Q~`1>&tL3bUhc6f?b6{w4x@cIHvQHc!52yCb~6_TpMH^*-HIc=XC`SktGET%A1 zuQAZU;Y>FSZFe2~_xO|Y{c#J^hShZPGKmQfXyM)&bt>=*b_Wo)Jf)8M9pR`^(uj`n znk4VxOM5Q8dMFhPgtKuzxR+F=@;)f18Vr>((lUSgwJ|2!WmPQ6I|N|Krb;THcaOek z1%M*3Luu(3>?iLL$x&|(5g@9ej>RF*687y}BrG|jx!+KpZWM~iHxh`}fan#`*r8;u zU+@$cff?cGp{Dd%)c11?48GBBijN`6Jl|Xf{Boy4zlr1Z;c=UFnm~$ieZ5na zqLVmj$k+NNQ;3GJv4KevKM@R}3nrVPWX6;PzI&yGjr4M zT%_Do^GKPVRr+vxaj4uUYmf>Gdd5CpID#EQ!+`J?X)m2K#&gwZ%ayW9JyvbsXT;wI zM1O5iE)Ht%oCw7ivTT2Bep9W=<-KOO6IG?A9n~ctlp%@EEYsyhEV9B&l^C54fAJN7 zscA6vSQcfHV%cTD>?Wq(4h9jWESk1UCOJzAGVfqHa8d!grvsIJF*MF5E(etPjwc8V zXB*B?RwO?7G!wwNn3jvDibPy1;@uw2!Sv0CMhp)_M)QU)YjQsHzAVWdxJ5T}-qibgSMy~Av%en9j;QP5J@TGrk$x#3m zPMjb4f{$eF?epESrDrFep(AO8vZn^03^h-GFn%$@D_b*>g?2ipo^_?xyse&bbam}QECve69_0^gEu7&{S8fkeIQsmbIDTfoNngP~>l_ceVo(d~pg(ey8kY*$bBWZWxgqOpFE zw0tnInBicr?yIQqB%y@1%P8h@y2qD*6i73Y zu21qp&ZY5y;zs{mStu?amaJBoF6{ICnmA2$;e-+(Wgb2aQ@hhkj^@Yv+wa{ml%jl3 z+WJ)cr<~$E3Y!9sP|CP$%y1SX6Es}O)F@yWE?rT?snE?`^6Xy)--%2nChvzpMfp@$ z8v!|Mtk(s1zp4vsG~cvsSOBe zxXU8(#m-@72E2F$BaikC{9VEZ>9gj|Td%fsl({@7!|e-K!HO&B7MlrCeUsY&0r1d` z4}Z=Vf`W&HKuVxj+Lytr_G`qn)d*Y~H8@`*%`h7(SlW*Rk5K6$78%iH2ARZl2ccV_w+-jkXWXoJ``n$LC(y~{aSh?`$7qmAE@Yj!>c&Z6AZD{F{T zugSyURUygu69Wm7;x(EsC!!d?WJ2t0-fD2jby$k-v53V2hzRH{ei~o~jaBx|z)1-) zz-kH6j!7h`NFj<)axGTg{`?QzXy!MnaC==FaFwrB-f86aw^enMGVNdAy#WS=I5Cux zK()U+xWCqsa7tTJ?hNG*i-h~XyE(xo!ufttjBFl$I0~4PA&pI0F&85K#PS`+_G~&& zNvAY*Wlqci$-*xp*gpa8W9(KNEXL>FMr9P-)11G+cIE)u@e+B-O?8Put)Qz=Sj)7` z5L^Xg*&poRK;a;BHB0D_AB;d?RLR+&Nvx2P@8O+=#o$?Ibq$ugi^0&+V7zViMRz~p zK~wOoNklh=L-O{BYLC|eP@8sK8R#x;zm&#VU5%;-~=5%;aWtlZ)AXi^h4cU zs*=Cd0A9FL6Dq!Wrd6227kWA<=wwv&n=D*9jBKaskiq_nhoLQ&suLyI)X;StD-T~6 zmy???L-_rjhu`mKw?v8)bdUHL;8(&wc)~M)5%zL#pVFxzbnvQhQvAe(c+@)5`e;A) z2Q>CNX|HS+JJo96F+f)P5(wkJtl z{fj~H%yas({GF~H-CyuhH%D7VXX8~;N>E2NhnI9Kl=;}cT!MsNA^gSQIo(sj7n5m{ z<>d6`C8=G)b=4H`s%wa)mb-=6Y6dO12;mMY@l1Jnr`_lt-*Y%YNhL20 zlswd&_~%oi*~L1srdf+glp6xK=*f|+0v+t<#Rs@nAOMgv8neJ#y@U3TZMgm4kk%cB zNld)?v1RCZ5$0E{HRq#{1@Jm;VA1{~t81BF)kdA$(NU|IMCrq)PEo0(=e?s=y;z#4 zDqMEN~C<`-|2BNDcRA1f=>d%Kl83^H)+Pqp9B8rtFq8B7wtMSB{I&lUF*Kc~x>v0zoG5e6)WXH}02wT2co$2DA4^f6>Q$Xr3o>vP$y5KY5)6tmmKQfjJy*2ZZ8) z1we*0>Y8T*KQ?UPj=!UeTK__2%}h%PjZB!wGvvlRcBirkfz6LGrG&M1GTC&EuM^9{ ziD*{J-KVZQ3d`&aS%+^WNGi6NT%uSP$nT1zN_bz-SW9tjPTYi_M$Z8}O*-AZc`)m1 z$iRujr1yD@2#^F}NB4i!MGTW?Oi}YKLtCFrKBfp*RN41(1!(wRxf7b5ro+QV{p9gX zj=&A+?a&84L_XIPBOmkF`1`teFwn^thsX`MJHb@MK43(lxeq%NnQHG<@~f$CyOtB> zt4RS(ECed+-Ae;laQSQ8K~_X`TyIse=t$2v{~>SqmCOf1qWQmyW)Ot5AF7wJA+sST z#}evfAeL^GZ{$DiKD4knDcO|aI=n!Po^GKykfh8`md;07X&&5&poRykB+O)bUr-h+ zrfLsnZ(a%&5jvegBsB742v3J0@L@Ou%U>b3%Vh3b&sRFJSsi)HHcvCjax*!_G0e1V zzlw;17DPv!Egay1ilwWD6cyDB+$zRnPVC+3tGIh1I2K84U=m4FPGt#8uPs%xyZ z8BPA%1=%sk$2W8$hu5Zm^P;e`43E@55(c&b!7fd;&3N)dOj!;khyS1)##B1T*2ROJ zn`6n|bxnAVpK#>;TuyHu_nNla<+0iI>f%+scXQ>I8KT`|`(9oRK3)=bkp`$+hO}$s zRFF$0G||!xe;as2U6){yXkBJiakGl~+ji*y_s2x+=k#0wOM9K7|CL;})S`)(QyRS4 zz^vIy!AwDEo;~#fO|qSvjQTJb#(7qj8kO40FCuz6s#h^@e$0d+D56_I+gP#b=6gH? zm|vWJy4{mM?>ieBiAwwWH#1f`>1mZ5WE2)o$8kAT z&nOF=J8bppg`d&F(u=Po6PabJq-^`y{lnO&M+&VtbtD$ zhzcRt-=BGOq&LMRVk^H4WLrR-sGCjdJ9|ur%17o15;GF!7&BP#*f=9XUee82CCUIY zokN<$>*Da^dRGI5(RTRi+J5-j5X7)C@_A)FKZYWXAo0T8Vx(K$NFR*ANTjq!j9k;( zChX+;0wsJLT0<$ziuOiQ=3C@3Gj+ct#mtElERB>7?SJ#s_)ko|ZKzB&u&t}kv(~h? zAnHCl2OFGalNAh|U*%mt_Zel<=w6fF-7tZ>Etk_h;&bB5&58MB;X(LcMg+|Qp4ZcOT=XvWa?66^ zKX2ztR7sZjh@tXZjSmlUc)n6a-O|q5Y(}PkO&o?0XtY<)1ub&^X)dK6BUAAody}}B zgAnf9mUTUvnXn(Hbhr-IYG$XbaDM%;Mm-p+{IHr4-M4NHNhzL^IAxS)Sv8%Uqa)o* zqIG)DXMtOU9Ay{>rwoO?`yBUZzw6{g?tUxLCy;wG#}o}ZU^b>!uyB?%VAmSvs1n6b zJ+R>Jn3EAjmYZ5KQe(t^l-dsi7AjdO-R1-q$>8twPH@S@Ly}6b>|iPL2HCaSvSt8v z7t9(TzhkAGOGiZ@2hLoT8wooRX}S|7wDJo(5gZPW+jW&4aiNFw!13r2_EU!=`y};w z197j`CxKsvECv~@l(tIZ=Y;&5MS61^Q`u?V+p6gaTBxX{Sj|CMp4*mGDKDitoz%Ft zR4Kd;+<4rD&!?Du@UM8x1Wm_sDJ{+Qx7Ve^Ra9-K7&j$YagFSO05+BT z^S{5?%FTI1=u-7SR~v*GK7ysE*uu4cL?L}Cat;R_C8=#Pa|XFXapNTg{CwG;*~SMi zQy4up1D~_J@2|VqS2n&$TWT>+gWyc#1g@4uj1r5x3$66>_rPDNf}}5#C;Hfan3ENX zV1YCFlCe5GIDA|+SPEAl5;V%6Q~!Kqmq5jndX$=eFAo6LL-dUC1#+rA6i8D!oCY&R zDf6o~mwpu8uvl4t+++T{*U6T)#Gxc)R)DrI?5r6}Usva#cDwiQ|>M0Gc7@)i{#d>GCvtrsZs#M!ZGoLs} zL|PD(^B%Aej`wuOdk#X%D`*v$dGZ#^M`|)cA zfxdP*QUyLWIrRYz^)PiPE{^=68?^yQTu@!V4-04lYEe7uWaX59V-1ofF=3D5jg3#q zO>wH&y!9;PuKDw$^jFN1sVF`hB}<>q%4QLroNDxb$$S_gM)F#+7?5_7aWfLNij{gA zk)ak4N%4(;dFBbNO~&Q7vqrV1plR&~l)_)Zm($w*?KKR_su#>D7`I{Sk6N$A!7>^j zakTdEun$P(zeVcTBfqGX{p=^!6r>9sU2Rt*@ziXPfvV?@XAvxDnF#wY};(4#F;(#fPf%5Q{sIFndksg;(Cq)!=eH+uD-uF+Fdy~_E!=TRerU9KR zP?swAq5svxk!tb89$#JpyEtAi=qvKr8KG%}APTzUgPk2hwYtEZ1u_mlyhF-Z4dC<6 zen-cf`%)FMM7uL$`cm%1(c&z;%cCM?yI_apGMN90>NYd7!An0&#qkrqPH#1gV;}yU zY{sDfFA3>YSi75{^WX5$YJTL&toH+4yRmsHN3=vNbC$C4yM?WdC8AxSI1<5uUrV3A zuX$;Ce-y-tw)H3ZW8!QLg-TK>k>T>ycVu%pxlnHY-P0PiNV>T)UFVtlcUf995buW> z^~KHI&`~AWtS$u5g#I>M(wSOd=fhXP_*~p^AGC zF)4B>E1#8pHgC#(Mr2#yY@H%xaqlps3i7yGp=x^wEnREKy=QOpY8W&N?1uh8Kg4b- zcW4Ss6=7OjC7+M``F=FZAN5bP48c%tPO@b4fPv@aBjc^X@c2E?vyDJsAjV}p6#J#p z-B~u7oUfeDE$clr_+2edKe)AvNIKCgSKv-y$Vcl7Z-5nshJB6*8R%vH*3e={eY^Nf zRY8<~+;pL7$VM__5PqJfCYMOC5&5${@W*;cU#BxUOPF3R6_p>~)Yf#69kG{lRnedE zQp#!!$7lSBRUfFxs z>Ar?M1<&GvzEZU`g?T_jSwFDI?M&)C_T%+cDDF?@h;1weDjlVls`1?>9%NONsBbsv z$J!GAa)b4X4Q^|XMFH)>m9ES5z_OUCVESJvnY9Dp7EOI?do;47QSu*PSR9ac^mpHA z$uf6h|xL~>ogBLIFE7JGm7{weg zU(Qj9Rh1RbSrhkO(n-^0yYq>fUG+&E`Xh{ylChb7zJx#M{Kh;Mj`(-TDWkri%Mu_x zh7`^#g4w-C+yUDSw)STf<7TJ<3fTtqn2apxn+P&$(gB0^!KaWWBq@Udi`qR0^4~VW zZS}rr#WIRvE$7uGS<57qKg@H#J5^WgcTr$*K@0&;FRq*AiI9YN64tvv%ER=?C%7fu=`i^n* zY|>>^U+ZoRHXG`!R_Fewh%YX3iykv%j7hLHQ$rl!c{D-{C;MhKIjgw1Sqx8{io#d` z0lbdt@7b-QQoX0A03$}8Gv7lTS&aGIoS6h$(`kl~d|3og@hP7=-hT#M`*;iFR4M&&g_nFd>p#b_w!la`CqPc%M z&$5S!F5BTIVftWdi;Dvr<=9xW_F6ipY#SR9R0yNsySdG z)n;T9To{c*dqVS%o~k(6zwIuMuT! zw*w9rJv-Q>j!DKJf}~3`e*tJpR1Q|>&*UG6h&5pu;w zI>&T*s-nX}N5gRDdj160?QAFKy;%H=G51M}>YmWWb7tLGn5i4LP|F_K!EEhJrysRg zs65y3zG8DK1xm#(2gK09>`JLjO0$in;(u=X#*0l;ai<@KkG+{d1~1n7T{B>z&QVqH zOfJAkgdTP2B}ndB&+)%gzCcU@+Bk+tAgs>g71+C`E>*NgE zc~#JuUIlm0k&R#wMCCW>ibadMV#Dr7s<3gt-7MT6-M3Y|qP+eVXv(9cOu`Nd2#WYs z{+v!!xdu*7QGCxL1zq0`zXenD!TRrCOI(51?`#i0N%_hK18VmSO(Z;#Znx-GWFKI@ z!AzA4z$dOfSa`k_6*!wMQPl7-qo+kjm1cLtC5L>U5?7FOU%T0-wCVxS&}_tkyQnWTFy>PkkDEN1?muW+}2Yfcw-f6y8U^0(BF7|uAFoaBxvHX zl%mESO6jJ)?K z!@bh1>3?M<>*)t9u1ouh<0etIcgE5*db$UFg%B|>txJEdreEZ>l8E4JnX5lq5~=pL z?+%f0jdV{6XI*-=Psa^G&f*EO<&mwWnX&%@lfc2*^kIo18MQPdE&4?%wc2PemO-F1 z(cX48p~e`lhDS4|bYee!9)owMOe-)lZB}A6p5Zs%QsQTzl>}mvk=M~mzcGegHNYe6 zrQxLHZXs}FU`M4YsM~(%T!Pal?&<8Q8B=+gW9_)hG5!pC;{Vrl>2-P4)P4$LBT05Q zvK{!+{eGTh;fhV`GwRYm5}~r4UjYhIsedm7Bmr8_7{H3rK+K$=HZ{Ia&pMeCmtpHz zVSKCeju?aK%I*h05m)xg&>3v)s`()~)O>Opp!#DcV`V^-@_`5xR~cXg7F)swz)lj zC3bCDq;P0b(Of=hCHh79K6bKOl78v^m6u%G-0s{mn#q}T4|#j1Z>rsUkiOM=L$otTF^9LI#?pcvQPRJVsV^xrR-O8CWG2Mn^=LP z<-ik}(WZ~sr%mZGrgS!Ccrq^ZSgn}(IYOOMKY(#g&2&PIhx z@8t+P;h8g`G%p%$Qcp+W7O9x}WzX<0TVWXk)A|39^%X#EKEb2G-K|h86lrlOUW&UF zS_p+g(BkecMN28}Zf&s!cY;H4C%C&54Ho33zyG}V=FR&ileuK}cK3Go_V)X}-8-x( z`rMcy9v95^J0(Ure9|{rRV#P*0UibPq%D-^(&Ayp!0&1@YRSgmwpXkRvYURED;-V` zQTVpz?8z=g+l{}zUf`Ej`CDuNn*R3cE!^V^j*LLq7F`K&Iq6YdDUpdqj<&js`mn-r zIRfA3&$!@3+C_O)U%I6HIAG>P^D)MZB+O+2^-Os&o5^Ce71t$}-KVS9%#gQSNMa|zL zo5*rbVx~W%Q&ZCWEnM2DFw9n zk~QLkZ;a$XU92iYj$3RpMqpp($UQ}RiUWUn?VoqepVm%&T;BFfFw}v3f2j4n-Ss_! zH)o*A@GQjqQZr$SOK4|jl*5fwios1FaP+5$jHOY!G!*d0dQ+vqa4TzrGcn}1oW@!6 zbl}4cUZrd8<8P8SIEDlJVq0V6AzWm!{7uL3QX1aO_DKi7SoTHAYM%|tGQtFbqAxwc zNuSV*Iz-UplddDu+E;P0>fs=CDctxbWRNR zy37faW)oYbXG%8iaall?o{`qzh|2-Ss7-5g&94&%Le4QOuJ*s-Ygh zem7i&A|)(fo0aWyL~$@`Wh5_YuRF6ivK`Vjt99mMN(4ecX|r>rz0#MO>vNKO(jWRP zX-k!Sv3Hh~icgp0vhM#vq81Bct@UH2W?!A{q`F8_EI5ii+vtVe&+z=wFbfR(*86vN z)=gji)yY8A`Db`&M4BN}>hd0b$|uv0=}VixpRjI^%S;I&F^)>yfi@Sbxbbjz{YPSR z@&;#oR4&?TVsusmeVnQ^-GTR{>^wVkXzEcb;KdEN|G%kOH81wK~(cAft0*~2m%(BxKk5R!w(z>=>YTDpa^%dd9aA|nDE>z=J>POYA}HO;W3M!W2XXNBD)ai}~#>%Fv4 za3tSM?o@+3ilxWZEC%cf95hUmkzLnk_{VK7d$+-#4dZ%)fa=0q$a;ABd39p!?oSlb zPODGWi#wC{WbTI11QQQG1k!Vu15@HI=um$>5pR48`^agj@9?QKl<&w*IP+4p`6t+L z_+f+@R?9KbJG}Brz;ZKr=GA)CbG%k91va1@_P}c@{#W#T22v|K?$<=24|NfG?#&{f z#oOGPN2+U7yl%@ah}zG*-AJ{$db-e}fp1Jt>E7)5MIxF-?&q_3jXd_6mwL~0utTJz z=xG?SLl`5$7}CV(|KI2L7tVd$5XL3qcI3Ao&d)PjnlEQWYR{&2Wx`+GtcF_Oe2R>I z|Bk4Od>a6iW+ZMOv|M^%+NH(JCqSUJG^ujiGecRTV zjxiJ*KS~0urHLiV?~E(EnNg>ttV%4SlSy3TNigLrFuZ=Q>*>mXi2daGk>@^a!o~mL zqzKRrQ3A?f3(`OD`uV8`Qq_I&NAs*R)H3yC;|bS!=l0izx>wI0aSkND;Uh9<;#Q!i z^7oCHmshxXR^Z5kqK1PbKx&NWJcblXWPD5UA12zaUD5z|>c7v9js8w(vRQZAa-9XiTVD}))^3Y>?UgY4 zC_Nh$NU~6DTf6}`BO>DK`B1h}E{G;nBzl&juHf>p(0h}J(k$wBa*DB-VN{nwd}gcO z-s~`j{`aitURy6U5gDO37#^+?Mug{0zk%1Sws46V_`Fm5YQDE}BxXD3@M}T5*7*4p zN6^2n^6e?ItWMT@O){Z^vS~-MhsmaG<%8 zl;Tv%)xooOkVK~Rca?{2iBn5SC|884l`;DiQmTo-wu<_$yiaAH2`-g`9|E#R*4O*gRzHj*@{0+a0UF~c6&2d03BRxlp#{nsR& z=RvX2ioDb|=%1f0q<_bCPNC*6{jw%(`G6AMn94Ipd09%&kJ?Nyy@=-2A|cJP0UJY5 zV7K!#{DjShvbH=bN>o8@R^75oBi6=z!{QQYW=mw1Ix5gf>zLG+#whL3d~^oW5LioT zfp}Vgb_Y$Zs2ljRc3u3uFDsU>rwTY{EfplTORj-GBw3kHWxtjZG>h|j(3H>VC6WP5 zQjHD9InF+QF7^v#sSLV-mpzE-nI={o>I!As+!_D4ex+^5hdz2L>}{AEi1}# zptUK2BvSw;9zI)4mlk5L0AXm*7f+4^!(^Z{kl)}cHX1MMn3m1&sCWQ3L0RHd3AP?f zVS)47L{q+xr&zDCeET^_+o#3#A!-x>kANb|@>vb!pat#(B{2=Q031AoU@~6^Z5W}b zOM|#X(sxH-3XHbpvYNVl@iikDyyE>$GBHJd2TH&OIUbfI z^+D(d)^*)iD*&N3A+}?$b(7zogR|6_h1i|Cvm6cuGaH~YTMl5%tzf4n(-c#r$@e#B z%I-4kiPuN-=R6vtzE>Ut%F-x9Bl#XX|+8*`FbL^Zn$uDBT?_=se`uNuk}tAn@Tb3qTs~qN?mJ`zg|f zicbW;Q;m=k@M&hp_~hFn#&U&MCDh27>^K#|UZ;c8${6rq(@qF5ZZI%UyA8WU=*GJ( z$B}e=4p6EDya+!kE9#Iy&wPIsw%4#yMLR^4WPIyWjA1)!T<5_qnhjoOgTW7L4KXb~ z@3U>Hh{pl&`Gd_72;@-DDl9P>gl4(`<__?g^J<`fP z1jt*DN|nvk{G@TtQi?F1a8p{>Cy-THCXU4mr)3htI_!kzr&nUiRQ`@wk#O+%OWUDd zdsD+xk2DtmBSB!YWE0p{YAA?OYHXP~tcFyAE~Mh8tP@GDW1ScfYt6eK-&Y=oGIL%W zW;P#c)L__ic%b`*>aD{aYhyi<^Y2xgNiV<8ykrXX%-#M@Q>9g}@dpJUqD%2SD>6cS z(_|tPC~VYocwVS*cE~a8X?A-YUED(u8%p)|$s#y$@Qee>hIqgY8V5d~QHpqdc?jt6 zd#zX*YSct^mf*NOGV;MM%O>C$#pjG8h zq}T%dLr$Xp`HRPTMkBg%&mKx{G5p;7nE(k(7KWc0U^ZK}vqLgJ%^pa_jk^M*)_9-S zuUfC7NhM=#;n09mM6vNpaZ3L)RSP-nc^}V!cKxI08R#j6AY1FsNs}A%`KP-tZ5Jo+ z$&!5X>UO(AD%C2^Pf1o8KaL)<6vzzw>F0z?A|C&yCUoqO;3?7=b)G;`W0@kjW zDLtfQIs4DXl2m1~qlb@8i8~r9>{(`77}P*!oqgJ0rM19^-rBFWiR=x7+R=HvdiP<|1P`Q-QnC$*Dcc#pzdWt;<2eum&Ny3$hh3ca(r% zAd}cUCvh`Gcd?b_G~~etv;FO3{c!TgBTq>VSF)?%eFyqvgmtWDXJ9;G<1gC-p z4|0;hEV}FZib@!m_Xf+Py&8N?5G<|;KXe`5p)4Yu>7osZ5DymV-RB5rYy+wfumUb7 z4o4NFnHRVPG80~ydh#<3)5;n)FQ8WFRb;1Bf;G2cKM6nR8SU=%;!_@ma(=0kKN13l zU$%WJE44s7@WdL^NKGzgJFKJbtT(OmKw3n_I;g9ywkzy%lexo=4%L6?$R#(Zxr=mC z2-sE-{>#WtrpS|R2XZ|osiEJz5NT{srLW3$OYjtU=o=tt5g-^ngl3Gt@ny~FQmh+L z%f!cPM=@#gzUHPGt?_PI`o2KQqH_;UG8bvQh}0~XNqYbcBAtAr6BZ`fq};7gvlCrp zP|Tinu*@wtDN{6u9}!|$2uigrk8w?G2N=d18IagrdPJnt{b53D)NI~68|FMKyf3UfP- z(x{o^W(I)s!UHIl)*&Cyrq^iJonZ!LAf=?5qaQ zLy{k8$cf)cAIZ_I$MBJ)JPW3|JVN=>1=%tP%d1YJs=*He7GL^Q70-^lt^eKGLK`PY}`5OHDZvoFbiZAz&d1zwT=H-BP(eQL#d2v zN(nmmS@?|`VCHr^|7w~8?~1kqzp1|lxDg{EyGPqz?S=V?ExIPu@gkJS!6%k@J#|;N z4r8X`kzwY>*@_kf$)Y=7rtZ>M?--EYgDh(Ec;lO|=9Z>b-y3uRO|p1E8!^5(jdA7o|!%r|VKZ}98f z!i{GA3*WwkY#d5lbxa_pVbS2No6BsIA1$8>I{*Fi8u4DCHtI zcF&IcC>9--&paEeq3u5{)~2T9!W*p$!#7F$@gOEcVwN389fHEEtV!`{b^wsZ7#AIS ziK+M~f}P^!7}!P}ZoLF~r}RK<4tVDbU32+BR{Z_~2v8(!dgV1^J@Z5u;w}uOZ#Lu# zZ9+f!@iO;TPH=~fV@S5tPR2kSeFD>X+!RH^&%rf=2t~x<&wHu7`%A_-4lmrV&rm(T zKkxeVMW)>bFYf&dQ}^V!s|T8;uj*)pUG{gdZ~(2)R-INO%+<{2*tyI}275`=2HTYa z_dyI*K89Bp$9e_QJ}2H%G41~H+m58V?BdBo?v*+AV(~k@kNIc=z>B`NitLcR+Is5* zG#PEqSB&QJAC!WOSVjwo1=?$~+?jB2vsEw|WX-@p)fa^Et-fHxtyuj%H2KA#02YkR zG@eKl#-zOQpXTEP6`Ohm#2Ok>=aD)iEsX9o?m2p3$GK*xgc) zs;D^?B%smu66hy{|wI zdt^pJ&Yf^v?s1$oLoshPCvl`_RDwLB3{Y(Ay)k#Z1t|3lTjX8^qw6IFVf;$XJEbT6 zyMJo+@ZfPuxQ9SfK$18_l&&W~BQoSb$zU1iI>;rpH+i2J`YRz4YhE1hgX%jkBAg31 zuh)4zj*7;sC~bkUHKJG^m+&CyXNi2?{b8j&FUTz*B^6MSU{}?=O4`u1BZSkb$`qs= zl?Lch!NENNn&c41iat#r{M_Mk@;L{qv3Z7WeL4A_*ZTJ#qIu}&FOF)TS#YEUiR0K= zqzZfF?HUpYChC;2jY3jfCx0dX<9cU*8uO~8H3uKtcE^mF0e9P7vhrwD$G>eH1Wr5? zo^lvKhy^1{ilD|?rdGm6sIOI+hI4ut>y@yLOle%C#m`8|nMq0dRd0y=m>@O%6mKPg zx0l{_46WolHHZV$-`E-}FanU>FdLy#X>jPfS11e2c=ymAGwV3w)*jTfj3`Y z70ak8r7E^j!b>hv&Y%x0($b9aPOYK+uXo|(r}DhJCwgW)Pnr?RMw%<5mceW%RLeBC zz4wbV)-~UtZ&yJ_jK{Y1fQ|d?RP2r&MTAv(X%t1X2I_0&H}$>=1V)@duYo0@<^T+K zmX}H_$FW<}+@dAy->ko3LwPS^^?u)&`V@yMrulX`aD}O_R}VX{m_9&oKgHRlD#q0e z-Kl&~diU$T$4|oG?x5u7w84{AE>2>CtoS);o_vDIi7IEcp3Eufek|*8{RicD#7cVK zQ%_s0M_O|&iv^0k&%#C~nx^HsflB8#pNR_Kygr=-Lwkp-*Mfyk%}vg1l1GQ-8EJeo z%0ZL^n66;c$^jO19u#YHOHTh}bc~Xshxjy)D3iW6{u4{8(@qO3moDneeVUL|73}mL zi{3cH?zI4!2HMt@&pwyQHgl5iI9~@XqxKuRRlMF7A_0Gq5$J_u9r~iBjQKxF z_tdMFlVYTt50Yu=EyL5gqMC7d;ywZ6m;t&iE$%aQRClifzJJKH)SoEN3-iw&{C%}* znq(Puhvu|X@iwq~gFp7}>vN^*Zg`u@FwHWFdhm>!FA*DxK6A$loN>lF^P&0CbI6LVwW=jH+_R-zdWJmp&hk z2>`Ejr4~-YmUk2o=p;xPRLS2ZpXwfbqj^spzgWmQU5)eTUBz%MOd%cR07M_&^qc z$BL+5-NDDc8<=n-R9?308Y}e#yvkpSf_5W@GtPvA+|3yyD%6;{pMFZK~T-8_tr@HI}M}w70s-@8VPz7@`7=x^{Kw=W;h|IJtXrWy{bLo-1jq@{@q%61T3+ z0VZM$-2tnm;!(bo_xyo1H@tn8Z6gmD%5H(k+N;1$?SFPamV!vYNc@1De?+w-QFu4( z|6|-AbK{hDX!EY|Fgq5=>WHz^3DH0$p>fKQtrf3HJ(sIVYm6i0cjh=fDi`-SR@NWq z*7lxC77=D-UH-~a2`bn|)o(mO>^w*}MB;p}UXOUzjhxNB*K`1f+njTD2sul7DC0nx z=*Lea8rl&C&fN!n_uPS!?W%VVAvV!6KDA>d{sF1)v(c>*y!L!a33yV7Tg`2Ai7m0WqCI8*~n*nVzpu>j=s&eJ3h=b=7JYsLOLWLDhy z-Wa&=As1wr4Lr^$HSc8N%!f5!##P!nNx0Wq#*CK6!F@`!#Wc=+YNKvEN$W(XF1C2o zY8WSoFz*k>{@PYIcK!GYbzuuiG1`S)@ghjkR+*8rRDX6_z5*R1O0MUO^etp0Se;(H z{4e%E|0u-Yawl68%)qL6&$N>QHxarw-GoJ?e?TKkQNcNtG9@<&P|XTkNwm^~F#)}l zhe}#7kn0a)ypqU&>eQ3L`b4txf|;Qim5R%(vHf&iZ}nXxbiJGU<-^k1UHE`$Q!;0z zGAB@pe^!|J8BQw5EC;)M5mgG^KXl6#hWoSQ@3wpS*zyv{REQqKaR&<9)o^z7|C>g` zESFtm9gu4CZ@mOQ!x11%L0#+`knD?&RR3H>g0j zN@)z@eM%Ze=@eAPR$wX_3h4IwKM1bwk!yUKufL$pB^>VWUcbE&;a*3YXSnf(xZW+L zhzqbHsEYe76nk<)f{f+cf1yE0eC4L`$e+U&<12*C^d3jcwpY{waZ+56Ix?uRWL`iE zh4Ca5IBqwMM9;65m0wi#dDAt4Vr5Gv?uDm`*In=Bo!3Ent2Oq>^o%)>?5!w~|c z?Amvs#0{wywA2$~uie+oP2e6dRI$-N)!IO8SEy7YbOFXf`p z!?=;y7|?MwN8c+gt)ABweSwymbDW$yr#uS%N(5x9R9AeITBRnpOHq2a_n!ZTmIiTP zVSO2)w9*D)#|SNYw%3hr5d57bEN#C1M-g%)$@p)>`=G2G%7yAkASi@gaqc7E@RgWw zEBc=9#>VmRnf~giAVaoVhzum*X|83FmCb|~w_BMpm|SV>>8Z~2*x_}tKPhH|vM3Cz zzk$B2@LG;MWwf9iGVyxrr*qnO>XLRyX+y_k8Py$26a1Aex|J7mQ7qm>kOspdxr$+W#n_n53QDxfpBGX?FF;F;&MLX?Vljlmu$ma+nH5m9;ZgpJ zk|98vHKJUI+PLx89`lQU4u)PF(Nu}&A5#9XbnzZiq{`wEt z)w(QBwuM~93r+Vzv2ARv8GoIrAk%KQKapw7bAd#{l(C{>%Ss1JFXVKtb!dGSBMH5T zFPjv1D&lXE{~aB2Us#nNS00oLZVr>ojBVO)LitDi4_~l;PjlXxNd7wqTB%lQKfA`6 ziS;i|+&Cd)`hPAly2tmTBohP(6}f5btK`VNaDSEp;qyMzFWP5QNrFc7%8q}%X|2!r z@{c%3EDq@yuLZY#OyvG@tkL+~iL-~MJnBxRt;#U{rm;z?ATz(%Vz?d;Nc>T+1Wl388~p91C9LhvK(W6% zv6_6%iUW13`)VM?aW|zjq}wfZtz@NAj&~bWw0x5bfw4U*ZPF($k25RqBU=_7Gw?nS z$`=sEeNd&~B$d2bkd}St0o@A}QGfZlv(2`j5QqUVN$!vOz%|;S?X4LE;5D%J;uDG+ zlo3J~blCT2Jar2$G@0ZHr||a2Kny~)?cZD#k-Vjj;<$ajT=d8jqjMLbk!p0i^gBZ^ zU~GHpri52Jui=CSPkCZzD~0*r#A?_yumBlIcffz{iQz&j_ zyirg&vK@d2=kAxC%13?Dyn;tq`~R^ubYPn_7oiaKtD46$W~3I)=u-d>p>i0)KZq8! zJkEozlKm@UFfUOcGkWT!?VN7{wx;^B96S@b?0j~?GW>8vt*Efd;!T0NcuNvQf%oc@5Z90`J||K-)sPRQ zbLXu_8)tR*0N-2RA`mp%o5FinCw3;u^_F})Sp6ac^J*7CQw7Og1{Kf2uA+jIFG?u^ zCMRZ@sUj{0mjV4_YZ?E6#(4W1&;8@MxT_;&S4R|3vtWX`M)*AbDZo($En0_8xEXq# zZLG5y({aRsxRdl2pxXW81*|Og&aU*i!#p1Jsv{`d_bqzg;?_VS3gN4a`B0JL*@fiM zk{sZf{h@yUAc!%`_~L@5-Kycx#@X^=YLWEvT50YfI!N^5yu=!gLUnXVx%NP`V+JnU zlBH1){ocv;yphlK?*xUg5g}5KRw%rs1Z|0bd1+8M2b6+E_;-xn3L=>)orA?v0q~>8 zM$h;G*!z9iEgGVVu$H`@jzRC3qnZmdVoCfx{0`O^y&FonWth2Mia(iu@L_fI+xHi; zji)z(LIQ;7>c8iRt!1Y779JXotKk2m=H(K7fKet5=Jx4`KH+D6(aU|r;SaciOQC2r zYVbgC8FBX;xCtX8^v?Eh8`RiIQ8~7BUZ&gzG|o(;@bchA>Kw#G4rh~@y)T4d}y-$Y+$vctq-~w#vH}kwEYRMNYwWFpZ6eiO6GOYnme6i zaHPT`+1r=?^7G%X1xZ*gX8b5T-nE3yCa4X~|2?#2QfayU+GqL`(1L1J#*6G*p5Dw< z*(%zO3OV%Zwh&H&7TEP87KjCR%5H6-HeXL2EiJ($b36RaU}~NbF(c=%gWXzgR_Pim zSzwfPL%)Ay;51z`euCg!(+8AwwEM;R8IT?EC-G~xWgQ2iHFApZFNp@}ASAp=N()qS zd=;zgi0DtX35j^S#j7{wyA9{rR?0m%iLnl>6Pm#y$~nzn>GabTU6Ml7mQp2!s(C_0E9m8(83ddn68_bm6GiU~6T9U#&KU{y?8pGBnOB;-G zw@r$|?rg59<{mJ>fq}t0aVkp`ab4LV=z^br{O)2F!f9m>>NP-fx)#}wL*O}q{Y&U1 zkirrKQ$$RQz$o+%$iKZa`+UHzX4spBZ9;TBxR~rvP*EL$TNZZT7@#-z%p_;JVSWZWw^x~4QyEuwMN;QYE z1tHco?LNcoLGCq!y2!=JwP)L0e6=wdl6}?E%>;ShdABfFHThDN0GrsA0DV>RUUJ<6?7!$&O!QLk9Tqsm4C`4 zau<<;z~iV+nmWD1x2rkc3PfDrE{&3T_i|59xW<=vVsc~AG!6Qi=ROsmCSSaix%T@B z8CwMeXmUn1R^8WSLU!}2M8F&c?A`SsCftcvWZl_TvqGQtDIs}0f?b&DLGHBnFB*)l z>U7D|;dMgb5zqzPt)!H8v9A)&{PE)4<|7382D|mu3?5jg`?xCslK-AO{X)=99#QJ0 zi1K}{jJ@TC-8>r-%?BP*Q@vX>@J*`4;0pB$-fZm<``#QcUgQm)%Hs%uzWtg8LCWW@ z%6g3?+{N@u;A~$hC*F^pYr!YV^T2e7{O3^CysSG& zk;FwQKnVXo(3wNOihoSfDwf~kg7UT7OT;^@(_!$A@4tW1x_@&siGkz~q2;Z>T>TBc z5^qUAlfHD$$H;jr30U6QgimGv4e3jP+=@N=7gfk;DFfE}uvVs?Ne+t*_ zsWV1PHr~6X{o>XZR~;>!@q6fBp)2FXmPH}A*O%+ret3d;IAIf7TUh}$$3Du^6RvY> z-3Lc<5Tvukh-#u;7eSJtpOwr;r#d9|ZHpn)lW7;Poiy+C_ie*p@4O)xCn4xp@of_< za-B+fcv`x6Qv>a~OsRr$?*h&I)xExmYGPbc{Ag%XxVu|ekiGM-ke$>DJ|I02>H;4h zUi%wf|BVX(!#;s`_Yc#dsCOB`P*irOanM>mQ|Z3>|60^8t1x#a{^anx4uqT7$nXe5f)t72UfJitxi0 zPNGT_@nB6HNW$)T6gP@G=XPRE6xCp_l=i}Sd4g)mUra3jDq$aBhi-&cXKr@N$kqRM zrrOd!Z0t z*P*ahXQ^6A%qH*C0zo%Ut{U2ZPsh>+VakNQ-4N^}p7k^;1RS=pjc~GYQCK`DD^xYKeilL!S9fYG+4UFkV;{K;=ys>Yr`Fa6cQo3|=} zUN0O~FP#p;8xhmQTu{)rvihsx z77Ohr7(TSeQxGh{4B5B6qgeokUs6>*RdA7fw9feV?UY`T7t>uB%S-cfDdgTM&tF_5vygj}>~zi2&( zoy5>vcbB?}hb-kk4QtGj+mKNygQq|Z5OG5?RI3|KpRTge&&D%!nqvfA7x1aEPh(hm zS?l3~2QI=H;#BE=e!8#r509>~>WvZ~r;wd8O7SEkK=iIvwq4M!B&Xvq2;{6dN z`p7){v;wOeYq+{_-@lzTKcOnm&aKp|+F{Ew^-Jm47uR?MJKl(y)1ag{9)?>==Q{f! z_WA;Ha$&NpNIk!}yNgo?SfQi@9U3Nr+XDl27=BpGqrKvPiF$+qRviI->btl*%rK9K zLRD1np(^ocx6SLCmit{osqswGe~CoA>ilQ=59pc)cshpc2g3jI+Z09G%?lvwGq|3Q z+P1NNEP_BLN!uaXTL5Um&UL?YovfiA@~3$gWDJv15@YMqq5=ZeFfROaY)!RR@b(+K zDMCLQ|FTZI)bBycQ*Nv4bNuhIh9VXMPU-V4Tb|qNv4-+%JX+{%Y|RWzzvrZkJbe^Y zr9~y+JtOay2>Ce$uP-9!EBuQ8_3{Hxwf0CcKGqdXaDd6*KRM;bKbZ;Zw5~h%dvj#~ zxcHCTHC9g_Ta|@*(CnMlg0?41^@delmvjBMo7?w%-`8BJiR}8`6;Iu8UZ40ff?Wer z>4NISb6OqzQU(qU_%3D#%a%r%7$DnzUE8X!z}zC)L-!pTP+rHo45-Fd8Q^eRC${L4 zL7ve-!e&dy52LAX9rQ75u8AJ4miDD9vb9JFw2`$_Z}D1kPK7% z1*U&fE-;|W)A+@y4;E)-HtcOj0z&T1rF(~qDXyTftsgj4HI_*+ZPlXw7RNQfevIt` z@H`kqz*(985A>-A1B}JV_QMN~Hl!JF%;OSyTZ8lu?kIEEt?b4qohBV2Itt(kls4(M zpOKj`v~Pl{;O>zV7eDZFcP-QDop5E)8n1CDI|eO$eWrc_+_M9Z>-d3m1a@GKN)oRJ zfNmHjmmUj$6Rjyr;R+N)vMvaylIw9 zArwgJZ&6di|Mk2LwlHgHQ4)us1l3V;XduP*;!Zy}rUR*>t-3(lxv2-1gv6Au2_sQj zMNu$7$CV?_+XSe>ncxW#C+hQHn5S{!Df_a~73W%S>jyXcz>$}eLHYn=SS>Sh3dQc( zVhEu^PLOVXh6u;1o9{s@8o-HW_t%qo@S@@)Oqer9^c+#;Ul@Gcd6BnN}?&ElB|mP zVGmvmWDDzfl!R>ZJMcKRj97Dm3$6r?FEUsv)A!1t$ont@;~{S@&9}bUUb7_#SINmO zxH?)HsxU-JJ>tsEJQT})E006A!KKJN_Bl7u>+Q0G%a(OO9UWYt;@X9lI$?{(B$#-`gM- zl#)4PDR4wf#5kKCZ;)Pi+C@OVtfb-z47Zd`U6ox9jDoR@f!t*6(9Im zMw~gK)HG3=8tO)6C)~!4a&${UoEyLj{sSQdv|<7JF~7VB{UiCL6FKLWrsFSk=S--J{P(wa$`a1Uwc_aSo!iqiS-JggDti!u$2eo3VN4xBmlauZ@VgUGP65oC0@gma7O0Pi6AhY=+Q97a3WqD- z59%SycW%5i&w877;z#*O+i|mr%PgNlUsg@Kw@i`&-3rrXtb`{&Gs`r)K`y=^<~zkX zi#Br?F?0JMUkEH~7L*kV8hbI3)gTfyy30i76Z?)sqr|UYlgL0^l>Q@_0JIRD@ey1< zo96r}>ovk*MWhJb>#4pIl1v4TggUlk&Z=$6u_)ZcZEp)|KC1ZXY}B>)=j;p6U5{35q&CDJOV6hwR8jyfr*)KRcz zX!IVBU$9{{+m-oYFscj<(4a7@k>+kn7)W-03`-&Ai66IBDyPBOpXXTTy;f|El*}YQ zRLwr;FEUM*B?O|Y?j_4bZn~h;CEW9AElUQat`OrTx4Do$3=I=BR_m*WTy(K1=iP*&6UAt*jq5NMSUHkH~JsOh}AlHv54U)a;j zQl??0N4$rgFrmcql(AuR?LfiMrL7F?MsCYjH$fKHiZZFLgGfsK>xBI_B#pAKD{jUD?ESj?h+Hsy zsZm{l6je|BhHmx+*d@S-cP*h5ACB-#X>o}F!SU#TQ*#6jy+dx~7=4LB8HJ*qCvOoo{NTPg3-1Ub58{hWwnv5s9ekI9~j5LdB_ZKYF(7+t8&L^TCR=L zIMUy|UedfxV=4-#mDxuUi45hANJ$SET@PaX3|`-34TdUl-Grq2R5X6^7R|viVs9VJ zxZ&!3{O;^{TDHi5vkXC6MD`#Kzh>%A?6A3Uxe&UHe;MBo;+t^DRd%Lq61wp`_f;|& zXA3wV?j^FLR|_JAVwW&QT>hxY!mlrf=T0Q0Lgc)GKqEQ#=8wAtd$HDCXKJSxz(=Wl z)e$WQA|Kp|6Atvpv3NU~*70@{XbG+F_3G9wZv}38eS>br43ItS)E*mh&x#Z}I2%`)+~Ky!7qwC$WNR~vpK zKM`86LCbdjAgp5KPtsq07?&2!_-kd^I#0D7_G1jztEHa$OwO53euQ)S&PB>FU5VV^ zpF+OQs=uP(yta2GGaZYjOhE}h*3DgnU48Dljs-swwaYK)zR>^}YdZEstwrsAoT?(+ zN5RUc=*9V-#ycc2wA*7|S+xGnUCq@&2l}}$(oIGHVfjYA@8-Up&1pxCkeN?wm$h_c z%TGzo|I?4-Ru%)tgN$^DJ&xeO>6PcTaI~7oOKR2b^dTF$V4Qft0#OznK5L^)`mx*! zd`9gzXK!ppXW#f`XOG@-67go4MB*gM>hS-L#Nm*_sUJuPz3ATtyyn(kTRn3Q6vRi#T=}dxpPPqJ)w-ORg5aWAI&KQ479zJ_@jkuV zy1_{LzE&c@mXz^E`|vez=4}`8ue79{(;y6*hAHj1i)5nR*fM8h_bzUSX9bk+m>zQS zSzR6$xuSS4R$^jO>q0Ze=OeJW@cU8pa=Jebwjg(Gw`$rzr6TtROxg1ll`_v>RgGNr0$b>2HHw})F&PfuINh@`)f6i4Gl54kL~R zZ9td7d2-VW^L|u-Xlt%b?-tw-W3b(502EZKG76&65T+lR+2<7zdS&`=D=JGnWrE8kf0{?wbRYRSPFO?VdcP z4p;~qeM$x``{$+4!&ehTdNz}>uy+ zziH}A_o`1D4p8lXBZ=`V_S!%hT64d-Pib|tJ?x#mlh|$eW*0DYpS$X#u6jUP8>&lG zjl3H+xOEQT@Emq5-gTP%b<1_lr$%cm5!5s4sD(6?O=?SC_>QHwDHHp$zM7XmWxW64 zJ?Bh)*KCZ&3bo_8eN6$BG3LE>ezxH06ePr_&nUe{L7noMFYHHg+Mn|p6%ohO+`7VF zT#{vLB@C6*p}%>YUd&SM`8<|Ek%{1lg}actLZ#O+jlEV}n*6!Kzsa-vlGt(?*gv&E zjmrY_8z&PuYS%v)fJ(IW3*2r**VR$G?pr8*;?j@kbX2=ueSDBfWNL~c&3Ly(J_vh7 z`zpsPhp&HdwtrFworHtWM1l3s?zDO9jgubqt}jO~84~}D55ve*wo?w{rC5xz3iCOu zT6V80<^1SOyKr4v(d@BCxrUu;>A@$4mah#<@wsFx=0d!SyLl|bpVXMkw5>(ToQ|s@ zGam(xS!bsQgU?v?$p%5Q{j4EuIhN}E4&yP7)40ElR^Wd zWk(oY@bvY66)AUTQsZMW+1vlTN?3eD%2+VQ1*6w$#OfBb&sf$Rj0nz=kk~ z1okiVWxupdw{fDrDT`WJw~E9&DjV)^GM(+pITX+WMC8@b#dYNu@E6Fo)UB+go1#cE zli~Q^1 zK7htxdS!GguHO&|s9Y(*9DDJvqxnbPGom9R*AK2zP8N+$-17{qXPE^WKbn4$m%+gp zRK}kJn2#69<<}jut~bN>$1OM4g(&{-cgEKdCTYF-76m8>(TS_>XG8 zt)%g-)0UaZMtoIhCX?rN@zrbIhU4pYv~dhQ>!8~tmQaQ8|r%kHU6rmZ#g{+Xmm)J`qd~cQA zaLvECSmbzZk=U{(g(W5Z59Q?2h(a&25&UI7veKd+A%jnd!Pj0GOrpk?nkma)F|h_m zu9$_DNjF4-8gdMu8%c^VS`Fx|>&1v$FH%i>6|cxcko*lQ`@OjtlD&2 zhzLPDqp7%AI#l}m*yOzL?ec=3Rc(LPv}-*Xi_=l2+geB&acjBar!7mAy8Xk(h_>2m zX`tl6HHuKpC#kDDD!W)yk zp!cn3f*KF3cUZojEt@Z~6;PfTbr4NoED}0S@gI<91%cZ$kcq>(R%0$*(kTN70t(N3P;pDB{d=jJ z4WZNyXyC+v>+4otT%1|I&b;v}63d*e0!NXrSUk${{-8>U_)rY9_3pe2ikUYiYi$w+ z7`JPyUda}-g@2Q1<~!6=lAOhEQfS+8kdIu~X%Dyh`~5*BMvE|6mk=drmkW{QtWa@{ zXcPm&*XIy@CT?1j0m+C44n%TBAuW3aEh+%R_Jk-SjLz+OR9z*EUGuaX-{SIN9NDD%7Fuu$g+Wb@saC0X_oLr6TYVTWZA%|W9t zIqZ`Y$gpetX3HQ*8CdxOl?lAVKK%t7QNUj${5CCp|qEL}&r zISL^QI03>pZfYjj2)G!sGgCa7%*u2YffV~3mDyLBIS51egG$l_L|l>xMbjkhA(KxL zi6}SXK}D|Cw4Ib>86A-v`Tt&fM82Mlne4>!T3%P!(&MiY$aL~9^~{96Q4=>)`Pu8( z(DcwBh7!06|M$7bzt$<~76l7H>UlK|C!m%svjbJi_kWr^E_X=`_~ljtfbx(GM+O*K zH_yKoUcb&3{B(#Izg2QH+_YU2fk8-W(4<@0q@%r}~HDUB3KC zD1B1vA0o-1)4Hg?To=cyKnY8--d$_hljMOLgNHi!e+4QB)%fiXX4s;4?Q5e=>MlN5 z9Eex9&+DU4c!-BY)O$AY7QwF2*PE+f??Yhvxrr)Y+vN0%9KF`5-6Y+z{jyca zmei!7+FE+mP4n9)``)v2s&e+MPdnmI$Gvm&kG(rwQWJ*4Fia8z0mb+JueR-!HkJXu zd{j*Mh#XS6*QA10 zLOQ|{GwmO(v!=o?e`jAfP<^ew{(E1&;%q6!YdWx!*L9foRfm-KRr4Bm;V**#HrqxI zI>Hp=fzEIywe7N8)`b~ZNA(dmHVA zjMBz3A)=;0+NC}R^L2~YW~OU@OFK77z~(MJ1+NBR9nd$X>H$~ooHRyY9o5(K`?{h) z_-YS<&6lt)f09cz*B1uy^_Tj*y0B}ft>$$k4DqA2h;y*U*(!K706PPZ##9erPMw3g z53vF3sJ@=x*S4p3jse7zE;Wd(YbzP=Lzg4c1LM+2}bL|6Hc4(Q$O0(7Q;qP{Nw zinhCB6PRM3Oaw7LwkfZ9jU%)C6u<&lhZsOS#~32}5DT!5R9_896yIRp=o8&mj*flW zLoBFcWy z^?x9s?4V13(pO}1&A;<7FJ`(f!f$?X^_|Ex( zWhdcP2oFvox@3o&u)JA*!z* z;Aot4$Au=J3^b3_9>v(zkj&DYU^jxfbs3=3F&pa(C0FYd*VQNG^arQYA= zAotJ0CGkolfK_wqA{ZZsmm=VtanJ-&JL9 z&QsOb>Z>@q6tIHVJYb!QAOl#3@HH_+`x=HMUIReRN=&pB_S#zcbYC}O+RnOb-<|0T zxT>$!S8;SXU^TBASi!5ZfYb$6`4FE9><~m$iSqvKRzBTVzxOQXA!BQ61_<>Vx=&Tr zS8;SXUZj zR`4op6bq~ZArjb6;kDe2c+v9{sNc^0#YJXSU%$0?_bLp+Pyj~b?av@peE+BIAUKqg zXsu}UoGnWr%g4ni?$p;Quv1>w1h)BUi^!!h{XILSIY&+sv%@7(Juryu!8UYw2lV^SQq0=m{`7(x+EOPk?WM zoz#7I4{$6YuK?Q!c?H-;$SVK<000000000009?cyp$)rpye4t100000NkvXXu0mjf Ds7LFc literal 0 HcmV?d00001 diff --git a/crates/bevy_image/src/texture_atlas.rs b/crates/bevy_image/src/texture_atlas.rs index e9cf114ae27e4..57aa0c379e6ac 100644 --- a/crates/bevy_image/src/texture_atlas.rs +++ b/crates/bevy_image/src/texture_atlas.rs @@ -182,8 +182,12 @@ impl TextureAtlasLayout { /// - [`animated sprite sheet example`](https://github.com/bevyengine/bevy/blob/latest/examples/2d/sprite_sheet.rs) /// - [`sprite animation event example`](https://github.com/bevyengine/bevy/blob/latest/examples/2d/sprite_animation.rs) /// - [`texture atlas example`](https://github.com/bevyengine/bevy/blob/latest/examples/2d/texture_atlas.rs) -#[derive(Default, Debug, Clone)] -#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Default, Debug))] +#[derive(Default, Debug, Clone, PartialEq, Eq, Hash)] +#[cfg_attr( + feature = "bevy_reflect", + derive(Reflect), + reflect(Default, Debug, PartialEq, Hash) +)] pub struct TextureAtlas { /// Texture atlas layout handle pub layout: Handle, diff --git a/crates/bevy_winit/src/cursor.rs b/crates/bevy_winit/src/cursor.rs index c2a8139ef8458..1dbb66e6368b0 100644 --- a/crates/bevy_winit/src/cursor.rs +++ b/crates/bevy_winit/src/cursor.rs @@ -6,6 +6,10 @@ use crate::{ }; #[cfg(feature = "custom_cursor")] use crate::{ + custom_cursor::{ + calculate_effective_rect, extract_and_transform_rgba_pixels, extract_rgba_pixels, + CustomCursorPlugin, + }, state::{CustomCursorCache, CustomCursorCacheKey}, WinitCustomCursor, }; @@ -25,21 +29,21 @@ use bevy_ecs::{ world::{OnRemove, Ref}, }; #[cfg(feature = "custom_cursor")] -use bevy_image::Image; +use bevy_image::{Image, TextureAtlas, TextureAtlasLayout}; +#[cfg(feature = "custom_cursor")] +use bevy_math::URect; use bevy_reflect::{std_traits::ReflectDefault, Reflect}; use bevy_utils::HashSet; use bevy_window::{SystemCursorIcon, Window}; #[cfg(feature = "custom_cursor")] use tracing::warn; -#[cfg(feature = "custom_cursor")] -use wgpu_types::TextureFormat; pub(crate) struct CursorPlugin; impl Plugin for CursorPlugin { fn build(&self, app: &mut App) { #[cfg(feature = "custom_cursor")] - app.init_resource::(); + app.add_plugins(CustomCursorPlugin); app.register_type::() .add_systems(Last, update_cursors); @@ -87,6 +91,19 @@ pub enum CustomCursor { /// The image must be in 8 bit int or 32 bit float rgba. PNG images /// work well for this. handle: Handle, + /// The (optional) texture atlas used to render the image. + texture_atlas: Option, + /// Whether the image should be flipped along its x-axis. + flip_x: bool, + /// Whether the image should be flipped along its y-axis. + flip_y: bool, + /// An optional rectangle representing the region of the image to + /// render, instead of rendering the full image. This is an easy one-off + /// alternative to using a [`TextureAtlas`]. + /// + /// When used with a [`TextureAtlas`], the rect is offset by the atlas's + /// minimal (top-left) corner position. + rect: Option, /// X and Y coordinates of the hotspot in pixels. The hotspot must be /// within the image bounds. hotspot: (u16, u16), @@ -108,6 +125,7 @@ fn update_cursors( windows: Query<(Entity, Ref), With>, #[cfg(feature = "custom_cursor")] cursor_cache: Res, #[cfg(feature = "custom_cursor")] images: Res>, + #[cfg(feature = "custom_cursor")] texture_atlases: Res>, mut queue: Local>, ) { for (entity, cursor) in windows.iter() { @@ -117,8 +135,22 @@ fn update_cursors( let cursor_source = match cursor.as_ref() { #[cfg(feature = "custom_cursor")] - CursorIcon::Custom(CustomCursor::Image { handle, hotspot }) => { - let cache_key = CustomCursorCacheKey::Asset(handle.id()); + CursorIcon::Custom(CustomCursor::Image { + handle, + texture_atlas, + flip_x, + flip_y, + rect, + hotspot, + }) => { + let cache_key = CustomCursorCacheKey::Image { + id: handle.id(), + texture_atlas_layout_id: texture_atlas.as_ref().map(|a| a.layout.id()), + texture_atlas_index: texture_atlas.as_ref().map(|a| a.index), + flip_x: *flip_x, + flip_y: *flip_y, + rect: *rect, + }; if cursor_cache.0.contains_key(&cache_key) { CursorSource::CustomCached(cache_key) @@ -130,17 +162,25 @@ fn update_cursors( queue.insert(entity); continue; }; - let Some(rgba) = image_to_rgba_pixels(image) else { + + let (rect, needs_sub_image) = + calculate_effective_rect(&texture_atlases, image, texture_atlas, rect); + + let maybe_rgba = if *flip_x || *flip_y || needs_sub_image { + extract_and_transform_rgba_pixels(image, *flip_x, *flip_y, rect) + } else { + extract_rgba_pixels(image) + }; + + let Some(rgba) = maybe_rgba else { warn!("Cursor image {handle:?} not accepted because it's not rgba8 or rgba32float format"); continue; }; - let width = image.texture_descriptor.size.width; - let height = image.texture_descriptor.size.height; let source = match WinitCustomCursor::from_rgba( rgba, - width as u16, - height as u16, + rect.width() as u16, + rect.height() as u16, hotspot.0, hotspot.1, ) { @@ -190,28 +230,3 @@ fn on_remove_cursor_icon(trigger: Trigger, mut commands: C convert_system_cursor_icon(SystemCursorIcon::Default), )))); } - -#[cfg(feature = "custom_cursor")] -/// Returns the image data as a `Vec`. -/// Only supports rgba8 and rgba32float formats. -fn image_to_rgba_pixels(image: &Image) -> Option> { - match image.texture_descriptor.format { - TextureFormat::Rgba8Unorm - | TextureFormat::Rgba8UnormSrgb - | TextureFormat::Rgba8Snorm - | TextureFormat::Rgba8Uint - | TextureFormat::Rgba8Sint => Some(image.data.clone()), - TextureFormat::Rgba32Float => Some( - image - .data - .chunks(4) - .map(|chunk| { - let chunk = chunk.try_into().unwrap(); - let num = bytemuck::cast_ref::<[u8; 4], f32>(chunk); - (num * 255.0) as u8 - }) - .collect(), - ), - _ => None, - } -} diff --git a/crates/bevy_winit/src/custom_cursor.rs b/crates/bevy_winit/src/custom_cursor.rs new file mode 100644 index 0000000000000..6776c59ad798f --- /dev/null +++ b/crates/bevy_winit/src/custom_cursor.rs @@ -0,0 +1,490 @@ +use bevy_app::{App, Plugin}; +use bevy_asset::Assets; +use bevy_image::{Image, TextureAtlas, TextureAtlasLayout, TextureAtlasPlugin}; +use bevy_math::{ops, Rect, URect, UVec2, Vec2}; +use wgpu_types::TextureFormat; + +use crate::state::CustomCursorCache; + +/// Adds support for custom cursors. +pub(crate) struct CustomCursorPlugin; + +impl Plugin for CustomCursorPlugin { + fn build(&self, app: &mut App) { + if !app.is_plugin_added::() { + app.add_plugins(TextureAtlasPlugin); + } + + app.init_resource::(); + } +} + +/// Determines the effective rect and returns it along with a flag to indicate +/// whether a sub-image operation is needed. The flag allows the caller to +/// determine whether the image data needs a sub-image extracted from it. Note: +/// To avoid lossy comparisons between [`Rect`] and [`URect`], the flag is +/// always set to `true` when a [`TextureAtlas`] is used. +#[inline(always)] +pub(crate) fn calculate_effective_rect( + texture_atlas_layouts: &Assets, + image: &Image, + texture_atlas: &Option, + rect: &Option, +) -> (Rect, bool) { + let atlas_rect = texture_atlas + .as_ref() + .and_then(|s| s.texture_rect(texture_atlas_layouts)) + .map(|r| r.as_rect()); + + match (atlas_rect, rect) { + (None, None) => ( + Rect { + min: Vec2::ZERO, + max: Vec2::new( + image.texture_descriptor.size.width as f32, + image.texture_descriptor.size.height as f32, + ), + }, + false, + ), + (None, Some(image_rect)) => ( + image_rect.as_rect(), + image_rect + != &URect { + min: UVec2::ZERO, + max: UVec2::new( + image.texture_descriptor.size.width, + image.texture_descriptor.size.height, + ), + }, + ), + (Some(atlas_rect), None) => (atlas_rect, true), + (Some(atlas_rect), Some(image_rect)) => ( + { + let mut image_rect = image_rect.as_rect(); + image_rect.min += atlas_rect.min; + image_rect.max += atlas_rect.min; + image_rect + }, + true, + ), + } +} + +/// Extracts the RGBA pixel data from `image`, converting it if necessary. +/// +/// Only supports rgba8 and rgba32float formats. +pub(crate) fn extract_rgba_pixels(image: &Image) -> Option> { + match image.texture_descriptor.format { + TextureFormat::Rgba8Unorm + | TextureFormat::Rgba8UnormSrgb + | TextureFormat::Rgba8Snorm + | TextureFormat::Rgba8Uint + | TextureFormat::Rgba8Sint => Some(image.data.clone()), + TextureFormat::Rgba32Float => Some( + image + .data + .chunks(4) + .map(|chunk| { + let chunk = chunk.try_into().unwrap(); + let num = bytemuck::cast_ref::<[u8; 4], f32>(chunk); + ops::round(num.clamp(0.0, 1.0) * 255.0) as u8 + }) + .collect(), + ), + _ => None, + } +} + +/// Returns the `image` data as a `Vec` for the specified sub-region. +/// +/// The image is flipped along the x and y axes if `flip_x` and `flip_y` are +/// `true`, respectively. +/// +/// Only supports rgba8 and rgba32float formats. +pub(crate) fn extract_and_transform_rgba_pixels( + image: &Image, + flip_x: bool, + flip_y: bool, + rect: Rect, +) -> Option> { + let image_data = extract_rgba_pixels(image)?; + + let width = rect.width() as usize; + let height = rect.height() as usize; + let mut sub_image_data = Vec::with_capacity(width * height * 4); // assuming 4 bytes per pixel (RGBA8) + + for y in 0..height { + for x in 0..width { + let src_x = if flip_x { width - 1 - x } else { x }; + let src_y = if flip_y { height - 1 - y } else { y }; + let index = ((rect.min.y as usize + src_y) + * image.texture_descriptor.size.width as usize + + (rect.min.x as usize + src_x)) + * 4; + sub_image_data.extend_from_slice(&image_data[index..index + 4]); + } + } + + Some(sub_image_data) +} + +#[cfg(test)] +mod tests { + use bevy_app::App; + use bevy_asset::RenderAssetUsages; + use bevy_image::Image; + use bevy_math::Rect; + use bevy_math::Vec2; + use wgpu_types::{Extent3d, TextureDimension}; + + use super::*; + + fn create_image_rgba8(data: &[u8]) -> Image { + Image::new( + Extent3d { + width: 3, + height: 3, + depth_or_array_layers: 1, + }, + TextureDimension::D2, + data.to_vec(), + TextureFormat::Rgba8UnormSrgb, + RenderAssetUsages::default(), + ) + } + + macro_rules! test_calculate_effective_rect { + ($name:ident, $use_texture_atlas:expr, $rect:expr, $expected_rect:expr, $expected_needs_sub_image:expr) => { + #[test] + fn $name() { + let mut app = App::new(); + let mut texture_atlas_layout_assets = Assets::::default(); + + // Create a simple 3x3 texture atlas layout for the test cases + // that use a texture atlas. In the future we could adjust the + // test cases to use different texture atlas layouts. + let layout = TextureAtlasLayout::from_grid(UVec2::new(3, 3), 1, 1, None, None); + let layout_handle = texture_atlas_layout_assets.add(layout); + + app.insert_resource(texture_atlas_layout_assets); + + let texture_atlases = app + .world() + .get_resource::>() + .unwrap(); + + let image = create_image_rgba8(&[0; 3 * 3 * 4]); // 3x3 image + + let texture_atlas = if $use_texture_atlas { + Some(TextureAtlas::from(layout_handle)) + } else { + None + }; + + let rect = $rect; + + let (result_rect, needs_sub_image) = + calculate_effective_rect(&texture_atlases, &image, &texture_atlas, &rect); + + assert_eq!(result_rect, $expected_rect); + assert_eq!(needs_sub_image, $expected_needs_sub_image); + } + }; + } + + test_calculate_effective_rect!( + no_texture_atlas_no_rect, + false, + None, + Rect { + min: Vec2::ZERO, + max: Vec2::new(3.0, 3.0) + }, + false + ); + + test_calculate_effective_rect!( + no_texture_atlas_with_partial_rect, + false, + Some(URect { + min: UVec2::new(1, 1), + max: UVec2::new(3, 3) + }), + Rect { + min: Vec2::new(1.0, 1.0), + max: Vec2::new(3.0, 3.0) + }, + true + ); + + test_calculate_effective_rect!( + no_texture_atlas_with_full_rect, + false, + Some(URect { + min: UVec2::ZERO, + max: UVec2::new(3, 3) + }), + Rect { + min: Vec2::ZERO, + max: Vec2::new(3.0, 3.0) + }, + false + ); + + test_calculate_effective_rect!( + texture_atlas_no_rect, + true, + None, + Rect { + min: Vec2::ZERO, + max: Vec2::new(3.0, 3.0) + }, + true // always needs sub-image to avoid comparing Rect against URect + ); + + test_calculate_effective_rect!( + texture_atlas_rect, + true, + Some(URect { + min: UVec2::ZERO, + max: UVec2::new(3, 3) + }), + Rect { + min: Vec2::new(0.0, 0.0), + max: Vec2::new(3.0, 3.0) + }, + true // always needs sub-image to avoid comparing Rect against URect + ); + + fn create_image_rgba32float(data: &[u8]) -> Image { + let float_data: Vec = data + .chunks(4) + .flat_map(|chunk| { + chunk + .iter() + .map(|&x| x as f32 / 255.0) // convert each channel to f32 + .collect::>() + }) + .collect(); + + Image::new( + Extent3d { + width: 3, + height: 3, + depth_or_array_layers: 1, + }, + TextureDimension::D2, + bytemuck::cast_slice(&float_data).to_vec(), + TextureFormat::Rgba32Float, + RenderAssetUsages::default(), + ) + } + + macro_rules! test_extract_and_transform_rgba_pixels { + ($name:ident, $flip_x:expr, $flip_y:expr, $rect:expr, $expected:expr) => { + #[test] + fn $name() { + let image_data: &[u8] = &[ + // Row 1: Red, Green, Blue + 255, 0, 0, 255, // Red + 0, 255, 0, 255, // Green + 0, 0, 255, 255, // Blue + // Row 2: Yellow, Cyan, Magenta + 255, 255, 0, 255, // Yellow + 0, 255, 255, 255, // Cyan + 255, 0, 255, 255, // Magenta + // Row 3: White, Gray, Black + 255, 255, 255, 255, // White + 128, 128, 128, 255, // Gray + 0, 0, 0, 255, // Black + ]; + + // RGBA8 test + { + let image = create_image_rgba8(image_data); + let rect = $rect; + let result = extract_and_transform_rgba_pixels(&image, $flip_x, $flip_y, rect); + assert_eq!(result, Some($expected.to_vec())); + } + + // RGBA32Float test + { + let image = create_image_rgba32float(image_data); + let rect = $rect; + let result = extract_and_transform_rgba_pixels(&image, $flip_x, $flip_y, rect); + assert_eq!(result, Some($expected.to_vec())); + } + } + }; + } + + test_extract_and_transform_rgba_pixels!( + no_flip_full_image, + false, + false, + Rect { + min: Vec2::ZERO, + max: Vec2::new(3.0, 3.0) + }, + [ + // Row 1: Red, Green, Blue + 255, 0, 0, 255, // Red + 0, 255, 0, 255, // Green + 0, 0, 255, 255, // Blue + // Row 2: Yellow, Cyan, Magenta + 255, 255, 0, 255, // Yellow + 0, 255, 255, 255, // Cyan + 255, 0, 255, 255, // Magenta + // Row 3: White, Gray, Black + 255, 255, 255, 255, // White + 128, 128, 128, 255, // Gray + 0, 0, 0, 255, // Black + ] + ); + + test_extract_and_transform_rgba_pixels!( + flip_x_full_image, + true, + false, + Rect { + min: Vec2::ZERO, + max: Vec2::new(3.0, 3.0) + }, + [ + // Row 1 flipped: Blue, Green, Red + 0, 0, 255, 255, // Blue + 0, 255, 0, 255, // Green + 255, 0, 0, 255, // Red + // Row 2 flipped: Magenta, Cyan, Yellow + 255, 0, 255, 255, // Magenta + 0, 255, 255, 255, // Cyan + 255, 255, 0, 255, // Yellow + // Row 3 flipped: Black, Gray, White + 0, 0, 0, 255, // Black + 128, 128, 128, 255, // Gray + 255, 255, 255, 255, // White + ] + ); + + test_extract_and_transform_rgba_pixels!( + flip_y_full_image, + false, + true, + Rect { + min: Vec2::ZERO, + max: Vec2::new(3.0, 3.0) + }, + [ + // Row 3: White, Gray, Black + 255, 255, 255, 255, // White + 128, 128, 128, 255, // Gray + 0, 0, 0, 255, // Black + // Row 2: Yellow, Cyan, Magenta + 255, 255, 0, 255, // Yellow + 0, 255, 255, 255, // Cyan + 255, 0, 255, 255, // Magenta + // Row 1: Red, Green, Blue + 255, 0, 0, 255, // Red + 0, 255, 0, 255, // Green + 0, 0, 255, 255, // Blue + ] + ); + + test_extract_and_transform_rgba_pixels!( + flip_both_full_image, + true, + true, + Rect { + min: Vec2::ZERO, + max: Vec2::new(3.0, 3.0) + }, + [ + // Row 3 flipped: Black, Gray, White + 0, 0, 0, 255, // Black + 128, 128, 128, 255, // Gray + 255, 255, 255, 255, // White + // Row 2 flipped: Magenta, Cyan, Yellow + 255, 0, 255, 255, // Magenta + 0, 255, 255, 255, // Cyan + 255, 255, 0, 255, // Yellow + // Row 1 flipped: Blue, Green, Red + 0, 0, 255, 255, // Blue + 0, 255, 0, 255, // Green + 255, 0, 0, 255, // Red + ] + ); + + test_extract_and_transform_rgba_pixels!( + no_flip_rect, + false, + false, + Rect { + min: Vec2::new(1.0, 1.0), + max: Vec2::new(3.0, 3.0) + }, + [ + // Only includes part of the original image (sub-rectangle) + // Row 2, columns 2-3: Cyan, Magenta + 0, 255, 255, 255, // Cyan + 255, 0, 255, 255, // Magenta + // Row 3, columns 2-3: Gray, Black + 128, 128, 128, 255, // Gray + 0, 0, 0, 255, // Black + ] + ); + + test_extract_and_transform_rgba_pixels!( + flip_x_rect, + true, + false, + Rect { + min: Vec2::new(1.0, 1.0), + max: Vec2::new(3.0, 3.0) + }, + [ + // Row 2 flipped: Magenta, Cyan + 255, 0, 255, 255, // Magenta + 0, 255, 255, 255, // Cyan + // Row 3 flipped: Black, Gray + 0, 0, 0, 255, // Black + 128, 128, 128, 255, // Gray + ] + ); + + test_extract_and_transform_rgba_pixels!( + flip_y_rect, + false, + true, + Rect { + min: Vec2::new(1.0, 1.0), + max: Vec2::new(3.0, 3.0) + }, + [ + // Row 3 first: Gray, Black + 128, 128, 128, 255, // Gray + 0, 0, 0, 255, // Black + // Row 2 second: Cyan, Magenta + 0, 255, 255, 255, // Cyan + 255, 0, 255, 255, // Magenta + ] + ); + + test_extract_and_transform_rgba_pixels!( + flip_both_rect, + true, + true, + Rect { + min: Vec2::new(1.0, 1.0), + max: Vec2::new(3.0, 3.0) + }, + [ + // Row 3 flipped: Black, Gray + 0, 0, 0, 255, // Black + 128, 128, 128, 255, // Gray + // Row 2 flipped: Magenta, Cyan + 255, 0, 255, 255, // Magenta + 0, 255, 255, 255, // Cyan + ] + ); +} diff --git a/crates/bevy_winit/src/lib.rs b/crates/bevy_winit/src/lib.rs index cac00ecbf7712..123f2c123c172 100644 --- a/crates/bevy_winit/src/lib.rs +++ b/crates/bevy_winit/src/lib.rs @@ -50,6 +50,8 @@ use crate::{ pub mod accessibility; mod converters; pub mod cursor; +#[cfg(feature = "custom_cursor")] +mod custom_cursor; mod state; mod system; mod winit_config; diff --git a/crates/bevy_winit/src/state.rs b/crates/bevy_winit/src/state.rs index e8cfd0a691054..370b4a87e37c0 100644 --- a/crates/bevy_winit/src/state.rs +++ b/crates/bevy_winit/src/state.rs @@ -11,12 +11,14 @@ use bevy_ecs::{ world::FromWorld, }; #[cfg(feature = "custom_cursor")] -use bevy_image::Image; +use bevy_image::{Image, TextureAtlasLayout}; use bevy_input::{ gestures::*, mouse::{MouseButtonInput, MouseMotion, MouseScrollUnit, MouseWheel}, }; use bevy_log::{error, trace, warn}; +#[cfg(feature = "custom_cursor")] +use bevy_math::URect; use bevy_math::{ivec2, DVec2, Vec2}; #[cfg(not(target_arch = "wasm32"))] use bevy_tasks::tick_global_task_pools_on_main_thread; @@ -150,10 +152,17 @@ impl WinitAppRunnerState { /// Identifiers for custom cursors used in caching. #[derive(Debug, Clone, Hash, PartialEq, Eq)] pub enum CustomCursorCacheKey { - /// An `AssetId` to a cursor. - Asset(AssetId), + /// A custom cursor with an image. + Image { + id: AssetId, + texture_atlas_layout_id: Option>, + texture_atlas_index: Option, + flip_x: bool, + flip_y: bool, + rect: Option, + }, #[cfg(all(target_family = "wasm", target_os = "unknown"))] - /// An URL to a cursor. + /// A custom cursor with a URL. Url(String), } diff --git a/examples/README.md b/examples/README.md index a39043e7c7833..aef50d96279a4 100644 --- a/examples/README.md +++ b/examples/README.md @@ -544,6 +544,7 @@ Example | Description Example | Description --- | --- [Clear Color](../examples/window/clear_color.rs) | Creates a solid color window +[Custom Cursor Image](../examples/window/custom_cursor_image.rs) | Demonstrates creating an animated custom cursor from an image [Custom User Event](../examples/window/custom_user_event.rs) | Handles custom user events within the event loop [Low Power](../examples/window/low_power.rs) | Demonstrates settings to reduce power use for bevy applications [Monitor info](../examples/window/monitor_info.rs) | Displays information about available monitors (displays). diff --git a/examples/window/custom_cursor_image.rs b/examples/window/custom_cursor_image.rs new file mode 100644 index 0000000000000..b63a90e2df0ea --- /dev/null +++ b/examples/window/custom_cursor_image.rs @@ -0,0 +1,228 @@ +//! Illustrates how to use a custom cursor image with a texture atlas and +//! animation. + +use std::time::Duration; + +use bevy::winit::cursor::CustomCursor; +use bevy::{prelude::*, winit::cursor::CursorIcon}; + +fn main() { + App::new() + .add_plugins(DefaultPlugins) + .add_systems( + Startup, + (setup_cursor_icon, setup_camera, setup_instructions), + ) + .add_systems( + Update, + ( + execute_animation, + toggle_texture_atlas, + toggle_flip_x, + toggle_flip_y, + cycle_rect, + ), + ) + .run(); +} + +fn setup_cursor_icon( + mut commands: Commands, + asset_server: Res, + mut texture_atlas_layouts: ResMut>, + window: Single>, +) { + let layout = + TextureAtlasLayout::from_grid(UVec2::splat(64), 20, 10, Some(UVec2::splat(5)), None); + let texture_atlas_layout = texture_atlas_layouts.add(layout); + + let animation_config = AnimationConfig::new(0, 199, 1, 4); + + commands.entity(*window).insert(( + CursorIcon::Custom(CustomCursor::Image { + // Image to use as the cursor. + handle: asset_server + .load("cursors/kenney_crosshairPack/Tilesheet/crosshairs_tilesheet_white.png"), + // Optional texture atlas allows you to pick a section of the image + // and animate it. + texture_atlas: Some(TextureAtlas { + layout: texture_atlas_layout.clone(), + index: animation_config.first_sprite_index, + }), + flip_x: false, + flip_y: false, + // Optional section of the image to use as the cursor. + rect: None, + // The hotspot is the point in the cursor image that will be + // positioned at the mouse cursor's position. + hotspot: (0, 0), + }), + animation_config, + )); +} + +fn setup_camera(mut commands: Commands) { + commands.spawn(Camera3d::default()); +} + +fn setup_instructions(mut commands: Commands) { + commands.spawn(( + Text::new( + "Press T to toggle the cursor's `texture_atlas`.\n +Press X to toggle the cursor's `flip_x` setting.\n +Press Y to toggle the cursor's `flip_y` setting.\n +Press C to cycle through the sections of the cursor's image using `rect`.", + ), + Node { + position_type: PositionType::Absolute, + bottom: Val::Px(12.0), + left: Val::Px(12.0), + ..default() + }, + )); +} + +#[derive(Component)] +struct AnimationConfig { + first_sprite_index: usize, + last_sprite_index: usize, + increment: usize, + fps: u8, + frame_timer: Timer, +} + +impl AnimationConfig { + fn new(first: usize, last: usize, increment: usize, fps: u8) -> Self { + Self { + first_sprite_index: first, + last_sprite_index: last, + increment, + fps, + frame_timer: Self::timer_from_fps(fps), + } + } + + fn timer_from_fps(fps: u8) -> Timer { + Timer::new(Duration::from_secs_f32(1.0 / (fps as f32)), TimerMode::Once) + } +} + +/// This system loops through all the sprites in the [`CursorIcon`]'s +/// [`TextureAtlas`], from [`AnimationConfig`]'s `first_sprite_index` to +/// `last_sprite_index`. +fn execute_animation(time: Res