From 6620dd9f4df8901f93f1bcbc41a6822ed4f8d2cc Mon Sep 17 00:00:00 2001 From: james7132 Date: Sat, 15 Jan 2022 00:41:39 -0800 Subject: [PATCH 01/60] Crude initial implementation --- crates/bevy_ecs/Cargo.toml | 1 + crates/bevy_ecs/src/archetype.rs | 22 +++++++---- crates/bevy_ecs/src/component.rs | 36 ++++++++++------- crates/bevy_ecs/src/entity/mod.rs | 3 ++ crates/bevy_ecs/src/lib.rs | 3 ++ crates/bevy_ecs/src/storage/sparse_set.rs | 47 +++++++++++++++-------- 6 files changed, 75 insertions(+), 37 deletions(-) diff --git a/crates/bevy_ecs/Cargo.toml b/crates/bevy_ecs/Cargo.toml index 86e379dfb2216..fc3c8a7f543b1 100644 --- a/crates/bevy_ecs/Cargo.toml +++ b/crates/bevy_ecs/Cargo.toml @@ -24,6 +24,7 @@ fixedbitset = "0.4" fxhash = "0.2" thiserror = "1.0" downcast-rs = "1.2" +static_assertions = "1.1" serde = "1" [dev-dependencies] diff --git a/crates/bevy_ecs/src/archetype.rs b/crates/bevy_ecs/src/archetype.rs index ca9d62cb46314..883f104354cd7 100644 --- a/crates/bevy_ecs/src/archetype.rs +++ b/crates/bevy_ecs/src/archetype.rs @@ -11,6 +11,7 @@ use std::{ borrow::Cow, collections::HashMap, hash::Hash, + num::NonZeroUsize, ops::{Index, IndexMut}, }; @@ -336,28 +337,35 @@ pub struct ArchetypeIdentity { } #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -pub struct ArchetypeComponentId(usize); +pub struct ArchetypeComponentId(NonZeroUsize); + +assert_eq_size!(ArchetypeComponentId, Option); impl ArchetypeComponentId { #[inline] - pub const fn new(index: usize) -> Self { - Self(index) + pub const unsafe fn new_unchecked(index: usize) -> Self { + Self(NonZeroUsize::new_unchecked(index)) + } + + #[inline] + pub fn new(index: usize) -> Self { + Self(NonZeroUsize::new(index).unwrap()) } #[inline] pub fn index(self) -> usize { - self.0 + self.0.get() } } impl SparseSetIndex for ArchetypeComponentId { #[inline] fn sparse_set_index(&self) -> usize { - self.0 + self.index() } fn get_sparse_set_index(value: usize) -> Self { - Self(value) + Self::new(value) } } @@ -487,7 +495,7 @@ impl Archetypes { let archetypes = &mut self.archetypes; let archetype_component_count = &mut self.archetype_component_count; let mut next_archetype_component_id = move || { - let id = ArchetypeComponentId(*archetype_component_count); + let id = ArchetypeComponentId::new(*archetype_component_count); *archetype_component_count += 1; id }; diff --git a/crates/bevy_ecs/src/component.rs b/crates/bevy_ecs/src/component.rs index 69b3254dea8cb..0a5f956d05d2b 100644 --- a/crates/bevy_ecs/src/component.rs +++ b/crates/bevy_ecs/src/component.rs @@ -8,6 +8,7 @@ pub use bevy_ecs_macros::Component; use std::{ alloc::Layout, any::{Any, TypeId}, + num::NonZeroUsize, }; use thiserror::Error; @@ -138,17 +139,24 @@ impl ComponentInfo { } #[derive(Debug, Copy, Clone, Hash, Ord, PartialOrd, Eq, PartialEq)] -pub struct ComponentId(usize); +pub struct ComponentId(NonZeroUsize); + +assert_eq_size!(ComponentId, Option); impl ComponentId { #[inline] - pub const fn new(index: usize) -> ComponentId { - ComponentId(index) + pub const unsafe fn new_unchecked(index: usize) -> ComponentId { + ComponentId(NonZeroUsize::new_unchecked(index)) + } + + #[inline] + pub fn new(index: usize) -> ComponentId { + ComponentId(NonZeroUsize::new(index).unwrap()) } #[inline] pub fn index(self) -> usize { - self.0 + self.0.get() } } @@ -159,7 +167,7 @@ impl SparseSetIndex for ComponentId { } fn get_sparse_set_index(value: usize) -> Self { - Self(value) + Self::new(value) } } @@ -262,14 +270,14 @@ impl Components { let index = self.indices.entry(type_id).or_insert_with(|| { let index = components.len(); let descriptor = ComponentDescriptor::new::(); - let info = ComponentInfo::new(ComponentId(index), descriptor); + let info = ComponentInfo::new(ComponentId::new(index), descriptor); if T::Storage::STORAGE_TYPE == StorageType::SparseSet { storages.sparse_sets.get_or_insert(&info); } components.push(info); index }); - ComponentId(*index) + ComponentId::new(*index) } #[inline] @@ -284,7 +292,7 @@ impl Components { #[inline] pub fn get_info(&self, id: ComponentId) -> Option<&ComponentInfo> { - self.components.get(id.0) + self.components.get(id.0.get()) } /// # Safety @@ -293,19 +301,21 @@ impl Components { #[inline] pub unsafe fn get_info_unchecked(&self, id: ComponentId) -> &ComponentInfo { debug_assert!(id.index() < self.components.len()); - self.components.get_unchecked(id.0) + self.components.get_unchecked(id.0.get()) } #[inline] pub fn get_id(&self, type_id: TypeId) -> Option { - self.indices.get(&type_id).map(|index| ComponentId(*index)) + self.indices + .get(&type_id) + .map(|index| ComponentId::new(*index)) } #[inline] pub fn get_resource_id(&self, type_id: TypeId) -> Option { self.resource_indices .get(&type_id) - .map(|index| ComponentId(*index)) + .map(|index| ComponentId::new(*index)) } #[inline] @@ -341,11 +351,11 @@ impl Components { let index = self.resource_indices.entry(type_id).or_insert_with(|| { let descriptor = func(); let index = components.len(); - components.push(ComponentInfo::new(ComponentId(index), descriptor)); + components.push(ComponentInfo::new(ComponentId::new(index), descriptor)); index }); - ComponentId(*index) + ComponentId::new(*index) } } diff --git a/crates/bevy_ecs/src/entity/mod.rs b/crates/bevy_ecs/src/entity/mod.rs index 75ee18ee49dfb..ca82b2c6c5453 100644 --- a/crates/bevy_ecs/src/entity/mod.rs +++ b/crates/bevy_ecs/src/entity/mod.rs @@ -50,6 +50,9 @@ pub struct Entity { pub(crate) id: u32, } +// TODO: Optimize memory usage of Option +// assert_size_eq!(Entity, Option); + pub enum AllocAtWithoutReplacement { Exists(EntityLocation), DidNotExist, diff --git a/crates/bevy_ecs/src/lib.rs b/crates/bevy_ecs/src/lib.rs index 30d70ca17efab..dc924303e12f7 100644 --- a/crates/bevy_ecs/src/lib.rs +++ b/crates/bevy_ecs/src/lib.rs @@ -1,5 +1,8 @@ #![doc = include_str!("../README.md")] +#[macro_use] +extern crate static_assertions; + pub mod archetype; pub mod bundle; pub mod change_detection; diff --git a/crates/bevy_ecs/src/storage/sparse_set.rs b/crates/bevy_ecs/src/storage/sparse_set.rs index 0b4efbe9bf339..52b907325e1ca 100644 --- a/crates/bevy_ecs/src/storage/sparse_set.rs +++ b/crates/bevy_ecs/src/storage/sparse_set.rs @@ -3,7 +3,11 @@ use crate::{ entity::Entity, storage::BlobVec, }; -use std::{cell::UnsafeCell, marker::PhantomData}; +use std::{ + cell::UnsafeCell, + marker::PhantomData, + num::{NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU8, NonZeroUsize}, +}; #[derive(Debug)] pub struct SparseArray { @@ -238,7 +242,7 @@ impl ComponentSparseSet { pub struct SparseSet { dense: Vec, indices: Vec, - sparse: SparseArray, + sparse: SparseArray, } impl Default for SparseSet { @@ -274,10 +278,11 @@ impl SparseSet { if let Some(dense_index) = self.sparse.get(index.clone()).cloned() { // SAFE: dense indices stored in self.sparse always exist unsafe { - *self.dense.get_unchecked_mut(dense_index) = value; + *self.dense.get_unchecked_mut(dense_index.get() - 1) = value; } } else { - self.sparse.insert(index.clone(), self.dense.len()); + let dense = NonZeroUsize::new(self.dense.len() + 1).unwrap(); + self.sparse.insert(index.clone(), dense); self.indices.push(index); self.dense.push(value); } @@ -308,15 +313,15 @@ impl SparseSet { pub fn get_or_insert_with(&mut self, index: I, func: impl FnOnce() -> V) -> &mut V { if let Some(dense_index) = self.sparse.get(index.clone()).cloned() { // SAFE: dense indices stored in self.sparse always exist - unsafe { self.dense.get_unchecked_mut(dense_index) } + unsafe { self.dense.get_unchecked_mut(dense_index.get() - 1) } } else { let value = func(); - let dense_index = self.dense.len(); + let dense_index = NonZeroUsize::new(self.dense.len() + 1).unwrap(); self.sparse.insert(index.clone(), dense_index); self.indices.push(index); self.dense.push(value); // SAFE: dense index was just populated above - unsafe { self.dense.get_unchecked_mut(dense_index) } + unsafe { self.dense.get_unchecked_mut(dense_index.get() - 1) } } } @@ -338,7 +343,7 @@ impl SparseSet { pub fn get(&self, index: I) -> Option<&V> { self.sparse.get(index).map(|dense_index| { // SAFE: if the sparse index points to something in the dense vec, it exists - unsafe { self.dense.get_unchecked(*dense_index) } + unsafe { self.dense.get_unchecked(dense_index.get() - 1) } }) } @@ -346,18 +351,19 @@ impl SparseSet { let dense = &mut self.dense; self.sparse.get(index).map(move |dense_index| { // SAFE: if the sparse index points to something in the dense vec, it exists - unsafe { dense.get_unchecked_mut(*dense_index) } + unsafe { dense.get_unchecked_mut(dense_index.get() - 1) } }) } pub fn remove(&mut self, index: I) -> Option { - self.sparse.remove(index).map(|dense_index| { + self.sparse.remove(index).map(|dense_idx| { + let dense_index = dense_idx.get() - 1; let is_last = dense_index == self.dense.len() - 1; let value = self.dense.swap_remove(dense_index); self.indices.swap_remove(dense_index); if !is_last { let swapped_index = self.indices[dense_index].clone(); - *self.sparse.get_mut(swapped_index).unwrap() = dense_index; + *self.sparse.get_mut(swapped_index).unwrap() = dense_idx; } value }) @@ -382,20 +388,27 @@ pub trait SparseSetIndex: Clone { } macro_rules! impl_sparse_set_index { - ($($ty:ty),+) => { - $(impl SparseSetIndex for $ty { + ($ty:ty, $underlying:ty) => { + impl SparseSetIndex for $ty { fn sparse_set_index(&self) -> usize { - *self as usize + self.get() as usize } fn get_sparse_set_index(value: usize) -> Self { - value as $ty + <$ty>::new(value as $underlying).unwrap() } - })* + } + + assert_eq_size!($ty, Option<$ty>); + assert_eq_size!($ty, $underlying); }; } -impl_sparse_set_index!(u8, u16, u32, u64, usize); +impl_sparse_set_index!(NonZeroU8, u8); +impl_sparse_set_index!(NonZeroU16, u16); +impl_sparse_set_index!(NonZeroU32, u32); +impl_sparse_set_index!(NonZeroU64, u64); +impl_sparse_set_index!(NonZeroUsize, usize); /// A collection of [`ComponentSparseSet`] storages, indexed by [`ComponentId`] /// From 37bd31f44d5f38e1177783b4ed5ef0690638f0a2 Mon Sep 17 00:00:00 2001 From: james7132 Date: Sat, 15 Jan 2022 01:02:57 -0800 Subject: [PATCH 02/60] Use nonmax instead. --- crates/bevy_ecs/Cargo.toml | 1 + crates/bevy_ecs/src/archetype.rs | 8 +++--- crates/bevy_ecs/src/component.rs | 8 +++--- crates/bevy_ecs/src/storage/sparse_set.rs | 35 +++++++++++------------ 4 files changed, 25 insertions(+), 27 deletions(-) diff --git a/crates/bevy_ecs/Cargo.toml b/crates/bevy_ecs/Cargo.toml index fc3c8a7f543b1..80b19520e9010 100644 --- a/crates/bevy_ecs/Cargo.toml +++ b/crates/bevy_ecs/Cargo.toml @@ -25,6 +25,7 @@ fxhash = "0.2" thiserror = "1.0" downcast-rs = "1.2" static_assertions = "1.1" +nonmax = "0.5" serde = "1" [dev-dependencies] diff --git a/crates/bevy_ecs/src/archetype.rs b/crates/bevy_ecs/src/archetype.rs index 883f104354cd7..cc5efef9d24cf 100644 --- a/crates/bevy_ecs/src/archetype.rs +++ b/crates/bevy_ecs/src/archetype.rs @@ -7,11 +7,11 @@ use crate::{ entity::{Entity, EntityLocation}, storage::{Column, SparseArray, SparseSet, SparseSetIndex, TableId}, }; +use nonmax::NonMaxUsize; use std::{ borrow::Cow, collections::HashMap, hash::Hash, - num::NonZeroUsize, ops::{Index, IndexMut}, }; @@ -337,19 +337,19 @@ pub struct ArchetypeIdentity { } #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -pub struct ArchetypeComponentId(NonZeroUsize); +pub struct ArchetypeComponentId(NonMaxUsize); assert_eq_size!(ArchetypeComponentId, Option); impl ArchetypeComponentId { #[inline] pub const unsafe fn new_unchecked(index: usize) -> Self { - Self(NonZeroUsize::new_unchecked(index)) + Self(NonMaxUsize::new_unchecked(index)) } #[inline] pub fn new(index: usize) -> Self { - Self(NonZeroUsize::new(index).unwrap()) + Self(NonMaxUsize::new(index).unwrap()) } #[inline] diff --git a/crates/bevy_ecs/src/component.rs b/crates/bevy_ecs/src/component.rs index 0a5f956d05d2b..d97c310ddbef6 100644 --- a/crates/bevy_ecs/src/component.rs +++ b/crates/bevy_ecs/src/component.rs @@ -5,10 +5,10 @@ use crate::{ system::Resource, }; pub use bevy_ecs_macros::Component; +use nonmax::NonMaxUsize; use std::{ alloc::Layout, any::{Any, TypeId}, - num::NonZeroUsize, }; use thiserror::Error; @@ -139,19 +139,19 @@ impl ComponentInfo { } #[derive(Debug, Copy, Clone, Hash, Ord, PartialOrd, Eq, PartialEq)] -pub struct ComponentId(NonZeroUsize); +pub struct ComponentId(NonMaxUsize); assert_eq_size!(ComponentId, Option); impl ComponentId { #[inline] pub const unsafe fn new_unchecked(index: usize) -> ComponentId { - ComponentId(NonZeroUsize::new_unchecked(index)) + ComponentId(NonMaxUsize::new_unchecked(index)) } #[inline] pub fn new(index: usize) -> ComponentId { - ComponentId(NonZeroUsize::new(index).unwrap()) + ComponentId(NonMaxUsize::new(index).unwrap()) } #[inline] diff --git a/crates/bevy_ecs/src/storage/sparse_set.rs b/crates/bevy_ecs/src/storage/sparse_set.rs index 52b907325e1ca..b1ebe287e1365 100644 --- a/crates/bevy_ecs/src/storage/sparse_set.rs +++ b/crates/bevy_ecs/src/storage/sparse_set.rs @@ -3,11 +3,8 @@ use crate::{ entity::Entity, storage::BlobVec, }; -use std::{ - cell::UnsafeCell, - marker::PhantomData, - num::{NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU8, NonZeroUsize}, -}; +use nonmax::{NonMaxU16, NonMaxU32, NonMaxU64, NonMaxU8, NonMaxUsize}; +use std::{cell::UnsafeCell, marker::PhantomData}; #[derive(Debug)] pub struct SparseArray { @@ -242,7 +239,7 @@ impl ComponentSparseSet { pub struct SparseSet { dense: Vec, indices: Vec, - sparse: SparseArray, + sparse: SparseArray, } impl Default for SparseSet { @@ -278,10 +275,10 @@ impl SparseSet { if let Some(dense_index) = self.sparse.get(index.clone()).cloned() { // SAFE: dense indices stored in self.sparse always exist unsafe { - *self.dense.get_unchecked_mut(dense_index.get() - 1) = value; + *self.dense.get_unchecked_mut(dense_index.get()) = value; } } else { - let dense = NonZeroUsize::new(self.dense.len() + 1).unwrap(); + let dense = NonMaxUsize::new(self.dense.len()).unwrap(); self.sparse.insert(index.clone(), dense); self.indices.push(index); self.dense.push(value); @@ -313,15 +310,15 @@ impl SparseSet { pub fn get_or_insert_with(&mut self, index: I, func: impl FnOnce() -> V) -> &mut V { if let Some(dense_index) = self.sparse.get(index.clone()).cloned() { // SAFE: dense indices stored in self.sparse always exist - unsafe { self.dense.get_unchecked_mut(dense_index.get() - 1) } + unsafe { self.dense.get_unchecked_mut(dense_index.get()) } } else { let value = func(); - let dense_index = NonZeroUsize::new(self.dense.len() + 1).unwrap(); + let dense_index = NonMaxUsize::new(self.dense.len()).unwrap(); self.sparse.insert(index.clone(), dense_index); self.indices.push(index); self.dense.push(value); // SAFE: dense index was just populated above - unsafe { self.dense.get_unchecked_mut(dense_index.get() - 1) } + unsafe { self.dense.get_unchecked_mut(dense_index.get()) } } } @@ -343,7 +340,7 @@ impl SparseSet { pub fn get(&self, index: I) -> Option<&V> { self.sparse.get(index).map(|dense_index| { // SAFE: if the sparse index points to something in the dense vec, it exists - unsafe { self.dense.get_unchecked(dense_index.get() - 1) } + unsafe { self.dense.get_unchecked(dense_index.get()) } }) } @@ -351,13 +348,13 @@ impl SparseSet { let dense = &mut self.dense; self.sparse.get(index).map(move |dense_index| { // SAFE: if the sparse index points to something in the dense vec, it exists - unsafe { dense.get_unchecked_mut(dense_index.get() - 1) } + unsafe { dense.get_unchecked_mut(dense_index.get()) } }) } pub fn remove(&mut self, index: I) -> Option { self.sparse.remove(index).map(|dense_idx| { - let dense_index = dense_idx.get() - 1; + let dense_index = dense_idx.get(); let is_last = dense_index == self.dense.len() - 1; let value = self.dense.swap_remove(dense_index); self.indices.swap_remove(dense_index); @@ -404,11 +401,11 @@ macro_rules! impl_sparse_set_index { }; } -impl_sparse_set_index!(NonZeroU8, u8); -impl_sparse_set_index!(NonZeroU16, u16); -impl_sparse_set_index!(NonZeroU32, u32); -impl_sparse_set_index!(NonZeroU64, u64); -impl_sparse_set_index!(NonZeroUsize, usize); +impl_sparse_set_index!(NonMaxU8, u8); +impl_sparse_set_index!(NonMaxU16, u16); +impl_sparse_set_index!(NonMaxU32, u32); +impl_sparse_set_index!(NonMaxU64, u64); +impl_sparse_set_index!(NonMaxUsize, usize); /// A collection of [`ComponentSparseSet`] storages, indexed by [`ComponentId`] /// From 28fe7a01723d61333d2acf27a002d8bad32654d5 Mon Sep 17 00:00:00 2001 From: james7132 Date: Sat, 15 Jan 2022 01:21:52 -0800 Subject: [PATCH 03/60] Implement it for BundleId --- crates/bevy_ecs/src/bundle.rs | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/crates/bevy_ecs/src/bundle.rs b/crates/bevy_ecs/src/bundle.rs index 3bf6881eb31d2..4ff12c54bb594 100644 --- a/crates/bevy_ecs/src/bundle.rs +++ b/crates/bevy_ecs/src/bundle.rs @@ -11,6 +11,7 @@ use crate::{ storage::{SparseSetIndex, SparseSets, Storages, Table}, }; use bevy_ecs_macros::all_tuples; +use nonmax::NonMaxUsize; use std::{any::TypeId, collections::HashMap}; /// An ordered collection of [`Component`]s. @@ -129,12 +130,24 @@ macro_rules! tuple_impl { all_tuples!(tuple_impl, 0, 15, C); #[derive(Debug, Clone, Copy)] -pub struct BundleId(usize); +pub struct BundleId(NonMaxUsize); + +assert_eq_size!(BundleId, Option); impl BundleId { + #[inline] + pub const unsafe fn new_unchecked(value: usize) -> Self { + Self(NonMaxUsize::new_unchecked(value)) + } + + #[inline] + pub fn new(value: usize) -> Self { + Self(NonMaxUsize::new(value).unwrap()) + } + #[inline] pub fn index(self) -> usize { - self.0 + self.0.get() } } @@ -145,7 +158,7 @@ impl SparseSetIndex for BundleId { } fn get_sparse_set_index(value: usize) -> Self { - Self(value) + Self::new(value) } } @@ -597,7 +610,7 @@ impl Bundles { let bundle_infos = &mut self.bundle_infos; let id = self.bundle_ids.entry(TypeId::of::()).or_insert_with(|| { let component_ids = T::component_ids(components, storages); - let id = BundleId(bundle_infos.len()); + let id = BundleId::new(bundle_infos.len()); // SAFE: T::component_id ensures info was created let bundle_info = unsafe { initialize_bundle(std::any::type_name::(), component_ids, id, components) @@ -606,7 +619,7 @@ impl Bundles { id }); // SAFE: index either exists, or was initialized - unsafe { self.bundle_infos.get_unchecked(id.0) } + unsafe { self.bundle_infos.get_unchecked(id.index()) } } } From 8bca091fb476818c0468fbfdb25eefe5060be5e7 Mon Sep 17 00:00:00 2001 From: james7132 Date: Sat, 15 Jan 2022 02:00:21 -0800 Subject: [PATCH 04/60] Fix tests --- crates/bevy_ecs/src/query/access.rs | 81 +++++++++++++++++------------ 1 file changed, 47 insertions(+), 34 deletions(-) diff --git a/crates/bevy_ecs/src/query/access.rs b/crates/bevy_ecs/src/query/access.rs index 25c1f39054008..e6a2247daa946 100644 --- a/crates/bevy_ecs/src/query/access.rs +++ b/crates/bevy_ecs/src/query/access.rs @@ -237,54 +237,67 @@ impl Default for FilteredAccessSet { #[cfg(test)] mod tests { use crate::query::{Access, FilteredAccess}; + use nonmax::NonMaxUsize; #[test] fn access_get_conflicts() { - let mut access_a = Access::::default(); - access_a.add_read(0); - access_a.add_read(1); - - let mut access_b = Access::::default(); - access_b.add_read(0); - access_b.add_write(1); - - assert_eq!(access_a.get_conflicts(&access_b), vec![1]); - - let mut access_c = Access::::default(); - access_c.add_write(0); - access_c.add_write(1); - - assert_eq!(access_a.get_conflicts(&access_c), vec![0, 1]); - assert_eq!(access_b.get_conflicts(&access_c), vec![0, 1]); - - let mut access_d = Access::::default(); - access_d.add_read(0); + let mut access_a = Access::::default(); + access_a.add_read(NonMaxUsize::new(0).unwrap()); + access_a.add_read(NonMaxUsize::new(1).unwrap()); + + let mut access_b = Access::::default(); + access_b.add_read(NonMaxUsize::new(0).unwrap()); + access_b.add_write(NonMaxUsize::new(1).unwrap()); + + assert_eq!( + access_a.get_conflicts(&access_b), + vec![NonMaxUsize::new(1).unwrap()] + ); + + let mut access_c = Access::::default(); + access_c.add_write(NonMaxUsize::new(0).unwrap()); + access_c.add_write(NonMaxUsize::new(1).unwrap()); + + assert_eq!( + access_a.get_conflicts(&access_c), + vec![NonMaxUsize::new(0).unwrap(), NonMaxUsize::new(1).unwrap()] + ); + assert_eq!( + access_b.get_conflicts(&access_c), + vec![NonMaxUsize::new(0).unwrap(), NonMaxUsize::new(1).unwrap()] + ); + + let mut access_d = Access::::default(); + access_d.add_read(NonMaxUsize::new(0).unwrap()); assert_eq!(access_d.get_conflicts(&access_a), vec![]); assert_eq!(access_d.get_conflicts(&access_b), vec![]); - assert_eq!(access_d.get_conflicts(&access_c), vec![0]); + assert_eq!( + access_d.get_conflicts(&access_c), + vec![NonMaxUsize::new(0).unwrap()] + ); } #[test] fn filtered_access_extend() { - let mut access_a = FilteredAccess::::default(); - access_a.add_read(0); - access_a.add_read(1); - access_a.add_with(2); + let mut access_a = FilteredAccess::::default(); + access_a.add_read(NonMaxUsize::new(0).unwrap()); + access_a.add_read(NonMaxUsize::new(1).unwrap()); + access_a.add_with(NonMaxUsize::new(2).unwrap()); - let mut access_b = FilteredAccess::::default(); - access_b.add_read(0); - access_b.add_write(3); - access_b.add_without(4); + let mut access_b = FilteredAccess::::default(); + access_b.add_read(NonMaxUsize::new(0).unwrap()); + access_b.add_write(NonMaxUsize::new(3).unwrap()); + access_b.add_without(NonMaxUsize::new(4).unwrap()); access_a.extend(&access_b); - let mut expected = FilteredAccess::::default(); - expected.add_read(0); - expected.add_read(1); - expected.add_with(2); - expected.add_write(3); - expected.add_without(4); + let mut expected = FilteredAccess::::default(); + expected.add_read(NonMaxUsize::new(0).unwrap()); + expected.add_read(NonMaxUsize::new(1).unwrap()); + expected.add_with(NonMaxUsize::new(2).unwrap()); + expected.add_write(NonMaxUsize::new(3).unwrap()); + expected.add_without(NonMaxUsize::new(4).unwrap()); assert!(access_a.eq(&expected)); } From f01f1d21cde96c3ca026ff29eee758dd2bc00972 Mon Sep 17 00:00:00 2001 From: james7132 Date: Sat, 15 Jan 2022 02:20:00 -0800 Subject: [PATCH 05/60] Add missing safety and panic docs. --- crates/bevy_ecs/src/archetype.rs | 9 +++++++++ crates/bevy_ecs/src/bundle.rs | 9 +++++++++ crates/bevy_ecs/src/component.rs | 9 +++++++++ 3 files changed, 27 insertions(+) diff --git a/crates/bevy_ecs/src/archetype.rs b/crates/bevy_ecs/src/archetype.rs index cc5efef9d24cf..ca305ba11c9a4 100644 --- a/crates/bevy_ecs/src/archetype.rs +++ b/crates/bevy_ecs/src/archetype.rs @@ -342,11 +342,20 @@ pub struct ArchetypeComponentId(NonMaxUsize); assert_eq_size!(ArchetypeComponentId, Option); impl ArchetypeComponentId { + /// Creates a new [`ArchetypeComponentId`] from an index without + /// checking for the type's invariants. + /// + /// # Safety + /// This function is only safe if `index` is not equal to [`usize::MAX`]. #[inline] pub const unsafe fn new_unchecked(index: usize) -> Self { Self(NonMaxUsize::new_unchecked(index)) } + /// Creates a new [`ArchetypeComponentId`] from an index. + /// + /// # Panic + /// This function will panic if `index` is equal to [`usize::MAX`]. #[inline] pub fn new(index: usize) -> Self { Self(NonMaxUsize::new(index).unwrap()) diff --git a/crates/bevy_ecs/src/bundle.rs b/crates/bevy_ecs/src/bundle.rs index 4ff12c54bb594..4ba3d01ef0639 100644 --- a/crates/bevy_ecs/src/bundle.rs +++ b/crates/bevy_ecs/src/bundle.rs @@ -135,11 +135,20 @@ pub struct BundleId(NonMaxUsize); assert_eq_size!(BundleId, Option); impl BundleId { + /// Creates a new [`BundleId`] from an index without checking for the + /// type's invariants. + /// + /// # Safety + /// This function is only safe if `value` is not equal to [`usize::MAX`]. #[inline] pub const unsafe fn new_unchecked(value: usize) -> Self { Self(NonMaxUsize::new_unchecked(value)) } + /// Creates a new [`BundleId`] from an index. + /// + /// # Panic + /// This function will panic if `value` is equal to [`usize::MAX`]. #[inline] pub fn new(value: usize) -> Self { Self(NonMaxUsize::new(value).unwrap()) diff --git a/crates/bevy_ecs/src/component.rs b/crates/bevy_ecs/src/component.rs index d97c310ddbef6..8c65b916ece30 100644 --- a/crates/bevy_ecs/src/component.rs +++ b/crates/bevy_ecs/src/component.rs @@ -144,11 +144,20 @@ pub struct ComponentId(NonMaxUsize); assert_eq_size!(ComponentId, Option); impl ComponentId { + /// Creates a new [`ComponentId`] from an index without + /// checking for the type's invariants. + /// + /// # Safety + /// This function is only safe if `index` is not equal to [`usize::MAX`]. #[inline] pub const unsafe fn new_unchecked(index: usize) -> ComponentId { ComponentId(NonMaxUsize::new_unchecked(index)) } + /// Creates a new [`ArchetypeComponentId`] from an index. + /// + /// # Panic + /// This function will panic if `index` is equal to [`usize::MAX`]. #[inline] pub fn new(index: usize) -> ComponentId { ComponentId(NonMaxUsize::new(index).unwrap()) From d2ebb011583417490d3cb4553ffc7909a900df4a Mon Sep 17 00:00:00 2001 From: james7132 Date: Sat, 15 Jan 2022 02:32:35 -0800 Subject: [PATCH 06/60] Remove dependency on static_assertions --- crates/bevy_ecs/Cargo.toml | 1 - crates/bevy_ecs/src/archetype.rs | 15 +++++++++++++-- crates/bevy_ecs/src/bundle.rs | 15 +++++++++++++-- crates/bevy_ecs/src/component.rs | 17 ++++++++++++++--- crates/bevy_ecs/src/lib.rs | 3 --- crates/bevy_ecs/src/storage/sparse_set.rs | 3 --- 6 files changed, 40 insertions(+), 14 deletions(-) diff --git a/crates/bevy_ecs/Cargo.toml b/crates/bevy_ecs/Cargo.toml index 80b19520e9010..f7d5bf381105d 100644 --- a/crates/bevy_ecs/Cargo.toml +++ b/crates/bevy_ecs/Cargo.toml @@ -24,7 +24,6 @@ fixedbitset = "0.4" fxhash = "0.2" thiserror = "1.0" downcast-rs = "1.2" -static_assertions = "1.1" nonmax = "0.5" serde = "1" diff --git a/crates/bevy_ecs/src/archetype.rs b/crates/bevy_ecs/src/archetype.rs index ca305ba11c9a4..3db3a8fd3c284 100644 --- a/crates/bevy_ecs/src/archetype.rs +++ b/crates/bevy_ecs/src/archetype.rs @@ -339,8 +339,6 @@ pub struct ArchetypeIdentity { #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] pub struct ArchetypeComponentId(NonMaxUsize); -assert_eq_size!(ArchetypeComponentId, Option); - impl ArchetypeComponentId { /// Creates a new [`ArchetypeComponentId`] from an index without /// checking for the type's invariants. @@ -558,3 +556,16 @@ impl IndexMut for Archetypes { &mut self.archetypes[index.index()] } } + +#[cfg(test)] +mod test { + use super::ArchetypeComponentId; + + #[test] + pub fn test_archetyype_component_id_size_optimized() { + assert_eq!( + core::mem::sizeof::(), + core::mem::sizeof::>(), + ); + } +} \ No newline at end of file diff --git a/crates/bevy_ecs/src/bundle.rs b/crates/bevy_ecs/src/bundle.rs index 4ba3d01ef0639..962c494d5b14d 100644 --- a/crates/bevy_ecs/src/bundle.rs +++ b/crates/bevy_ecs/src/bundle.rs @@ -132,8 +132,6 @@ all_tuples!(tuple_impl, 0, 15, C); #[derive(Debug, Clone, Copy)] pub struct BundleId(NonMaxUsize); -assert_eq_size!(BundleId, Option); - impl BundleId { /// Creates a new [`BundleId`] from an index without checking for the /// type's invariants. @@ -662,3 +660,16 @@ unsafe fn initialize_bundle( storage_types, } } + +#[cfg(test)] +mod test { + use super::BundleId; + + #[test] + pub fn test_bundle_id_size_optimized() { + assert_eq!( + core::mem::size_of::(), + core::mem::size_of::>(), + ); + } +} \ No newline at end of file diff --git a/crates/bevy_ecs/src/component.rs b/crates/bevy_ecs/src/component.rs index 8c65b916ece30..7ad5eee3dfdaa 100644 --- a/crates/bevy_ecs/src/component.rs +++ b/crates/bevy_ecs/src/component.rs @@ -141,8 +141,6 @@ impl ComponentInfo { #[derive(Debug, Copy, Clone, Hash, Ord, PartialOrd, Eq, PartialEq)] pub struct ComponentId(NonMaxUsize); -assert_eq_size!(ComponentId, Option); - impl ComponentId { /// Creates a new [`ComponentId`] from an index without /// checking for the type's invariants. @@ -154,7 +152,7 @@ impl ComponentId { ComponentId(NonMaxUsize::new_unchecked(index)) } - /// Creates a new [`ArchetypeComponentId`] from an index. + /// Creates a new [`ComponentId`] from an index. /// /// # Panic /// This function will panic if `index` is equal to [`usize::MAX`]. @@ -432,3 +430,16 @@ fn check_tick(last_change_tick: &mut u32, change_tick: u32) { *last_change_tick = change_tick.wrapping_sub(MAX_DELTA); } } + +#[cfg(test)] +mod test { + use super::ComponentId; + + #[test] + pub fn test_component_id_size_optimized() { + assert_eq!( + core::mem::size_of::(), + core::mem::size_of::>(), + ); + } +} \ No newline at end of file diff --git a/crates/bevy_ecs/src/lib.rs b/crates/bevy_ecs/src/lib.rs index dc924303e12f7..30d70ca17efab 100644 --- a/crates/bevy_ecs/src/lib.rs +++ b/crates/bevy_ecs/src/lib.rs @@ -1,8 +1,5 @@ #![doc = include_str!("../README.md")] -#[macro_use] -extern crate static_assertions; - pub mod archetype; pub mod bundle; pub mod change_detection; diff --git a/crates/bevy_ecs/src/storage/sparse_set.rs b/crates/bevy_ecs/src/storage/sparse_set.rs index b1ebe287e1365..067d7000401e9 100644 --- a/crates/bevy_ecs/src/storage/sparse_set.rs +++ b/crates/bevy_ecs/src/storage/sparse_set.rs @@ -395,9 +395,6 @@ macro_rules! impl_sparse_set_index { <$ty>::new(value as $underlying).unwrap() } } - - assert_eq_size!($ty, Option<$ty>); - assert_eq_size!($ty, $underlying); }; } From 1c2525cfd33e7936b99d7f637f036bcd54cf96c3 Mon Sep 17 00:00:00 2001 From: james7132 Date: Sat, 15 Jan 2022 02:36:39 -0800 Subject: [PATCH 07/60] Fix formatting --- crates/bevy_ecs/src/archetype.rs | 2 +- crates/bevy_ecs/src/bundle.rs | 2 +- crates/bevy_ecs/src/component.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/bevy_ecs/src/archetype.rs b/crates/bevy_ecs/src/archetype.rs index 3db3a8fd3c284..bcd3a7408fb77 100644 --- a/crates/bevy_ecs/src/archetype.rs +++ b/crates/bevy_ecs/src/archetype.rs @@ -568,4 +568,4 @@ mod test { core::mem::sizeof::>(), ); } -} \ No newline at end of file +} diff --git a/crates/bevy_ecs/src/bundle.rs b/crates/bevy_ecs/src/bundle.rs index 962c494d5b14d..dc98998e6cd52 100644 --- a/crates/bevy_ecs/src/bundle.rs +++ b/crates/bevy_ecs/src/bundle.rs @@ -672,4 +672,4 @@ mod test { core::mem::size_of::>(), ); } -} \ No newline at end of file +} diff --git a/crates/bevy_ecs/src/component.rs b/crates/bevy_ecs/src/component.rs index 7ad5eee3dfdaa..24dd002e57778 100644 --- a/crates/bevy_ecs/src/component.rs +++ b/crates/bevy_ecs/src/component.rs @@ -442,4 +442,4 @@ mod test { core::mem::size_of::>(), ); } -} \ No newline at end of file +} From 6aa4a971d85c181a6abf2761aefcd93056032e40 Mon Sep 17 00:00:00 2001 From: james7132 Date: Sat, 15 Jan 2022 02:39:59 -0800 Subject: [PATCH 08/60] Fix test --- crates/bevy_ecs/src/archetype.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/bevy_ecs/src/archetype.rs b/crates/bevy_ecs/src/archetype.rs index bcd3a7408fb77..aa96398bb6bdd 100644 --- a/crates/bevy_ecs/src/archetype.rs +++ b/crates/bevy_ecs/src/archetype.rs @@ -564,8 +564,8 @@ mod test { #[test] pub fn test_archetyype_component_id_size_optimized() { assert_eq!( - core::mem::sizeof::(), - core::mem::sizeof::>(), + core::mem::size_of::(), + core::mem::size_of::>(), ); } } From ab57a2b767de5405b3d857a18e5bed0ccbbb8859 Mon Sep 17 00:00:00 2001 From: james7132 Date: Wed, 26 Jan 2022 10:16:41 -0800 Subject: [PATCH 09/60] Optimize space for ArchetypeId --- crates/bevy_ecs/src/archetype.rs | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/crates/bevy_ecs/src/archetype.rs b/crates/bevy_ecs/src/archetype.rs index aa96398bb6bdd..c62fe74f947ee 100644 --- a/crates/bevy_ecs/src/archetype.rs +++ b/crates/bevy_ecs/src/archetype.rs @@ -16,21 +16,26 @@ use std::{ }; #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -pub struct ArchetypeId(usize); +pub struct ArchetypeId(NonMaxUsize); impl ArchetypeId { - pub const EMPTY: ArchetypeId = ArchetypeId(0); - pub const RESOURCE: ArchetypeId = ArchetypeId(1); - pub const INVALID: ArchetypeId = ArchetypeId(usize::MAX); + pub const EMPTY: ArchetypeId = unsafe { ArchetypeId::new_unchecked(0) }; + pub const RESOURCE: ArchetypeId = unsafe { ArchetypeId::new_unchecked(1) }; + pub const INVALID: ArchetypeId = unsafe { ArchetypeId::new_unchecked(usize::MAX - 1) }; #[inline] - pub const fn new(index: usize) -> Self { - ArchetypeId(index) + pub fn new(index: usize) -> Self { + Self(NonMaxUsize::new(index).unwrap()) } #[inline] - pub fn index(self) -> usize { - self.0 + pub const unsafe fn new_unchecked(index: usize) -> Self { + Self(NonMaxUsize::new_unchecked(index)) + } + + #[inline] + pub const fn index(self) -> usize { + self.0.get() } } @@ -510,7 +515,7 @@ impl Archetypes { .archetype_ids .entry(archetype_identity) .or_insert_with(move || { - let id = ArchetypeId(archetypes.len()); + let id = ArchetypeId::new(archetypes.len()); let table_archetype_components = (0..table_components.len()) .map(|_| next_archetype_component_id()) .collect(); From 85fc10cf47a326d65e63c6b833fbabbea37fc715 Mon Sep 17 00:00:00 2001 From: james7132 Date: Thu, 27 Jan 2022 09:57:24 -0800 Subject: [PATCH 10/60] Update docs --- crates/bevy_ecs/src/archetype.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/crates/bevy_ecs/src/archetype.rs b/crates/bevy_ecs/src/archetype.rs index c62fe74f947ee..940e0a66c7ab9 100644 --- a/crates/bevy_ecs/src/archetype.rs +++ b/crates/bevy_ecs/src/archetype.rs @@ -23,16 +23,26 @@ impl ArchetypeId { pub const RESOURCE: ArchetypeId = unsafe { ArchetypeId::new_unchecked(1) }; pub const INVALID: ArchetypeId = unsafe { ArchetypeId::new_unchecked(usize::MAX - 1) }; + /// Creates a new [`ArchetypeId`]. + /// + /// # Panics + /// This will panic if `index` is `usize::MAX`. #[inline] pub fn new(index: usize) -> Self { Self(NonMaxUsize::new(index).unwrap()) } + /// Creates a new [`ArchetypeId`] without checking for validity of the + /// provided index. + /// + /// # Safety + /// The produced value is only safe if `index` is not `usize::MAX`. #[inline] pub const unsafe fn new_unchecked(index: usize) -> Self { Self(NonMaxUsize::new_unchecked(index)) } + /// Gets the corresponding index for the ID. #[inline] pub const fn index(self) -> usize { self.0.get() From eea54655c41049a7323408e0aa549493a8dc0643 Mon Sep 17 00:00:00 2001 From: james7132 Date: Thu, 27 Jan 2022 09:59:18 -0800 Subject: [PATCH 11/60] Provide better panic messages --- crates/bevy_ecs/src/archetype.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_ecs/src/archetype.rs b/crates/bevy_ecs/src/archetype.rs index 940e0a66c7ab9..4a8033c2d52fe 100644 --- a/crates/bevy_ecs/src/archetype.rs +++ b/crates/bevy_ecs/src/archetype.rs @@ -29,7 +29,7 @@ impl ArchetypeId { /// This will panic if `index` is `usize::MAX`. #[inline] pub fn new(index: usize) -> Self { - Self(NonMaxUsize::new(index).unwrap()) + Self(NonMaxUsize::new(index).expect("ArchetypeID cannot be created from usize::MAX")) } /// Creates a new [`ArchetypeId`] without checking for validity of the From 41d7c08f5032614c763c031e969a4167f07286d7 Mon Sep 17 00:00:00 2001 From: James Liu Date: Sun, 6 Mar 2022 06:47:42 -0500 Subject: [PATCH 12/60] Address safety comment review Co-authored-by: Daniel McNab <36049421+DJMcNab@users.noreply.github.com> --- crates/bevy_ecs/src/archetype.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_ecs/src/archetype.rs b/crates/bevy_ecs/src/archetype.rs index 4a8033c2d52fe..7fd7899d778f1 100644 --- a/crates/bevy_ecs/src/archetype.rs +++ b/crates/bevy_ecs/src/archetype.rs @@ -36,7 +36,7 @@ impl ArchetypeId { /// provided index. /// /// # Safety - /// The produced value is only safe if `index` is not `usize::MAX`. + /// `index` must not be `usize::MAX` #[inline] pub const unsafe fn new_unchecked(index: usize) -> Self { Self(NonMaxUsize::new_unchecked(index)) From db305285e2567b5837f9939911a722f1580c967d Mon Sep 17 00:00:00 2001 From: james7132 Date: Sun, 6 Mar 2022 04:42:52 -0800 Subject: [PATCH 13/60] Remvoe ArchetypeId by niching EntityLocation --- crates/bevy_ecs/src/archetype.rs | 1 - crates/bevy_ecs/src/bundle.rs | 10 +++---- crates/bevy_ecs/src/entity/mod.rs | 36 ++++++++++++++----------- crates/bevy_ecs/src/world/entity_ref.rs | 6 ++--- crates/bevy_ecs/src/world/mod.rs | 4 +-- 5 files changed, 30 insertions(+), 27 deletions(-) diff --git a/crates/bevy_ecs/src/archetype.rs b/crates/bevy_ecs/src/archetype.rs index 7fd7899d778f1..207fb31326f03 100644 --- a/crates/bevy_ecs/src/archetype.rs +++ b/crates/bevy_ecs/src/archetype.rs @@ -21,7 +21,6 @@ pub struct ArchetypeId(NonMaxUsize); impl ArchetypeId { pub const EMPTY: ArchetypeId = unsafe { ArchetypeId::new_unchecked(0) }; pub const RESOURCE: ArchetypeId = unsafe { ArchetypeId::new_unchecked(1) }; - pub const INVALID: ArchetypeId = unsafe { ArchetypeId::new_unchecked(usize::MAX - 1) }; /// Creates a new [`ArchetypeId`]. /// diff --git a/crates/bevy_ecs/src/bundle.rs b/crates/bevy_ecs/src/bundle.rs index dc98998e6cd52..c556f7840a506 100644 --- a/crates/bevy_ecs/src/bundle.rs +++ b/crates/bevy_ecs/src/bundle.rs @@ -464,10 +464,10 @@ impl<'a, 'b> BundleInserter<'a, 'b> { InsertBundleResult::NewArchetypeSameTable { new_archetype } => { let result = self.archetype.swap_remove(location.index); if let Some(swapped_entity) = result.swapped_entity { - self.entities.meta[swapped_entity.id as usize].location = location; + self.entities.meta[swapped_entity.id as usize].location = Some(location); } let new_location = new_archetype.allocate(entity, result.table_row); - self.entities.meta[entity.id as usize].location = new_location; + self.entities.meta[entity.id as usize].location = Some(new_location); // PERF: this could be looked up during Inserter construction and stored (but borrowing makes this nasty) let add_bundle = self @@ -492,7 +492,7 @@ impl<'a, 'b> BundleInserter<'a, 'b> { } => { let result = self.archetype.swap_remove(location.index); if let Some(swapped_entity) = result.swapped_entity { - self.entities.meta[swapped_entity.id as usize].location = location; + self.entities.meta[swapped_entity.id as usize].location = Some(location); } // PERF: store "non bundle" components in edge, then just move those to avoid // redundant copies @@ -500,7 +500,7 @@ impl<'a, 'b> BundleInserter<'a, 'b> { .table .move_to_superset_unchecked(result.table_row, *new_table); let new_location = new_archetype.allocate(entity, move_result.new_row); - self.entities.meta[entity.id as usize].location = new_location; + self.entities.meta[entity.id as usize].location = Some(new_location); // if an entity was moved into this entity's table spot, update its table row if let Some(swapped_entity) = move_result.swapped_entity { @@ -576,7 +576,7 @@ impl<'a, 'b> BundleSpawner<'a, 'b> { self.change_tick, bundle, ); - self.entities.meta[entity.id as usize].location = location; + self.entities.meta[entity.id as usize].location = Some(location); location } diff --git a/crates/bevy_ecs/src/entity/mod.rs b/crates/bevy_ecs/src/entity/mod.rs index ca82b2c6c5453..6e70cca9b94ab 100644 --- a/crates/bevy_ecs/src/entity/mod.rs +++ b/crates/bevy_ecs/src/entity/mod.rs @@ -360,10 +360,10 @@ impl Entities { self.len += 1; None } else { - Some(mem::replace( + mem::replace( &mut self.meta[entity.id as usize].location, EntityMeta::EMPTY.location, - )) + ) }; self.meta[entity.id as usize].generation = entity.generation; @@ -392,10 +392,10 @@ impl Entities { AllocAtWithoutReplacement::DidNotExist } else { let current_meta = &mut self.meta[entity.id as usize]; - if current_meta.location.archetype_id == ArchetypeId::INVALID { + if current_meta.location.is_none() { AllocAtWithoutReplacement::DidNotExist } else if current_meta.generation == entity.generation { - AllocAtWithoutReplacement::Exists(current_meta.location) + AllocAtWithoutReplacement::Exists(current_meta.location.unwrap()) } else { return AllocAtWithoutReplacement::ExistsWithWrongGeneration; } @@ -424,7 +424,7 @@ impl Entities { let new_free_cursor = self.pending.len() as i64; *self.free_cursor.get_mut() = new_free_cursor; self.len -= 1; - Some(loc) + loc } /// Ensure at least `n` allocations can succeed without reallocating. @@ -457,12 +457,10 @@ impl Entities { pub fn get(&self, entity: Entity) -> Option { if (entity.id as usize) < self.meta.len() { let meta = &self.meta[entity.id as usize]; - if meta.generation != entity.generation - || meta.location.archetype_id == ArchetypeId::INVALID - { + if meta.generation != entity.generation { return None; } - Some(meta.location) + meta.location } else { None } @@ -499,7 +497,7 @@ impl Entities { /// Flush _must_ set the entity location to the correct [`ArchetypeId`] for the given [`Entity`] /// each time init is called. This _can_ be [`ArchetypeId::INVALID`], provided the [`Entity`] /// has not been assigned to an [`Archetype`][crate::archetype::Archetype]. - pub unsafe fn flush(&mut self, mut init: impl FnMut(Entity, &mut EntityLocation)) { + pub unsafe fn flush(&mut self, mut init: impl FnMut(Entity, &mut Option)) { let free_cursor = self.free_cursor.get_mut(); let current_free_cursor = *free_cursor; @@ -542,7 +540,7 @@ impl Entities { pub fn flush_as_invalid(&mut self) { unsafe { self.flush(|_entity, location| { - location.archetype_id = ArchetypeId::INVALID; + *location = None; }) } } @@ -561,16 +559,13 @@ impl Entities { #[derive(Copy, Clone, Debug)] pub struct EntityMeta { pub generation: u32, - pub location: EntityLocation, + pub location: Option, } impl EntityMeta { const EMPTY: EntityMeta = EntityMeta { generation: 0, - location: EntityLocation { - archetype_id: ArchetypeId::INVALID, - index: usize::MAX, // dummy value, to be filled in - }, + location: None, }; } @@ -621,4 +616,13 @@ mod tests { assert!(entities.contains(e)); assert!(entities.get(e).is_none()); } + + #[test] + fn entity_location_is_size_optimized() { + assert_eq!( + std::mem::size_of::(), + std::mem::size_of::>(), + ) + } + } diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index b85fa2b74f0f2..f7e332267c908 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -295,7 +295,7 @@ impl<'w> EntityMut<'w> { let old_archetype = &mut archetypes[old_archetype_id]; let remove_result = old_archetype.swap_remove(old_location.index); if let Some(swapped_entity) = remove_result.swapped_entity { - entities.meta[swapped_entity.id as usize].location = old_location; + entities.meta[swapped_entity.id as usize].location = Some(old_location); } let old_table_row = remove_result.table_row; let old_table_id = old_archetype.table_id(); @@ -329,7 +329,7 @@ impl<'w> EntityMut<'w> { }; *self_location = new_location; - entities.meta[entity.id as usize].location = new_location; + entities.meta[entity.id as usize].location = Some(new_location); } // TODO: move to BundleInfo @@ -420,7 +420,7 @@ impl<'w> EntityMut<'w> { } let remove_result = archetype.swap_remove(location.index); if let Some(swapped_entity) = remove_result.swapped_entity { - world.entities.meta[swapped_entity.id as usize].location = location; + world.entities.meta[swapped_entity.id as usize].location = Some(location); } table_row = remove_result.table_row; diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index c07892a35243a..424feb33bb709 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -355,7 +355,7 @@ impl World { self.entities .meta .get_unchecked_mut(entity.id() as usize) - .location = location; + .location = Some(location); EntityMut::new(self, entity, location) } @@ -1099,7 +1099,7 @@ impl World { self.entities.flush(|entity, location| { // SAFE: no components are allocated by archetype.allocate() because the archetype // is empty - *location = empty_archetype.allocate(entity, table.allocate(entity)); + *location = Some(empty_archetype.allocate(entity, table.allocate(entity))); }); } } From f01b97a3537fc8c96041334c7cf032e0ae447421 Mon Sep 17 00:00:00 2001 From: james7132 Date: Sun, 6 Mar 2022 04:46:28 -0800 Subject: [PATCH 14/60] Remove comment about Entity niching --- crates/bevy_ecs/src/entity/mod.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/crates/bevy_ecs/src/entity/mod.rs b/crates/bevy_ecs/src/entity/mod.rs index c0ecaf3ab52d4..4db6af30e21e9 100644 --- a/crates/bevy_ecs/src/entity/mod.rs +++ b/crates/bevy_ecs/src/entity/mod.rs @@ -50,9 +50,6 @@ pub struct Entity { pub(crate) id: u32, } -// TODO: Optimize memory usage of Option -// assert_size_eq!(Entity, Option); - pub enum AllocAtWithoutReplacement { Exists(EntityLocation), DidNotExist, From 692b4a09e25291145c6482dfe5272e851228ad10 Mon Sep 17 00:00:00 2001 From: james7132 Date: Sun, 6 Mar 2022 04:52:07 -0800 Subject: [PATCH 15/60] Fix up existing safety doc comments --- crates/bevy_ecs/src/archetype.rs | 2 +- crates/bevy_ecs/src/bundle.rs | 2 +- crates/bevy_ecs/src/entity/mod.rs | 1 - 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/crates/bevy_ecs/src/archetype.rs b/crates/bevy_ecs/src/archetype.rs index ed50af2cdbac3..b1e030cd0595b 100644 --- a/crates/bevy_ecs/src/archetype.rs +++ b/crates/bevy_ecs/src/archetype.rs @@ -358,7 +358,7 @@ impl ArchetypeComponentId { /// checking for the type's invariants. /// /// # Safety - /// This function is only safe if `index` is not equal to [`usize::MAX`]. + /// `index` must not be [`usize::MAX`]. #[inline] pub const unsafe fn new_unchecked(index: usize) -> Self { Self(NonMaxUsize::new_unchecked(index)) diff --git a/crates/bevy_ecs/src/bundle.rs b/crates/bevy_ecs/src/bundle.rs index d7152656ea3ff..6e7fa2dbe01f8 100644 --- a/crates/bevy_ecs/src/bundle.rs +++ b/crates/bevy_ecs/src/bundle.rs @@ -137,7 +137,7 @@ impl BundleId { /// type's invariants. /// /// # Safety - /// This function is only safe if `value` is not equal to [`usize::MAX`]. + /// `value` must not be [`usize::MAX`]. #[inline] pub const unsafe fn new_unchecked(value: usize) -> Self { Self(NonMaxUsize::new_unchecked(value)) diff --git a/crates/bevy_ecs/src/entity/mod.rs b/crates/bevy_ecs/src/entity/mod.rs index 4db6af30e21e9..f678df22aeedf 100644 --- a/crates/bevy_ecs/src/entity/mod.rs +++ b/crates/bevy_ecs/src/entity/mod.rs @@ -621,5 +621,4 @@ mod tests { std::mem::size_of::>(), ) } - } From 307775ed671fc4e94405e657baf515b5b23820e0 Mon Sep 17 00:00:00 2001 From: james7132 Date: Mon, 7 Mar 2022 02:55:20 -0800 Subject: [PATCH 16/60] Fix broken doc reference --- crates/bevy_ecs/src/entity/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/bevy_ecs/src/entity/mod.rs b/crates/bevy_ecs/src/entity/mod.rs index f678df22aeedf..e714c65028c40 100644 --- a/crates/bevy_ecs/src/entity/mod.rs +++ b/crates/bevy_ecs/src/entity/mod.rs @@ -492,8 +492,8 @@ impl Entities { /// /// # Safety /// Flush _must_ set the entity location to the correct [`ArchetypeId`] for the given [`Entity`] - /// each time init is called. This _can_ be [`ArchetypeId::INVALID`], provided the [`Entity`] - /// has not been assigned to an [`Archetype`][crate::archetype::Archetype]. + /// each time init is called. This _can_ be `None`, provided the [`Entity`] has not been assigned + /// to an [`Archetype`][crate::archetype::Archetype]. pub unsafe fn flush(&mut self, mut init: impl FnMut(Entity, &mut Option)) { let free_cursor = self.free_cursor.get_mut(); let current_free_cursor = *free_cursor; From ceeea059e099ce1cf75f16afb9c1dcdab2f463b9 Mon Sep 17 00:00:00 2001 From: james7132 Date: Fri, 20 May 2022 14:53:02 -0700 Subject: [PATCH 17/60] Formatting --- crates/bevy_ecs/src/bundle.rs | 2 +- crates/bevy_ecs/src/component.rs | 2 +- crates/bevy_ecs/src/entity/mod.rs | 2 +- crates/bevy_ecs/src/storage/sparse_set.rs | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/bevy_ecs/src/bundle.rs b/crates/bevy_ecs/src/bundle.rs index f6c674e8ebcaa..2a8496918f148 100644 --- a/crates/bevy_ecs/src/bundle.rs +++ b/crates/bevy_ecs/src/bundle.rs @@ -11,8 +11,8 @@ use crate::{ storage::{SparseSetIndex, SparseSets, Storages, Table}, }; use bevy_ecs_macros::all_tuples; -use nonmax::NonMaxUsize; use bevy_ptr::OwningPtr; +use nonmax::NonMaxUsize; use std::{any::TypeId, collections::HashMap}; /// An ordered collection of [`Component`]s. diff --git a/crates/bevy_ecs/src/component.rs b/crates/bevy_ecs/src/component.rs index 830fa1a880138..068811f8b4935 100644 --- a/crates/bevy_ecs/src/component.rs +++ b/crates/bevy_ecs/src/component.rs @@ -6,8 +6,8 @@ use crate::{ system::Resource, }; pub use bevy_ecs_macros::Component; -use nonmax::NonMaxUsize; use bevy_ptr::OwningPtr; +use nonmax::NonMaxUsize; use std::{ alloc::Layout, any::{Any, TypeId}, diff --git a/crates/bevy_ecs/src/entity/mod.rs b/crates/bevy_ecs/src/entity/mod.rs index 28291d31b17bd..448b82e4cf23d 100644 --- a/crates/bevy_ecs/src/entity/mod.rs +++ b/crates/bevy_ecs/src/entity/mod.rs @@ -492,7 +492,7 @@ impl Entities { /// /// # Safety /// Flush _must_ set the entity location to the correct [`ArchetypeId`] for the given [`Entity`] - /// each time init is called. This _can_ be `None`, provided the [`Entity`] has not been assigned + /// each time init is called. This _can_ be `None`, provided the [`Entity`] has not been assigned /// to an [`Archetype`][crate::archetype::Archetype]. pub unsafe fn flush(&mut self, mut init: impl FnMut(Entity, &mut Option)) { let free_cursor = self.free_cursor.get_mut(); diff --git a/crates/bevy_ecs/src/storage/sparse_set.rs b/crates/bevy_ecs/src/storage/sparse_set.rs index c4e21ddab91b8..adeb56f15a7ca 100644 --- a/crates/bevy_ecs/src/storage/sparse_set.rs +++ b/crates/bevy_ecs/src/storage/sparse_set.rs @@ -3,8 +3,8 @@ use crate::{ entity::Entity, storage::BlobVec, }; -use nonmax::{NonMaxU16, NonMaxU32, NonMaxU64, NonMaxU8, NonMaxUsize}; use bevy_ptr::{OwningPtr, Ptr}; +use nonmax::{NonMaxU16, NonMaxU32, NonMaxU64, NonMaxU8, NonMaxUsize}; use std::{cell::UnsafeCell, hash::Hash, marker::PhantomData}; #[derive(Debug)] From a1bd8c4bf52654c7689f8f2aba3fcae7d6810a0a Mon Sep 17 00:00:00 2001 From: james7132 Date: Sat, 21 May 2022 21:36:49 -0700 Subject: [PATCH 18/60] Per-ID type Repr --- crates/bevy_ecs/src/archetype.rs | 15 +++++++- crates/bevy_ecs/src/bundle.rs | 13 +++++++ crates/bevy_ecs/src/component.rs | 12 +++++++ crates/bevy_ecs/src/entity/mod.rs | 15 ++++++++ crates/bevy_ecs/src/storage/sparse_set.rs | 42 ++++++++++++++++------- 5 files changed, 84 insertions(+), 13 deletions(-) diff --git a/crates/bevy_ecs/src/archetype.rs b/crates/bevy_ecs/src/archetype.rs index cd0e58eacfed4..262273a52d4ca 100644 --- a/crates/bevy_ecs/src/archetype.rs +++ b/crates/bevy_ecs/src/archetype.rs @@ -379,14 +379,27 @@ impl ArchetypeComponentId { } impl SparseSetIndex for ArchetypeComponentId { + type Repr = NonMaxUsize; + #[inline] fn sparse_set_index(&self) -> usize { - self.index() + self.0.get() as usize } + #[inline] fn get_sparse_set_index(value: usize) -> Self { Self::new(value) } + + #[inline] + fn repr_from_index(index : usize) -> Self::Repr { + NonMaxUsize::new(index).unwrap() + } + + #[inline] + fn repr_to_index(repr: &Self::Repr) -> usize { + repr.get() + } } pub struct Archetypes { diff --git a/crates/bevy_ecs/src/bundle.rs b/crates/bevy_ecs/src/bundle.rs index 2a8496918f148..3ee054fd6f536 100644 --- a/crates/bevy_ecs/src/bundle.rs +++ b/crates/bevy_ecs/src/bundle.rs @@ -160,14 +160,27 @@ impl BundleId { } impl SparseSetIndex for BundleId { + type Repr = NonMaxUsize; + #[inline] fn sparse_set_index(&self) -> usize { self.index() } + #[inline] fn get_sparse_set_index(value: usize) -> Self { Self::new(value) } + + #[inline] + fn repr_from_index(index : usize) -> Self::Repr { + NonMaxUsize::new(index).unwrap() + } + + #[inline] + fn repr_to_index(repr: &Self::Repr) -> usize { + repr.get() as usize + } } pub struct BundleInfo { diff --git a/crates/bevy_ecs/src/component.rs b/crates/bevy_ecs/src/component.rs index 068811f8b4935..479975b0b0f0d 100644 --- a/crates/bevy_ecs/src/component.rs +++ b/crates/bevy_ecs/src/component.rs @@ -168,6 +168,8 @@ impl ComponentId { } impl SparseSetIndex for ComponentId { + type Repr = NonMaxUsize; + #[inline] fn sparse_set_index(&self) -> usize { self.index() @@ -176,6 +178,16 @@ impl SparseSetIndex for ComponentId { fn get_sparse_set_index(value: usize) -> Self { Self::new(value) } + + #[inline] + fn repr_from_index(index : usize) -> Self::Repr { + NonMaxUsize::new(index).unwrap() + } + + #[inline] + fn repr_to_index(repr: &Self::Repr) -> usize { + repr.get() as usize + } } pub struct ComponentDescriptor { diff --git a/crates/bevy_ecs/src/entity/mod.rs b/crates/bevy_ecs/src/entity/mod.rs index 448b82e4cf23d..9a87f85ea5205 100644 --- a/crates/bevy_ecs/src/entity/mod.rs +++ b/crates/bevy_ecs/src/entity/mod.rs @@ -29,6 +29,7 @@ pub use self::serde::*; pub use map_entities::*; use crate::{archetype::ArchetypeId, storage::SparseSetIndex}; +use nonmax::NonMaxUsize; use std::{ convert::TryFrom, fmt, mem, @@ -154,13 +155,27 @@ impl fmt::Debug for Entity { } impl SparseSetIndex for Entity { + type Repr = NonMaxUsize; + + #[inline] fn sparse_set_index(&self) -> usize { self.id() as usize } + #[inline] fn get_sparse_set_index(value: usize) -> Self { Entity::from_raw(value as u32) } + + #[inline] + fn repr_from_index(index : usize) -> Self::Repr { + NonMaxUsize::new(index).unwrap() + } + + #[inline] + fn repr_to_index(repr: &Self::Repr) -> usize { + repr.get() + } } /// An [`Iterator`] returning a sequence of [`Entity`] values from diff --git a/crates/bevy_ecs/src/storage/sparse_set.rs b/crates/bevy_ecs/src/storage/sparse_set.rs index adeb56f15a7ca..5f05bbfe3d874 100644 --- a/crates/bevy_ecs/src/storage/sparse_set.rs +++ b/crates/bevy_ecs/src/storage/sparse_set.rs @@ -232,10 +232,10 @@ impl ComponentSparseSet { /// /// `I` is the type of the indices, while `V` is the type of data stored in the dense storage. #[derive(Debug)] -pub struct SparseSet { +pub struct SparseSet { dense: Vec, indices: Vec, - sparse: SparseArray, + sparse: SparseArray, } impl Default for SparseSet { @@ -243,7 +243,7 @@ impl Default for SparseSet { Self::new() } } -impl SparseSet { +impl SparseSet { pub const fn new() -> Self { Self { dense: Vec::new(), @@ -271,10 +271,10 @@ impl SparseSet { if let Some(dense_index) = self.sparse.get(index.clone()).cloned() { // SAFE: dense indices stored in self.sparse always exist unsafe { - *self.dense.get_unchecked_mut(dense_index.get()) = value; + *self.dense.get_unchecked_mut(I::repr_to_index(&dense_index)) = value; } } else { - let dense = NonMaxUsize::new(self.dense.len()).unwrap(); + let dense = I::repr_from_index(self.dense.len()); self.sparse.insert(index.clone(), dense); self.indices.push(index); self.dense.push(value); @@ -306,15 +306,15 @@ impl SparseSet { pub fn get_or_insert_with(&mut self, index: I, func: impl FnOnce() -> V) -> &mut V { if let Some(dense_index) = self.sparse.get(index.clone()).cloned() { // SAFE: dense indices stored in self.sparse always exist - unsafe { self.dense.get_unchecked_mut(dense_index.get()) } + unsafe { self.dense.get_unchecked_mut(I::repr_to_index(&dense_index)) } } else { let value = func(); - let dense_index = NonMaxUsize::new(self.dense.len()).unwrap(); - self.sparse.insert(index.clone(), dense_index); + let dense_index = self.dense.len(); + self.sparse.insert(index.clone(), I::repr_from_index(dense_index)); self.indices.push(index); self.dense.push(value); // SAFE: dense index was just populated above - unsafe { self.dense.get_unchecked_mut(dense_index.get()) } + unsafe { self.dense.get_unchecked_mut(dense_index) } } } @@ -336,7 +336,7 @@ impl SparseSet { pub fn get(&self, index: I) -> Option<&V> { self.sparse.get(index).map(|dense_index| { // SAFE: if the sparse index points to something in the dense vec, it exists - unsafe { self.dense.get_unchecked(dense_index.get()) } + unsafe { self.dense.get_unchecked(I::repr_to_index(dense_index)) } }) } @@ -344,13 +344,13 @@ impl SparseSet { let dense = &mut self.dense; self.sparse.get(index).map(move |dense_index| { // SAFE: if the sparse index points to something in the dense vec, it exists - unsafe { dense.get_unchecked_mut(dense_index.get()) } + unsafe { dense.get_unchecked_mut(I::repr_to_index(dense_index)) } }) } pub fn remove(&mut self, index: I) -> Option { self.sparse.remove(index).map(|dense_idx| { - let dense_index = dense_idx.get(); + let dense_index = I::repr_to_index(&dense_idx); let is_last = dense_index == self.dense.len() - 1; let value = self.dense.swap_remove(dense_index); self.indices.swap_remove(dense_index); @@ -376,20 +376,38 @@ impl SparseSet { } pub trait SparseSetIndex: Clone + PartialEq + Eq + Hash { + type Repr: Clone; fn sparse_set_index(&self) -> usize; fn get_sparse_set_index(value: usize) -> Self; + // fn to_repr(&self) -> Self::Repr; + fn repr_from_index(index: usize) -> Self::Repr; + fn repr_to_index(repr: &Self::Repr) -> usize; } macro_rules! impl_sparse_set_index { ($ty:ty, $underlying:ty) => { impl SparseSetIndex for $ty { + type Repr = Self; + + #[inline] fn sparse_set_index(&self) -> usize { self.get() as usize } + #[inline] fn get_sparse_set_index(value: usize) -> Self { <$ty>::new(value as $underlying).unwrap() } + + #[inline] + fn repr_from_index(index : usize) -> Self::Repr { + <$ty>::new(index as $underlying).unwrap() + } + + #[inline] + fn repr_to_index(repr: &Self::Repr) -> usize { + repr.get() as usize + } } }; } From fd34430fa2df36d67e581b8f562eef482b5519ae Mon Sep 17 00:00:00 2001 From: james7132 Date: Sat, 21 May 2022 21:37:32 -0700 Subject: [PATCH 19/60] Formatting --- crates/bevy_ecs/src/archetype.rs | 2 +- crates/bevy_ecs/src/bundle.rs | 2 +- crates/bevy_ecs/src/component.rs | 2 +- crates/bevy_ecs/src/entity/mod.rs | 4 ++-- crates/bevy_ecs/src/storage/sparse_set.rs | 5 +++-- 5 files changed, 8 insertions(+), 7 deletions(-) diff --git a/crates/bevy_ecs/src/archetype.rs b/crates/bevy_ecs/src/archetype.rs index 262273a52d4ca..cb2edd8853454 100644 --- a/crates/bevy_ecs/src/archetype.rs +++ b/crates/bevy_ecs/src/archetype.rs @@ -392,7 +392,7 @@ impl SparseSetIndex for ArchetypeComponentId { } #[inline] - fn repr_from_index(index : usize) -> Self::Repr { + fn repr_from_index(index: usize) -> Self::Repr { NonMaxUsize::new(index).unwrap() } diff --git a/crates/bevy_ecs/src/bundle.rs b/crates/bevy_ecs/src/bundle.rs index 3ee054fd6f536..2f72c842cc0c1 100644 --- a/crates/bevy_ecs/src/bundle.rs +++ b/crates/bevy_ecs/src/bundle.rs @@ -173,7 +173,7 @@ impl SparseSetIndex for BundleId { } #[inline] - fn repr_from_index(index : usize) -> Self::Repr { + fn repr_from_index(index: usize) -> Self::Repr { NonMaxUsize::new(index).unwrap() } diff --git a/crates/bevy_ecs/src/component.rs b/crates/bevy_ecs/src/component.rs index 479975b0b0f0d..17f9c7054b008 100644 --- a/crates/bevy_ecs/src/component.rs +++ b/crates/bevy_ecs/src/component.rs @@ -180,7 +180,7 @@ impl SparseSetIndex for ComponentId { } #[inline] - fn repr_from_index(index : usize) -> Self::Repr { + fn repr_from_index(index: usize) -> Self::Repr { NonMaxUsize::new(index).unwrap() } diff --git a/crates/bevy_ecs/src/entity/mod.rs b/crates/bevy_ecs/src/entity/mod.rs index 9a87f85ea5205..1ca9e70f4c288 100644 --- a/crates/bevy_ecs/src/entity/mod.rs +++ b/crates/bevy_ecs/src/entity/mod.rs @@ -155,7 +155,7 @@ impl fmt::Debug for Entity { } impl SparseSetIndex for Entity { - type Repr = NonMaxUsize; + type Repr = NonMaxUsize; #[inline] fn sparse_set_index(&self) -> usize { @@ -168,7 +168,7 @@ impl SparseSetIndex for Entity { } #[inline] - fn repr_from_index(index : usize) -> Self::Repr { + fn repr_from_index(index: usize) -> Self::Repr { NonMaxUsize::new(index).unwrap() } diff --git a/crates/bevy_ecs/src/storage/sparse_set.rs b/crates/bevy_ecs/src/storage/sparse_set.rs index 5f05bbfe3d874..be01d043c4916 100644 --- a/crates/bevy_ecs/src/storage/sparse_set.rs +++ b/crates/bevy_ecs/src/storage/sparse_set.rs @@ -310,7 +310,8 @@ impl SparseSet { } else { let value = func(); let dense_index = self.dense.len(); - self.sparse.insert(index.clone(), I::repr_from_index(dense_index)); + self.sparse + .insert(index.clone(), I::repr_from_index(dense_index)); self.indices.push(index); self.dense.push(value); // SAFE: dense index was just populated above @@ -400,7 +401,7 @@ macro_rules! impl_sparse_set_index { } #[inline] - fn repr_from_index(index : usize) -> Self::Repr { + fn repr_from_index(index: usize) -> Self::Repr { <$ty>::new(index as $underlying).unwrap() } From 77f9a15876bc3e0717ce0fa47621bdf75ca6427f Mon Sep 17 00:00:00 2001 From: james7132 Date: Sat, 21 May 2022 21:50:33 -0700 Subject: [PATCH 20/60] Shrink ComponentId to u32 --- crates/bevy_ecs/src/component.rs | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/crates/bevy_ecs/src/component.rs b/crates/bevy_ecs/src/component.rs index 17f9c7054b008..454ada8c91bf4 100644 --- a/crates/bevy_ecs/src/component.rs +++ b/crates/bevy_ecs/src/component.rs @@ -7,7 +7,7 @@ use crate::{ }; pub use bevy_ecs_macros::Component; use bevy_ptr::OwningPtr; -use nonmax::NonMaxUsize; +use nonmax::NonMaxU32; use std::{ alloc::Layout, any::{Any, TypeId}, @@ -139,36 +139,38 @@ impl ComponentInfo { } #[derive(Debug, Copy, Clone, Hash, Ord, PartialOrd, Eq, PartialEq)] -pub struct ComponentId(NonMaxUsize); +pub struct ComponentId(NonMaxU32); impl ComponentId { /// Creates a new [`ComponentId`] from an index without /// checking for the type's invariants. /// /// # Safety - /// This function is only safe if `index` is not equal to [`usize::MAX`]. + /// This function is only safe if `index` is less than [`u32::MAX`]. #[inline] pub const unsafe fn new_unchecked(index: usize) -> ComponentId { - ComponentId(NonMaxUsize::new_unchecked(index)) + ComponentId(NonMaxU32::new_unchecked(index as u32)) } /// Creates a new [`ComponentId`] from an index. /// /// # Panic - /// This function will panic if `index` is equal to [`usize::MAX`]. + /// This function will panic if `index` is greater than or equal to [`u32::MAX`]. #[inline] pub fn new(index: usize) -> ComponentId { - ComponentId(NonMaxUsize::new(index).unwrap()) + assert!(index < u32::MAX as usize); + // SAFE: The above assertion will fail if the value is not valid. + unsafe { Self(NonMaxU32::new_unchecked(index as u32)) } } #[inline] pub fn index(self) -> usize { - self.0.get() + self.0.get() as usize } } impl SparseSetIndex for ComponentId { - type Repr = NonMaxUsize; + type Repr = NonMaxU32; #[inline] fn sparse_set_index(&self) -> usize { @@ -181,7 +183,9 @@ impl SparseSetIndex for ComponentId { #[inline] fn repr_from_index(index: usize) -> Self::Repr { - NonMaxUsize::new(index).unwrap() + assert!(index < u32::MAX as usize); + // SAFE: The above assertion will fail if the value is not valid. + unsafe { NonMaxU32::new_unchecked(index as u32) } } #[inline] @@ -316,7 +320,7 @@ impl Components { #[inline] pub fn get_info(&self, id: ComponentId) -> Option<&ComponentInfo> { - self.components.get(id.0.get()) + self.components.get(id.index()) } /// # Safety @@ -325,7 +329,7 @@ impl Components { #[inline] pub unsafe fn get_info_unchecked(&self, id: ComponentId) -> &ComponentInfo { debug_assert!(id.index() < self.components.len()); - self.components.get_unchecked(id.0.get()) + self.components.get_unchecked(id.index()) } #[inline] From 96ddad108a838a36b3b1cd04b6321bc78f72718d Mon Sep 17 00:00:00 2001 From: james7132 Date: Sat, 21 May 2022 21:58:22 -0700 Subject: [PATCH 21/60] Shrink ArchetypeId to u32 --- crates/bevy_ecs/src/archetype.rs | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/crates/bevy_ecs/src/archetype.rs b/crates/bevy_ecs/src/archetype.rs index cb2edd8853454..12b0e8ff68a65 100644 --- a/crates/bevy_ecs/src/archetype.rs +++ b/crates/bevy_ecs/src/archetype.rs @@ -7,7 +7,7 @@ use crate::{ entity::{Entity, EntityLocation}, storage::{Column, SparseArray, SparseSet, SparseSetIndex, TableId}, }; -use nonmax::NonMaxUsize; +use nonmax::{NonMaxU32, NonMaxUsize}; use std::{ collections::HashMap, hash::Hash, @@ -15,7 +15,7 @@ use std::{ }; #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -pub struct ArchetypeId(NonMaxUsize); +pub struct ArchetypeId(NonMaxU32); impl ArchetypeId { pub const EMPTY: ArchetypeId = unsafe { ArchetypeId::new_unchecked(0) }; @@ -24,26 +24,31 @@ impl ArchetypeId { /// Creates a new [`ArchetypeId`]. /// /// # Panics - /// This will panic if `index` is `usize::MAX`. + /// This will panic if `index` is `u32::MAX` or greater. #[inline] pub fn new(index: usize) -> Self { - Self(NonMaxUsize::new(index).expect("ArchetypeID cannot be created from usize::MAX")) + assert!( + index < u32::MAX as usize, + "ArchetypeID cannot be u32::MAX or greater" + ); + // SAFE: The above assertion will fail if the value is not valid. + unsafe { Self(NonMaxU32::new_unchecked(index as u32)) } } /// Creates a new [`ArchetypeId`] without checking for validity of the /// provided index. /// /// # Safety - /// `index` must not be `usize::MAX` + /// This is only safe if `index` is not `u32::MAX` or greater. #[inline] pub const unsafe fn new_unchecked(index: usize) -> Self { - Self(NonMaxUsize::new_unchecked(index)) + Self(NonMaxU32::new_unchecked(index as u32)) } /// Gets the corresponding index for the ID. #[inline] pub const fn index(self) -> usize { - self.0.get() + self.0.get() as usize } } From 0a275db0b90dbad536a9384376fb9e0cca00c9b0 Mon Sep 17 00:00:00 2001 From: james7132 Date: Sat, 21 May 2022 22:14:18 -0700 Subject: [PATCH 22/60] Shrink BundleId to u32 --- crates/bevy_ecs/src/bundle.rs | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/crates/bevy_ecs/src/bundle.rs b/crates/bevy_ecs/src/bundle.rs index 2f72c842cc0c1..47cdc26f75cc3 100644 --- a/crates/bevy_ecs/src/bundle.rs +++ b/crates/bevy_ecs/src/bundle.rs @@ -12,7 +12,7 @@ use crate::{ }; use bevy_ecs_macros::all_tuples; use bevy_ptr::OwningPtr; -use nonmax::NonMaxUsize; +use nonmax::NonMaxU32; use std::{any::TypeId, collections::HashMap}; /// An ordered collection of [`Component`]s. @@ -131,7 +131,7 @@ macro_rules! tuple_impl { all_tuples!(tuple_impl, 0, 15, C); #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] -pub struct BundleId(NonMaxUsize); +pub struct BundleId(NonMaxU32); impl BundleId { /// Creates a new [`BundleId`] from an index without checking for the @@ -141,7 +141,7 @@ impl BundleId { /// `value` must not be [`usize::MAX`]. #[inline] pub const unsafe fn new_unchecked(value: usize) -> Self { - Self(NonMaxUsize::new_unchecked(value)) + Self(NonMaxU32::new_unchecked(value as u32)) } /// Creates a new [`BundleId`] from an index. @@ -149,18 +149,23 @@ impl BundleId { /// # Panic /// This function will panic if `value` is equal to [`usize::MAX`]. #[inline] - pub fn new(value: usize) -> Self { - Self(NonMaxUsize::new(value).unwrap()) + pub const fn new(value: usize) -> Self { + assert!( + value < u32::MAX as usize,148ggkjjkkkkkkkkkkkkkkk + "BundleID cannot be u32::MAX or greater" + ); + // SAFE: The above assertion will fail if the value is not valid. + unsafe { Self::new_unchecked(value) } } #[inline] pub fn index(self) -> usize { - self.0.get() + self.0.get() as usize } } impl SparseSetIndex for BundleId { - type Repr = NonMaxUsize; + type Repr = NonMaxU32; #[inline] fn sparse_set_index(&self) -> usize { @@ -174,7 +179,7 @@ impl SparseSetIndex for BundleId { #[inline] fn repr_from_index(index: usize) -> Self::Repr { - NonMaxUsize::new(index).unwrap() + NonMaxU32::new(index as u32).unwrap() } #[inline] From a0edde8ad823d600bd888c33be1621272b46688a Mon Sep 17 00:00:00 2001 From: james7132 Date: Sat, 21 May 2022 22:35:42 -0700 Subject: [PATCH 23/60] Shrink ArchetypeComponentId --- crates/bevy_ecs/src/archetype.rs | 23 ++++++++++++++--------- crates/bevy_ecs/src/bundle.rs | 6 +++--- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/crates/bevy_ecs/src/archetype.rs b/crates/bevy_ecs/src/archetype.rs index 12b0e8ff68a65..e261d5eeba3de 100644 --- a/crates/bevy_ecs/src/archetype.rs +++ b/crates/bevy_ecs/src/archetype.rs @@ -7,7 +7,7 @@ use crate::{ entity::{Entity, EntityLocation}, storage::{Column, SparseArray, SparseSet, SparseSetIndex, TableId}, }; -use nonmax::{NonMaxU32, NonMaxUsize}; +use nonmax::NonMaxU32; use std::{ collections::HashMap, hash::Hash, @@ -355,7 +355,7 @@ pub struct ArchetypeIdentity { } #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -pub struct ArchetypeComponentId(NonMaxUsize); +pub struct ArchetypeComponentId(NonMaxU32); impl ArchetypeComponentId { /// Creates a new [`ArchetypeComponentId`] from an index without @@ -365,7 +365,7 @@ impl ArchetypeComponentId { /// `index` must not be [`usize::MAX`]. #[inline] pub const unsafe fn new_unchecked(index: usize) -> Self { - Self(NonMaxUsize::new_unchecked(index)) + Self(NonMaxU32::new_unchecked(index as u32)) } /// Creates a new [`ArchetypeComponentId`] from an index. @@ -373,18 +373,23 @@ impl ArchetypeComponentId { /// # Panic /// This function will panic if `index` is equal to [`usize::MAX`]. #[inline] - pub fn new(index: usize) -> Self { - Self(NonMaxUsize::new(index).unwrap()) + pub const fn new(index: usize) -> Self { + assert!( + index < u32::MAX as usize, + "ArchetypeComponentId cannot be u32::MAX or greater" + ); + // SAFE: The above assertion will fail if the value is not valid. + unsafe { Self::new_unchecked(index) } } #[inline] pub fn index(self) -> usize { - self.0.get() + self.0.get() as usize } } impl SparseSetIndex for ArchetypeComponentId { - type Repr = NonMaxUsize; + type Repr = NonMaxU32; #[inline] fn sparse_set_index(&self) -> usize { @@ -398,12 +403,12 @@ impl SparseSetIndex for ArchetypeComponentId { #[inline] fn repr_from_index(index: usize) -> Self::Repr { - NonMaxUsize::new(index).unwrap() + NonMaxU32::new(index as u32).unwrap() } #[inline] fn repr_to_index(repr: &Self::Repr) -> usize { - repr.get() + repr.get() as usize } } diff --git a/crates/bevy_ecs/src/bundle.rs b/crates/bevy_ecs/src/bundle.rs index 47cdc26f75cc3..0ffccced0cacb 100644 --- a/crates/bevy_ecs/src/bundle.rs +++ b/crates/bevy_ecs/src/bundle.rs @@ -138,7 +138,7 @@ impl BundleId { /// type's invariants. /// /// # Safety - /// `value` must not be [`usize::MAX`]. + /// `value` must be less than [`u32::MAX`]. #[inline] pub const unsafe fn new_unchecked(value: usize) -> Self { Self(NonMaxU32::new_unchecked(value as u32)) @@ -147,11 +147,11 @@ impl BundleId { /// Creates a new [`BundleId`] from an index. /// /// # Panic - /// This function will panic if `value` is equal to [`usize::MAX`]. + /// This function will panic if `value` is greater than or equal to [`u32::MAX`]. #[inline] pub const fn new(value: usize) -> Self { assert!( - value < u32::MAX as usize,148ggkjjkkkkkkkkkkkkkkk + value < u32::MAX as usize, "BundleID cannot be u32::MAX or greater" ); // SAFE: The above assertion will fail if the value is not valid. From 3ae86f0ff702807af131c6ea62ab67fddc6241b6 Mon Sep 17 00:00:00 2001 From: james7132 Date: Tue, 24 May 2022 21:53:34 -0700 Subject: [PATCH 24/60] cleanup ComponentId mappingsk --- crates/bevy_ecs/src/component.rs | 32 ++++++++++++++------------------ 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/crates/bevy_ecs/src/component.rs b/crates/bevy_ecs/src/component.rs index 454ada8c91bf4..988642afbc025 100644 --- a/crates/bevy_ecs/src/component.rs +++ b/crates/bevy_ecs/src/component.rs @@ -286,8 +286,8 @@ impl ComponentDescriptor { #[derive(Debug, Default)] pub struct Components { components: Vec, - indices: std::collections::HashMap, - resource_indices: std::collections::HashMap, + indices: std::collections::HashMap, + resource_indices: std::collections::HashMap, } impl Components { @@ -295,17 +295,17 @@ impl Components { pub fn init_component(&mut self, storages: &mut Storages) -> ComponentId { let type_id = TypeId::of::(); let components = &mut self.components; - let index = self.indices.entry(type_id).or_insert_with(|| { - let index = components.len(); + let id = self.indices.entry(type_id).or_insert_with(|| { + let id = ComponentId::new(components.len()); let descriptor = ComponentDescriptor::new::(); - let info = ComponentInfo::new(ComponentId::new(index), descriptor); + let info = ComponentInfo::new(id, descriptor); if T::Storage::STORAGE_TYPE == StorageType::SparseSet { storages.sparse_sets.get_or_insert(&info); } components.push(info); - index + id }); - ComponentId::new(*index) + *id } #[inline] @@ -334,16 +334,12 @@ impl Components { #[inline] pub fn get_id(&self, type_id: TypeId) -> Option { - self.indices - .get(&type_id) - .map(|index| ComponentId::new(*index)) + self.indices.get(&type_id).copied() } #[inline] pub fn get_resource_id(&self, type_id: TypeId) -> Option { - self.resource_indices - .get(&type_id) - .map(|index| ComponentId::new(*index)) + self.resource_indices.get(&type_id).copied() } #[inline] @@ -376,14 +372,14 @@ impl Components { func: impl FnOnce() -> ComponentDescriptor, ) -> ComponentId { let components = &mut self.components; - let index = self.resource_indices.entry(type_id).or_insert_with(|| { + let id = self.resource_indices.entry(type_id).or_insert_with(|| { let descriptor = func(); - let index = components.len(); - components.push(ComponentInfo::new(ComponentId::new(index), descriptor)); - index + let id = ComponentId::new(components.len()); + components.push(ComponentInfo::new(id, descriptor)); + id }); - ComponentId::new(*index) + *id } } From 83a8b577b5be81ebfc77f230e4fce78616686847 Mon Sep 17 00:00:00 2001 From: james7132 Date: Tue, 24 May 2022 22:15:05 -0700 Subject: [PATCH 25/60] Optimize ComponentSparseSet --- crates/bevy_ecs/src/storage/sparse_set.rs | 33 ++++++++++++----------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/crates/bevy_ecs/src/storage/sparse_set.rs b/crates/bevy_ecs/src/storage/sparse_set.rs index be01d043c4916..1c3327d0de4d6 100644 --- a/crates/bevy_ecs/src/storage/sparse_set.rs +++ b/crates/bevy_ecs/src/storage/sparse_set.rs @@ -98,7 +98,7 @@ pub struct ComponentSparseSet { dense: BlobVec, ticks: Vec>, entities: Vec, - sparse: SparseArray, + sparse: SparseArray, } impl ComponentSparseSet { @@ -139,13 +139,14 @@ impl ComponentSparseSet { /// inside the [`ComponentInfo`] given when constructing this sparse set. pub unsafe fn insert(&mut self, entity: Entity, value: OwningPtr<'_>, change_tick: u32) { if let Some(&dense_index) = self.sparse.get(entity) { + let dense_index = dense_index.get(); self.dense.replace_unchecked(dense_index, value); *self.ticks.get_unchecked_mut(dense_index) = UnsafeCell::new(ComponentTicks::new(change_tick)); } else { let dense_index = self.dense.len(); self.dense.push(value); - self.sparse.insert(entity, dense_index); + self.sparse.insert(entity, NonMaxUsize::new(dense_index).unwrap()); debug_assert_eq!(self.ticks.len(), dense_index); debug_assert_eq!(self.entities.len(), dense_index); self.ticks @@ -163,13 +164,13 @@ impl ComponentSparseSet { pub fn get(&self, entity: Entity) -> Option> { self.sparse.get(entity).map(|dense_index| { // SAFE: if the sparse index points to something in the dense vec, it exists - unsafe { self.dense.get_unchecked(*dense_index) } + unsafe { self.dense.get_unchecked(dense_index.get()) } }) } #[inline] pub fn get_with_ticks(&self, entity: Entity) -> Option<(Ptr<'_>, &UnsafeCell)> { - let dense_index = *self.sparse.get(entity)?; + let dense_index = self.sparse.get(entity)?.get(); // SAFE: if the sparse index points to something in the dense vec, it exists unsafe { Some(( @@ -181,7 +182,7 @@ impl ComponentSparseSet { #[inline] pub fn get_ticks(&self, entity: Entity) -> Option<&UnsafeCell> { - let dense_index = *self.sparse.get(entity)?; + let dense_index = self.sparse.get(entity)?.get(); // SAFE: if the sparse index points to something in the dense vec, it exists unsafe { Some(self.ticks.get_unchecked(dense_index)) } } @@ -191,13 +192,14 @@ impl ComponentSparseSet { #[must_use = "The returned pointer must be used to drop the removed component."] pub fn remove_and_forget(&mut self, entity: Entity) -> Option> { self.sparse.remove(entity).map(|dense_index| { - self.ticks.swap_remove(dense_index); - self.entities.swap_remove(dense_index); - let is_last = dense_index == self.dense.len() - 1; + let index = dense_index.get(); + self.ticks.swap_remove(index); + self.entities.swap_remove(index); + let is_last = index == self.dense.len() - 1; // SAFE: dense_index was just removed from `sparse`, which ensures that it is valid - let value = unsafe { self.dense.swap_remove_and_forget_unchecked(dense_index) }; + let value = unsafe { self.dense.swap_remove_and_forget_unchecked(index) }; if !is_last { - let swapped_entity = self.entities[dense_index]; + let swapped_entity = self.entities[index]; *self.sparse.get_mut(swapped_entity).unwrap() = dense_index; } value @@ -206,13 +208,14 @@ impl ComponentSparseSet { pub fn remove(&mut self, entity: Entity) -> bool { if let Some(dense_index) = self.sparse.remove(entity) { - self.ticks.swap_remove(dense_index); - self.entities.swap_remove(dense_index); - let is_last = dense_index == self.dense.len() - 1; + let index = dense_index.get(); + self.ticks.swap_remove(index); + self.entities.swap_remove(index); + let is_last = index == self.dense.len() - 1; // SAFE: if the sparse index points to something in the dense vec, it exists - unsafe { self.dense.swap_remove_and_drop_unchecked(dense_index) } + unsafe { self.dense.swap_remove_and_drop_unchecked(index) } if !is_last { - let swapped_entity = self.entities[dense_index]; + let swapped_entity = self.entities[index]; *self.sparse.get_mut(swapped_entity).unwrap() = dense_index; } true From e74dc1f9051685967ccbbfb3975f5d0049b26878 Mon Sep 17 00:00:00 2001 From: james7132 Date: Tue, 24 May 2022 22:23:36 -0700 Subject: [PATCH 26/60] Formatting --- crates/bevy_ecs/src/storage/sparse_set.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/bevy_ecs/src/storage/sparse_set.rs b/crates/bevy_ecs/src/storage/sparse_set.rs index 1c3327d0de4d6..dd3901c06571b 100644 --- a/crates/bevy_ecs/src/storage/sparse_set.rs +++ b/crates/bevy_ecs/src/storage/sparse_set.rs @@ -146,7 +146,8 @@ impl ComponentSparseSet { } else { let dense_index = self.dense.len(); self.dense.push(value); - self.sparse.insert(entity, NonMaxUsize::new(dense_index).unwrap()); + self.sparse + .insert(entity, NonMaxUsize::new(dense_index).unwrap()); debug_assert_eq!(self.ticks.len(), dense_index); debug_assert_eq!(self.entities.len(), dense_index); self.ticks From c70b98baa61b2fc6c04c9608baf79bb9fe1cf8ab Mon Sep 17 00:00:00 2001 From: james7132 Date: Tue, 31 May 2022 14:01:21 -0700 Subject: [PATCH 27/60] Fix CI --- crates/bevy_ecs/src/entity/mod.rs | 2 +- crates/bevy_ecs/src/query/access.rs | 81 +++++++++++-------------- crates/bevy_ecs/src/world/entity_ref.rs | 4 +- crates/bevy_ecs/src/world/mod.rs | 2 +- 4 files changed, 38 insertions(+), 51 deletions(-) diff --git a/crates/bevy_ecs/src/entity/mod.rs b/crates/bevy_ecs/src/entity/mod.rs index 1ca9e70f4c288..689e9b593e002 100644 --- a/crates/bevy_ecs/src/entity/mod.rs +++ b/crates/bevy_ecs/src/entity/mod.rs @@ -640,6 +640,6 @@ mod tests { assert_eq!( std::mem::size_of::(), std::mem::size_of::>(), - ) + ); } } diff --git a/crates/bevy_ecs/src/query/access.rs b/crates/bevy_ecs/src/query/access.rs index af7b02ed2be62..8b3dd275d412d 100644 --- a/crates/bevy_ecs/src/query/access.rs +++ b/crates/bevy_ecs/src/query/access.rs @@ -363,67 +363,54 @@ impl Default for FilteredAccessSet { #[cfg(test)] mod tests { use crate::query::{Access, FilteredAccess}; - use nonmax::NonMaxUsize; #[test] fn access_get_conflicts() { - let mut access_a = Access::::default(); - access_a.add_read(NonMaxUsize::new(0).unwrap()); - access_a.add_read(NonMaxUsize::new(1).unwrap()); - - let mut access_b = Access::::default(); - access_b.add_read(NonMaxUsize::new(0).unwrap()); - access_b.add_write(NonMaxUsize::new(1).unwrap()); - - assert_eq!( - access_a.get_conflicts(&access_b), - vec![NonMaxUsize::new(1).unwrap()] - ); - - let mut access_c = Access::::default(); - access_c.add_write(NonMaxUsize::new(0).unwrap()); - access_c.add_write(NonMaxUsize::new(1).unwrap()); - - assert_eq!( - access_a.get_conflicts(&access_c), - vec![NonMaxUsize::new(0).unwrap(), NonMaxUsize::new(1).unwrap()] - ); - assert_eq!( - access_b.get_conflicts(&access_c), - vec![NonMaxUsize::new(0).unwrap(), NonMaxUsize::new(1).unwrap()] - ); - - let mut access_d = Access::::default(); - access_d.add_read(NonMaxUsize::new(0).unwrap()); + let mut access_a = Access::::default(); + access_a.add_read(0); + access_a.add_read(1); + + let mut access_b = Access::::default(); + access_b.add_read(0); + access_b.add_write(1); + + assert_eq!(access_a.get_conflicts(&access_b), vec![1]); + + let mut access_c = Access::::default(); + access_c.add_write(0); + access_c.add_write(1); + + assert_eq!(access_a.get_conflicts(&access_c), vec![0, 1]); + assert_eq!(access_b.get_conflicts(&access_c), vec![0, 1]); + + let mut access_d = Access::::default(); + access_d.add_read(0); assert_eq!(access_d.get_conflicts(&access_a), vec![]); assert_eq!(access_d.get_conflicts(&access_b), vec![]); - assert_eq!( - access_d.get_conflicts(&access_c), - vec![NonMaxUsize::new(0).unwrap()] - ); + assert_eq!(access_d.get_conflicts(&access_c), vec![0]); } #[test] fn filtered_access_extend() { - let mut access_a = FilteredAccess::::default(); - access_a.add_read(NonMaxUsize::new(0).unwrap()); - access_a.add_read(NonMaxUsize::new(1).unwrap()); - access_a.add_with(NonMaxUsize::new(2).unwrap()); + let mut access_a = FilteredAccess::::default(); + access_a.add_read(0); + access_a.add_read(1); + access_a.add_with(2); - let mut access_b = FilteredAccess::::default(); - access_b.add_read(NonMaxUsize::new(0).unwrap()); - access_b.add_write(NonMaxUsize::new(3).unwrap()); - access_b.add_without(NonMaxUsize::new(4).unwrap()); + let mut access_b = FilteredAccess::::default(); + access_b.add_read(0); + access_b.add_write(3); + access_b.add_without(4); access_a.extend(&access_b); - let mut expected = FilteredAccess::::default(); - expected.add_read(NonMaxUsize::new(0).unwrap()); - expected.add_read(NonMaxUsize::new(1).unwrap()); - expected.add_with(NonMaxUsize::new(2).unwrap()); - expected.add_write(NonMaxUsize::new(3).unwrap()); - expected.add_without(NonMaxUsize::new(4).unwrap()); + let mut expected = FilteredAccess::::default(); + expected.add_read(0); + expected.add_read(1); + expected.add_with(2); + expected.add_write(3); + expected.add_without(4); assert!(access_a.eq(&expected)); } diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index d19e094ff98fa..4f0649f9d0e15 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -962,7 +962,7 @@ mod tests { #[test] fn entity_ref_get_by_id_invalid_component_id() { - let invalid_component_id = ComponentId::new(usize::MAX); + let invalid_component_id = ComponentId::new((u32::MAX - 1) as usize); let mut world = World::new(); let entity = world.spawn().id(); @@ -972,7 +972,7 @@ mod tests { #[test] fn entity_mut_get_by_id_invalid_component_id() { - let invalid_component_id = ComponentId::new(usize::MAX); + let invalid_component_id = ComponentId::new((u32::MAX - 1) as usize); let mut world = World::new(); let mut entity = world.spawn(); diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index a3d2acadf6d6b..600a375d02402 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -1719,7 +1719,7 @@ mod tests { #[test] #[should_panic = "insert_resource_by_id called with component id which doesn't exist in this world"] fn insert_resource_by_id_invalid_component_id() { - let invalid_component_id = ComponentId::new(usize::MAX); + let invalid_component_id = ComponentId::new((u32::MAX - 1) as usize); let mut world = World::new(); OwningPtr::make((), |ptr| unsafe { From 675cdca80f2ffddad952abbf79defa00a98d65cb Mon Sep 17 00:00:00 2001 From: james7132 Date: Sun, 20 Nov 2022 14:47:06 -0800 Subject: [PATCH 28/60] Fix CI --- crates/bevy_ecs/src/archetype.rs | 13 ++++++++----- crates/bevy_ecs/src/bundle.rs | 2 +- crates/bevy_ecs/src/component.rs | 4 ++-- crates/bevy_ecs/src/entity/mod.rs | 2 +- crates/bevy_ecs/src/storage/sparse_set.rs | 10 +++++----- 5 files changed, 17 insertions(+), 14 deletions(-) diff --git a/crates/bevy_ecs/src/archetype.rs b/crates/bevy_ecs/src/archetype.rs index 4314c524ac463..be6738e2c4e4d 100644 --- a/crates/bevy_ecs/src/archetype.rs +++ b/crates/bevy_ecs/src/archetype.rs @@ -18,7 +18,10 @@ use std::{ pub struct ArchetypeId(NonMaxU32); impl ArchetypeId { - pub const EMPTY: ArchetypeId = unsafe { ArchetypeId::new_unchecked(0) }; + pub const EMPTY: ArchetypeId = { + // SAFETY: 0 is guarenteed to not equal u32::MAX + unsafe { ArchetypeId::new_unchecked(0) } + }; /// Creates a new [`ArchetypeId`]. /// @@ -30,7 +33,7 @@ impl ArchetypeId { index < u32::MAX as usize, "ArchetypeID cannot be u32::MAX or greater" ); - // SAFE: The above assertion will fail if the value is not valid. + // SAFETY: The above assertion will fail if the value is not valid. unsafe { Self(NonMaxU32::new_unchecked(index as u32)) } } @@ -409,7 +412,7 @@ impl ArchetypeComponentId { index < u32::MAX as usize, "ArchetypeComponentId cannot be u32::MAX or greater" ); - // SAFE: The above assertion will fail if the value is not valid. + // SAFETY: The above assertion will fail if the value is not valid. unsafe { Self::new_unchecked(index) } } @@ -543,11 +546,11 @@ impl Archetypes { let table_start = *archetype_component_count; *archetype_component_count += table_components.len(); let table_archetype_components = - (table_start..*archetype_component_count).map(ArchetypeComponentId); + (table_start..*archetype_component_count).map(ArchetypeComponentId::new); let sparse_start = *archetype_component_count; *archetype_component_count += sparse_set_components.len(); let sparse_set_archetype_components = - (sparse_start..*archetype_component_count).map(ArchetypeComponentId); + (sparse_start..*archetype_component_count).map(ArchetypeComponentId::new); archetypes.push(Archetype::new( id, diff --git a/crates/bevy_ecs/src/bundle.rs b/crates/bevy_ecs/src/bundle.rs index 303af729c86cb..cf5ac28bc35d8 100644 --- a/crates/bevy_ecs/src/bundle.rs +++ b/crates/bevy_ecs/src/bundle.rs @@ -255,7 +255,7 @@ impl BundleId { value < u32::MAX as usize, "BundleID cannot be u32::MAX or greater" ); - // SAFE: The above assertion will fail if the value is not valid. + // SAFETY: The above assertion will fail if the value is not valid. unsafe { Self::new_unchecked(value) } } diff --git a/crates/bevy_ecs/src/component.rs b/crates/bevy_ecs/src/component.rs index aa9c927d01dc5..63f9dd3c5bc89 100644 --- a/crates/bevy_ecs/src/component.rs +++ b/crates/bevy_ecs/src/component.rs @@ -247,7 +247,7 @@ impl ComponentId { #[inline] pub fn new(index: usize) -> ComponentId { assert!(index < u32::MAX as usize); - // SAFE: The above assertion will fail if the value is not valid. + // SAFETY: The above assertion will fail if the value is not valid. unsafe { Self(NonMaxU32::new_unchecked(index as u32)) } } @@ -272,7 +272,7 @@ impl SparseSetIndex for ComponentId { #[inline] fn repr_from_index(index: usize) -> Self::Repr { assert!(index < u32::MAX as usize); - // SAFE: The above assertion will fail if the value is not valid. + // SAFETY: The above assertion will fail if the value is not valid. unsafe { NonMaxU32::new_unchecked(index as u32) } } diff --git a/crates/bevy_ecs/src/entity/mod.rs b/crates/bevy_ecs/src/entity/mod.rs index 03579b2486524..97d98527a7e1a 100644 --- a/crates/bevy_ecs/src/entity/mod.rs +++ b/crates/bevy_ecs/src/entity/mod.rs @@ -589,7 +589,7 @@ impl Entities { /// /// # Safety /// Flush _must_ set the entity location to the correct [`ArchetypeId`] for the given [`Entity`] - /// each time init is called. This _can_ be [`ArchetypeId::INVALID`], provided the [`Entity`] + /// each time init is called. This _can_ be `None`, provided the [`Entity`] /// has not been assigned to an [`Archetype`][crate::archetype::Archetype]. /// /// Note: freshly-allocated entities (ones which don't come from the pending list) are guaranteed diff --git a/crates/bevy_ecs/src/storage/sparse_set.rs b/crates/bevy_ecs/src/storage/sparse_set.rs index 7cc4417de033f..7d6725744783e 100644 --- a/crates/bevy_ecs/src/storage/sparse_set.rs +++ b/crates/bevy_ecs/src/storage/sparse_set.rs @@ -277,10 +277,10 @@ pub struct SparseSet { /// A space-optimized version of [`SparseSet`] that cannot be changed /// after construction. #[derive(Debug)] -pub(crate) struct ImmutableSparseSet { +pub(crate) struct ImmutableSparseSet { dense: Box<[V]>, indices: Box<[I]>, - sparse: ImmutableSparseArray, + sparse: ImmutableSparseArray, } macro_rules! impl_sparse_set { @@ -299,7 +299,7 @@ macro_rules! impl_sparse_set { pub fn get(&self, index: I) -> Option<&V> { self.sparse.get(index).map(|dense_index| { // SAFETY: if the sparse index points to something in the dense vec, it exists - unsafe { self.dense.get_unchecked(*dense_index) } + unsafe { self.dense.get_unchecked(I::repr_to_index(dense_index)) } }) } @@ -307,7 +307,7 @@ macro_rules! impl_sparse_set { let dense = &mut self.dense; self.sparse.get(index).map(move |dense_index| { // SAFETY: if the sparse index points to something in the dense vec, it exists - unsafe { dense.get_unchecked_mut(*dense_index) } + unsafe { dense.get_unchecked_mut(I::repr_to_index(dense_index)) } }) } @@ -383,7 +383,7 @@ impl SparseSet { pub fn get_or_insert_with(&mut self, index: I, func: impl FnOnce() -> V) -> &mut V { if let Some(dense_index) = self.sparse.get(index.clone()).cloned() { - // SAFE: dense indices stored in self.sparse always exist + // SAFETY: dense indices stored in self.sparse always exist unsafe { self.dense.get_unchecked_mut(I::repr_to_index(&dense_index)) } } else { let value = func(); From e23485bd9efef1b4460adff14662f725567a41a1 Mon Sep 17 00:00:00 2001 From: james7132 Date: Tue, 22 Nov 2022 21:08:36 -0800 Subject: [PATCH 29/60] Use NonZeroU32 for Components --- crates/bevy_ecs/src/component.rs | 44 ++++++++++++++++++++++++-------- 1 file changed, 33 insertions(+), 11 deletions(-) diff --git a/crates/bevy_ecs/src/component.rs b/crates/bevy_ecs/src/component.rs index 2e5afb095f2d0..3809ed3aa40d1 100644 --- a/crates/bevy_ecs/src/component.rs +++ b/crates/bevy_ecs/src/component.rs @@ -6,7 +6,6 @@ use crate::{ system::Resource, }; pub use bevy_ecs_macros::Component; -use nonmax::NonMaxU32; use bevy_ptr::{OwningPtr, UnsafeCellDeref}; use std::cell::UnsafeCell; use std::{ @@ -14,6 +13,7 @@ use std::{ any::{Any, TypeId}, borrow::Cow, mem::needs_drop, + num::NonZeroU32, }; /// A data type that can be used to store data for an [entity]. @@ -228,28 +228,28 @@ impl ComponentInfo { /// one `World` to access the metadata of a `Component` in a different `World` is undefined behaviour /// and must not be attempted. #[derive(Debug, Copy, Clone, Hash, Ord, PartialOrd, Eq, PartialEq)] -pub struct ComponentId(NonMaxU32); +pub struct ComponentId(NonZeroU32); impl ComponentId { /// Creates a new [`ComponentId`] from an index without /// checking for the type's invariants. /// /// # Safety - /// This function is only safe if `index` is less than [`u32::MAX`]. + /// This function is only safe if `index` is not in the range of `0 < index <= u32::MAX`. #[inline] pub const unsafe fn new_unchecked(index: usize) -> ComponentId { - ComponentId(NonMaxU32::new_unchecked(index as u32)) + ComponentId(NonZeroU32::new_unchecked(index as u32)) } /// Creates a new [`ComponentId`] from an index. /// /// # Panic - /// This function will panic if `index` is greater than or equal to [`u32::MAX`]. + /// This function panic if `index` is not in the range of `0 < index <= u32::MAX`. #[inline] pub fn new(index: usize) -> ComponentId { - assert!(index < u32::MAX as usize); + assert!(index > 0 && index <= u32::MAX as usize); // SAFETY: The above assertion will fail if the value is not valid. - unsafe { Self(NonMaxU32::new_unchecked(index as u32)) } + unsafe { Self(NonZeroU32::new_unchecked(index as u32)) } } #[inline] @@ -259,7 +259,7 @@ impl ComponentId { } impl SparseSetIndex for ComponentId { - type Repr = NonMaxU32; + type Repr = NonZeroU32; #[inline] fn sparse_set_index(&self) -> usize { @@ -274,12 +274,12 @@ impl SparseSetIndex for ComponentId { fn repr_from_index(index: usize) -> Self::Repr { assert!(index < u32::MAX as usize); // SAFETY: The above assertion will fail if the value is not valid. - unsafe { NonMaxU32::new_unchecked(index as u32) } + unsafe { NonZeroU32::new_unchecked((index + 1) as u32) } } #[inline] fn repr_to_index(repr: &Self::Repr) -> usize { - repr.get() as usize + repr.get() as usize - 1 } } @@ -394,7 +394,7 @@ impl ComponentDescriptor { } } -#[derive(Debug, Default)] +#[derive(Debug)] pub struct Components { components: Vec, indices: std::collections::HashMap, @@ -545,6 +545,28 @@ impl Components { } } +impl Default for Components { + fn default() -> Self { + Components { + // Dummy value to fill the zero-index slot. + // Inaccessible except via Debug + components: vec![ComponentInfo { + id: ComponentId::new(1), + descriptor: ComponentDescriptor { + name: Cow::Borrowed(""), + storage_type: StorageType::Table, + is_send_and_sync: false, + type_id: None, + layout: Layout::from_size_align(1, 1).unwrap(), + drop: None, + }, + }], + indices: Default::default(), + resource_indices: Default::default(), + } + } +} + /// Used to track changes in state between system runs, e.g. components being added or accessed mutably. #[derive(Copy, Clone, Debug)] pub struct Tick { From ef3082ce76c9da3cb2a005535c1a0ee8003f118a Mon Sep 17 00:00:00 2001 From: james7132 Date: Wed, 23 Nov 2022 01:50:52 -0800 Subject: [PATCH 30/60] NonMax -> NonZero --- crates/bevy_ecs/Cargo.toml | 1 - crates/bevy_ecs/src/archetype.rs | 39 +++++++++++------------ crates/bevy_ecs/src/bundle.rs | 21 ++++++------ crates/bevy_ecs/src/component.rs | 3 +- crates/bevy_ecs/src/entity/mod.rs | 11 ++++--- crates/bevy_ecs/src/storage/sparse_set.rs | 23 +++++++------ 6 files changed, 51 insertions(+), 47 deletions(-) diff --git a/crates/bevy_ecs/Cargo.toml b/crates/bevy_ecs/Cargo.toml index 2e5f0d2c7f18a..000c2121a7d1c 100644 --- a/crates/bevy_ecs/Cargo.toml +++ b/crates/bevy_ecs/Cargo.toml @@ -26,7 +26,6 @@ thread_local = "1.1.4" fixedbitset = "0.4" fxhash = "0.2" downcast-rs = "1.2" -nonmax = "0.5" serde = { version = "1", features = ["derive"] } [dev-dependencies] diff --git a/crates/bevy_ecs/src/archetype.rs b/crates/bevy_ecs/src/archetype.rs index be6738e2c4e4d..81c26906ea03d 100644 --- a/crates/bevy_ecs/src/archetype.rs +++ b/crates/bevy_ecs/src/archetype.rs @@ -7,20 +7,20 @@ use crate::{ entity::{Entity, EntityLocation}, storage::{ImmutableSparseSet, SparseArray, SparseSet, SparseSetIndex, TableId}, }; -use nonmax::NonMaxU32; use std::{ collections::HashMap, hash::Hash, + num::NonZeroU32, ops::{Index, IndexMut}, }; #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -pub struct ArchetypeId(NonMaxU32); +pub struct ArchetypeId(NonZeroU32); impl ArchetypeId { pub const EMPTY: ArchetypeId = { // SAFETY: 0 is guarenteed to not equal u32::MAX - unsafe { ArchetypeId::new_unchecked(0) } + unsafe { Self::new_unchecked(1) } }; /// Creates a new [`ArchetypeId`]. @@ -28,13 +28,13 @@ impl ArchetypeId { /// # Panics /// This will panic if `index` is `u32::MAX` or greater. #[inline] - pub fn new(index: usize) -> Self { + pub(crate) fn new(index: usize) -> Self { assert!( - index < u32::MAX as usize, + index > 0 && index <= u32::MAX as usize, "ArchetypeID cannot be u32::MAX or greater" ); // SAFETY: The above assertion will fail if the value is not valid. - unsafe { Self(NonMaxU32::new_unchecked(index as u32)) } + unsafe { Self::new_unchecked(index) } } /// Creates a new [`ArchetypeId`] without checking for validity of the @@ -43,13 +43,13 @@ impl ArchetypeId { /// # Safety /// This is only safe if `index` is not `u32::MAX` or greater. #[inline] - pub const unsafe fn new_unchecked(index: usize) -> Self { - Self(NonMaxU32::new_unchecked(index as u32)) + pub(crate) const unsafe fn new_unchecked(index: usize) -> Self { + Self(NonZeroU32::new_unchecked(index as u32)) } /// Gets the corresponding index for the ID. #[inline] - pub const fn index(self) -> usize { + pub(crate) const fn index(self) -> usize { self.0.get() as usize } } @@ -389,7 +389,7 @@ pub struct ArchetypeIdentity { } #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -pub struct ArchetypeComponentId(NonMaxU32); +pub struct ArchetypeComponentId(NonZeroU32); impl ArchetypeComponentId { /// Creates a new [`ArchetypeComponentId`] from an index without @@ -398,8 +398,8 @@ impl ArchetypeComponentId { /// # Safety /// `index` must not be [`usize::MAX`]. #[inline] - pub const unsafe fn new_unchecked(index: usize) -> Self { - Self(NonMaxU32::new_unchecked(index as u32)) + pub(crate) const unsafe fn new_unchecked(index: usize) -> Self { + Self(NonZeroU32::new_unchecked(index as u32)) } /// Creates a new [`ArchetypeComponentId`] from an index. @@ -407,7 +407,7 @@ impl ArchetypeComponentId { /// # Panic /// This function will panic if `index` is equal to [`usize::MAX`]. #[inline] - pub const fn new(index: usize) -> Self { + pub(crate) const fn new(index: usize) -> Self { assert!( index < u32::MAX as usize, "ArchetypeComponentId cannot be u32::MAX or greater" @@ -415,15 +415,11 @@ impl ArchetypeComponentId { // SAFETY: The above assertion will fail if the value is not valid. unsafe { Self::new_unchecked(index) } } - - #[inline] - pub fn index(self) -> usize { - self.0.get() as usize - } } impl SparseSetIndex for ArchetypeComponentId { - type Repr = NonMaxU32; + type Repr = NonZeroU32; + const MAX_SIZE: usize = NonZeroU32::MAX_SIZE; #[inline] fn sparse_set_index(&self) -> usize { @@ -437,12 +433,13 @@ impl SparseSetIndex for ArchetypeComponentId { #[inline] fn repr_from_index(index: usize) -> Self::Repr { - NonMaxU32::new(index as u32).unwrap() + debug_assert!(index < Self::MAX_SIZE); + unsafe { NonZeroU32::new_unchecked((index + 1) as u32) } } #[inline] fn repr_to_index(repr: &Self::Repr) -> usize { - repr.get() as usize + repr.get() as usize - 1 } } diff --git a/crates/bevy_ecs/src/bundle.rs b/crates/bevy_ecs/src/bundle.rs index 4050951a1aea0..71b1bf15a7dd4 100644 --- a/crates/bevy_ecs/src/bundle.rs +++ b/crates/bevy_ecs/src/bundle.rs @@ -15,8 +15,7 @@ use crate::{ }; use bevy_ecs_macros::all_tuples; use bevy_ptr::OwningPtr; -use nonmax::NonMaxU32; -use std::{any::TypeId, collections::HashMap}; +use std::{any::TypeId, collections::HashMap, num::NonZeroU32}; /// The `Bundle` trait enables insertion and removal of [`Component`]s from an entity. /// @@ -232,7 +231,7 @@ macro_rules! tuple_impl { all_tuples!(tuple_impl, 0, 15, B); #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] -pub struct BundleId(NonMaxU32); +pub struct BundleId(NonZeroU32); impl BundleId { /// Creates a new [`BundleId`] from an index without checking for the @@ -241,8 +240,8 @@ impl BundleId { /// # Safety /// `value` must be less than [`u32::MAX`]. #[inline] - pub const unsafe fn new_unchecked(value: usize) -> Self { - Self(NonMaxU32::new_unchecked(value as u32)) + pub(crate) const unsafe fn new_unchecked(value: usize) -> Self { + Self(NonZeroU32::new_unchecked(value as u32)) } /// Creates a new [`BundleId`] from an index. @@ -250,9 +249,9 @@ impl BundleId { /// # Panic /// This function will panic if `value` is greater than or equal to [`u32::MAX`]. #[inline] - pub const fn new(value: usize) -> Self { + pub(crate) const fn new(value: usize) -> Self { assert!( - value < u32::MAX as usize, + value > 0 && value <= u32::MAX as usize, "BundleID cannot be u32::MAX or greater" ); // SAFETY: The above assertion will fail if the value is not valid. @@ -260,13 +259,14 @@ impl BundleId { } #[inline] - pub fn index(self) -> usize { + pub(crate) fn index(self) -> usize { self.0.get() as usize } } impl SparseSetIndex for BundleId { - type Repr = NonMaxU32; + type Repr = NonZeroU32; + const MAX_SIZE: usize = (u32::MAX - 1) as usize; #[inline] fn sparse_set_index(&self) -> usize { @@ -280,7 +280,8 @@ impl SparseSetIndex for BundleId { #[inline] fn repr_from_index(index: usize) -> Self::Repr { - NonMaxU32::new(index as u32).unwrap() + debug_assert!(index < Self::MAX_SIZE); + unsafe { NonZeroU32::new_unchecked((index + 1) as u32) } } #[inline] diff --git a/crates/bevy_ecs/src/component.rs b/crates/bevy_ecs/src/component.rs index 3809ed3aa40d1..2ea5e0f5208a5 100644 --- a/crates/bevy_ecs/src/component.rs +++ b/crates/bevy_ecs/src/component.rs @@ -260,6 +260,7 @@ impl ComponentId { impl SparseSetIndex for ComponentId { type Repr = NonZeroU32; + const MAX_SIZE: usize = ::MAX_SIZE; #[inline] fn sparse_set_index(&self) -> usize { @@ -272,7 +273,7 @@ impl SparseSetIndex for ComponentId { #[inline] fn repr_from_index(index: usize) -> Self::Repr { - assert!(index < u32::MAX as usize); + debug_assert!(index < Self::MAX_SIZE); // SAFETY: The above assertion will fail if the value is not valid. unsafe { NonZeroU32::new_unchecked((index + 1) as u32) } } diff --git a/crates/bevy_ecs/src/entity/mod.rs b/crates/bevy_ecs/src/entity/mod.rs index 97d98527a7e1a..a8eb300144d5d 100644 --- a/crates/bevy_ecs/src/entity/mod.rs +++ b/crates/bevy_ecs/src/entity/mod.rs @@ -35,9 +35,8 @@ mod map_entities; pub use map_entities::*; use crate::{archetype::ArchetypeId, storage::SparseSetIndex}; -use nonmax::NonMaxUsize; use serde::{Deserialize, Serialize}; -use std::{convert::TryFrom, fmt, mem, sync::atomic::Ordering}; +use std::{convert::TryFrom, fmt, mem, num::NonZeroUsize, sync::atomic::Ordering}; #[cfg(target_has_atomic = "64")] use std::sync::atomic::AtomicI64 as AtomicIdCursor; @@ -217,7 +216,8 @@ impl fmt::Debug for Entity { } impl SparseSetIndex for Entity { - type Repr = NonMaxUsize; + type Repr = NonZeroUsize; + const MAX_SIZE: usize = u32::MAX as usize; #[inline] fn sparse_set_index(&self) -> usize { @@ -231,12 +231,13 @@ impl SparseSetIndex for Entity { #[inline] fn repr_from_index(index: usize) -> Self::Repr { - NonMaxUsize::new(index).unwrap() + debug_assert!(index < Self::MAX_SIZE); + unsafe { NonZeroUsize::new_unchecked(index + 1) } } #[inline] fn repr_to_index(repr: &Self::Repr) -> usize { - repr.get() + repr.get() + 1 } } diff --git a/crates/bevy_ecs/src/storage/sparse_set.rs b/crates/bevy_ecs/src/storage/sparse_set.rs index e29e719017c6b..d5014a5f1fe34 100644 --- a/crates/bevy_ecs/src/storage/sparse_set.rs +++ b/crates/bevy_ecs/src/storage/sparse_set.rs @@ -4,7 +4,7 @@ use crate::{ storage::Column, }; use bevy_ptr::{OwningPtr, Ptr}; -use nonmax::{NonMaxU16, NonMaxU32, NonMaxU64, NonMaxU8, NonMaxUsize}; +use std::num::{NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU8, NonZeroUsize}; use std::{cell::UnsafeCell, hash::Hash, marker::PhantomData}; type EntityIndex = u32; @@ -453,6 +453,7 @@ impl SparseSet { /// is proportional to the **value** of those `usize`. pub trait SparseSetIndex: Clone + PartialEq + Eq + Hash { type Repr: Clone; + const MAX_SIZE: usize; fn sparse_set_index(&self) -> usize; fn get_sparse_set_index(value: usize) -> Self; // fn to_repr(&self) -> Self::Repr; @@ -464,6 +465,7 @@ macro_rules! impl_sparse_set_index { ($ty:ty) => { impl SparseSetIndex for $ty { type Repr = Self; + const MAX_SIZE: usize = Self::MAX as usize; #[inline] fn sparse_set_index(&self) -> usize { @@ -488,10 +490,11 @@ macro_rules! impl_sparse_set_index { }; } -macro_rules! impl_nonmax_sparse_set_index { +macro_rules! impl_nonzero_sparse_set_index { ($ty:ty, $underlying:ty) => { impl SparseSetIndex for $ty { type Repr = Self; + const MAX_SIZE: usize = (<$underlying>::MAX - 1) as usize; #[inline] fn sparse_set_index(&self) -> usize { @@ -505,12 +508,14 @@ macro_rules! impl_nonmax_sparse_set_index { #[inline] fn repr_from_index(index: usize) -> Self::Repr { - <$ty>::new(index as $underlying).unwrap() + debug_assert!(index < ::MAX_SIZE); + // SAFETY: Index is guarenteed to not exceed Self::MAX + unsafe { <$ty>::new_unchecked((index + 1) as $underlying) } } #[inline] fn repr_to_index(repr: &Self::Repr) -> usize { - repr.get() as usize + repr.get() as usize - 1 } } }; @@ -522,11 +527,11 @@ impl_sparse_set_index!(u32); impl_sparse_set_index!(u64); impl_sparse_set_index!(usize); -impl_nonmax_sparse_set_index!(NonMaxU8, u8); -impl_nonmax_sparse_set_index!(NonMaxU16, u16); -impl_nonmax_sparse_set_index!(NonMaxU32, u32); -impl_nonmax_sparse_set_index!(NonMaxU64, u64); -impl_nonmax_sparse_set_index!(NonMaxUsize, usize); +impl_nonzero_sparse_set_index!(NonZeroU8, u8); +impl_nonzero_sparse_set_index!(NonZeroU16, u16); +impl_nonzero_sparse_set_index!(NonZeroU32, u32); +impl_nonzero_sparse_set_index!(NonZeroU64, u64); +impl_nonzero_sparse_set_index!(NonZeroUsize, usize); /// A collection of [`ComponentSparseSet`] storages, indexed by [`ComponentId`] /// From ea2d275e9f7522a8034ea1052550c3a66c2126fa Mon Sep 17 00:00:00 2001 From: james7132 Date: Wed, 23 Nov 2022 02:05:25 -0800 Subject: [PATCH 31/60] De-niche ArchetypeComponentId --- crates/bevy_ecs/src/archetype.rs | 41 ++++++++++---------------------- crates/bevy_ecs/src/component.rs | 2 +- 2 files changed, 13 insertions(+), 30 deletions(-) diff --git a/crates/bevy_ecs/src/archetype.rs b/crates/bevy_ecs/src/archetype.rs index 81c26906ea03d..97fe6c46390fd 100644 --- a/crates/bevy_ecs/src/archetype.rs +++ b/crates/bevy_ecs/src/archetype.rs @@ -389,57 +389,40 @@ pub struct ArchetypeIdentity { } #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -pub struct ArchetypeComponentId(NonZeroU32); +pub struct ArchetypeComponentId(usize); impl ArchetypeComponentId { - /// Creates a new [`ArchetypeComponentId`] from an index without - /// checking for the type's invariants. - /// - /// # Safety - /// `index` must not be [`usize::MAX`]. - #[inline] - pub(crate) const unsafe fn new_unchecked(index: usize) -> Self { - Self(NonZeroU32::new_unchecked(index as u32)) - } - /// Creates a new [`ArchetypeComponentId`] from an index. - /// - /// # Panic - /// This function will panic if `index` is equal to [`usize::MAX`]. #[inline] pub(crate) const fn new(index: usize) -> Self { - assert!( - index < u32::MAX as usize, - "ArchetypeComponentId cannot be u32::MAX or greater" - ); - // SAFETY: The above assertion will fail if the value is not valid. - unsafe { Self::new_unchecked(index) } + Self(index) } } impl SparseSetIndex for ArchetypeComponentId { - type Repr = NonZeroU32; - const MAX_SIZE: usize = NonZeroU32::MAX_SIZE; + type Repr = usize; + const MAX_SIZE: usize = usize::MAX; #[inline] fn sparse_set_index(&self) -> usize { - self.0.get() as usize + self.0 } #[inline] fn get_sparse_set_index(value: usize) -> Self { - Self::new(value) + Self(value) } #[inline] - fn repr_from_index(index: usize) -> Self::Repr { - debug_assert!(index < Self::MAX_SIZE); - unsafe { NonZeroU32::new_unchecked((index + 1) as u32) } + fn repr_from_index(_index: usize) -> Self::Repr { + // Intentionally unimplemented, not used in SparseSets/SparseArrays as key + unimplemented!(); } #[inline] - fn repr_to_index(repr: &Self::Repr) -> usize { - repr.get() as usize - 1 + fn repr_to_index(_repr: &Self::Repr) -> usize { + // Intentionally unimplemented, not used in SparseSets/SparseArrays as key + unimplemented!(); } } diff --git a/crates/bevy_ecs/src/component.rs b/crates/bevy_ecs/src/component.rs index 2ea5e0f5208a5..c2d63c52f727e 100644 --- a/crates/bevy_ecs/src/component.rs +++ b/crates/bevy_ecs/src/component.rs @@ -260,7 +260,7 @@ impl ComponentId { impl SparseSetIndex for ComponentId { type Repr = NonZeroU32; - const MAX_SIZE: usize = ::MAX_SIZE; + const MAX_SIZE: usize = NonZeroU32::MAX_SIZE; #[inline] fn sparse_set_index(&self) -> usize { From b8219fa1a1db359fae6275126689ee5d9a39a28a Mon Sep 17 00:00:00 2001 From: james7132 Date: Wed, 23 Nov 2022 02:25:00 -0800 Subject: [PATCH 32/60] Make repr_to_index unsafe --- crates/bevy_ecs/src/archetype.rs | 2 +- crates/bevy_ecs/src/bundle.rs | 6 +++- crates/bevy_ecs/src/component.rs | 7 ++-- crates/bevy_ecs/src/entity/mod.rs | 9 +++-- crates/bevy_ecs/src/storage/sparse_set.rs | 41 ++++++++++++++++++----- 5 files changed, 51 insertions(+), 14 deletions(-) diff --git a/crates/bevy_ecs/src/archetype.rs b/crates/bevy_ecs/src/archetype.rs index 97fe6c46390fd..9f2c1df05ae6a 100644 --- a/crates/bevy_ecs/src/archetype.rs +++ b/crates/bevy_ecs/src/archetype.rs @@ -414,7 +414,7 @@ impl SparseSetIndex for ArchetypeComponentId { } #[inline] - fn repr_from_index(_index: usize) -> Self::Repr { + unsafe fn repr_from_index(_index: usize) -> Self::Repr { // Intentionally unimplemented, not used in SparseSets/SparseArrays as key unimplemented!(); } diff --git a/crates/bevy_ecs/src/bundle.rs b/crates/bevy_ecs/src/bundle.rs index 71b1bf15a7dd4..7573eac3ed4c2 100644 --- a/crates/bevy_ecs/src/bundle.rs +++ b/crates/bevy_ecs/src/bundle.rs @@ -279,8 +279,12 @@ impl SparseSetIndex for BundleId { } #[inline] - fn repr_from_index(index: usize) -> Self::Repr { + unsafe fn repr_from_index(index: usize) -> Self::Repr { debug_assert!(index < Self::MAX_SIZE); + // SAFETY: Caller guarentees that `index` does not exceed `u32::MAX - 1`. + // This addition cannot overflow under these guarentees. + // + // Created index cannot be zero as it's being incremented by one. unsafe { NonZeroU32::new_unchecked((index + 1) as u32) } } diff --git a/crates/bevy_ecs/src/component.rs b/crates/bevy_ecs/src/component.rs index c2d63c52f727e..9a0b49dc08029 100644 --- a/crates/bevy_ecs/src/component.rs +++ b/crates/bevy_ecs/src/component.rs @@ -272,9 +272,12 @@ impl SparseSetIndex for ComponentId { } #[inline] - fn repr_from_index(index: usize) -> Self::Repr { + unsafe fn repr_from_index(index: usize) -> Self::Repr { debug_assert!(index < Self::MAX_SIZE); - // SAFETY: The above assertion will fail if the value is not valid. + // SAFETY: Caller guarentees that `index` does not exceed `u32::MAX - 1`. + // This addition cannot overflow under these guarentees. + // + // Created index cannot be zero as it's being incremented by one. unsafe { NonZeroU32::new_unchecked((index + 1) as u32) } } diff --git a/crates/bevy_ecs/src/entity/mod.rs b/crates/bevy_ecs/src/entity/mod.rs index a8eb300144d5d..63fbca859990d 100644 --- a/crates/bevy_ecs/src/entity/mod.rs +++ b/crates/bevy_ecs/src/entity/mod.rs @@ -230,14 +230,19 @@ impl SparseSetIndex for Entity { } #[inline] - fn repr_from_index(index: usize) -> Self::Repr { + unsafe fn repr_from_index(index: usize) -> Self::Repr { debug_assert!(index < Self::MAX_SIZE); + // SAFETY: Index is guarenteed not to exceed Self::MAX_SIZE. + // The created index cannot exceed u32::MAX, which is guarenteed + // to be least as big as usize::MAX on 32/64-bit platforms. + // + // Input cannot be zero as it's incremented by one. unsafe { NonZeroUsize::new_unchecked(index + 1) } } #[inline] fn repr_to_index(repr: &Self::Repr) -> usize { - repr.get() + 1 + repr.get() - 1 } } diff --git a/crates/bevy_ecs/src/storage/sparse_set.rs b/crates/bevy_ecs/src/storage/sparse_set.rs index d5014a5f1fe34..19ba394950a89 100644 --- a/crates/bevy_ecs/src/storage/sparse_set.rs +++ b/crates/bevy_ecs/src/storage/sparse_set.rs @@ -395,7 +395,14 @@ impl SparseSet { *self.dense.get_unchecked_mut(I::repr_to_index(&dense_index)) = value; } } else { - let dense = I::repr_from_index(self.dense.len()); + assert!( + self.dense.len() < I::MAX_SIZE, + "A SparseSet keyed by {} cannot exceed {} elements", + std::any::type_name::(), + I::MAX_SIZE + ); + // SAFETY: The above assertion esnures that the index is less than I::MAX_SIZE. + let dense = unsafe { I::repr_from_index(self.dense.len()) }; self.sparse.insert(index.clone(), dense); self.indices.push(index); self.dense.push(value); @@ -407,10 +414,17 @@ impl SparseSet { // SAFETY: dense indices stored in self.sparse always exist unsafe { self.dense.get_unchecked_mut(I::repr_to_index(&dense_index)) } } else { + assert!( + self.dense.len() < I::MAX_SIZE, + "A SparseSet keyed by {} cannot exceed {} elements", + std::any::type_name::(), + I::MAX_SIZE + ); let value = func(); let dense_index = self.dense.len(); - self.sparse - .insert(index.clone(), I::repr_from_index(dense_index)); + // SAFETY: The above assertion esnures that the index is less than I::MAX_SIZE. + let dense = unsafe { I::repr_from_index(dense_index) }; + self.sparse.insert(index.clone(), dense); self.indices.push(index); self.dense.push(value); // SAFETY: dense index was just populated above @@ -452,12 +466,23 @@ impl SparseSet { /// zero), as the number of bits needed to represent a `SparseSetIndex` in a `FixedBitSet` /// is proportional to the **value** of those `usize`. pub trait SparseSetIndex: Clone + PartialEq + Eq + Hash { + /// The internal representation type used to represent a dense index within a [`SparseArray`] + /// or [`SparseSet`]. + /// + /// It's advised to use a space-optimized type (i.e. the `std::num::NonZero*` types), as this + /// cut the overall memory overhead by ~50%. type Repr: Clone; + + /// The maximum number of elements that can be stored in a [`SparseArray`]/[`SparseSet`] + /// when keyed by this type. const MAX_SIZE: usize; + fn sparse_set_index(&self) -> usize; fn get_sparse_set_index(value: usize) -> Self; - // fn to_repr(&self) -> Self::Repr; - fn repr_from_index(index: usize) -> Self::Repr; + + /// # Safety + /// This function is only safe if `index <= Self::MAX_SIZE` + unsafe fn repr_from_index(index: usize) -> Self::Repr; fn repr_to_index(repr: &Self::Repr) -> usize; } @@ -478,7 +503,7 @@ macro_rules! impl_sparse_set_index { } #[inline] - fn repr_from_index(index: usize) -> Self::Repr { + unsafe fn repr_from_index(index: usize) -> Self::Repr { index as Self } @@ -507,9 +532,9 @@ macro_rules! impl_nonzero_sparse_set_index { } #[inline] - fn repr_from_index(index: usize) -> Self::Repr { + unsafe fn repr_from_index(index: usize) -> Self::Repr { debug_assert!(index < ::MAX_SIZE); - // SAFETY: Index is guarenteed to not exceed Self::MAX + // SAFETY: Index is guarenteed by the caller to not exceed Self::MAX_SIZE unsafe { <$ty>::new_unchecked((index + 1) as $underlying) } } From 92ca951aa898e2e01846d1dad703098c7de79fe0 Mon Sep 17 00:00:00 2001 From: james7132 Date: Wed, 23 Nov 2022 02:39:46 -0800 Subject: [PATCH 33/60] Zero-index pad Archetypes and Bundles --- crates/bevy_ecs/src/archetype.rs | 11 +++++++++-- crates/bevy_ecs/src/bundle.rs | 15 ++++++++++++++- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/crates/bevy_ecs/src/archetype.rs b/crates/bevy_ecs/src/archetype.rs index 9f2c1df05ae6a..befdc2479396a 100644 --- a/crates/bevy_ecs/src/archetype.rs +++ b/crates/bevy_ecs/src/archetype.rs @@ -373,7 +373,7 @@ pub struct ArchetypeGeneration(usize); impl ArchetypeGeneration { #[inline] pub const fn initial() -> Self { - ArchetypeGeneration(0) + ArchetypeGeneration(1) } #[inline] @@ -435,7 +435,14 @@ pub struct Archetypes { impl Default for Archetypes { fn default() -> Self { let mut archetypes = Archetypes { - archetypes: Vec::new(), + // Dummy value to fill the zero-index slot. + archetypes: vec![Archetype { + id: ArchetypeId::new(1), + table_id: TableId::empty(), + edges: Default::default(), + entities: Vec::new(), + components: SparseSet::new().into_immutable(), + }], archetype_ids: Default::default(), archetype_component_count: 0, }; diff --git a/crates/bevy_ecs/src/bundle.rs b/crates/bevy_ecs/src/bundle.rs index 7573eac3ed4c2..8ef2b86e4a49a 100644 --- a/crates/bevy_ecs/src/bundle.rs +++ b/crates/bevy_ecs/src/bundle.rs @@ -723,7 +723,6 @@ impl<'a, 'b> BundleSpawner<'a, 'b> { } } -#[derive(Default)] pub struct Bundles { bundle_infos: Vec, bundle_ids: HashMap, @@ -762,6 +761,20 @@ impl Bundles { } } +impl Default for Bundles { + fn default() -> Self { + Bundles { + // Dummy value to fill the zero-index slot. + bundle_infos: vec![BundleInfo { + id: BundleId::new(1), + component_ids: Vec::new(), + storage_types: Vec::new(), + }], + bundle_ids: Default::default(), + } + } +} + /// # Safety /// /// `component_id` must be valid [`ComponentId`]'s From 2aa3fe05dedd615255861f80f0fd12b66a12fb08 Mon Sep 17 00:00:00 2001 From: james7132 Date: Wed, 23 Nov 2022 02:40:10 -0800 Subject: [PATCH 34/60] Delete outdated test --- crates/bevy_ecs/src/archetype.rs | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/crates/bevy_ecs/src/archetype.rs b/crates/bevy_ecs/src/archetype.rs index befdc2479396a..c41aaae8d8dbe 100644 --- a/crates/bevy_ecs/src/archetype.rs +++ b/crates/bevy_ecs/src/archetype.rs @@ -578,16 +578,3 @@ impl IndexMut for Archetypes { &mut self.archetypes[index.index()] } } - -#[cfg(test)] -mod test { - use super::ArchetypeComponentId; - - #[test] - pub fn test_archetyype_component_id_size_optimized() { - assert_eq!( - core::mem::size_of::(), - core::mem::size_of::>(), - ); - } -} From e28d7ac9936cc3ad8ee71e0e81e73f31683100a2 Mon Sep 17 00:00:00 2001 From: james7132 Date: Wed, 23 Nov 2022 02:57:02 -0800 Subject: [PATCH 35/60] Fix CI --- crates/bevy_ecs/src/archetype.rs | 33 +++++++++++++---------- crates/bevy_ecs/src/storage/sparse_set.rs | 7 +++-- 2 files changed, 22 insertions(+), 18 deletions(-) diff --git a/crates/bevy_ecs/src/archetype.rs b/crates/bevy_ecs/src/archetype.rs index c41aaae8d8dbe..66ee6ca7fed69 100644 --- a/crates/bevy_ecs/src/archetype.rs +++ b/crates/bevy_ecs/src/archetype.rs @@ -10,7 +10,7 @@ use crate::{ use std::{ collections::HashMap, hash::Hash, - num::NonZeroU32, + num::{NonZeroU32, NonZeroUsize}, ops::{Index, IndexMut}, }; @@ -389,40 +389,45 @@ pub struct ArchetypeIdentity { } #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -pub struct ArchetypeComponentId(usize); +pub struct ArchetypeComponentId(NonZeroUsize); impl ArchetypeComponentId { /// Creates a new [`ArchetypeComponentId`] from an index. #[inline] pub(crate) const fn new(index: usize) -> Self { - Self(index) + assert!(index != 0); + // SAFETY: The value is guarenteed not to be 0 by the above check. + Self(unsafe { NonZeroUsize::new_unchecked(index) }) } } impl SparseSetIndex for ArchetypeComponentId { - type Repr = usize; - const MAX_SIZE: usize = usize::MAX; + type Repr = NonZeroUsize; + const MAX_SIZE: usize = NonZeroUsize::MAX_SIZE; #[inline] fn sparse_set_index(&self) -> usize { - self.0 + self.0.get() } #[inline] fn get_sparse_set_index(value: usize) -> Self { - Self(value) + Self::new(value) } #[inline] - unsafe fn repr_from_index(_index: usize) -> Self::Repr { - // Intentionally unimplemented, not used in SparseSets/SparseArrays as key - unimplemented!(); + unsafe fn repr_from_index(index: usize) -> Self::Repr { + debug_assert!(index < Self::MAX_SIZE); + // SAFETY: Caller guarentees that `index` does not exceed `u32::MAX - 1`. + // This addition cannot overflow under these guarentees. + // + // Created index cannot be zero as it's being incremented by one. + unsafe { NonZeroUsize::new_unchecked(index + 1) } } #[inline] - fn repr_to_index(_repr: &Self::Repr) -> usize { - // Intentionally unimplemented, not used in SparseSets/SparseArrays as key - unimplemented!(); + fn repr_to_index(repr: &Self::Repr) -> usize { + repr.get() as usize - 1 } } @@ -444,7 +449,7 @@ impl Default for Archetypes { components: SparseSet::new().into_immutable(), }], archetype_ids: Default::default(), - archetype_component_count: 0, + archetype_component_count: 1, }; archetypes.get_id_or_insert(TableId::empty(), Vec::new(), Vec::new()); archetypes diff --git a/crates/bevy_ecs/src/storage/sparse_set.rs b/crates/bevy_ecs/src/storage/sparse_set.rs index 19ba394950a89..56b6f9aa234f7 100644 --- a/crates/bevy_ecs/src/storage/sparse_set.rs +++ b/crates/bevy_ecs/src/storage/sparse_set.rs @@ -466,15 +466,14 @@ impl SparseSet { /// zero), as the number of bits needed to represent a `SparseSetIndex` in a `FixedBitSet` /// is proportional to the **value** of those `usize`. pub trait SparseSetIndex: Clone + PartialEq + Eq + Hash { - /// The internal representation type used to represent a dense index within a [`SparseArray`] - /// or [`SparseSet`]. + /// The internal representation type used to represent a dense index within a [`SparseSet`]. /// /// It's advised to use a space-optimized type (i.e. the `std::num::NonZero*` types), as this /// cut the overall memory overhead by ~50%. type Repr: Clone; - /// The maximum number of elements that can be stored in a [`SparseArray`]/[`SparseSet`] - /// when keyed by this type. + /// The maximum number of elements that can be stored in a [`SparseSet`] when keyed by + /// this type. const MAX_SIZE: usize; fn sparse_set_index(&self) -> usize; From e94501568886fe91b8738250aebe23d2c660a17b Mon Sep 17 00:00:00 2001 From: james7132 Date: Thu, 5 Jan 2023 21:57:16 -0800 Subject: [PATCH 36/60] Fix build --- crates/bevy_ecs/src/bundle.rs | 45 ++++++++-------------------- crates/bevy_ecs/src/entity/mod.rs | 2 +- crates/bevy_ecs/src/storage/table.rs | 2 -- 3 files changed, 13 insertions(+), 36 deletions(-) diff --git a/crates/bevy_ecs/src/bundle.rs b/crates/bevy_ecs/src/bundle.rs index 3a1d09af71cb7..1f73f715eda1e 100644 --- a/crates/bevy_ecs/src/bundle.rs +++ b/crates/bevy_ecs/src/bundle.rs @@ -15,7 +15,7 @@ use crate::{ }; use bevy_ecs_macros::all_tuples; use bevy_ptr::OwningPtr; -use std::{any::TypeId, collections::HashMap, num::NonZeroU32}; +use std::{any::TypeId, collections::HashMap}; /// The `Bundle` trait enables insertion and removal of [`Component`]s from an entity. /// @@ -238,41 +238,26 @@ macro_rules! tuple_impl { all_tuples!(tuple_impl, 0, 15, B); #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] -pub struct BundleId(NonZeroU32); +pub struct BundleId(usize); impl BundleId { - /// Creates a new [`BundleId`] from an index without checking for the - /// type's invariants. - /// - /// # Safety - /// `value` must be less than [`u32::MAX`]. - #[inline] - pub(crate) const unsafe fn new_unchecked(value: usize) -> Self { - Self(NonZeroU32::new_unchecked(value as u32)) - } - /// Creates a new [`BundleId`] from an index. /// /// # Panic /// This function will panic if `value` is greater than or equal to [`u32::MAX`]. #[inline] pub(crate) const fn new(value: usize) -> Self { - assert!( - value > 0 && value <= u32::MAX as usize, - "BundleID cannot be u32::MAX or greater" - ); - // SAFETY: The above assertion will fail if the value is not valid. - unsafe { Self::new_unchecked(value) } + Self(value) } #[inline] pub(crate) fn index(self) -> usize { - self.0.get() as usize + self.0 } } impl SparseSetIndex for BundleId { - type Repr = NonZeroU32; + type Repr = usize; const MAX_SIZE: usize = (u32::MAX - 1) as usize; #[inline] @@ -287,17 +272,12 @@ impl SparseSetIndex for BundleId { #[inline] unsafe fn repr_from_index(index: usize) -> Self::Repr { - debug_assert!(index < Self::MAX_SIZE); - // SAFETY: Caller guarentees that `index` does not exceed `u32::MAX - 1`. - // This addition cannot overflow under these guarentees. - // - // Created index cannot be zero as it's being incremented by one. - unsafe { NonZeroU32::new_unchecked((index + 1) as u32) } + index } #[inline] fn repr_to_index(repr: &Self::Repr) -> usize { - repr.get() as usize + *repr } } @@ -596,10 +576,10 @@ impl<'a, 'b> BundleInserter<'a, 'b> { InsertBundleResult::NewArchetypeSameTable { new_archetype } => { let result = self.archetype.swap_remove(location.archetype_row); if let Some(swapped_entity) = result.swapped_entity { - self.entities.set(swapped_entity.index(), Some(location)); + self.entities.set(swapped_entity.index(), location); } let new_location = new_archetype.allocate(entity, result.table_row); - self.entities.set(entity.index(), Some(new_location)); + self.entities.set(entity.index(), new_location); // PERF: this could be looked up during Inserter construction and stored (but borrowing makes this nasty) let add_bundle = self @@ -624,7 +604,7 @@ impl<'a, 'b> BundleInserter<'a, 'b> { } => { let result = self.archetype.swap_remove(location.archetype_row); if let Some(swapped_entity) = result.swapped_entity { - self.entities.set(swapped_entity.index(), Some(location)); + self.entities.set(swapped_entity.index(), location); } // PERF: store "non bundle" components in edge, then just move those to avoid // redundant copies @@ -632,7 +612,7 @@ impl<'a, 'b> BundleInserter<'a, 'b> { .table .move_to_superset_unchecked(result.table_row, new_table); let new_location = new_archetype.allocate(entity, move_result.new_row); - self.entities.set(entity.index(), Some(new_location)); + self.entities.set(entity.index(), new_location); // if an entity was moved into this entity's table spot, update its table row if let Some(swapped_entity) = move_result.swapped_entity { @@ -707,7 +687,7 @@ impl<'a, 'b> BundleSpawner<'a, 'b> { self.change_tick, bundle, ); - self.entities.set(entity.index(), Some(location)); + self.entities.set(entity.index(), location); location } @@ -767,7 +747,6 @@ impl Default for Bundles { bundle_infos: vec![BundleInfo { id: BundleId::new(1), component_ids: Vec::new(), - storage_types: Vec::new(), }], bundle_ids: Default::default(), } diff --git a/crates/bevy_ecs/src/entity/mod.rs b/crates/bevy_ecs/src/entity/mod.rs index 9e8282024ce64..b88e740e7a367 100644 --- a/crates/bevy_ecs/src/entity/mod.rs +++ b/crates/bevy_ecs/src/entity/mod.rs @@ -606,7 +606,7 @@ impl Entities { /// before handing control to unknown code. pub(crate) unsafe fn set(&mut self, index: u32, location: EntityLocation) { // SAFETY: Caller guarentees that `index` a valid entity index - self.meta.get_unchecked_mut(index as usize).location = location; + self.meta.get_unchecked_mut(index as usize).location = Some(location); } /// Get the [`Entity`] with a given id, if it exists in this [`Entities`] collection diff --git a/crates/bevy_ecs/src/storage/table.rs b/crates/bevy_ecs/src/storage/table.rs index e5dc58dc26d5a..54fd030b680ab 100644 --- a/crates/bevy_ecs/src/storage/table.rs +++ b/crates/bevy_ecs/src/storage/table.rs @@ -29,8 +29,6 @@ use std::{ pub struct TableId(u32); impl TableId { - pub(crate) const INVALID: TableId = TableId(u32::MAX); - #[inline] pub fn new(index: usize) -> Self { TableId(index as u32) From 3388b4a3c1a3af75dabcc6754563ed6857260f20 Mon Sep 17 00:00:00 2001 From: James Liu Date: Fri, 6 Jan 2023 21:10:22 -0800 Subject: [PATCH 37/60] Fix safety comment. Co-authored-by: bjorn3 <17426603+bjorn3@users.noreply.github.com> --- crates/bevy_ecs/src/archetype.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_ecs/src/archetype.rs b/crates/bevy_ecs/src/archetype.rs index 07cd68ece0f06..994f0c0320a07 100644 --- a/crates/bevy_ecs/src/archetype.rs +++ b/crates/bevy_ecs/src/archetype.rs @@ -75,7 +75,7 @@ pub struct ArchetypeId(NonZeroU32); impl ArchetypeId { /// The ID for the [`Archetype`] without any components. pub const EMPTY: ArchetypeId = { - // SAFETY: 0 is guarenteed to not equal 0 + // SAFETY: 1 is guaranteed to not equal 0 unsafe { Self::new_unchecked(1) } }; From 79c65da70e07eb4f6e0631aad1fd78e4ab9f954b Mon Sep 17 00:00:00 2001 From: James Liu Date: Wed, 11 Jan 2023 15:17:33 -0500 Subject: [PATCH 38/60] Discard incorrect test --- crates/bevy_ecs/src/bundle.rs | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/crates/bevy_ecs/src/bundle.rs b/crates/bevy_ecs/src/bundle.rs index 1f73f715eda1e..c65402ff988b8 100644 --- a/crates/bevy_ecs/src/bundle.rs +++ b/crates/bevy_ecs/src/bundle.rs @@ -772,16 +772,3 @@ unsafe fn initialize_bundle( BundleInfo { id, component_ids } } - -#[cfg(test)] -mod test { - use super::BundleId; - - #[test] - pub fn test_bundle_id_size_optimized() { - assert_eq!( - core::mem::size_of::(), - core::mem::size_of::>(), - ); - } -} From 83b03f83f582f6724431476961d1990fc091f6e0 Mon Sep 17 00:00:00 2001 From: iiYese Date: Fri, 6 Jan 2023 15:40:10 +0000 Subject: [PATCH 39/60] Added missing details to SystemParam Local documentation. (#7106) # Objective `SystemParam` `Local`s documentation currently leaves out information that should be documented. - What happens when multiple `SystemParam`s within the same system have the same `Local` type. - What lifetime parameter is expected by `Local`. ## Solution - Added sentences to documentation to communicate this information. - Renamed `Local` lifetimes in code to `'s` where they previously were not. Users can get complicated incorrect suggested fixes if they pass the wrong lifetime. Some instance of the code had `'w` indicating the expected lifetime might not have been known to those that wrote the code either. Co-authored-by: iiYese <83026177+iiYese@users.noreply.github.com> --- crates/bevy_ecs/src/system/system_param.rs | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/crates/bevy_ecs/src/system/system_param.rs b/crates/bevy_ecs/src/system/system_param.rs index d845f48be7f1a..7751995ef1cb7 100644 --- a/crates/bevy_ecs/src/system/system_param.rs +++ b/crates/bevy_ecs/src/system/system_param.rs @@ -798,6 +798,10 @@ unsafe impl SystemParamState for WorldState { /// /// A local may only be accessed by the system itself and is therefore not visible to other systems. /// If two or more systems specify the same local type each will have their own unique local. +/// If multiple [`SystemParam`]s within the same system each specify the same local type +/// each will get their own distinct data storage. +/// +/// The supplied lifetime parameter is the [`SystemParam`]s `'s` lifetime. /// /// # Examples /// @@ -837,12 +841,12 @@ unsafe impl SystemParamState for WorldState { /// // .add_system(reset_to_system(my_config)) /// # assert_is_system(reset_to_system(Config(10))); /// ``` -pub struct Local<'a, T: FromWorld + Send + 'static>(pub(crate) &'a mut T); +pub struct Local<'s, T: FromWorld + Send + 'static>(pub(crate) &'s mut T); // SAFETY: Local only accesses internal state -unsafe impl<'a, T: FromWorld + Send + 'static> ReadOnlySystemParam for Local<'a, T> {} +unsafe impl<'s, T: FromWorld + Send + 'static> ReadOnlySystemParam for Local<'s, T> {} -impl<'a, T: FromWorld + Send + Sync + 'static> Debug for Local<'a, T> +impl<'s, T: FromWorld + Send + Sync + 'static> Debug for Local<'s, T> where T: Debug, { @@ -851,7 +855,7 @@ where } } -impl<'a, T: FromWorld + Send + Sync + 'static> Deref for Local<'a, T> { +impl<'s, T: FromWorld + Send + Sync + 'static> Deref for Local<'s, T> { type Target = T; #[inline] @@ -860,14 +864,14 @@ impl<'a, T: FromWorld + Send + Sync + 'static> Deref for Local<'a, T> { } } -impl<'a, T: FromWorld + Send + Sync + 'static> DerefMut for Local<'a, T> { +impl<'s, T: FromWorld + Send + Sync + 'static> DerefMut for Local<'s, T> { #[inline] fn deref_mut(&mut self) -> &mut Self::Target { self.0 } } -impl<'w, 'a, T: FromWorld + Send + 'static> IntoIterator for &'a Local<'w, T> +impl<'s, 'a, T: FromWorld + Send + 'static> IntoIterator for &'a Local<'s, T> where &'a T: IntoIterator, { @@ -879,7 +883,7 @@ where } } -impl<'w, 'a, T: FromWorld + Send + 'static> IntoIterator for &'a mut Local<'w, T> +impl<'s, 'a, T: FromWorld + Send + 'static> IntoIterator for &'a mut Local<'s, T> where &'a mut T: IntoIterator, { @@ -895,7 +899,7 @@ where #[doc(hidden)] pub struct LocalState(pub(crate) SyncCell); -impl<'a, T: FromWorld + Send + 'static> SystemParam for Local<'a, T> { +impl<'s, T: FromWorld + Send + 'static> SystemParam for Local<'s, T> { type State = LocalState; } From b0b7e5821d31b53d4f65450501e637e075a4d8f5 Mon Sep 17 00:00:00 2001 From: A-Walrus Date: Fri, 6 Jan 2023 17:46:44 +0000 Subject: [PATCH 40/60] Fix doc comment "Turbo" -> "Extreme" (#7091) # Objective Doc comment mentions turbo which is a sensitivity that doesn't exist. ## Solution Change the comment to "Extreme" which does exist --- crates/bevy_core_pipeline/src/fxaa/mod.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/bevy_core_pipeline/src/fxaa/mod.rs b/crates/bevy_core_pipeline/src/fxaa/mod.rs index f49425e7250c4..c843d0df1e87a 100644 --- a/crates/bevy_core_pipeline/src/fxaa/mod.rs +++ b/crates/bevy_core_pipeline/src/fxaa/mod.rs @@ -47,7 +47,8 @@ pub struct Fxaa { /// Use lower sensitivity for a sharper, faster, result. /// Use higher sensitivity for a slower, smoother, result. - /// Ultra and Turbo settings can result in significant smearing and loss of detail. + /// [Ultra](`Sensitivity::Ultra`) and [Extreme](`Sensitivity::Extreme`) + /// settings can result in significant smearing and loss of detail. /// The minimum amount of local contrast required to apply algorithm. pub edge_threshold: Sensitivity, From bebfb01c76d2e1e4467947eb2c607f2e55adf9ae Mon Sep 17 00:00:00 2001 From: 1e1001 Date: Fri, 6 Jan 2023 18:00:22 +0000 Subject: [PATCH 41/60] add `Axis::devices` to get all the input devices (#5400) (github made me type out a message for the commit which looked like it was for the pr, sorry) # Objective - Add a way to get all of the input devices of an `Axis`, primarily useful for looping through them ## Solution - Adds `Axis::devices()` which returns a `FixedSizeIterator` - Adds a (probably unneeded) `test_axis_devices` test because tests are cool. --- ## Changelog - Added `Axis::devices()` method ## Migration Guide Not a breaking change. --- crates/bevy_input/src/axis.rs | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/crates/bevy_input/src/axis.rs b/crates/bevy_input/src/axis.rs index 228365f16bdff..6c88145da13b7 100644 --- a/crates/bevy_input/src/axis.rs +++ b/crates/bevy_input/src/axis.rs @@ -52,6 +52,10 @@ where pub fn remove(&mut self, input_device: T) -> Option { self.axis_data.remove(&input_device) } + /// Returns an iterator of all the input devices that have position data + pub fn devices(&self) -> impl ExactSizeIterator { + self.axis_data.keys() + } } #[cfg(test)] @@ -108,4 +112,34 @@ mod tests { assert_eq!(expected, actual); } } + + #[test] + fn test_axis_devices() { + let mut axis = Axis::::default(); + assert_eq!(axis.devices().count(), 0); + + axis.set( + GamepadButton::new(Gamepad::new(1), GamepadButtonType::RightTrigger), + 0.1, + ); + assert_eq!(axis.devices().count(), 1); + + axis.set( + GamepadButton::new(Gamepad::new(1), GamepadButtonType::LeftTrigger), + 0.5, + ); + assert_eq!(axis.devices().count(), 2); + + axis.set( + GamepadButton::new(Gamepad::new(1), GamepadButtonType::RightTrigger), + -0.1, + ); + assert_eq!(axis.devices().count(), 2); + + axis.remove(GamepadButton::new( + Gamepad::new(1), + GamepadButtonType::RightTrigger, + )); + assert_eq!(axis.devices().count(), 1); + } } From 5fa57ab4f18e2085d5a9b6a71d24d1e2f96374aa Mon Sep 17 00:00:00 2001 From: JoJoJet <21144246+JoJoJet@users.noreply.github.com> Date: Fri, 6 Jan 2023 23:24:25 +0000 Subject: [PATCH 42/60] Update an outdated example for `Mut::map_unchanged` (#7115) # Objective - The doctest for `Mut::map_unchanged` uses a fake function `set_if_not_equal` to demonstrate usage. - Now that #6853 has been merged, we can use `Mut::set_if_neq` directly instead of mocking it. --- crates/bevy_ecs/src/change_detection.rs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/crates/bevy_ecs/src/change_detection.rs b/crates/bevy_ecs/src/change_detection.rs index 10bd2b5da3c9f..d9ce86e60294d 100644 --- a/crates/bevy_ecs/src/change_detection.rs +++ b/crates/bevy_ecs/src/change_detection.rs @@ -216,20 +216,17 @@ macro_rules! impl_methods { /// /// ```rust /// # use bevy_ecs::prelude::*; - /// # pub struct Vec2; + /// # #[derive(PartialEq)] pub struct Vec2; /// # impl Vec2 { pub const ZERO: Self = Self; } /// # #[derive(Component)] pub struct Transform { translation: Vec2 } - /// # mod my_utils { - /// # pub fn set_if_not_equal(x: bevy_ecs::prelude::Mut, val: T) { unimplemented!() } - /// # } /// // When run, zeroes the translation of every entity. /// fn reset_positions(mut transforms: Query<&mut Transform>) { /// for transform in &mut transforms { /// // We pinky promise not to modify `t` within the closure. /// // Breaking this promise will result in logic errors, but will never cause undefined behavior. - /// let translation = transform.map_unchanged(|t| &mut t.translation); + /// let mut translation = transform.map_unchanged(|t| &mut t.translation); /// // Only reset the translation if it isn't already zero; - /// my_utils::set_if_not_equal(translation, Vec2::ZERO); + /// translation.set_if_neq(Vec2::ZERO); /// } /// } /// # bevy_ecs::system::assert_is_system(reset_positions); From 563cb2ee8a9aa7b33ce2eb0a1df910a6cbe4abcf Mon Sep 17 00:00:00 2001 From: JoJoJet <21144246+JoJoJet@users.noreply.github.com> Date: Sat, 7 Jan 2023 23:20:32 +0000 Subject: [PATCH 43/60] Remove the `SystemParamState` trait and remove types like `ResState` (#6919) Spiritual successor to #5205. Actual successor to #6865. # Objective Currently, system params are defined using three traits: `SystemParam`, `ReadOnlySystemParam`, `SystemParamState`. The behavior for each param is specified by the `SystemParamState` trait, while `SystemParam` simply defers to the state. Splitting the traits in this way makes it easier to implement within macros, but it increases the cognitive load. Worst of all, this approach requires each `MySystemParam` to have a public `MySystemParamState` type associated with it. ## Solution * Merge the trait `SystemParamState` into `SystemParam`. * Remove all trivial `SystemParam` state types. * `OptionNonSendMutState`: you will not be missed. --- - [x] Fix/resolve the remaining test failure. ## Changelog * Removed the trait `SystemParamState`, merging its functionality into `SystemParam`. ## Migration Guide **Note**: this should replace the migration guide for #6865. This is relative to Bevy 0.9, not main. The traits `SystemParamState` and `SystemParamFetch` have been removed, and their functionality has been transferred to `SystemParam`. ```rust // Before (0.9) impl SystemParam for MyParam<'_, '_> { type State = MyParamState; } unsafe impl SystemParamState for MyParamState { fn init(world: &mut World, system_meta: &mut SystemMeta) -> Self { ... } } unsafe impl<'w, 's> SystemParamFetch<'w, 's> for MyParamState { type Item = MyParam<'w, 's>; fn get_param(&mut self, ...) -> Self::Item; } unsafe impl ReadOnlySystemParamFetch for MyParamState { } // After (0.10) unsafe impl SystemParam for MyParam<'_, '_> { type State = MyParamState; type Item<'w, 's> = MyParam<'w, 's>; fn init_state(world: &mut World, system_meta: &mut SystemMeta) -> Self::State { ... } fn get_param<'w, 's>(state: &mut Self::State, ...) -> Self::Item<'w, 's>; } unsafe impl ReadOnlySystemParam for MyParam<'_, '_> { } ``` The trait `ReadOnlySystemParamFetch` has been replaced with `ReadOnlySystemParam`. ```rust // Before unsafe impl ReadOnlySystemParamFetch for MyParamState {} // After unsafe impl ReadOnlySystemParam for MyParam<'_, '_> {} ``` --- crates/bevy_ecs/macros/src/lib.rs | 94 ++- .../src/system/commands/parallel_scope.rs | 21 +- .../src/system/exclusive_function_system.rs | 11 +- .../src/system/exclusive_system_param.rs | 70 +-- crates/bevy_ecs/src/system/function_system.rs | 36 +- crates/bevy_ecs/src/system/system_param.rs | 546 ++++++------------ crates/bevy_render/src/extract_param.rs | 37 +- 7 files changed, 288 insertions(+), 527 deletions(-) diff --git a/crates/bevy_ecs/macros/src/lib.rs b/crates/bevy_ecs/macros/src/lib.rs index b697c9d0ba497..1990f89107774 100644 --- a/crates/bevy_ecs/macros/src/lib.rs +++ b/crates/bevy_ecs/macros/src/lib.rs @@ -216,7 +216,6 @@ pub fn impl_param_set(_input: TokenStream) -> TokenStream { let mut tokens = TokenStream::new(); let max_params = 8; let params = get_idents(|i| format!("P{i}"), max_params); - let params_state = get_idents(|i| format!("PF{i}"), max_params); let metas = get_idents(|i| format!("m{i}"), max_params); let mut param_fn_muts = Vec::new(); for (i, param) in params.iter().enumerate() { @@ -238,7 +237,7 @@ pub fn impl_param_set(_input: TokenStream) -> TokenStream { // Conflicting params in ParamSet are not accessible at the same time // ParamSets are guaranteed to not conflict with other SystemParams unsafe { - <#param::State as SystemParamState>::get_param(&mut self.param_states.#index, &self.system_meta, self.world, self.change_tick) + #param::get_param(&mut self.param_states.#index, &self.system_meta, self.world, self.change_tick) } } }); @@ -246,36 +245,29 @@ pub fn impl_param_set(_input: TokenStream) -> TokenStream { for param_count in 1..=max_params { let param = ¶ms[0..param_count]; - let param_state = ¶ms_state[0..param_count]; let meta = &metas[0..param_count]; let param_fn_mut = ¶m_fn_muts[0..param_count]; tokens.extend(TokenStream::from(quote! { - impl<'w, 's, #(#param: SystemParam,)*> SystemParam for ParamSet<'w, 's, (#(#param,)*)> - { - type State = ParamSetState<(#(#param::State,)*)>; - } - - // SAFETY: All parameters are constrained to ReadOnlyState, so World is only read - + // SAFETY: All parameters are constrained to ReadOnlySystemParam, so World is only read unsafe impl<'w, 's, #(#param,)*> ReadOnlySystemParam for ParamSet<'w, 's, (#(#param,)*)> where #(#param: ReadOnlySystemParam,)* { } // SAFETY: Relevant parameter ComponentId and ArchetypeComponentId access is applied to SystemMeta. If any ParamState conflicts // with any prior access, a panic will occur. - - unsafe impl<#(#param_state: SystemParamState,)*> SystemParamState for ParamSetState<(#(#param_state,)*)> + unsafe impl<'_w, '_s, #(#param: SystemParam,)*> SystemParam for ParamSet<'_w, '_s, (#(#param,)*)> { - type Item<'w, 's> = ParamSet<'w, 's, (#(<#param_state as SystemParamState>::Item::<'w, 's>,)*)>; + type State = (#(#param::State,)*); + type Item<'w, 's> = ParamSet<'w, 's, (#(#param,)*)>; - fn init(world: &mut World, system_meta: &mut SystemMeta) -> Self { + 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 let mut #meta = system_meta.clone(); #meta.component_access_set.clear(); #meta.archetype_component_access.clear(); - #param_state::init(world, &mut #meta); - let #param = #param_state::init(world, &mut system_meta.clone()); + #param::init_state(world, &mut #meta); + let #param = #param::init_state(world, &mut system_meta.clone()); )* #( system_meta @@ -285,29 +277,26 @@ pub fn impl_param_set(_input: TokenStream) -> TokenStream { .archetype_component_access .extend(&#meta.archetype_component_access); )* - ParamSetState((#(#param,)*)) + (#(#param,)*) } - fn new_archetype(&mut self, archetype: &Archetype, system_meta: &mut SystemMeta) { - let (#(#param,)*) = &mut self.0; - #( - #param.new_archetype(archetype, system_meta); - )* + fn new_archetype(state: &mut Self::State, archetype: &Archetype, system_meta: &mut SystemMeta) { + <(#(#param,)*) as SystemParam>::new_archetype(state, archetype, system_meta); } - fn apply(&mut self, system_meta: &SystemMeta, world: &mut World) { - self.0.apply(system_meta, world) + fn apply(state: &mut Self::State, system_meta: &SystemMeta, world: &mut World) { + <(#(#param,)*) as SystemParam>::apply(state, system_meta, world); } #[inline] unsafe fn get_param<'w, 's>( - state: &'s mut Self, + state: &'s mut Self::State, system_meta: &SystemMeta, world: &'w World, change_tick: u32, ) -> Self::Item<'w, 's> { ParamSet { - param_states: &mut state.0, + param_states: state, system_meta: system_meta.clone(), world, change_tick, @@ -317,7 +306,6 @@ pub fn impl_param_set(_input: TokenStream) -> TokenStream { impl<'w, 's, #(#param: SystemParam,)*> ParamSet<'w, 's, (#(#param,)*)> { - #(#param_fn_mut)* } })); @@ -411,7 +399,7 @@ pub fn derive_system_param(input: TokenStream) -> TokenStream { } } - let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); + let (_impl_generics, ty_generics, where_clause) = generics.split_for_impl(); let lifetimeless_generics: Vec<_> = generics .params @@ -419,6 +407,12 @@ pub fn derive_system_param(input: TokenStream) -> TokenStream { .filter(|g| !matches!(g, GenericParam::Lifetime(_))) .collect(); + let mut shadowed_lifetimes: Vec<_> = generics.lifetimes().map(|x| x.lifetime.clone()).collect(); + for lifetime in &mut shadowed_lifetimes { + let shadowed_ident = format_ident!("_{}", lifetime.ident); + lifetime.ident = shadowed_ident; + } + let mut punctuated_generics = Punctuated::<_, Token![,]>::new(); punctuated_generics.extend(lifetimeless_generics.iter().map(|g| match g { GenericParam::Type(g) => GenericParam::Type(TypeParam { @@ -432,15 +426,6 @@ pub fn derive_system_param(input: TokenStream) -> TokenStream { _ => unreachable!(), })); - let mut punctuated_generics_no_bounds = punctuated_generics.clone(); - for g in &mut punctuated_generics_no_bounds { - match g { - GenericParam::Type(g) => g.bounds.clear(), - GenericParam::Lifetime(g) => g.bounds.clear(), - GenericParam::Const(_) => {} - } - } - let mut punctuated_generic_idents = Punctuated::<_, Token![,]>::new(); punctuated_generic_idents.extend(lifetimeless_generics.iter().map(|g| match g { GenericParam::Type(g) => &g.ident, @@ -479,10 +464,6 @@ pub fn derive_system_param(input: TokenStream) -> TokenStream { // The struct can still be accessed via SystemParam::State, e.g. EventReaderState can be accessed via // as SystemParam>::State const _: () = { - impl #impl_generics #path::system::SystemParam for #struct_name #ty_generics #where_clause { - type State = FetchState<'static, 'static, #punctuated_generic_idents>; - } - #[doc(hidden)] #state_struct_visibility struct FetchState <'w, 's, #(#lifetimeless_generics,)*> { state: (#(<#tuple_types as #path::system::SystemParam>::State,)*), @@ -492,34 +473,33 @@ pub fn derive_system_param(input: TokenStream) -> TokenStream { )>, } - unsafe impl<#punctuated_generics> #path::system::SystemParamState for - FetchState<'static, 'static, #punctuated_generic_idents> - #where_clause { - type Item<'w, 's> = #struct_name #ty_generics; + unsafe impl<'w, 's, #punctuated_generics> #path::system::SystemParam for #struct_name #ty_generics #where_clause { + type State = FetchState<'static, 'static, #punctuated_generic_idents>; + type Item<'_w, '_s> = #struct_name <#(#shadowed_lifetimes,)* #punctuated_generic_idents>; - fn init(world: &mut #path::world::World, system_meta: &mut #path::system::SystemMeta) -> Self { - Self { - state: #path::system::SystemParamState::init(world, system_meta), + fn init_state(world: &mut #path::world::World, system_meta: &mut #path::system::SystemMeta) -> Self::State { + FetchState { + state: <(#(#tuple_types,)*) as #path::system::SystemParam>::init_state(world, system_meta), marker: std::marker::PhantomData, } } - fn new_archetype(&mut self, archetype: &#path::archetype::Archetype, system_meta: &mut #path::system::SystemMeta) { - self.state.new_archetype(archetype, system_meta) + fn new_archetype(state: &mut Self::State, archetype: &#path::archetype::Archetype, system_meta: &mut #path::system::SystemMeta) { + <(#(#tuple_types,)*) as #path::system::SystemParam>::new_archetype(&mut state.state, archetype, system_meta) } - fn apply(&mut self, system_meta: &#path::system::SystemMeta, world: &mut #path::world::World) { - self.state.apply(system_meta, world) + fn apply(state: &mut Self::State, system_meta: &#path::system::SystemMeta, world: &mut #path::world::World) { + <(#(#tuple_types,)*) as #path::system::SystemParam>::apply(&mut state.state, system_meta, world); } - unsafe fn get_param<'w, 's>( - state: &'s mut Self, + unsafe fn get_param<'w2, 's2>( + state: &'s2 mut Self::State, system_meta: &#path::system::SystemMeta, - world: &'w #path::world::World, + world: &'w2 #path::world::World, change_tick: u32, - ) -> Self::Item<'w, 's> { + ) -> Self::Item<'w2, 's2> { let (#(#tuple_patterns,)*) = < - <(#(#tuple_types,)*) as #path::system::SystemParam>::State as #path::system::SystemParamState + (#(#tuple_types,)*) as #path::system::SystemParam >::get_param(&mut state.state, system_meta, world, change_tick); #struct_name { #(#fields: #field_locals,)* diff --git a/crates/bevy_ecs/src/system/commands/parallel_scope.rs b/crates/bevy_ecs/src/system/commands/parallel_scope.rs index 72c8118aa5152..5975e9d668d68 100644 --- a/crates/bevy_ecs/src/system/commands/parallel_scope.rs +++ b/crates/bevy_ecs/src/system/commands/parallel_scope.rs @@ -5,14 +5,14 @@ use thread_local::ThreadLocal; use crate::{ entity::Entities, prelude::World, - system::{SystemMeta, SystemParam, SystemParamState}, + system::{SystemMeta, SystemParam}, }; use super::{CommandQueue, Commands}; +/// The internal [`SystemParam`] state of the [`ParallelCommands`] type #[doc(hidden)] #[derive(Default)] -/// The internal [`SystemParamState`] of the [`ParallelCommands`] type pub struct ParallelCommandsState { thread_local_storage: ThreadLocal>, } @@ -48,30 +48,27 @@ pub struct ParallelCommands<'w, 's> { entities: &'w Entities, } -impl SystemParam for ParallelCommands<'_, '_> { - type State = ParallelCommandsState; -} - // SAFETY: no component or resource access to report -unsafe impl SystemParamState for ParallelCommandsState { +unsafe impl SystemParam for ParallelCommands<'_, '_> { + type State = ParallelCommandsState; type Item<'w, 's> = ParallelCommands<'w, 's>; - fn init(_: &mut World, _: &mut crate::system::SystemMeta) -> Self { - Self::default() + fn init_state(_: &mut World, _: &mut crate::system::SystemMeta) -> Self::State { + ParallelCommandsState::default() } - fn apply(&mut self, _system_meta: &SystemMeta, world: &mut World) { + fn apply(state: &mut Self::State, _system_meta: &SystemMeta, world: &mut World) { #[cfg(feature = "trace")] let _system_span = bevy_utils::tracing::info_span!("system_commands", name = _system_meta.name()) .entered(); - for cq in &mut self.thread_local_storage { + for cq in &mut state.thread_local_storage { cq.get_mut().apply(world); } } unsafe fn get_param<'w, 's>( - state: &'s mut Self, + state: &'s mut Self::State, _: &crate::system::SystemMeta, world: &'w World, _: u32, diff --git a/crates/bevy_ecs/src/system/exclusive_function_system.rs b/crates/bevy_ecs/src/system/exclusive_function_system.rs index 8b1607c75898e..272b9bce17eff 100644 --- a/crates/bevy_ecs/src/system/exclusive_function_system.rs +++ b/crates/bevy_ecs/src/system/exclusive_function_system.rs @@ -6,7 +6,7 @@ use crate::{ schedule::{SystemLabel, SystemLabelId}, system::{ check_system_change_tick, AsSystemLabel, ExclusiveSystemParam, ExclusiveSystemParamItem, - ExclusiveSystemParamState, IntoSystem, System, SystemMeta, SystemTypeIdLabel, + IntoSystem, System, SystemMeta, SystemTypeIdLabel, }, world::{World, WorldId}, }; @@ -94,7 +94,7 @@ where let saved_last_tick = world.last_change_tick; world.last_change_tick = self.system_meta.last_change_tick; - let params = ::State::get_param( + let params = Param::get_param( self.param_state.as_mut().expect(PARAM_MESSAGE), &self.system_meta, ); @@ -122,17 +122,14 @@ where #[inline] fn apply_buffers(&mut self, world: &mut World) { let param_state = self.param_state.as_mut().expect(PARAM_MESSAGE); - param_state.apply(world); + Param::apply(param_state, world); } #[inline] fn initialize(&mut self, world: &mut World) { self.world_id = Some(world.id()); self.system_meta.last_change_tick = world.change_tick().wrapping_sub(MAX_CHANGE_AGE); - self.param_state = Some(::init( - world, - &mut self.system_meta, - )); + self.param_state = Some(Param::init(world, &mut self.system_meta)); } fn update_archetype_component_access(&mut self, _world: &World) {} diff --git a/crates/bevy_ecs/src/system/exclusive_system_param.rs b/crates/bevy_ecs/src/system/exclusive_system_param.rs index 6c52738901254..4b6342660439e 100644 --- a/crates/bevy_ecs/src/system/exclusive_system_param.rs +++ b/crates/bevy_ecs/src/system/exclusive_system_param.rs @@ -1,108 +1,89 @@ use crate::{ prelude::{FromWorld, QueryState}, query::{ReadOnlyWorldQuery, WorldQuery}, - system::{Local, LocalState, SystemMeta, SystemParam, SystemState}, + system::{Local, SystemMeta, SystemParam, SystemState}, world::World, }; use bevy_ecs_macros::all_tuples; use bevy_utils::synccell::SyncCell; pub trait ExclusiveSystemParam: Sized { - type State: ExclusiveSystemParamState; -} - -pub type ExclusiveSystemParamItem<'s, P> = - <

::State as ExclusiveSystemParamState>::Item<'s>; - -/// The state of a [`SystemParam`]. -pub trait ExclusiveSystemParamState: Send + Sync + 'static { - type Item<'s>: ExclusiveSystemParam; + type State: Send + Sync + 'static; + type Item<'s>: ExclusiveSystemParam; - fn init(world: &mut World, system_meta: &mut SystemMeta) -> Self; + fn init(world: &mut World, system_meta: &mut SystemMeta) -> Self::State; #[inline] - fn apply(&mut self, _world: &mut World) {} + fn apply(_state: &mut Self::State, _world: &mut World) {} - fn get_param<'s>(state: &'s mut Self, system_meta: &SystemMeta) -> Self::Item<'s>; + fn get_param<'s>(state: &'s mut Self::State, system_meta: &SystemMeta) -> Self::Item<'s>; } +pub type ExclusiveSystemParamItem<'s, P> =

::Item<'s>; + impl<'a, Q: WorldQuery + 'static, F: ReadOnlyWorldQuery + 'static> ExclusiveSystemParam for &'a mut QueryState { type State = QueryState; -} - -impl ExclusiveSystemParamState - for QueryState -{ type Item<'s> = &'s mut QueryState; - fn init(world: &mut World, _system_meta: &mut SystemMeta) -> Self { + fn init(world: &mut World, _system_meta: &mut SystemMeta) -> Self::State { QueryState::new(world) } - fn get_param<'s>(state: &'s mut Self, _system_meta: &SystemMeta) -> Self::Item<'s> { + fn get_param<'s>(state: &'s mut Self::State, _system_meta: &SystemMeta) -> Self::Item<'s> { state } } impl<'a, P: SystemParam + 'static> ExclusiveSystemParam for &'a mut SystemState

{ type State = SystemState

; -} - -impl ExclusiveSystemParamState for SystemState

{ type Item<'s> = &'s mut SystemState

; - fn init(world: &mut World, _system_meta: &mut SystemMeta) -> Self { + fn init(world: &mut World, _system_meta: &mut SystemMeta) -> Self::State { SystemState::new(world) } - fn get_param<'s>(state: &'s mut Self, _system_meta: &SystemMeta) -> Self::Item<'s> { + fn get_param<'s>(state: &'s mut Self::State, _system_meta: &SystemMeta) -> Self::Item<'s> { state } } -impl<'s, T: FromWorld + Send + Sync + 'static> ExclusiveSystemParam for Local<'s, T> { - type State = LocalState; -} - -impl ExclusiveSystemParamState for LocalState { +impl<'_s, T: FromWorld + Send + Sync + 'static> ExclusiveSystemParam for Local<'_s, T> { + type State = SyncCell; type Item<'s> = Local<'s, T>; - fn init(world: &mut World, _system_meta: &mut SystemMeta) -> Self { - Self(SyncCell::new(T::from_world(world))) + fn init(world: &mut World, _system_meta: &mut SystemMeta) -> Self::State { + SyncCell::new(T::from_world(world)) } - fn get_param<'s>(state: &'s mut Self, _system_meta: &SystemMeta) -> Self::Item<'s> { - Local(state.0.get()) + fn get_param<'s>(state: &'s mut Self::State, _system_meta: &SystemMeta) -> Self::Item<'s> { + Local(state.get()) } } macro_rules! impl_exclusive_system_param_tuple { ($($param: ident),*) => { - impl<$($param: ExclusiveSystemParam),*> ExclusiveSystemParam for ($($param,)*) { - type State = ($($param::State,)*); - } - #[allow(unused_variables)] #[allow(non_snake_case)] - impl<$($param: ExclusiveSystemParamState),*> ExclusiveSystemParamState for ($($param,)*) { + 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 { + fn init(_world: &mut World, _system_meta: &mut SystemMeta) -> Self::State { (($($param::init(_world, _system_meta),)*)) } #[inline] - fn apply(&mut self, _world: &mut World) { - let ($($param,)*) = self; - $($param.apply(_world);)* + fn apply(state: &mut Self::State, _world: &mut World) { + let ($($param,)*) = state; + $($param::apply($param, _world);)* } #[inline] #[allow(clippy::unused_unit)] fn get_param<'s>( - state: &'s mut Self, + state: &'s mut Self::State, system_meta: &SystemMeta, ) -> Self::Item<'s> { @@ -110,7 +91,6 @@ macro_rules! impl_exclusive_system_param_tuple { ($($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 2cc88f9e30b77..f99ae67b15a4a 100644 --- a/crates/bevy_ecs/src/system/function_system.rs +++ b/crates/bevy_ecs/src/system/function_system.rs @@ -5,10 +5,7 @@ use crate::{ prelude::FromWorld, query::{Access, FilteredAccessSet}, schedule::{SystemLabel, SystemLabelId}, - system::{ - check_system_change_tick, ReadOnlySystemParam, System, SystemParam, SystemParamItem, - SystemParamState, - }, + system::{check_system_change_tick, ReadOnlySystemParam, System, SystemParam, SystemParamItem}, world::{World, WorldId}, }; use bevy_ecs_macros::all_tuples; @@ -141,7 +138,7 @@ impl SystemMeta { /// ``` pub struct SystemState { meta: SystemMeta, - param_state: ::State, + param_state: Param::State, world_id: WorldId, archetype_generation: ArchetypeGeneration, } @@ -150,7 +147,7 @@ impl SystemState { pub fn new(world: &mut World) -> Self { let mut meta = SystemMeta::new::(); meta.last_change_tick = world.change_tick().wrapping_sub(MAX_CHANGE_AGE); - let param_state = ::init(world, &mut meta); + let param_state = Param::init_state(world, &mut meta); Self { meta, param_state, @@ -188,7 +185,7 @@ impl SystemState { /// This function should be called manually after the values returned by [`SystemState::get`] and [`SystemState::get_mut`] /// are finished being used. pub fn apply(&mut self, world: &mut World) { - self.param_state.apply(&self.meta, world); + Param::apply(&mut self.param_state, &self.meta, world); } #[inline] @@ -204,7 +201,8 @@ impl SystemState { let archetype_index_range = old_generation.value()..new_generation.value(); for archetype_index in archetype_index_range { - self.param_state.new_archetype( + Param::new_archetype( + &mut self.param_state, &archetypes[ArchetypeId::new(archetype_index)], &mut self.meta, ); @@ -223,12 +221,7 @@ impl SystemState { world: &'w World, ) -> SystemParamItem<'w, 's, Param> { let change_tick = world.increment_change_tick(); - let param = ::get_param( - &mut self.param_state, - &self.meta, - world, - change_tick, - ); + let param = Param::get_param(&mut self.param_state, &self.meta, world, change_tick); self.meta.last_change_tick = change_tick; param } @@ -400,7 +393,7 @@ where // We update the archetype component access correctly based on `Param`'s requirements // in `update_archetype_component_access`. // Our caller upholds the requirements. - let params = ::State::get_param( + let params = Param::get_param( self.param_state.as_mut().expect(Self::PARAM_MESSAGE), &self.system_meta, world, @@ -422,17 +415,14 @@ where #[inline] fn apply_buffers(&mut self, world: &mut World) { let param_state = self.param_state.as_mut().expect(Self::PARAM_MESSAGE); - param_state.apply(&self.system_meta, world); + Param::apply(param_state, &self.system_meta, world); } #[inline] fn initialize(&mut self, world: &mut World) { self.world_id = Some(world.id()); self.system_meta.last_change_tick = world.change_tick().wrapping_sub(MAX_CHANGE_AGE); - self.param_state = Some(::init( - world, - &mut self.system_meta, - )); + self.param_state = Some(Param::init_state(world, &mut self.system_meta)); } fn update_archetype_component_access(&mut self, world: &World) { @@ -443,7 +433,9 @@ where let archetype_index_range = old_generation.value()..new_generation.value(); for archetype_index in archetype_index_range { - self.param_state.as_mut().unwrap().new_archetype( + let param_state = self.param_state.as_mut().unwrap(); + Param::new_archetype( + param_state, &archetypes[ArchetypeId::new(archetype_index)], &mut self.system_meta, ); @@ -514,7 +506,7 @@ impl Copy for SystemTypeIdLabel {} /// pub fn pipe( /// mut a: A, /// mut b: B, -/// ) -> impl FnMut(In, ParamSet<(SystemParamItem, SystemParamItem)>) -> BOut +/// ) -> impl FnMut(In, ParamSet<(AParam, BParam)>) -> BOut /// where /// // We need A and B to be systems, add those bounds /// A: SystemParamFunction, diff --git a/crates/bevy_ecs/src/system/system_param.rs b/crates/bevy_ecs/src/system/system_param.rs index 7751995ef1cb7..814ac6316e441 100644 --- a/crates/bevy_ecs/src/system/system_param.rs +++ b/crates/bevy_ecs/src/system/system_param.rs @@ -72,7 +72,7 @@ use std::{ /// /// ```text /// expected ... [ParamType] -/// found associated type `<<[ParamType] as SystemParam>::State as SystemParamState>::Item<'_, '_>` +/// found associated type `<[ParamType] as SystemParam>::Item<'_, '_>` /// ``` /// where `[ParamType]` is the type of one of your fields. /// To solve this error, you can wrap the field of type `[ParamType]` with [`StaticSystemParam`] @@ -81,7 +81,7 @@ use std::{ /// ## Details /// /// The derive macro requires that the [`SystemParam`] implementation of -/// each field `F`'s [`State`](`SystemParam::State`)'s [`Item`](`SystemParamState::Item`) is itself `F` +/// each field `F`'s [`Item`](`SystemParam::Item`)'s is itself `F` /// (ignoring lifetimes for simplicity). /// This assumption is due to type inference reasons, so that the derived [`SystemParam`] can be /// used as an argument to a function system. @@ -121,35 +121,46 @@ use std::{ /// /// [`SyncCell`]: bevy_utils::synccell::SyncCell /// [`Exclusive`]: https://doc.rust-lang.org/nightly/std/sync/struct.Exclusive.html -pub trait SystemParam: Sized { - type State: SystemParamState; -} - -pub type SystemParamItem<'w, 's, P> = <

::State as SystemParamState>::Item<'w, 's>; - -/// The state of a [`SystemParam`]. /// /// # Safety /// -/// It is the implementor's responsibility to ensure `system_meta` is populated with the _exact_ -/// [`World`] access used by the [`SystemParamState`]. -/// Additionally, it is the implementor's responsibility to ensure there is no -/// conflicting access across all [`SystemParam`]'s. -pub unsafe trait SystemParamState: Send + Sync + 'static { - fn init(world: &mut World, system_meta: &mut SystemMeta) -> Self; +/// The implementor must ensure that [`SystemParam::init_state`] correctly registers all +/// [`World`] accesses used by this [`SystemParam`] with the provided [`system_meta`](SystemMeta). +pub unsafe trait SystemParam: Sized { + /// Used to store data which persists across invocations of a system. + type State: Send + Sync + 'static; + + /// The item type returned when constructing this system param. + /// The value of this associated type should be `Self`, instantiated with new lifetimes. + /// + /// You could think of `SystemParam::Item<'w, 's>` as being an *operation* that changes the lifetimes bound to `Self`. + type Item<'world, 'state>: SystemParam; + + /// Registers any [`World`] access used by this [`SystemParam`] + /// and creates a new instance of this param's [`State`](Self::State). + fn init_state(world: &mut World, system_meta: &mut SystemMeta) -> Self::State; + + /// For the specified [`Archetype`], registers the components accessed by this [`SystemParam`] (if applicable). #[inline] - fn new_archetype(&mut self, _archetype: &Archetype, _system_meta: &mut SystemMeta) {} + fn new_archetype( + _state: &mut Self::State, + _archetype: &Archetype, + _system_meta: &mut SystemMeta, + ) { + } + + /// Applies any deferred mutations stored in this [`SystemParam`]'s state. + /// This is used to apply [`Commands`] at the end of a stage. #[inline] #[allow(unused_variables)] - fn apply(&mut self, system_meta: &SystemMeta, _world: &mut World) {} + fn apply(state: &mut Self::State, system_meta: &SystemMeta, world: &mut World) {} - type Item<'world, 'state>: SystemParam; /// # Safety /// - /// This call might access any of the input parameters in an unsafe way. Make sure the data - /// access is safe in the context of the system scheduler. + /// This call might use any of the [`World`] accesses that were registered in [`Self::init_state`]. + /// You must ensure that none of those accesses conflict with any other [`SystemParam`]s running in parallel with this one. unsafe fn get_param<'world, 'state>( - state: &'state mut Self, + state: &'state mut Self::State, system_meta: &SystemMeta, world: &'world World, change_tick: u32, @@ -159,14 +170,11 @@ pub unsafe trait SystemParamState: Send + Sync + 'static { /// A [`SystemParam`] that only reads a given [`World`]. /// /// # Safety -/// This must only be implemented for [`SystemParam`] impls that exclusively read the World passed in to [`SystemParamState::get_param`] +/// This must only be implemented for [`SystemParam`] impls that exclusively read the World passed in to [`SystemParam::get_param`] pub unsafe trait ReadOnlySystemParam: SystemParam {} -impl<'w, 's, Q: WorldQuery + 'static, F: ReadOnlyWorldQuery + 'static> SystemParam - for Query<'w, 's, Q, F> -{ - type State = QueryState; -} +/// Shorthand way of accessing the associated type [`SystemParam::Item`] for a given [`SystemParam`]. +pub type SystemParamItem<'w, 's, P> =

::Item<'w, 's>; // SAFETY: QueryState is constrained to read-only fetches, so it only reads World. unsafe impl<'w, 's, Q: ReadOnlyWorldQuery + 'static, F: ReadOnlyWorldQuery + 'static> @@ -175,13 +183,14 @@ unsafe impl<'w, 's, Q: ReadOnlyWorldQuery + 'static, F: ReadOnlyWorldQuery + 'st } // SAFETY: Relevant query ComponentId and ArchetypeComponentId access is applied to SystemMeta. If -// this QueryState conflicts with any prior access, a panic will occur. -unsafe impl SystemParamState - for QueryState +// this Query conflicts with any prior access, a panic will occur. +unsafe impl SystemParam + for Query<'_, '_, Q, F> { + type State = QueryState; type Item<'w, 's> = Query<'w, 's, Q, F>; - fn init(world: &mut World, system_meta: &mut SystemMeta) -> Self { + fn init_state(world: &mut World, system_meta: &mut SystemMeta) -> Self::State { let state = QueryState::new(world); assert_component_access_compatibility( &system_meta.name, @@ -200,16 +209,16 @@ unsafe impl SystemPara state } - fn new_archetype(&mut self, archetype: &Archetype, system_meta: &mut SystemMeta) { - self.new_archetype(archetype); + fn new_archetype(state: &mut Self::State, archetype: &Archetype, system_meta: &mut SystemMeta) { + state.new_archetype(archetype); system_meta .archetype_component_access - .extend(&self.archetype_component_access); + .extend(&state.archetype_component_access); } #[inline] unsafe fn get_param<'w, 's>( - state: &'s mut Self, + state: &'s mut Self::State, system_meta: &SystemMeta, world: &'w World, change_tick: u32, @@ -353,8 +362,6 @@ pub struct ParamSet<'w, 's, T: SystemParam> { system_meta: SystemMeta, change_tick: u32, } -/// The [`SystemParamState`] of [`ParamSet`]. -pub struct ParamSetState(T); impl_param_set!(); @@ -410,9 +417,6 @@ pub struct Res<'w, T: Resource> { change_tick: u32, } -// SAFETY: Res only reads a single World resource -unsafe impl<'w, T: Resource> ReadOnlySystemParam for Res<'w, T> {} - impl<'w, T: Resource> Debug for Res<'w, T> where T: Debug, @@ -491,23 +495,16 @@ where } } -/// The [`SystemParamState`] of [`Res`]. -#[doc(hidden)] -pub struct ResState { - component_id: ComponentId, - marker: PhantomData, -} - -impl<'a, T: Resource> SystemParam for Res<'a, T> { - type State = ResState; -} +// SAFETY: Res only reads a single World resource +unsafe impl<'a, T: Resource> ReadOnlySystemParam for Res<'a, T> {} // SAFETY: Res ComponentId and ArchetypeComponentId access is applied to SystemMeta. If this Res // conflicts with any prior access, a panic will occur. -unsafe impl SystemParamState for ResState { +unsafe impl<'a, T: Resource> SystemParam for Res<'a, T> { + type State = ComponentId; type Item<'w, 's> = Res<'w, T>; - fn init(world: &mut World, system_meta: &mut SystemMeta) -> Self { + fn init_state(world: &mut World, system_meta: &mut SystemMeta) -> Self::State { let component_id = world.initialize_resource::(); let combined_access = system_meta.component_access_set.combined_access(); assert!( @@ -526,21 +523,19 @@ unsafe impl SystemParamState for ResState { system_meta .archetype_component_access .add_read(archetype_component_id); - Self { - component_id, - marker: PhantomData, - } + + component_id } #[inline] unsafe fn get_param<'w, 's>( - state: &'s mut Self, + &mut component_id: &'s mut Self::State, system_meta: &SystemMeta, world: &'w World, change_tick: u32, ) -> Self::Item<'w, 's> { let (ptr, ticks) = world - .get_resource_with_ticks(state.component_id) + .get_resource_with_ticks(component_id) .unwrap_or_else(|| { panic!( "Resource requested by {} does not exist: {}", @@ -558,36 +553,27 @@ unsafe impl SystemParamState for ResState { } } -/// The [`SystemParamState`] of [`Option>`]. -/// See: [`Res`] -#[doc(hidden)] -pub struct OptionResState(ResState); - -impl<'a, T: Resource> SystemParam for Option> { - type State = OptionResState; -} - // SAFETY: Only reads a single World resource unsafe impl<'a, T: Resource> ReadOnlySystemParam for Option> {} -// SAFETY: this impl defers to `ResState`, which initializes -// and validates the correct world access -unsafe impl SystemParamState for OptionResState { +// SAFETY: this impl defers to `Res`, which initializes and validates the correct world access. +unsafe impl<'a, T: Resource> SystemParam for Option> { + type State = ComponentId; type Item<'w, 's> = Option>; - fn init(world: &mut World, system_meta: &mut SystemMeta) -> Self { - Self(ResState::init(world, system_meta)) + fn init_state(world: &mut World, system_meta: &mut SystemMeta) -> Self::State { + Res::::init_state(world, system_meta) } #[inline] unsafe fn get_param<'w, 's>( - state: &'s mut Self, + &mut component_id: &'s mut Self::State, system_meta: &SystemMeta, world: &'w World, change_tick: u32, ) -> Self::Item<'w, 's> { world - .get_resource_with_ticks(state.0.component_id) + .get_resource_with_ticks(component_id) .map(|(ptr, ticks)| Res { value: ptr.deref(), added: ticks.added.deref(), @@ -598,23 +584,13 @@ unsafe impl SystemParamState for OptionResState { } } -/// The [`SystemParamState`] of [`ResMut`]. -#[doc(hidden)] -pub struct ResMutState { - component_id: ComponentId, - marker: PhantomData, -} - -impl<'a, T: Resource> SystemParam for ResMut<'a, T> { - type State = ResMutState; -} - // SAFETY: Res ComponentId and ArchetypeComponentId access is applied to SystemMeta. If this Res // conflicts with any prior access, a panic will occur. -unsafe impl SystemParamState for ResMutState { +unsafe impl<'a, T: Resource> SystemParam for ResMut<'a, T> { + type State = ComponentId; type Item<'w, 's> = ResMut<'w, T>; - fn init(world: &mut World, system_meta: &mut SystemMeta) -> Self { + fn init_state(world: &mut World, system_meta: &mut SystemMeta) -> Self::State { let component_id = world.initialize_resource::(); let combined_access = system_meta.component_access_set.combined_access(); if combined_access.has_write(component_id) { @@ -636,21 +612,19 @@ unsafe impl SystemParamState for ResMutState { system_meta .archetype_component_access .add_write(archetype_component_id); - Self { - component_id, - marker: PhantomData, - } + + component_id } #[inline] unsafe fn get_param<'w, 's>( - state: &'s mut Self, + &mut component_id: &'s mut Self::State, system_meta: &SystemMeta, world: &'w World, change_tick: u32, ) -> Self::Item<'w, 's> { let value = world - .get_resource_unchecked_mut_with_id(state.component_id) + .get_resource_unchecked_mut_with_id(component_id) .unwrap_or_else(|| { panic!( "Resource requested by {} does not exist: {}", @@ -670,33 +644,24 @@ unsafe impl SystemParamState for ResMutState { } } -/// The [`SystemParamState`] of [`Option>`]. -/// See: [`ResMut`] -#[doc(hidden)] -pub struct OptionResMutState(ResMutState); - -impl<'a, T: Resource> SystemParam for Option> { - type State = OptionResMutState; -} - -// SAFETY: this impl defers to `ResMutState`, which initializes -// and validates the correct world access -unsafe impl SystemParamState for OptionResMutState { +// SAFETY: this impl defers to `ResMut`, which initializes and validates the correct world access. +unsafe impl<'a, T: Resource> SystemParam for Option> { + type State = ComponentId; type Item<'w, 's> = Option>; - fn init(world: &mut World, system_meta: &mut SystemMeta) -> Self { - Self(ResMutState::init(world, system_meta)) + fn init_state(world: &mut World, system_meta: &mut SystemMeta) -> Self::State { + ResMut::::init_state(world, system_meta) } #[inline] unsafe fn get_param<'w, 's>( - state: &'s mut Self, + &mut component_id: &'s mut Self::State, system_meta: &SystemMeta, world: &'w World, change_tick: u32, ) -> Self::Item<'w, 's> { world - .get_resource_unchecked_mut_with_id(state.0.component_id) + .get_resource_unchecked_mut_with_id(component_id) .map(|value| ResMut { value: value.value, ticks: Ticks { @@ -709,32 +674,29 @@ unsafe impl SystemParamState for OptionResMutState { } } -impl<'w, 's> SystemParam for Commands<'w, 's> { - type State = CommandQueue; -} - // SAFETY: Commands only accesses internal state unsafe impl<'w, 's> ReadOnlySystemParam for Commands<'w, 's> {} // SAFETY: only local state is accessed -unsafe impl SystemParamState for CommandQueue { +unsafe impl SystemParam for Commands<'_, '_> { + type State = CommandQueue; type Item<'w, 's> = Commands<'w, 's>; - fn init(_world: &mut World, _system_meta: &mut SystemMeta) -> Self { + fn init_state(_world: &mut World, _system_meta: &mut SystemMeta) -> Self::State { Default::default() } - fn apply(&mut self, _system_meta: &SystemMeta, world: &mut World) { + fn apply(state: &mut Self::State, _system_meta: &SystemMeta, world: &mut World) { #[cfg(feature = "trace")] let _system_span = bevy_utils::tracing::info_span!("system_commands", name = _system_meta.name()) .entered(); - self.apply(world); + state.apply(world); } #[inline] unsafe fn get_param<'w, 's>( - state: &'s mut Self, + state: &'s mut Self::State, _system_meta: &SystemMeta, world: &'w World, _change_tick: u32, @@ -746,19 +708,12 @@ unsafe impl SystemParamState for CommandQueue { /// SAFETY: only reads world unsafe impl<'w> ReadOnlySystemParam for &'w World {} -/// The [`SystemParamState`] of [`&World`](crate::world::World). -#[doc(hidden)] -pub struct WorldState; - -impl<'w> SystemParam for &'w World { - type State = WorldState; -} - // SAFETY: `read_all` access is set and conflicts result in a panic -unsafe impl SystemParamState for WorldState { +unsafe impl SystemParam for &'_ World { + type State = (); type Item<'w, 's> = &'w World; - fn init(_world: &mut World, system_meta: &mut SystemMeta) -> Self { + fn init_state(_world: &mut World, system_meta: &mut SystemMeta) -> Self::State { let mut access = Access::default(); access.read_all(); if !system_meta @@ -780,12 +735,10 @@ unsafe impl SystemParamState for WorldState { panic!("&World conflicts with a previous mutable system parameter. Allowing this would break Rust's mutability rules"); } system_meta.component_access_set.add(filtered_access); - - WorldState } unsafe fn get_param<'w, 's>( - _state: &'s mut Self, + _state: &'s mut Self::State, _system_meta: &SystemMeta, world: &'w World, _change_tick: u32, @@ -895,30 +848,23 @@ where } } -/// The [`SystemParamState`] of [`Local`]. -#[doc(hidden)] -pub struct LocalState(pub(crate) SyncCell); - -impl<'s, T: FromWorld + Send + 'static> SystemParam for Local<'s, T> { - type State = LocalState; -} - // SAFETY: only local state is accessed -unsafe impl SystemParamState for LocalState { +unsafe impl<'a, T: FromWorld + Send + 'static> SystemParam for Local<'a, T> { + type State = SyncCell; type Item<'w, 's> = Local<'s, T>; - fn init(world: &mut World, _system_meta: &mut SystemMeta) -> Self { - Self(SyncCell::new(T::from_world(world))) + fn init_state(world: &mut World, _system_meta: &mut SystemMeta) -> Self::State { + SyncCell::new(T::from_world(world)) } #[inline] unsafe fn get_param<'w, 's>( - state: &'s mut Self, + state: &'s mut Self::State, _system_meta: &SystemMeta, _world: &'w World, _change_tick: u32, ) -> Self::Item<'w, 's> { - Local(state.0.get()) + Local(state.get()) } } @@ -980,39 +926,26 @@ impl<'a, T: Component> IntoIterator for &'a RemovedComponents<'a, T> { // SAFETY: Only reads World components unsafe impl<'a, T: Component> ReadOnlySystemParam for RemovedComponents<'a, T> {} -/// The [`SystemParamState`] of [`RemovedComponents`]. -#[doc(hidden)] -pub struct RemovedComponentsState { - component_id: ComponentId, - marker: PhantomData, -} - -impl<'a, T: Component> SystemParam for RemovedComponents<'a, T> { - type State = RemovedComponentsState; -} - // SAFETY: no component access. removed component entity collections can be read in parallel and are // never mutably borrowed during system execution -unsafe impl SystemParamState for RemovedComponentsState { +unsafe impl<'a, T: Component> SystemParam for RemovedComponents<'a, T> { + type State = ComponentId; type Item<'w, 's> = RemovedComponents<'w, T>; - fn init(world: &mut World, _system_meta: &mut SystemMeta) -> Self { - Self { - component_id: world.init_component::(), - marker: PhantomData, - } + fn init_state(world: &mut World, _system_meta: &mut SystemMeta) -> Self::State { + world.init_component::() } #[inline] unsafe fn get_param<'w, 's>( - state: &'s mut Self, + &mut component_id: &'s mut Self::State, _system_meta: &SystemMeta, world: &'w World, _change_tick: u32, ) -> Self::Item<'w, 's> { RemovedComponents { world, - component_id: state.component_id, + component_id, marker: PhantomData, } } @@ -1083,23 +1016,13 @@ impl<'a, T> From> for NonSend<'a, T> { } } -/// The [`SystemParamState`] of [`NonSend`]. -#[doc(hidden)] -pub struct NonSendState { - component_id: ComponentId, - marker: PhantomData T>, -} - -impl<'a, T: 'static> SystemParam for NonSend<'a, T> { - type State = NonSendState; -} - // SAFETY: NonSendComponentId and ArchetypeComponentId access is applied to SystemMeta. If this // NonSend conflicts with any prior access, a panic will occur. -unsafe impl SystemParamState for NonSendState { +unsafe impl<'a, T: 'static> SystemParam for NonSend<'a, T> { + type State = ComponentId; type Item<'w, 's> = NonSend<'w, T>; - fn init(world: &mut World, system_meta: &mut SystemMeta) -> Self { + fn init_state(world: &mut World, system_meta: &mut SystemMeta) -> Self::State { system_meta.set_non_send(); let component_id = world.initialize_non_send_resource::(); @@ -1120,22 +1043,20 @@ unsafe impl SystemParamState for NonSendState { system_meta .archetype_component_access .add_read(archetype_component_id); - Self { - component_id, - marker: PhantomData, - } + + component_id } #[inline] unsafe fn get_param<'w, 's>( - state: &'s mut Self, + &mut component_id: &'s mut Self::State, system_meta: &SystemMeta, world: &'w World, change_tick: u32, ) -> Self::Item<'w, 's> { world.validate_non_send_access::(); let (ptr, ticks) = world - .get_resource_with_ticks(state.component_id) + .get_resource_with_ticks(component_id) .unwrap_or_else(|| { panic!( "Non-send resource requested by {} does not exist: {}", @@ -1153,37 +1074,25 @@ unsafe impl SystemParamState for NonSendState { } } -/// The [`SystemParamState`] of [`Option>`]. -/// See: [`NonSend`] -#[doc(hidden)] -pub struct OptionNonSendState(NonSendState); - -impl<'w, T: 'static> SystemParam for Option> { - type State = OptionNonSendState; -} - -// SAFETY: Only reads a single non-send resource -unsafe impl<'w, T: 'static> ReadOnlySystemParam for Option> {} - -// SAFETY: this impl defers to `NonSendState`, which initializes -// and validates the correct world access -unsafe impl SystemParamState for OptionNonSendState { +// SAFETY: this impl defers to `NonSend`, which initializes and validates the correct world access. +unsafe impl SystemParam for Option> { + type State = ComponentId; type Item<'w, 's> = Option>; - fn init(world: &mut World, system_meta: &mut SystemMeta) -> Self { - Self(NonSendState::init(world, system_meta)) + fn init_state(world: &mut World, system_meta: &mut SystemMeta) -> Self::State { + NonSend::::init_state(world, system_meta) } #[inline] unsafe fn get_param<'w, 's>( - state: &'s mut Self, + &mut component_id: &'s mut Self::State, system_meta: &SystemMeta, world: &'w World, change_tick: u32, ) -> Self::Item<'w, 's> { world.validate_non_send_access::(); world - .get_resource_with_ticks(state.0.component_id) + .get_resource_with_ticks(component_id) .map(|(ptr, ticks)| NonSend { value: ptr.deref(), ticks: ticks.read(), @@ -1193,23 +1102,16 @@ unsafe impl SystemParamState for OptionNonSendState { } } -/// The [`SystemParamState`] of [`NonSendMut`]. -#[doc(hidden)] -pub struct NonSendMutState { - component_id: ComponentId, - marker: PhantomData T>, -} - -impl<'a, T: 'static> SystemParam for NonSendMut<'a, T> { - type State = NonSendMutState; -} +// SAFETY: Only reads a single non-send resource +unsafe impl<'a, T: 'static> ReadOnlySystemParam for NonSendMut<'a, T> {} // SAFETY: NonSendMut ComponentId and ArchetypeComponentId access is applied to SystemMeta. If this // NonSendMut conflicts with any prior access, a panic will occur. -unsafe impl SystemParamState for NonSendMutState { +unsafe impl<'a, T: 'static> SystemParam for NonSendMut<'a, T> { + type State = ComponentId; type Item<'w, 's> = NonSendMut<'w, T>; - fn init(world: &mut World, system_meta: &mut SystemMeta) -> Self { + fn init_state(world: &mut World, system_meta: &mut SystemMeta) -> Self::State { system_meta.set_non_send(); let component_id = world.initialize_non_send_resource::(); @@ -1233,22 +1135,20 @@ unsafe impl SystemParamState for NonSendMutState { system_meta .archetype_component_access .add_write(archetype_component_id); - Self { - component_id, - marker: PhantomData, - } + + component_id } #[inline] unsafe fn get_param<'w, 's>( - state: &'s mut Self, + &mut component_id: &'s mut Self::State, system_meta: &SystemMeta, world: &'w World, change_tick: u32, ) -> Self::Item<'w, 's> { world.validate_non_send_access::(); let (ptr, ticks) = world - .get_resource_with_ticks(state.component_id) + .get_resource_with_ticks(component_id) .unwrap_or_else(|| { panic!( "Non-send resource requested by {} does not exist: {}", @@ -1263,34 +1163,25 @@ unsafe impl SystemParamState for NonSendMutState { } } -/// The [`SystemParamState`] of [`Option>`]. -/// See: [`NonSendMut`] -#[doc(hidden)] -pub struct OptionNonSendMutState(NonSendMutState); - -impl<'a, T: 'static> SystemParam for Option> { - type State = OptionNonSendMutState; -} - -// SAFETY: this impl defers to `NonSendMutState`, which initializes -// and validates the correct world access -unsafe impl SystemParamState for OptionNonSendMutState { +// SAFETY: this impl defers to `NonSendMut`, which initializes and validates the correct world access. +unsafe impl<'a, T: 'static> SystemParam for Option> { + type State = ComponentId; type Item<'w, 's> = Option>; - fn init(world: &mut World, system_meta: &mut SystemMeta) -> Self { - Self(NonSendMutState::init(world, system_meta)) + fn init_state(world: &mut World, system_meta: &mut SystemMeta) -> Self::State { + NonSendMut::::init_state(world, system_meta) } #[inline] unsafe fn get_param<'w, 's>( - state: &'s mut Self, + &mut component_id: &'s mut Self::State, system_meta: &SystemMeta, world: &'w World, change_tick: u32, ) -> Self::Item<'w, 's> { world.validate_non_send_access::(); world - .get_resource_with_ticks(state.0.component_id) + .get_resource_with_ticks(component_id) .map(|(ptr, ticks)| NonSendMut { value: ptr.assert_unique().deref_mut(), ticks: Ticks::from_tick_cells(ticks, system_meta.last_change_tick, change_tick), @@ -1298,28 +1189,19 @@ unsafe impl SystemParamState for OptionNonSendMutState { } } -impl<'a> SystemParam for &'a Archetypes { - type State = ArchetypesState; -} - // SAFETY: Only reads World archetypes unsafe impl<'a> ReadOnlySystemParam for &'a Archetypes {} -/// The [`SystemParamState`] of [`Archetypes`]. -#[doc(hidden)] -pub struct ArchetypesState; - // SAFETY: no component value access -unsafe impl SystemParamState for ArchetypesState { +unsafe impl<'a> SystemParam for &'a Archetypes { + type State = (); type Item<'w, 's> = &'w Archetypes; - fn init(_world: &mut World, _system_meta: &mut SystemMeta) -> Self { - Self - } + fn init_state(_world: &mut World, _system_meta: &mut SystemMeta) -> Self::State {} #[inline] unsafe fn get_param<'w, 's>( - _state: &'s mut Self, + _state: &'s mut Self::State, _system_meta: &SystemMeta, world: &'w World, _change_tick: u32, @@ -1328,28 +1210,19 @@ unsafe impl SystemParamState for ArchetypesState { } } -impl<'a> SystemParam for &'a Components { - type State = ComponentsState; -} - // SAFETY: Only reads World components unsafe impl<'a> ReadOnlySystemParam for &'a Components {} -/// The [`SystemParamState`] of [`Components`]. -#[doc(hidden)] -pub struct ComponentsState; - // SAFETY: no component value access -unsafe impl SystemParamState for ComponentsState { +unsafe impl<'a> SystemParam for &'a Components { + type State = (); type Item<'w, 's> = &'w Components; - fn init(_world: &mut World, _system_meta: &mut SystemMeta) -> Self { - Self - } + fn init_state(_world: &mut World, _system_meta: &mut SystemMeta) -> Self::State {} #[inline] unsafe fn get_param<'w, 's>( - _state: &'s mut Self, + _state: &'s mut Self::State, _system_meta: &SystemMeta, world: &'w World, _change_tick: u32, @@ -1358,28 +1231,19 @@ unsafe impl SystemParamState for ComponentsState { } } -impl<'a> SystemParam for &'a Entities { - type State = EntitiesState; -} - // SAFETY: Only reads World entities unsafe impl<'a> ReadOnlySystemParam for &'a Entities {} -/// The [`SystemParamState`] of [`Entities`]. -#[doc(hidden)] -pub struct EntitiesState; - // SAFETY: no component value access -unsafe impl SystemParamState for EntitiesState { +unsafe impl<'a> SystemParam for &'a Entities { + type State = (); type Item<'w, 's> = &'w Entities; - fn init(_world: &mut World, _system_meta: &mut SystemMeta) -> Self { - Self - } + fn init_state(_world: &mut World, _system_meta: &mut SystemMeta) -> Self::State {} #[inline] unsafe fn get_param<'w, 's>( - _state: &'s mut Self, + _state: &'s mut Self::State, _system_meta: &SystemMeta, world: &'w World, _change_tick: u32, @@ -1388,28 +1252,19 @@ unsafe impl SystemParamState for EntitiesState { } } -impl<'a> SystemParam for &'a Bundles { - type State = BundlesState; -} - // SAFETY: Only reads World bundles unsafe impl<'a> ReadOnlySystemParam for &'a Bundles {} -/// The [`SystemParamState`] of [`Bundles`]. -#[doc(hidden)] -pub struct BundlesState; - // SAFETY: no component value access -unsafe impl SystemParamState for BundlesState { +unsafe impl<'a> SystemParam for &'a Bundles { + type State = (); type Item<'w, 's> = &'w Bundles; - fn init(_world: &mut World, _system_meta: &mut SystemMeta) -> Self { - Self - } + fn init_state(_world: &mut World, _system_meta: &mut SystemMeta) -> Self::State {} #[inline] unsafe fn get_param<'w, 's>( - _state: &'s mut Self, + _state: &'s mut Self::State, _system_meta: &SystemMeta, world: &'w World, _change_tick: u32, @@ -1450,24 +1305,15 @@ impl SystemChangeTick { // SAFETY: Only reads internal system state unsafe impl ReadOnlySystemParam for SystemChangeTick {} -impl SystemParam for SystemChangeTick { - type State = SystemChangeTickState; -} - -/// The [`SystemParamState`] of [`SystemChangeTick`]. -#[doc(hidden)] -pub struct SystemChangeTickState {} - -// SAFETY: `SystemParamTickState` doesn't require any world access -unsafe impl SystemParamState for SystemChangeTickState { +// SAFETY: `SystemChangeTick` doesn't require any world access +unsafe impl SystemParam for SystemChangeTick { + type State = (); type Item<'w, 's> = SystemChangeTick; - fn init(_world: &mut World, _system_meta: &mut SystemMeta) -> Self { - Self {} - } + fn init_state(_world: &mut World, _system_meta: &mut SystemMeta) -> Self::State {} unsafe fn get_param<'w, 's>( - _state: &'s mut Self, + _state: &'s mut Self::State, system_meta: &SystemMeta, _world: &'w World, change_tick: u32, @@ -1526,79 +1372,61 @@ impl<'s> std::fmt::Display for SystemName<'s> { } } -impl<'s> SystemParam for SystemName<'s> { - type State = SystemNameState; -} - -// SAFETY: Only reads internal system state -unsafe impl<'s> ReadOnlySystemParam for SystemName<'s> {} - -/// The [`SystemParamState`] of [`SystemName`]. -#[doc(hidden)] -pub struct SystemNameState { - name: Cow<'static, str>, -} - // SAFETY: no component value access -unsafe impl SystemParamState for SystemNameState { +unsafe impl SystemParam for SystemName<'_> { + type State = Cow<'static, str>; type Item<'w, 's> = SystemName<'s>; - fn init(_world: &mut World, system_meta: &mut SystemMeta) -> Self { - Self { - name: system_meta.name.clone(), - } + fn init_state(_world: &mut World, system_meta: &mut SystemMeta) -> Self::State { + system_meta.name.clone() } #[inline] unsafe fn get_param<'w, 's>( - state: &'s mut Self, + name: &'s mut Self::State, _system_meta: &SystemMeta, _world: &'w World, _change_tick: u32, ) -> Self::Item<'w, 's> { - SystemName { - name: state.name.as_ref(), - } + SystemName { name } } } +// SAFETY: Only reads internal system state +unsafe impl<'s> ReadOnlySystemParam for SystemName<'s> {} + macro_rules! impl_system_param_tuple { ($($param: ident),*) => { - impl<$($param: SystemParam),*> SystemParam for ($($param,)*) { - type State = ($($param::State,)*); - } // SAFETY: tuple consists only of ReadOnlySystemParams unsafe impl<$($param: ReadOnlySystemParam),*> ReadOnlySystemParam for ($($param,)*) {} - - // SAFETY: implementors of each `SystemParamState` in the tuple have validated their impls + // 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)] - unsafe impl<$($param: SystemParamState),*> SystemParamState for ($($param,)*) { + unsafe impl<$($param: SystemParam),*> SystemParam for ($($param,)*) { + type State = ($($param::State,)*); type Item<'w, 's> = ($($param::Item::<'w, 's>,)*); #[inline] - fn init(_world: &mut World, _system_meta: &mut SystemMeta) -> Self { - (($($param::init(_world, _system_meta),)*)) + fn init_state(_world: &mut World, _system_meta: &mut SystemMeta) -> Self::State { + (($($param::init_state(_world, _system_meta),)*)) } #[inline] - fn new_archetype(&mut self, _archetype: &Archetype, _system_meta: &mut SystemMeta) { - let ($($param,)*) = self; - $($param.new_archetype(_archetype, _system_meta);)* + fn new_archetype(($($param,)*): &mut Self::State, _archetype: &Archetype, _system_meta: &mut SystemMeta) { + $($param::new_archetype($param, _archetype, _system_meta);)* } #[inline] - fn apply(&mut self, _system_meta: &SystemMeta, _world: &mut World) { - let ($($param,)*) = self; - $($param.apply(_system_meta, _world);)* + fn apply(($($param,)*): &mut Self::State, _system_meta: &SystemMeta, _world: &mut World) { + $($param::apply($param, _system_meta, _world);)* } #[inline] #[allow(clippy::unused_unit)] unsafe fn get_param<'w, 's>( - state: &'s mut Self, + state: &'s mut Self::State, _system_meta: &SystemMeta, _world: &'w World, _change_tick: u32, @@ -1698,48 +1526,37 @@ impl<'w, 's, P: SystemParam> StaticSystemParam<'w, 's, P> { } } -/// The [`SystemParamState`] of [`StaticSystemParam`]. -#[doc(hidden)] -pub struct StaticSystemParamState(S, PhantomData P>); - // SAFETY: This doesn't add any more reads, and the delegated fetch confirms it unsafe impl<'w, 's, P: ReadOnlySystemParam + 'static> ReadOnlySystemParam for StaticSystemParam<'w, 's, P> { } -impl<'world, 'state, P: SystemParam + 'static> SystemParam - for StaticSystemParam<'world, 'state, P> -{ - type State = StaticSystemParamState; -} - -// SAFETY: all methods are just delegated to `S`'s `SystemParamState` implementation -unsafe impl + 'static> SystemParamState - for StaticSystemParamState -{ +// SAFETY: all methods are just delegated to `P`'s `SystemParam` implementation +unsafe impl SystemParam for StaticSystemParam<'_, '_, P> { + type State = P::State; type Item<'world, 'state> = StaticSystemParam<'world, 'state, P>; - fn init(world: &mut World, system_meta: &mut SystemMeta) -> Self { - Self(S::init(world, system_meta), PhantomData) + fn init_state(world: &mut World, system_meta: &mut SystemMeta) -> Self::State { + P::init_state(world, system_meta) } - fn new_archetype(&mut self, archetype: &Archetype, system_meta: &mut SystemMeta) { - self.0.new_archetype(archetype, system_meta); + fn new_archetype(state: &mut Self::State, archetype: &Archetype, system_meta: &mut SystemMeta) { + P::new_archetype(state, archetype, system_meta); } - fn apply(&mut self, system_meta: &SystemMeta, world: &mut World) { - self.0.apply(system_meta, world); + fn apply(state: &mut Self::State, system_meta: &SystemMeta, world: &mut World) { + P::apply(state, system_meta, world); } unsafe fn get_param<'world, 'state>( - state: &'state mut Self, + state: &'state mut Self::State, system_meta: &SystemMeta, world: &'world World, change_tick: u32, ) -> Self::Item<'world, 'state> { - // SAFETY: We properly delegate SystemParamState - StaticSystemParam(S::get_param(&mut state.0, system_meta, world, change_tick)) + // SAFETY: Defer to the safety of P::SystemParam + StaticSystemParam(P::get_param(state, system_meta, world, change_tick)) } } @@ -1805,6 +1622,13 @@ mod tests { crate::system::assert_is_system(long_system); } + #[derive(SystemParam)] + struct MyParam<'w, T: Resource, Marker: 'static> { + _foo: Res<'w, T>, + #[system_param(ignore)] + marker: PhantomData, + } + #[derive(SystemParam)] pub struct UnitParam; diff --git a/crates/bevy_render/src/extract_param.rs b/crates/bevy_render/src/extract_param.rs index b3d4ba665a55a..4038a71aa6a50 100644 --- a/crates/bevy_render/src/extract_param.rs +++ b/crates/bevy_render/src/extract_param.rs @@ -1,10 +1,7 @@ use crate::MainWorld; use bevy_ecs::{ prelude::*, - system::{ - ReadOnlySystemParam, ResState, SystemMeta, SystemParam, SystemParamItem, SystemParamState, - SystemState, - }, + system::{ReadOnlySystemParam, SystemMeta, SystemParam, SystemParamItem, SystemState}, }; use std::ops::{Deref, DerefMut}; @@ -49,42 +46,36 @@ where item: SystemParamItem<'w, 's, P>, } -impl<'w, 's, P> SystemParam for Extract<'w, 's, P> -where - P: ReadOnlySystemParam, -{ - type State = ExtractState

; -} - #[doc(hidden)] pub struct ExtractState { state: SystemState

, - main_world_state: ResState, + main_world_state: as SystemParam>::State, } -// SAFETY: only accesses MainWorld resource with read only system params using ResState, +// SAFETY: only accesses MainWorld resource with read only system params using Res, // which is initialized in init() -unsafe impl SystemParamState for ExtractState

+unsafe impl

SystemParam for Extract<'_, '_, P> where - P: ReadOnlySystemParam + 'static, + P: ReadOnlySystemParam, { + type State = ExtractState

; type Item<'w, 's> = Extract<'w, 's, P>; - fn init(world: &mut World, system_meta: &mut SystemMeta) -> Self { + fn init_state(world: &mut World, system_meta: &mut SystemMeta) -> Self::State { let mut main_world = world.resource_mut::(); - Self { + ExtractState { state: SystemState::new(&mut main_world), - main_world_state: ResState::init(world, system_meta), + main_world_state: Res::::init_state(world, system_meta), } } unsafe fn get_param<'w, 's>( - state: &'s mut Self, + state: &'s mut Self::State, system_meta: &SystemMeta, world: &'w World, change_tick: u32, ) -> Self::Item<'w, 's> { - let main_world = ResState::::get_param( + let main_world = Res::::get_param( &mut state.main_world_state, system_meta, world, @@ -95,7 +86,7 @@ where } } -impl<'w, 's, P: SystemParam> Deref for Extract<'w, 's, P> +impl<'w, 's, P> Deref for Extract<'w, 's, P> where P: ReadOnlySystemParam, { @@ -107,7 +98,7 @@ where } } -impl<'w, 's, P: SystemParam> DerefMut for Extract<'w, 's, P> +impl<'w, 's, P> DerefMut for Extract<'w, 's, P> where P: ReadOnlySystemParam, { @@ -117,7 +108,7 @@ where } } -impl<'a, 'w, 's, P: SystemParam> IntoIterator for &'a Extract<'w, 's, P> +impl<'a, 'w, 's, P> IntoIterator for &'a Extract<'w, 's, P> where P: ReadOnlySystemParam, &'a SystemParamItem<'w, 's, P>: IntoIterator, From 8340a3bbd48eb053a2d55f0ce4173bca9ae1e672 Mon Sep 17 00:00:00 2001 From: Robert Swain Date: Mon, 9 Jan 2023 13:41:59 +0000 Subject: [PATCH 44/60] bevy_render: Run calculate_bounds in the end-of-update exclusive systems (#7127) # Objective - Avoid slower than necessary first frame after spawning many entities due to them not having `Aabb`s and so being marked visible - Avoids unnecessarily large system and VRAM allocations as a consequence ## Solution - I noticed when debugging the `many_cubes` stress test in Xcode that the `MeshUniform` binding was much larger than it needed to be. I realised that this was because initially, all mesh entities are marked as being visible because they don't have `Aabb`s because `calculate_bounds` is being run in `PostUpdate` and there are no system commands applications before executing the visibility check systems that need the `Aabb`s. The solution then is to run the `calculate_bounds` system just before the previous system commands are applied which is at the end of the `Update` stage. --- crates/bevy_pbr/src/lib.rs | 1 - crates/bevy_render/src/view/visibility/mod.rs | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/crates/bevy_pbr/src/lib.rs b/crates/bevy_pbr/src/lib.rs index 366456b51d3fe..8366dbe673e1f 100644 --- a/crates/bevy_pbr/src/lib.rs +++ b/crates/bevy_pbr/src/lib.rs @@ -189,7 +189,6 @@ impl Plugin for PbrPlugin { check_light_mesh_visibility .label(SimulationLightSystems::CheckLightVisibility) .after(TransformSystem::TransformPropagate) - .after(VisibilitySystems::CalculateBounds) .after(SimulationLightSystems::UpdateLightFrusta) // NOTE: This MUST be scheduled AFTER the core renderer visibility check // because that resets entity ComputedVisibility for the first view diff --git a/crates/bevy_render/src/view/visibility/mod.rs b/crates/bevy_render/src/view/visibility/mod.rs index e4a57217c1bc1..f3381ac74c790 100644 --- a/crates/bevy_render/src/view/visibility/mod.rs +++ b/crates/bevy_render/src/view/visibility/mod.rs @@ -212,7 +212,7 @@ impl Plugin for VisibilityPlugin { app.add_system_to_stage( CoreStage::PostUpdate, - calculate_bounds.label(CalculateBounds), + calculate_bounds.label(CalculateBounds).before_commands(), ) .add_system_to_stage( CoreStage::PostUpdate, @@ -252,7 +252,6 @@ impl Plugin for VisibilityPlugin { CoreStage::PostUpdate, check_visibility .label(CheckVisibility) - .after(CalculateBounds) .after(UpdateOrthographicFrusta) .after(UpdatePerspectiveFrusta) .after(UpdateProjectionFrusta) From 025010addd76ee18206e4156e2273e637c71b9f4 Mon Sep 17 00:00:00 2001 From: IceSentry Date: Mon, 9 Jan 2023 18:50:55 +0000 Subject: [PATCH 45/60] Support storage buffers in derive `AsBindGroup` (#6129) # Objective - Storage buffers are useful and not currently supported by the `AsBindGroup` derive which means you need to expand the macro if you need a storage buffer ## Solution - Add a new `#[storage]` attribute to the derive `AsBindGroup` macro. - Support and optional `read_only` parameter that defaults to false when not present. - Support visibility parameters like the texture and sampler attributes. --- ## Changelog - Add a new `#[storage(index)]` attribute to the derive `AsBindGroup` macro. Co-authored-by: IceSentry --- .../bevy_render/macros/src/as_bind_group.rs | 82 ++++++++++++++++++- crates/bevy_render/macros/src/lib.rs | 5 +- .../src/render_resource/bind_group.rs | 13 +++ 3 files changed, 97 insertions(+), 3 deletions(-) diff --git a/crates/bevy_render/macros/src/as_bind_group.rs b/crates/bevy_render/macros/src/as_bind_group.rs index bbc3c3850cf0b..ee8a048758d12 100644 --- a/crates/bevy_render/macros/src/as_bind_group.rs +++ b/crates/bevy_render/macros/src/as_bind_group.rs @@ -11,6 +11,7 @@ use syn::{ const UNIFORM_ATTRIBUTE_NAME: Symbol = Symbol("uniform"); const TEXTURE_ATTRIBUTE_NAME: Symbol = Symbol("texture"); const SAMPLER_ATTRIBUTE_NAME: Symbol = Symbol("sampler"); +const STORAGE_ATTRIBUTE_NAME: Symbol = Symbol("storage"); const BIND_GROUP_DATA_ATTRIBUTE_NAME: Symbol = Symbol("bind_group_data"); #[derive(Copy, Clone, Debug)] @@ -18,6 +19,7 @@ enum BindingType { Uniform, Texture, Sampler, + Storage, } #[derive(Clone)] @@ -55,7 +57,6 @@ pub fn derive_as_bind_group(ast: syn::DeriveInput) -> Result { } } else if attr_ident == UNIFORM_ATTRIBUTE_NAME { let (binding_index, converted_shader_type) = get_uniform_binding_attr(attr)?; - binding_impls.push(quote! {{ use #render_path::render_resource::AsBindGroupShaderType; let mut buffer = #render_path::render_resource::encase::UniformBuffer::new(Vec::new()); @@ -126,6 +127,8 @@ pub fn derive_as_bind_group(ast: syn::DeriveInput) -> Result { BindingType::Texture } else if attr_ident == SAMPLER_ATTRIBUTE_NAME { BindingType::Sampler + } else if attr_ident == STORAGE_ATTRIBUTE_NAME { + BindingType::Storage } else { continue; }; @@ -190,7 +193,45 @@ pub fn derive_as_bind_group(ast: syn::DeriveInput) -> Result { } match binding_type { - BindingType::Uniform => { /* uniform codegen is deferred to account for combined uniform bindings */ + BindingType::Uniform => { + // uniform codegen is deferred to account for combined uniform bindings + } + BindingType::Storage => { + let StorageAttrs { + visibility, + read_only, + } = get_storage_binding_attr(nested_meta_items)?; + let visibility = + visibility.hygenic_quote("e! { #render_path::render_resource }); + + let field_name = field.ident.as_ref().unwrap(); + let field_ty = &field.ty; + + binding_impls.push(quote! {{ + use #render_path::render_resource::AsBindGroupShaderType; + let mut buffer = #render_path::render_resource::encase::StorageBuffer::new(Vec::new()); + buffer.write(&self.#field_name).unwrap(); + #render_path::render_resource::OwnedBindingResource::Buffer(render_device.create_buffer_with_data( + &#render_path::render_resource::BufferInitDescriptor { + label: None, + usage: #render_path::render_resource::BufferUsages::COPY_DST | #render_path::render_resource::BufferUsages::STORAGE, + contents: buffer.as_ref(), + }, + )) + }}); + + binding_layouts.push(quote! { + #render_path::render_resource::BindGroupLayoutEntry { + binding: #binding_index, + visibility: #visibility, + ty: #render_path::render_resource::BindingType::Buffer { + ty: #render_path::render_resource::BufferBindingType::Storage { read_only: #read_only }, + has_dynamic_offset: false, + min_binding_size: Some(<#field_ty as #render_path::render_resource::ShaderType>::min_size()), + }, + count: None, + } + }); } BindingType::Texture => { let TextureAttrs { @@ -861,3 +902,40 @@ fn get_sampler_binding_type_value(lit_str: &LitStr) -> Result) -> Result { + let mut visibility = ShaderStageVisibility::vertex_fragment(); + let mut read_only = false; + + for meta in metas { + use syn::{Meta::List, Meta::Path, NestedMeta::Meta}; + match meta { + // Parse #[storage(0, visibility(...))]. + Meta(List(m)) if m.path == VISIBILITY => { + visibility = get_visibility_flag_value(&m.nested)?; + } + Meta(Path(path)) if path == READ_ONLY => { + read_only = true; + } + _ => { + return Err(Error::new_spanned( + meta, + "Not a valid attribute. Available attributes: `read_only`, `visibility`", + )); + } + } + } + + Ok(StorageAttrs { + visibility, + read_only, + }) +} diff --git a/crates/bevy_render/macros/src/lib.rs b/crates/bevy_render/macros/src/lib.rs index 863b48baf5fa0..e8da33ad3c48b 100644 --- a/crates/bevy_render/macros/src/lib.rs +++ b/crates/bevy_render/macros/src/lib.rs @@ -17,7 +17,10 @@ pub fn derive_extract_resource(input: TokenStream) -> TokenStream { extract_resource::derive_extract_resource(input) } -#[proc_macro_derive(AsBindGroup, attributes(uniform, texture, sampler, bind_group_data))] +#[proc_macro_derive( + AsBindGroup, + attributes(uniform, texture, sampler, bind_group_data, storage) +)] pub fn derive_as_bind_group(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); diff --git a/crates/bevy_render/src/render_resource/bind_group.rs b/crates/bevy_render/src/render_resource/bind_group.rs index 5bc13b1d1f47a..46e30771b03bd 100644 --- a/crates/bevy_render/src/render_resource/bind_group.rs +++ b/crates/bevy_render/src/render_resource/bind_group.rs @@ -82,6 +82,8 @@ impl Deref for BindGroup { /// #[texture(1)] /// #[sampler(2)] /// color_texture: Handle, +/// #[storage(3, read_only)] +/// values: Vec, /// } /// ``` /// @@ -94,6 +96,8 @@ impl Deref for BindGroup { /// var color_texture: texture_2d; /// @group(1) @binding(2) /// var color_sampler: sampler; +/// @group(1) @binding(3) +/// var values: array; /// ``` /// Note that the "group" index is determined by the usage context. It is not defined in [`AsBindGroup`]. For example, in Bevy material bind groups /// are generally bound to group 1. @@ -132,6 +136,15 @@ impl Deref for BindGroup { /// | `sampler_type` = "..." | `"filtering"`, `"non_filtering"`, `"comparison"`. | `"filtering"` | /// | `visibility(...)` | `all`, `none`, or a list-combination of `vertex`, `fragment`, `compute` | `vertex`, `fragment` | /// +/// * `storage(BINDING_INDEX, arguments)` +/// * The field will be converted to a shader-compatible type using the [`ShaderType`] trait, written to a [`Buffer`], and bound as a storage buffer. +/// * It supports and optional `read_only` parameter. Defaults to false if not present. +/// +/// | Arguments | Values | Default | +/// |------------------------|-------------------------------------------------------------------------|----------------------| +/// | `visibility(...)` | `all`, `none`, or a list-combination of `vertex`, `fragment`, `compute` | `vertex`, `fragment` | +/// | `read_only` | if present then value is true, otherwise false | `false` | +/// /// Note that fields without field-level binding attributes will be ignored. /// ``` /// # use bevy_render::{color::Color, render_resource::AsBindGroup}; From a15827b7be91caab57643a47c21a3a1f73049c75 Mon Sep 17 00:00:00 2001 From: Yyee Date: Mon, 9 Jan 2023 19:05:30 +0000 Subject: [PATCH 46/60] Expose symphonia features from rodio in bevy_audio and bevy (#6388) # Objective Fix #6301 ## Solution Add new features in `bevy_audio` to use `symphonia` sound format from `rodio` Also add in `bevy` --- Cargo.toml | 7 +++++++ crates/bevy_audio/Cargo.toml | 7 +++++++ crates/bevy_internal/Cargo.toml | 7 +++++++ docs/cargo_features.md | 7 +++++++ 4 files changed, 28 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index 11e7cc5fb06d9..2cd39656e74a1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -89,6 +89,13 @@ flac = ["bevy_internal/flac"] mp3 = ["bevy_internal/mp3"] vorbis = ["bevy_internal/vorbis"] wav = ["bevy_internal/wav"] +symphonia-aac = ["bevy_internal/symphonia-aac"] +symphonia-all = ["bevy_internal/symphonia-all"] +symphonia-flac = ["bevy_internal/symphonia-flac"] +symphonia-isomp4 = ["bevy_internal/symphonia-isomp4"] +symphonia-mp3 = ["bevy_internal/symphonia-mp3"] +symphonia-vorbis = ["bevy_internal/symphonia-vorbis"] +symphonia-wav = ["bevy_internal/symphonia-wav"] # Enable watching file system for asset hot reload filesystem_watcher = ["bevy_internal/filesystem_watcher"] diff --git a/crates/bevy_audio/Cargo.toml b/crates/bevy_audio/Cargo.toml index 3d5f9d89d04e7..eca14a2c34ba1 100644 --- a/crates/bevy_audio/Cargo.toml +++ b/crates/bevy_audio/Cargo.toml @@ -30,3 +30,10 @@ mp3 = ["rodio/mp3"] flac = ["rodio/flac"] wav = ["rodio/wav"] vorbis = ["rodio/vorbis"] +symphonia-aac = ["rodio/symphonia-aac"] +symphonia-all = ["rodio/symphonia-all"] +symphonia-flac = ["rodio/symphonia-flac"] +symphonia-isomp4 = ["rodio/symphonia-isomp4"] +symphonia-mp3 = ["rodio/symphonia-mp3"] +symphonia-vorbis = ["rodio/symphonia-vorbis"] +symphonia-wav = ["rodio/symphonia-wav"] diff --git a/crates/bevy_internal/Cargo.toml b/crates/bevy_internal/Cargo.toml index ac371a3d4a82a..a51d1a7ff0737 100644 --- a/crates/bevy_internal/Cargo.toml +++ b/crates/bevy_internal/Cargo.toml @@ -42,6 +42,13 @@ flac = ["bevy_audio/flac"] mp3 = ["bevy_audio/mp3"] vorbis = ["bevy_audio/vorbis"] wav = ["bevy_audio/wav"] +symphonia-aac = ["bevy_audio/symphonia-aac"] +symphonia-all = ["bevy_audio/symphonia-all"] +symphonia-flac = ["bevy_audio/symphonia-flac"] +symphonia-isomp4 = ["bevy_audio/symphonia-isomp4"] +symphonia-mp3 = ["bevy_audio/symphonia-mp3"] +symphonia-vorbis = ["bevy_audio/symphonia-vorbis"] +symphonia-wav = ["bevy_audio/symphonia-wav"] # Enable watching file system for asset hot reload filesystem_watcher = ["bevy_asset/filesystem_watcher"] diff --git a/docs/cargo_features.md b/docs/cargo_features.md index 8d0db4604380e..8934ad65ba63b 100644 --- a/docs/cargo_features.md +++ b/docs/cargo_features.md @@ -39,6 +39,13 @@ |flac|FLAC audio format support. It's included in bevy_audio feature.| |mp3|MP3 audio format support.| |wav|WAV audio format support.| +|symphonia-aac|AAC audio format support by Symphonia. For more details, see `symphonia-all`.| +|symphonia-all|AAC, FLAC, MP4, MP3, Vorbis, and WAV support by Symphonia. Add support for parsing multiple file formats using a single crate instead of compiling different crates. The other non-`symphonia` features are disabled when its corresponding `symphonia` feature is enabled. [Link to `symphonia` documentation](https://docs.rs/symphonia/latest/symphonia/). More information about this topic can be found [here](https://github.com/bevyengine/bevy/pull/6388#discussion_r1009622883) | +|symphonia-flac|FLAC audio format support by Symphonia. For more details, see `symphonia-all`.| +|symphonia-isomp4|MP4 audio format support by Symphonia. For more details, see `symphonia-all`.| +|symphonia-mp3|MP3 audio format support by Symphonia. For more details, see `symphonia-all`.| +|symphonia-vorbis|Vorbis audio format support by Symphonia. For more details, see `symphonia-all`.| +|symphonia-wav|WAV audio format support by Symphonia. For more details, see `symphonia-all`.| |serialize|Enables serialization of `bevy_input` types.| |wayland|Enable this to use Wayland display server protocol other than X11.| |subpixel_glyph_atlas|Enable this to cache glyphs using subpixel accuracy. This increases texture memory usage as each position requires a separate sprite in the glyph atlas, but provide more accurate character spacing.| From 49e76e6a065c21317eb079a61f57b49033ad1064 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Me=C3=9Fmer?= Date: Mon, 9 Jan 2023 19:24:51 +0000 Subject: [PATCH 47/60] Smooth Transition between Animations (#6922) # Objective - Fixes https://github.com/bevyengine/bevy/discussions/6338 This PR allows for smooth transitions between different animations. ## Solution - This PR uses very simple linear blending of animations. - When starting a new animation, you can give it a duration, and throughout that duration, the previous and the new animation are being linearly blended, until only the new animation is running. - I'm aware of https://github.com/bevyengine/rfcs/pull/49 and https://github.com/bevyengine/rfcs/pull/51, which are more complete solutions to this problem, but they seem still far from being implemented. Until they're ready, this PR allows for the most basic use case of blending, i.e. smoothly transitioning between different animations. ## Migration Guide - no bc breaking changes --- crates/bevy_animation/src/lib.rs | 244 +++++++++++++++++++++++----- examples/animation/animated_fox.rs | 6 +- examples/stress_tests/many_foxes.rs | 6 +- 3 files changed, 214 insertions(+), 42 deletions(-) diff --git a/crates/bevy_animation/src/lib.rs b/crates/bevy_animation/src/lib.rs index 9b9e8de991af9..6e098f0e24e50 100644 --- a/crates/bevy_animation/src/lib.rs +++ b/crates/bevy_animation/src/lib.rs @@ -3,12 +3,13 @@ #![warn(missing_docs)] use std::ops::Deref; +use std::time::Duration; use bevy_app::{App, CoreStage, Plugin}; use bevy_asset::{AddAsset, Assets, Handle}; use bevy_core::Name; use bevy_ecs::{ - change_detection::DetectChanges, + change_detection::{DetectChanges, Mut}, entity::Entity, prelude::Component, query::With, @@ -114,11 +115,8 @@ impl AnimationClip { } } -/// Animation controls -#[derive(Component, Reflect)] -#[reflect(Component)] -pub struct AnimationPlayer { - paused: bool, +#[derive(Reflect)] +struct PlayingAnimation { repeat: bool, speed: f32, elapsed: f32, @@ -126,10 +124,9 @@ pub struct AnimationPlayer { path_cache: Vec>>, } -impl Default for AnimationPlayer { +impl Default for PlayingAnimation { fn default() -> Self { Self { - paused: false, repeat: false, speed: 1.0, elapsed: 0.0, @@ -139,33 +136,106 @@ impl Default for AnimationPlayer { } } +/// An animation that is being faded out as part of a transition +struct AnimationTransition { + /// The current weight. Starts at 1.0 and goes to 0.0 during the fade-out. + current_weight: f32, + /// How much to decrease `current_weight` per second + weight_decline_per_sec: f32, + /// The animation that is being faded out + animation: PlayingAnimation, +} + +/// Animation controls +#[derive(Component, Default, Reflect)] +#[reflect(Component)] +pub struct AnimationPlayer { + paused: bool, + + animation: PlayingAnimation, + + // List of previous animations we're currently transitioning away from. + // Usually this is empty, when transitioning between animations, there is + // one entry. When another animation transition happens while a transition + // is still ongoing, then there can be more than one entry. + // Once a transition is finished, it will be automatically removed from the list + #[reflect(ignore)] + transitions: Vec, +} + impl AnimationPlayer { /// Start playing an animation, resetting state of the player + /// This will use a linear blending between the previous and the new animation to make a smooth transition pub fn start(&mut self, handle: Handle) -> &mut Self { - *self = Self { + self.animation = PlayingAnimation { animation_clip: handle, ..Default::default() }; + + // We want a hard transition. + // In case any previous transitions are still playing, stop them + self.transitions.clear(); + + self + } + + /// Start playing an animation, resetting state of the player + /// This will use a linear blending between the previous and the new animation to make a smooth transition + pub fn start_with_transition( + &mut self, + handle: Handle, + transition_duration: Duration, + ) -> &mut Self { + let mut animation = PlayingAnimation { + animation_clip: handle, + ..Default::default() + }; + std::mem::swap(&mut animation, &mut self.animation); + + // Add the current transition. If other transitions are still ongoing, + // this will keep those transitions running and cause a transition between + // the output of that previous transition to the new animation. + self.transitions.push(AnimationTransition { + current_weight: 1.0, + weight_decline_per_sec: 1.0 / transition_duration.as_secs_f32(), + animation, + }); + self } /// Start playing an animation, resetting state of the player, unless the requested animation is already playing. + /// If `transition_duration` is set, this will use a linear blending + /// between the previous and the new animation to make a smooth transition pub fn play(&mut self, handle: Handle) -> &mut Self { - if self.animation_clip != handle || self.is_paused() { + if self.animation.animation_clip != handle || self.is_paused() { self.start(handle); } self } + /// Start playing an animation, resetting state of the player, unless the requested animation is already playing. + /// This will use a linear blending between the previous and the new animation to make a smooth transition + pub fn play_with_transition( + &mut self, + handle: Handle, + transition_duration: Duration, + ) -> &mut Self { + if self.animation.animation_clip != handle || self.is_paused() { + self.start_with_transition(handle, transition_duration); + } + self + } + /// Set the animation to repeat pub fn repeat(&mut self) -> &mut Self { - self.repeat = true; + self.animation.repeat = true; self } /// Stop the animation from repeating pub fn stop_repeating(&mut self) -> &mut Self { - self.repeat = false; + self.animation.repeat = false; self } @@ -186,23 +256,23 @@ impl AnimationPlayer { /// Speed of the animation playback pub fn speed(&self) -> f32 { - self.speed + self.animation.speed } /// Set the speed of the animation playback pub fn set_speed(&mut self, speed: f32) -> &mut Self { - self.speed = speed; + self.animation.speed = speed; self } /// Time elapsed playing the animation pub fn elapsed(&self) -> f32 { - self.elapsed + self.animation.elapsed } /// Seek to a specific time in the animation pub fn set_elapsed(&mut self, elapsed: f32) -> &mut Self { - self.elapsed = elapsed; + self.animation.elapsed = elapsed; self } } @@ -283,37 +353,119 @@ pub fn animation_player( mut animation_players: Query<(Entity, Option<&Parent>, &mut AnimationPlayer)>, ) { animation_players.par_for_each_mut(10, |(root, maybe_parent, mut player)| { - let Some(animation_clip) = animations.get(&player.animation_clip) else { return }; - // Continue if paused unless the `AnimationPlayer` was changed - // This allow the animation to still be updated if the player.elapsed field was manually updated in pause - if player.paused && !player.is_changed() { - return; - } - if !player.paused { - player.elapsed += time.delta_seconds() * player.speed; + update_transitions(&mut player, &time); + run_animation_player( + root, + player, + &time, + &animations, + &names, + &transforms, + maybe_parent, + &parents, + &children, + ); + }); +} + +#[allow(clippy::too_many_arguments)] +fn run_animation_player( + root: Entity, + mut player: Mut, + time: &Time, + animations: &Assets, + names: &Query<&Name>, + transforms: &Query<&mut Transform>, + maybe_parent: Option<&Parent>, + parents: &Query<(Option>, Option<&Parent>)>, + children: &Query<&Children>, +) { + let paused = player.paused; + // Continue if paused unless the `AnimationPlayer` was changed + // This allow the animation to still be updated if the player.elapsed field was manually updated in pause + if paused && !player.is_changed() { + return; + } + + // Apply the main animation + apply_animation( + 1.0, + &mut player.animation, + paused, + root, + time, + animations, + names, + transforms, + maybe_parent, + parents, + children, + ); + + // Apply any potential fade-out transitions from previous animations + for AnimationTransition { + current_weight, + animation, + .. + } in &mut player.transitions + { + apply_animation( + *current_weight, + animation, + paused, + root, + time, + animations, + names, + transforms, + maybe_parent, + parents, + children, + ); + } +} + +#[allow(clippy::too_many_arguments)] +fn apply_animation( + weight: f32, + animation: &mut PlayingAnimation, + paused: bool, + root: Entity, + time: &Time, + animations: &Assets, + names: &Query<&Name>, + transforms: &Query<&mut Transform>, + maybe_parent: Option<&Parent>, + parents: &Query<(Option>, Option<&Parent>)>, + children: &Query<&Children>, +) { + if let Some(animation_clip) = animations.get(&animation.animation_clip) { + if !paused { + animation.elapsed += time.delta_seconds() * animation.speed; } - let mut elapsed = player.elapsed; - if player.repeat { + let mut elapsed = animation.elapsed; + if animation.repeat { elapsed %= animation_clip.duration; } if elapsed < 0.0 { elapsed += animation_clip.duration; } - if player.path_cache.len() != animation_clip.paths.len() { - player.path_cache = vec![Vec::new(); animation_clip.paths.len()]; + if animation.path_cache.len() != animation_clip.paths.len() { + animation.path_cache = vec![Vec::new(); animation_clip.paths.len()]; } - if !verify_no_ancestor_player(maybe_parent, &parents) { + if !verify_no_ancestor_player(maybe_parent, parents) { warn!("Animation player on {:?} has a conflicting animation player on an ancestor. Cannot safely animate.", root); return; } + for (path, bone_id) in &animation_clip.paths { - let cached_path = &mut player.path_cache[*bone_id]; + let cached_path = &mut animation.path_cache[*bone_id]; let curves = animation_clip.get_curves(*bone_id).unwrap(); - let Some(target) = find_bone(root, path, &children, &names, cached_path) else { continue }; + let Some(target) = find_bone(root, path, children, names, cached_path) else { continue }; // SAFETY: The verify_no_ancestor_player check above ensures that two animation players cannot alias // any of their descendant Transforms. - // - // The system scheduler prevents any other system from mutating Transforms at the same time, + // + // The system scheduler prevents any other system from mutating Transforms at the same time, // so the only way this fetch can alias is if two AnimationPlayers are targetting the same bone. // This can only happen if there are two or more AnimationPlayers are ancestors to the same // entities. By verifying that there is no other AnimationPlayer in the ancestors of a @@ -327,11 +479,16 @@ pub fn animation_player( // Some curves have only one keyframe used to set a transform if curve.keyframe_timestamps.len() == 1 { match &curve.keyframes { - Keyframes::Rotation(keyframes) => transform.rotation = keyframes[0], + Keyframes::Rotation(keyframes) => { + transform.rotation = transform.rotation.slerp(keyframes[0], weight); + } Keyframes::Translation(keyframes) => { - transform.translation = keyframes[0]; + transform.translation = + transform.translation.lerp(keyframes[0], weight); + } + Keyframes::Scale(keyframes) => { + transform.scale = transform.scale.lerp(keyframes[0], weight); } - Keyframes::Scale(keyframes) => transform.scale = keyframes[0], } continue; } @@ -362,24 +519,31 @@ pub fn animation_player( rot_end = -rot_end; } // Rotations are using a spherical linear interpolation - transform.rotation = - rot_start.normalize().slerp(rot_end.normalize(), lerp); + let rot = rot_start.normalize().slerp(rot_end.normalize(), lerp); + transform.rotation = transform.rotation.slerp(rot, weight); } Keyframes::Translation(keyframes) => { let translation_start = keyframes[step_start]; let translation_end = keyframes[step_start + 1]; let result = translation_start.lerp(translation_end, lerp); - transform.translation = result; + transform.translation = transform.translation.lerp(result, weight); } Keyframes::Scale(keyframes) => { let scale_start = keyframes[step_start]; let scale_end = keyframes[step_start + 1]; let result = scale_start.lerp(scale_end, lerp); - transform.scale = result; + transform.scale = transform.scale.lerp(result, weight); } } } } + } +} + +fn update_transitions(player: &mut AnimationPlayer, time: &Time) { + player.transitions.retain_mut(|animation| { + animation.current_weight -= animation.weight_decline_per_sec * time.delta_seconds(); + animation.current_weight > 0.0 }); } diff --git a/examples/animation/animated_fox.rs b/examples/animation/animated_fox.rs index c19fa97df34d2..1f1232d79d03a 100644 --- a/examples/animation/animated_fox.rs +++ b/examples/animation/animated_fox.rs @@ -1,6 +1,7 @@ //! Plays animations from a skinned glTF. use std::f32::consts::PI; +use std::time::Duration; use bevy::prelude::*; @@ -122,7 +123,10 @@ fn keyboard_animation_control( if keyboard_input.just_pressed(KeyCode::Return) { *current_animation = (*current_animation + 1) % animations.0.len(); player - .play(animations.0[*current_animation].clone_weak()) + .play_with_transition( + animations.0[*current_animation].clone_weak(), + Duration::from_millis(250), + ) .repeat(); } } diff --git a/examples/stress_tests/many_foxes.rs b/examples/stress_tests/many_foxes.rs index 9d5ae3d398b8c..58fcc5cc41646 100644 --- a/examples/stress_tests/many_foxes.rs +++ b/examples/stress_tests/many_foxes.rs @@ -2,6 +2,7 @@ //! animation to stress test skinned meshes. use std::f32::consts::PI; +use std::time::Duration; use bevy::{ diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin}, @@ -266,7 +267,10 @@ fn keyboard_animation_control( if keyboard_input.just_pressed(KeyCode::Return) { player - .play(animations.0[*current_animation].clone_weak()) + .play_with_transition( + animations.0[*current_animation].clone_weak(), + Duration::from_millis(250), + ) .repeat(); } } From 32dac8a6e955c4f26687ffb68083d39f894492d6 Mon Sep 17 00:00:00 2001 From: DevinLeamy Date: Mon, 9 Jan 2023 19:24:52 +0000 Subject: [PATCH 48/60] Gamepad events refactor (#6965) # Objective - Remove redundant gamepad events - Simplify consuming gamepad events. - Refactor: Separate handling of gamepad events into multiple systems. ## Solution - Removed `GamepadEventRaw`, and `GamepadEventType`. - Added bespoke `GamepadConnectionEvent`, `GamepadAxisChangedEvent`, and `GamepadButtonChangedEvent`. - Refactored `gamepad_event_system`. - Added `gamepad_button_event_system`, `gamepad_axis_event_system`, and `gamepad_connection_system`, which update the `Input` and `Axis` resources using their corresponding event type. Gamepad events are now handled in their own systems and have their own types. This allows for querying for gamepad events without having to match on `GamepadEventType` and makes creating handlers for specific gamepad event types, like a `GamepadConnectionEvent` or `GamepadButtonChangedEvent` possible. We remove `GamepadEventRaw` by filtering the gamepad events, using `GamepadSettings`, _at the source_, in `bevy_gilrs`. This way we can create `GamepadEvent`s directly and avoid creating `GamepadEventRaw` which do not pass the user defined filters. We expose ordered `GamepadEvent`s and we can respond to individual gamepad event types. ## Migration Guide - Replace `GamepadEvent` and `GamepadEventRaw` types with their specific gamepad event type. --- crates/bevy_gilrs/src/gilrs_system.rs | 81 ++-- crates/bevy_input/src/gamepad.rs | 587 ++++++++++++------------- crates/bevy_input/src/lib.rs | 34 +- examples/input/gamepad_input_events.rs | 57 ++- examples/tools/gamepad_viewer.rs | 50 +-- 5 files changed, 414 insertions(+), 395 deletions(-) diff --git a/crates/bevy_gilrs/src/gilrs_system.rs b/crates/bevy_gilrs/src/gilrs_system.rs index d0bb67c5c28c6..de1d0afb3c733 100644 --- a/crates/bevy_gilrs/src/gilrs_system.rs +++ b/crates/bevy_gilrs/src/gilrs_system.rs @@ -1,30 +1,45 @@ use crate::converter::{convert_axis, convert_button, convert_gamepad_id}; use bevy_ecs::event::EventWriter; -use bevy_ecs::system::{NonSend, NonSendMut}; -use bevy_input::gamepad::GamepadInfo; -use bevy_input::{gamepad::GamepadEventRaw, prelude::*}; +use bevy_ecs::system::{NonSend, NonSendMut, Res}; +use bevy_input::gamepad::{ + GamepadAxisChangedEvent, GamepadButtonChangedEvent, GamepadConnection, GamepadConnectionEvent, + GamepadSettings, +}; +use bevy_input::gamepad::{GamepadEvent, GamepadInfo}; +use bevy_input::prelude::{GamepadAxis, GamepadButton}; +use bevy_input::Axis; use gilrs::{ev::filter::axis_dpad_to_button, EventType, Filter, Gilrs}; -pub fn gilrs_event_startup_system(gilrs: NonSend, mut events: EventWriter) { +pub fn gilrs_event_startup_system( + gilrs: NonSend, + mut connection_events: EventWriter, +) { for (id, gamepad) in gilrs.gamepads() { let info = GamepadInfo { name: gamepad.name().into(), }; - events.send(GamepadEventRaw::new( - convert_gamepad_id(id), - GamepadEventType::Connected(info), - )); + connection_events.send(GamepadConnectionEvent { + gamepad: convert_gamepad_id(id), + connection: GamepadConnection::Connected(info), + }); } } -pub fn gilrs_event_system(mut gilrs: NonSendMut, mut events: EventWriter) { +pub fn gilrs_event_system( + mut gilrs: NonSendMut, + mut events: EventWriter, + gamepad_axis: Res>, + gamepad_buttons: Res>, + gamepad_settings: Res, +) { while let Some(gilrs_event) = gilrs .next_event() .filter_ev(&axis_dpad_to_button, &mut gilrs) { gilrs.update(&gilrs_event); + let gamepad = convert_gamepad_id(gilrs_event.id); match gilrs_event.event { EventType::Connected => { let pad = gilrs.gamepad(gilrs_event.id); @@ -32,31 +47,39 @@ pub fn gilrs_event_system(mut gilrs: NonSendMut, mut events: EventWriter< name: pad.name().into(), }; - events.send(GamepadEventRaw::new( - convert_gamepad_id(gilrs_event.id), - GamepadEventType::Connected(info), - )); + events.send( + GamepadConnectionEvent::new(gamepad, GamepadConnection::Connected(info)).into(), + ); } - EventType::Disconnected => { - events.send(GamepadEventRaw::new( - convert_gamepad_id(gilrs_event.id), - GamepadEventType::Disconnected, - )); - } - EventType::ButtonChanged(gilrs_button, value, _) => { + EventType::Disconnected => events + .send(GamepadConnectionEvent::new(gamepad, GamepadConnection::Disconnected).into()), + EventType::ButtonChanged(gilrs_button, raw_value, _) => { if let Some(button_type) = convert_button(gilrs_button) { - events.send(GamepadEventRaw::new( - convert_gamepad_id(gilrs_event.id), - GamepadEventType::ButtonChanged(button_type, value), - )); + let button = GamepadButton::new(gamepad, button_type); + let old_value = gamepad_buttons.get(button); + let button_settings = gamepad_settings.get_button_axis_settings(button); + + // Only send events that pass the user-defined change threshold + if let Some(filtered_value) = button_settings.filter(raw_value, old_value) { + events.send( + GamepadButtonChangedEvent::new(gamepad, button_type, filtered_value) + .into(), + ); + } } } - EventType::AxisChanged(gilrs_axis, value, _) => { + EventType::AxisChanged(gilrs_axis, raw_value, _) => { if let Some(axis_type) = convert_axis(gilrs_axis) { - events.send(GamepadEventRaw::new( - convert_gamepad_id(gilrs_event.id), - GamepadEventType::AxisChanged(axis_type, value), - )); + let axis = GamepadAxis::new(gamepad, axis_type); + let old_value = gamepad_axis.get(axis); + let axis_settings = gamepad_settings.get_axis_settings(axis); + + // Only send events that pass the user-defined change threshold + if let Some(filtered_value) = axis_settings.filter(raw_value, old_value) { + events.send( + GamepadAxisChangedEvent::new(gamepad, axis_type, filtered_value).into(), + ); + } } } _ => (), diff --git a/crates/bevy_input/src/gamepad.rs b/crates/bevy_input/src/gamepad.rs index 2cd56d94301b7..d0f17aa53d339 100644 --- a/crates/bevy_input/src/gamepad.rs +++ b/crates/bevy_input/src/gamepad.rs @@ -65,8 +65,8 @@ use bevy_reflect::{ReflectDeserialize, ReflectSerialize}; /// ## Usage /// /// The primary way to access the individual connected gamepads is done through the [`Gamepads`] -/// `bevy` resource. It is also used inside of [`GamepadEvent`]s and [`GamepadEventRaw`]s to distinguish -/// which gamepad an event corresponds to. +/// `bevy` resource. It is also used inside of [`GamepadConnectionEvent`]s to correspond a gamepad +/// with a connection event. /// /// ## Note /// @@ -111,8 +111,7 @@ pub struct GamepadInfo { /// ## Updating /// /// The [`Gamepad`]s are registered and deregistered in the [`gamepad_connection_system`] -/// whenever a [`GamepadEventType::Connected`] or [`GamepadEventType::Disconnected`] -/// event is received. +/// whenever a [`GamepadConnectionEvent`] is received. #[derive(Resource, Default, Debug)] pub struct Gamepads { /// The collection of the connected [`Gamepad`]s. @@ -145,184 +144,12 @@ impl Gamepads { } } -/// The data contained in a [`GamepadEvent`] or [`GamepadEventRaw`]. -#[derive(Debug, Clone, PartialEq, Reflect, FromReflect)] -#[reflect(Debug, PartialEq)] -#[cfg_attr( - feature = "serialize", - derive(serde::Serialize, serde::Deserialize), - reflect(Serialize, Deserialize) -)] -pub enum GamepadEventType { - /// A [`Gamepad`] has been connected. - Connected(GamepadInfo), - /// A [`Gamepad`] has been disconnected. - Disconnected, - - /// The value of a [`Gamepad`] button has changed. - ButtonChanged(GamepadButtonType, f32), - /// The value of a [`Gamepad`] axis has changed. - AxisChanged(GamepadAxisType, f32), -} - -/// An event of a [`Gamepad`]. -/// -/// This event is the translated version of the [`GamepadEventRaw`]. It is available to -/// the end user and can be used for game logic. -/// -/// ## Differences -/// -/// The difference between the [`GamepadEventRaw`] and the [`GamepadEvent`] is that the -/// former respects user defined [`GamepadSettings`] for the gamepad inputs when translating it -/// to the latter. The former also updates the [`Input`], [`Axis`], -/// and [`Axis`] resources accordingly. -/// -/// ## Gamepad input mocking -/// -/// When mocking gamepad input, use [`GamepadEventRaw`]s instead of [`GamepadEvent`]s. -/// Otherwise [`GamepadSettings`] won't be respected and the [`Input`], -/// [`Axis`], and [`Axis`] resources won't be updated correctly. -/// -/// An example for gamepad input mocking can be seen in the documentation of the [`GamepadEventRaw`]. -#[derive(Debug, Clone, PartialEq, Reflect, FromReflect)] -#[reflect(Debug, PartialEq)] -#[cfg_attr( - feature = "serialize", - derive(serde::Serialize, serde::Deserialize), - reflect(Serialize, Deserialize) -)] -pub struct GamepadEvent { - /// The gamepad this event corresponds to. - pub gamepad: Gamepad, - /// The type of the event. - pub event_type: GamepadEventType, -} - -impl GamepadEvent { - /// Creates a new [`GamepadEvent`]. - pub fn new(gamepad: Gamepad, event_type: GamepadEventType) -> Self { - Self { - gamepad, - event_type, - } - } -} - -/// A raw event of a [`Gamepad`]. -/// -/// This event is the translated version of the `EventType` from the `GilRs` crate. -/// It is available to the end user and can be used for game logic. -/// -/// ## Differences -/// -/// The difference between the `EventType` from the `GilRs` crate and the [`GamepadEventRaw`] -/// is that the latter has less events, because the button pressing logic is handled through the generic -/// [`Input`] instead of through events. -/// -/// The difference between the [`GamepadEventRaw`] and the [`GamepadEvent`] can be seen in the documentation -/// of the [`GamepadEvent`]. -/// -/// ## Gamepad input mocking -/// -/// The following example showcases how to mock gamepad input by manually sending [`GamepadEventRaw`]s. -/// -/// ``` -/// # use bevy_input::prelude::*; -/// # use bevy_input::InputPlugin; -/// # use bevy_input::gamepad::{GamepadEventRaw, GamepadInfo}; -/// # use bevy_app::prelude::*; -/// # use bevy_ecs::prelude::*; -/// #[derive(Resource)] -/// struct MyResource(bool); -/// -/// // This system sets the bool inside `MyResource` to `true` if the `South` button of the first gamepad is pressed. -/// fn change_resource_on_gamepad_button_press( -/// mut my_resource: ResMut, -/// gamepads: Res, -/// button_inputs: ResMut>, -/// ) { -/// let gamepad = gamepads.iter().next().unwrap(); -/// let gamepad_button = GamepadButton::new(gamepad, GamepadButtonType::South); -/// -/// my_resource.0 = button_inputs.pressed(gamepad_button); -/// } -/// -/// // Create our app. -/// let mut app = App::new(); -/// -/// // Add the input plugin and the system/resource we want to test. -/// app.add_plugin(InputPlugin) -/// .insert_resource(MyResource(false)) -/// .add_system(change_resource_on_gamepad_button_press); -/// -/// // Define our dummy gamepad input data. -/// let gamepad = Gamepad::new(0); -/// let button_type = GamepadButtonType::South; -/// -/// // Send the gamepad connected event to mark our gamepad as connected. -/// // This updates the `Gamepads` resource accordingly. -/// let info = GamepadInfo { name: "Mock Gamepad".into() }; -/// app.world.send_event(GamepadEventRaw::new(gamepad, GamepadEventType::Connected(info))); -/// -/// // Send the gamepad input event to mark the `South` gamepad button as pressed. -/// // This updates the `Input` resource accordingly. -/// app.world.send_event(GamepadEventRaw::new( -/// gamepad, -/// GamepadEventType::ButtonChanged(button_type, 1.0) -/// )); -/// -/// // Advance the execution of the schedule by a single cycle. -/// app.update(); -/// -/// // At this point you can check if your game logic corresponded correctly to the gamepad input. -/// // In this example we are checking if the bool in `MyResource` was updated from `false` to `true`. -/// assert!(app.world.resource::().0); -/// -/// // Send the gamepad input event to mark the `South` gamepad button as released. -/// // This updates the `Input` resource accordingly. -/// app.world.send_event(GamepadEventRaw::new( -/// gamepad, -/// GamepadEventType::ButtonChanged(button_type, 0.0) -/// )); -/// -/// // Advance the execution of the schedule by another cycle. -/// app.update(); -/// -/// // Check if the bool in `MyResource` was updated from `true` to `false`. -/// assert!(!app.world.resource::().0); -/// # -/// # bevy_ecs::system::assert_is_system(change_resource_on_gamepad_button_press); -/// ``` -#[derive(Debug, Clone, PartialEq, Reflect, FromReflect)] -#[reflect(Debug, PartialEq)] -#[cfg_attr( - feature = "serialize", - derive(serde::Serialize, serde::Deserialize), - reflect(Serialize, Deserialize) -)] -pub struct GamepadEventRaw { - /// The gamepad this event corresponds to. - pub gamepad: Gamepad, - /// The type of the event. - pub event_type: GamepadEventType, -} - -impl GamepadEventRaw { - /// Creates a new [`GamepadEventRaw`]. - pub fn new(gamepad: Gamepad, event_type: GamepadEventType) -> Self { - Self { - gamepad, - event_type, - } - } -} - /// A type of a [`GamepadButton`]. /// /// ## Usage /// /// This is used to determine which button has changed its value when receiving a -/// [`GamepadEventType::ButtonChanged`]. It is also used in the [`GamepadButton`] +/// [`GamepadButtonChangedEvent`]. It is also used in the [`GamepadButton`] /// which in turn is used to create the [`Input`] or /// [`Axis`] `bevy` resources. #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Reflect, FromReflect)] @@ -385,11 +212,11 @@ pub enum GamepadButtonType { /// ## Usage /// /// It is used as the generic `T` value of an [`Input`] and [`Axis`] to create `bevy` resources. These -/// resources store the data of the buttons and axes of a gamepad and can be accessed inside of a system. +/// resources store the data of the buttons of a gamepad and can be accessed inside of a system. /// /// ## Updating /// -/// The resources are updated inside of the [`gamepad_event_system`]. +/// The gamepad button resources are updated inside of the [`gamepad_button_event_system`]. #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Reflect, FromReflect)] #[reflect(Debug, Hash, PartialEq)] #[cfg_attr( @@ -430,7 +257,7 @@ impl GamepadButton { /// ## Usage /// /// This is used to determine which axis has changed its value when receiving a -/// [`GamepadEventType::AxisChanged`]. It is also used in the [`GamepadAxis`] +/// [`GamepadAxisChangedEvent`]. It is also used in the [`GamepadAxis`] /// which in turn is used to create the [`Axis`] `bevy` resource. #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Reflect, FromReflect)] #[reflect(Debug, Hash, PartialEq)] @@ -462,12 +289,12 @@ pub enum GamepadAxisType { /// /// ## Usage /// -/// It is used as the generic `T` value of an [`Axis`] to create a `bevy` resource. This resource -/// stores the data of the axes of a gamepad and can be accessed inside of a system. +/// It is used as the generic `T` value of an [`Axis`] to create `bevy` resources. These +/// resources store the data of the axes of a gamepad and can be accessed inside of a system. /// /// ## Updating /// -/// The resource is updated inside of the [`gamepad_event_system`]. +/// The gamepad axes resources are updated inside of the [`gamepad_axis_event_system`]. #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Reflect, FromReflect)] #[reflect(Debug, Hash, PartialEq)] #[cfg_attr( @@ -510,8 +337,9 @@ impl GamepadAxis { /// /// ## Note /// -/// The [`GamepadSettings`] are used inside of the [`gamepad_event_system`], but are never written to -/// inside of `bevy`. To modify these settings, mutate the corresponding resource. +/// The [`GamepadSettings`] are used inside of `bevy_gilrs` to determine when raw gamepad events from `girls`, +/// should register as a [`GamepadEvent`]. Events that don't meet the change thresholds defined in [`GamepadSettings`] +/// will not register. To modify these settings, mutate the corresponding resource. #[derive(Resource, Default, Debug, Reflect, FromReflect)] #[reflect(Debug, Default)] pub struct GamepadSettings { @@ -1032,25 +860,40 @@ impl AxisSettings { self.threshold } - fn filter(&self, new_value: f32, old_value: Option) -> Option { - let new_value = - if self.deadzone_lowerbound <= new_value && new_value <= self.deadzone_upperbound { - 0.0 - } else if new_value >= self.livezone_upperbound { - 1.0 - } else if new_value <= self.livezone_lowerbound { - -1.0 - } else { - new_value - }; - - if let Some(old_value) = old_value { - if (new_value - old_value).abs() <= self.threshold { - return None; - } + /// 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 { + 0.0 + } else if new_value >= self.livezone_upperbound { + 1.0 + } else if new_value <= self.livezone_lowerbound { + -1.0 + } else { + new_value + } + } + + /// Determines whether the change from `old_value` to `new_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; } - Some(new_value) + f32::abs(new_value - old_value.unwrap()) > self.threshold + } + + /// Filters the `new_value` based on the `old_value`, according to the [`AxisSettings`]. + /// + /// Returns the clamped `new_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); + + if self.should_register_change(new_value, old_value) { + return Some(new_value); + } + None } } @@ -1069,7 +912,7 @@ impl AxisSettings { /// /// ## Updating /// -/// The current value of a button is received through the [`GamepadEvent`]s or [`GamepadEventRaw`]s. +/// The current value of a button is received through the [`GamepadButtonChangedEvent`]. #[derive(Debug, Clone, Reflect, FromReflect)] #[reflect(Debug, Default)] pub struct ButtonAxisSettings { @@ -1092,137 +935,275 @@ impl Default for ButtonAxisSettings { } impl ButtonAxisSettings { - /// Filters the `new_value` according to the specified settings. + /// Clamps the `raw_value` according to the specified settings. /// - /// If the `new_value` is: + /// If the `raw_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 not be rounded. - /// - /// If the difference between the calculated value and the `old_value` is lower or - /// equal to the `threshold`, [`None`] will be returned. - fn filter(&self, new_value: f32, old_value: Option) -> Option { - let new_value = if new_value <= self.low { - 0.0 - } else if new_value >= self.high { - 1.0 - } else { - new_value - }; + fn clamp(&self, raw_value: f32) -> f32 { + if raw_value <= self.low { + return 0.0; + } + if raw_value >= self.high { + return 1.0; + } - if let Some(old_value) = old_value { - if (new_value - old_value).abs() <= self.threshold { - return None; - } + raw_value + } + + /// Determines whether the change from an `old_value` to a `new_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; } - Some(new_value) + f32::abs(new_value - old_value.unwrap()) > self.threshold + } + + /// Filters the `new_value` based on the `old_value`, according to the `ButtonAxisSettings`. + /// + /// Returns the clamped `new_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); + + if self.should_register_change(new_value, old_value) { + return Some(new_value); + } + None } } -/// Monitors gamepad connection and disconnection events and updates the [`Gamepads`] resource accordingly. +/// Handles [`GamepadConnectionEvent`]s and updates gamepad resources. +/// +/// Updates the [`Gamepads`] resource and resets and/or initializes +/// the [`Axis`] and [`Input`] resources. /// /// ## Note /// /// Whenever a [`Gamepad`] connects or disconnects, an information gets printed to the console using the [`info!`] macro. pub fn gamepad_connection_system( mut gamepads: ResMut, - mut gamepad_event: EventReader, + mut connection_events: EventReader, + mut axis: ResMut>, + mut button_axis: ResMut>, + mut button_input: ResMut>, ) { - for event in gamepad_event.iter() { - match &event.event_type { - GamepadEventType::Connected(info) => { - gamepads.register(event.gamepad, info.clone()); - info!("{:?} Connected", event.gamepad); + button_input.bypass_change_detection().clear(); + for connection_event in connection_events.iter() { + let gamepad = connection_event.gamepad; + + if let GamepadConnection::Connected(info) = &connection_event.connection { + gamepads.register(gamepad, info.clone()); + info!("{:?} Connected", gamepad); + + for button_type in &ALL_BUTTON_TYPES { + let gamepad_button = GamepadButton::new(gamepad, *button_type); + button_input.reset(gamepad_button); + button_axis.set(gamepad_button, 0.0); + } + for axis_type in &ALL_AXIS_TYPES { + axis.set(GamepadAxis::new(gamepad, *axis_type), 0.0); } + } else { + gamepads.deregister(gamepad); + info!("{:?} Disconnected", gamepad); - GamepadEventType::Disconnected => { - gamepads.deregister(event.gamepad); - info!("{:?} Disconnected", event.gamepad); + for button_type in &ALL_BUTTON_TYPES { + let gamepad_button = GamepadButton::new(gamepad, *button_type); + button_input.reset(gamepad_button); + button_axis.remove(gamepad_button); + } + for axis_type in &ALL_AXIS_TYPES { + axis.remove(GamepadAxis::new(gamepad, *axis_type)); } - _ => (), } } } -/// Modifies the gamepad resources and sends out gamepad events. -/// -/// The resources [`Input`], [`Axis`], and [`Axis`] are updated -/// and the [`GamepadEvent`]s are sent according to the received [`GamepadEventRaw`]s respecting the [`GamepadSettings`]. -/// -/// ## Differences -/// -/// The main difference between the events and the resources is that the latter allows you to check specific -/// buttons or axes, rather than reading the events one at a time. This is done through convenient functions -/// like [`Input::pressed`], [`Input::just_pressed`], [`Input::just_released`], and [`Axis::get`]. -pub fn gamepad_event_system( +#[derive(Debug, Clone, PartialEq, Reflect, FromReflect)] +#[reflect(Debug, PartialEq)] +#[cfg_attr( + feature = "serialize", + derive(serde::Serialize, serde::Deserialize), + reflect(Serialize, Deserialize) +)] +pub enum GamepadConnection { + Connected(GamepadInfo), + Disconnected, +} + +/// A Gamepad connection event. Created when a connection to a gamepad +/// is established and when a gamepad is disconnected. +#[derive(Debug, Clone, PartialEq, Reflect, FromReflect)] +#[reflect(Debug, PartialEq)] +#[cfg_attr( + feature = "serialize", + derive(serde::Serialize, serde::Deserialize), + reflect(Serialize, Deserialize) +)] +pub struct GamepadConnectionEvent { + /// The gamepad whose connection status changed. + pub gamepad: Gamepad, + /// The change in the gamepads connection. + pub connection: GamepadConnection, +} + +impl GamepadConnectionEvent { + pub fn new(gamepad: Gamepad, connection: GamepadConnection) -> Self { + Self { + gamepad, + connection, + } + } + + pub fn connected(&self) -> bool { + matches!(self.connection, GamepadConnection::Connected(_)) + } + + pub fn disconnected(&self) -> bool { + !self.connected() + } +} + +#[derive(Debug, Clone, PartialEq, Reflect, FromReflect)] +#[reflect(Debug, PartialEq)] +#[cfg_attr( + feature = "serialize", + derive(serde::Serialize, serde::Deserialize), + reflect(Serialize, Deserialize) +)] +pub struct GamepadAxisChangedEvent { + pub gamepad: Gamepad, + pub axis_type: GamepadAxisType, + pub value: f32, +} + +impl GamepadAxisChangedEvent { + pub fn new(gamepad: Gamepad, axis_type: GamepadAxisType, value: f32) -> Self { + Self { + gamepad, + axis_type, + value, + } + } +} + +/// Gamepad event for when the "value" (amount of pressure) on the button +/// changes by an amount larger than the threshold defined in [`GamepadSettings`]. +#[derive(Debug, Clone, PartialEq, Reflect, FromReflect)] +#[reflect(Debug, PartialEq)] +#[cfg_attr( + feature = "serialize", + derive(serde::Serialize, serde::Deserialize), + reflect(Serialize, Deserialize) +)] +pub struct GamepadButtonChangedEvent { + pub gamepad: Gamepad, + pub button_type: GamepadButtonType, + pub value: f32, +} + +impl GamepadButtonChangedEvent { + pub fn new(gamepad: Gamepad, button_type: GamepadButtonType, value: f32) -> Self { + Self { + gamepad, + button_type, + value, + } + } +} + +/// Uses [`GamepadAxisChangedEvent`]s to update update the relevant `Input` and `Axis` values. +pub fn gamepad_axis_event_system( + mut button_input: ResMut>, + mut gamepad_axis: ResMut>, + mut axis_events: EventReader, +) { + button_input.bypass_change_detection().clear(); + for axis_event in axis_events.iter() { + let axis = GamepadAxis::new(axis_event.gamepad, axis_event.axis_type); + gamepad_axis.set(axis, axis_event.value); + } +} + +/// Uses [`GamepadButtonChangedEvent`]s to update update the relevant `Input` and `Axis` values. +pub fn gamepad_button_event_system( + mut button_events: EventReader, mut button_input: ResMut>, - mut axis: ResMut>, mut button_axis: ResMut>, - mut raw_events: EventReader, - mut events: EventWriter, settings: Res, ) { button_input.bypass_change_detection().clear(); - for event in raw_events.iter() { - match &event.event_type { - GamepadEventType::Connected(_) => { - events.send(GamepadEvent::new(event.gamepad, event.event_type.clone())); - for button_type in &ALL_BUTTON_TYPES { - let gamepad_button = GamepadButton::new(event.gamepad, *button_type); - button_input.reset(gamepad_button); - button_axis.set(gamepad_button, 0.0); - } - for axis_type in &ALL_AXIS_TYPES { - axis.set(GamepadAxis::new(event.gamepad, *axis_type), 0.0); - } - } - GamepadEventType::Disconnected => { - events.send(GamepadEvent::new(event.gamepad, event.event_type.clone())); - for button_type in &ALL_BUTTON_TYPES { - let gamepad_button = GamepadButton::new(event.gamepad, *button_type); - button_input.reset(gamepad_button); - button_axis.remove(gamepad_button); - } - for axis_type in &ALL_AXIS_TYPES { - axis.remove(GamepadAxis::new(event.gamepad, *axis_type)); - } - } - GamepadEventType::AxisChanged(axis_type, value) => { - let gamepad_axis = GamepadAxis::new(event.gamepad, *axis_type); - if let Some(filtered_value) = settings - .get_axis_settings(gamepad_axis) - .filter(*value, axis.get(gamepad_axis)) - { - axis.set(gamepad_axis, filtered_value); - events.send(GamepadEvent::new( - event.gamepad, - GamepadEventType::AxisChanged(*axis_type, filtered_value), - )); - } - } - GamepadEventType::ButtonChanged(button_type, value) => { - let gamepad_button = GamepadButton::new(event.gamepad, *button_type); - if let Some(filtered_value) = settings - .get_button_axis_settings(gamepad_button) - .filter(*value, button_axis.get(gamepad_button)) - { - button_axis.set(gamepad_button, filtered_value); - events.send(GamepadEvent::new( - event.gamepad, - GamepadEventType::ButtonChanged(*button_type, filtered_value), - )); - } + for button_event in button_events.iter() { + let button = GamepadButton::new(button_event.gamepad, button_event.button_type); + let value = button_event.value; + let button_property = settings.get_button_settings(button); + + if button_property.is_released(value) { + // We don't have to check if the button was previously pressed + // because that check is performed within Input::release() + button_input.release(button); + } else if button_property.is_pressed(value) { + button_input.press(button); + }; - let button_property = settings.get_button_settings(gamepad_button); - if button_input.pressed(gamepad_button) { - if button_property.is_released(*value) { - button_input.release(gamepad_button); - } - } else if button_property.is_pressed(*value) { - button_input.press(gamepad_button); - } + button_axis.set(button, value); + } +} + +/// A gamepad event. +/// +/// This event type is used over the [`GamepadConnectionEvent`], +/// [`GamepadButtonChangedEvent`] and [`GamepadAxisChangedEvent`] when +/// the in-frame relative ordering of events is important. +#[derive(Debug, Clone, PartialEq, Reflect, FromReflect)] +#[reflect(Debug, PartialEq)] +#[cfg_attr( + feature = "serialize", + derive(serde::Serialize, serde::Deserialize), + reflect(Serialize, Deserialize) +)] +pub enum GamepadEvent { + Connection(GamepadConnectionEvent), + Button(GamepadButtonChangedEvent), + Axis(GamepadAxisChangedEvent), +} + +impl From for GamepadEvent { + fn from(value: GamepadConnectionEvent) -> Self { + Self::Connection(value) + } +} + +impl From for GamepadEvent { + fn from(value: GamepadButtonChangedEvent) -> Self { + Self::Button(value) + } +} + +impl From for GamepadEvent { + fn from(value: GamepadAxisChangedEvent) -> Self { + Self::Axis(value) + } +} + +/// Splits the [`GamepadEvent`] event stream into it's component events. +pub fn gamepad_event_system( + mut gamepad_events: EventReader, + mut connection_events: EventWriter, + mut button_events: EventWriter, + mut axis_events: EventWriter, +) { + for gamepad_event in gamepad_events.iter() { + match gamepad_event { + GamepadEvent::Connection(connection_event) => { + connection_events.send(connection_event.clone()); } + GamepadEvent::Button(button_event) => button_events.send(button_event.clone()), + GamepadEvent::Axis(axis_event) => axis_events.send(axis_event.clone()), } } } diff --git a/crates/bevy_input/src/lib.rs b/crates/bevy_input/src/lib.rs index 23583667ae973..fccc2027f56b4 100644 --- a/crates/bevy_input/src/lib.rs +++ b/crates/bevy_input/src/lib.rs @@ -12,8 +12,7 @@ pub mod prelude { #[doc(hidden)] pub use crate::{ gamepad::{ - Gamepad, GamepadAxis, GamepadAxisType, GamepadButton, GamepadButtonType, GamepadEvent, - GamepadEventType, Gamepads, + Gamepad, GamepadAxis, GamepadAxisType, GamepadButton, GamepadButtonType, Gamepads, }, keyboard::{KeyCode, ScanCode}, mouse::MouseButton, @@ -23,7 +22,7 @@ pub mod prelude { } use bevy_app::prelude::*; -use bevy_ecs::schedule::{IntoSystemDescriptor, SystemLabel}; +use bevy_ecs::schedule::{IntoSystemDescriptor, SystemLabel, SystemSet}; use bevy_reflect::{FromReflect, Reflect}; use keyboard::{keyboard_input_system, KeyCode, KeyboardInput, ScanCode}; use mouse::{ @@ -33,9 +32,11 @@ use mouse::{ use touch::{touch_screen_input_system, ForceTouch, TouchInput, TouchPhase, Touches}; use gamepad::{ - gamepad_connection_system, gamepad_event_system, AxisSettings, ButtonAxisSettings, - ButtonSettings, Gamepad, GamepadAxis, GamepadAxisType, GamepadButton, GamepadButtonType, - GamepadEvent, GamepadEventRaw, GamepadEventType, GamepadSettings, Gamepads, + gamepad_axis_event_system, gamepad_button_event_system, gamepad_connection_system, + gamepad_event_system, AxisSettings, ButtonAxisSettings, ButtonSettings, Gamepad, GamepadAxis, + GamepadAxisChangedEvent, GamepadAxisType, GamepadButton, GamepadButtonChangedEvent, + GamepadButtonType, GamepadConnection, GamepadConnectionEvent, GamepadEvent, GamepadSettings, + Gamepads, }; #[cfg(feature = "serialize")] @@ -69,20 +70,23 @@ impl Plugin for InputPlugin { mouse_button_input_system.label(InputSystem), ) // gamepad + .add_event::() + .add_event::() + .add_event::() .add_event::() - .add_event::() .init_resource::() .init_resource::() .init_resource::>() .init_resource::>() .init_resource::>() - .add_system_to_stage( - CoreStage::PreUpdate, - gamepad_event_system.label(InputSystem), - ) - .add_system_to_stage( + .add_system_set_to_stage( CoreStage::PreUpdate, - gamepad_connection_system.after(InputSystem), + SystemSet::new() + .with_system(gamepad_event_system) + .with_system(gamepad_button_event_system.after(gamepad_event_system)) + .with_system(gamepad_axis_event_system.after(gamepad_event_system)) + .with_system(gamepad_connection_system.after(gamepad_event_system)) + .label(InputSystem), ) // touch .add_event::() @@ -114,9 +118,7 @@ impl Plugin for InputPlugin { // Register gamepad types app.register_type::() - .register_type::() - .register_type::() - .register_type::() + .register_type::() .register_type::() .register_type::() .register_type::() diff --git a/examples/input/gamepad_input_events.rs b/examples/input/gamepad_input_events.rs index 3d33a0e32b138..d4d9725d0c444 100644 --- a/examples/input/gamepad_input_events.rs +++ b/examples/input/gamepad_input_events.rs @@ -1,7 +1,9 @@ //! Iterates and prints gamepad input and connection events. use bevy::{ - input::gamepad::{GamepadEvent, GamepadEventType}, + input::gamepad::{ + GamepadAxisChangedEvent, GamepadButtonChangedEvent, GamepadConnectionEvent, GamepadEvent, + }, prelude::*, }; @@ -9,30 +11,41 @@ fn main() { App::new() .add_plugins(DefaultPlugins) .add_system(gamepad_events) + .add_system(gamepad_ordered_events) .run(); } -fn gamepad_events(mut gamepad_event: EventReader) { - for event in gamepad_event.iter() { - match event.event_type { - GamepadEventType::Connected(_) => { - info!("{:?} Connected", event.gamepad); - } - GamepadEventType::Disconnected => { - info!("{:?} Disconnected", event.gamepad); - } - GamepadEventType::ButtonChanged(button_type, value) => { - info!( - "{:?} of {:?} is changed to {}", - button_type, event.gamepad, value - ); - } - GamepadEventType::AxisChanged(axis_type, value) => { - info!( - "{:?} of {:?} is changed to {}", - axis_type, event.gamepad, value - ); - } +fn gamepad_events( + mut gamepad_connection_events: EventReader, + mut gamepad_axis_events: EventReader, + mut gamepad_button_events: EventReader, +) { + for connection_event in gamepad_connection_events.iter() { + info!("{:?}", connection_event); + } + for axis_event in gamepad_axis_events.iter() { + info!( + "{:?} of {:?} is changed to {}", + axis_event.axis_type, axis_event.gamepad, axis_event.value + ); + } + for button_event in gamepad_button_events.iter() { + info!( + "{:?} of {:?} is changed to {}", + button_event.button_type, button_event.gamepad, button_event.value + ); + } +} + +// If you require in-frame relative event ordering, you can also read the `Gamepad` event +// stream directly. For standard use-cases, reading the events individually or using the +// `Input` or `Axis` resources is preferable. +fn gamepad_ordered_events(mut gamepad_events: EventReader) { + for gamepad_event in gamepad_events.iter() { + match gamepad_event { + GamepadEvent::Connection(connection_event) => info!("{:?}", connection_event), + GamepadEvent::Button(button_event) => info!("{:?}", button_event), + GamepadEvent::Axis(axis_event) => info!("{:?}", axis_event), } } } diff --git a/examples/tools/gamepad_viewer.rs b/examples/tools/gamepad_viewer.rs index bbc59a9d31cb5..2f7a77a57c04b 100644 --- a/examples/tools/gamepad_viewer.rs +++ b/examples/tools/gamepad_viewer.rs @@ -3,7 +3,9 @@ use std::f32::consts::PI; use bevy::{ - input::gamepad::{GamepadButton, GamepadSettings}, + input::gamepad::{ + GamepadAxisChangedEvent, GamepadButton, GamepadButtonChangedEvent, GamepadSettings, + }, prelude::*, sprite::{MaterialMesh2dBundle, Mesh2dHandle}, }; @@ -468,42 +470,40 @@ fn update_buttons( } fn update_button_values( - mut events: EventReader, + mut events: EventReader, mut query: Query<(&mut Text, &TextWithButtonValue)>, ) { - for event in events.iter() { - if let GamepadEventType::ButtonChanged(button_type, value) = event.event_type { - for (mut text, text_with_button_value) in query.iter_mut() { - if button_type == **text_with_button_value { - text.sections[0].value = format!("{value:.3}"); - } + for button_event in events.iter() { + for (mut text, text_with_button_value) in query.iter_mut() { + if button_event.button_type == **text_with_button_value { + text.sections[0].value = format!("{:.3}", button_event.value); } } } } fn update_axes( - mut events: EventReader, + mut axis_events: EventReader, mut query: Query<(&mut Transform, &MoveWithAxes)>, mut text_query: Query<(&mut Text, &TextWithAxes)>, ) { - for event in events.iter() { - if let GamepadEventType::AxisChanged(axis_type, value) = event.event_type { - for (mut transform, move_with) in query.iter_mut() { - if axis_type == move_with.x_axis { - transform.translation.x = value * move_with.scale; - } - if axis_type == move_with.y_axis { - transform.translation.y = value * move_with.scale; - } + for axis_event in axis_events.iter() { + let axis_type = axis_event.axis_type; + let value = axis_event.value; + for (mut transform, move_with) in query.iter_mut() { + if axis_type == move_with.x_axis { + transform.translation.x = value * move_with.scale; } - for (mut text, text_with_axes) in text_query.iter_mut() { - if axis_type == text_with_axes.x_axis { - text.sections[0].value = format!("{value:.3}"); - } - if axis_type == text_with_axes.y_axis { - text.sections[2].value = format!("{value:.3}"); - } + if axis_type == move_with.y_axis { + transform.translation.y = value * move_with.scale; + } + } + for (mut text, text_with_axes) in text_query.iter_mut() { + if axis_type == text_with_axes.x_axis { + text.sections[0].value = format!("{value:.3}"); + } + if axis_type == text_with_axes.y_axis { + text.sections[2].value = format!("{value:.3}"); } } } From 45ee432a69f0c9732f22b1093db0ddc9d0e197da Mon Sep 17 00:00:00 2001 From: Mike Date: Mon, 9 Jan 2023 19:24:54 +0000 Subject: [PATCH 49/60] Separate Extract from Sub App Schedule (#7046) # Objective - This pulls out some of the changes to Plugin setup and sub apps from #6503 to make that PR easier to review. - Separate the extract stage from running the sub app's schedule to allow for them to be run on separate threads in the future - Fixes #6990 ## Solution - add a run method to `SubApp` that runs the schedule - change the name of `sub_app_runner` to extract to make it clear that this function is only for extracting data between the main app and the sub app - remove the extract stage from the sub app schedule so it can be run separately. This is done by adding a `setup` method to the `Plugin` trait that runs after all plugin build methods run. This is required to allow the extract stage to be removed from the schedule after all the plugins have added their systems to the stage. We will also need the setup method for pipelined rendering to setup the render thread. See https://github.com/bevyengine/bevy/blob/e3267965e15f14be18eec942dcaf16807144eb05/crates/bevy_render/src/pipelined_rendering.rs#L57-L98 ## Changelog - Separate SubApp Extract stage from running the sub app schedule. ## Migration Guide ### SubApp `runner` has conceptually been changed to an `extract` function. The `runner` no longer is in charge of running the sub app schedule. It's only concern is now moving data between the main world and the sub app. The `sub_app.app.schedule` is now run for you after the provided function is called. ```rust // before fn main() { let sub_app = App::empty(); sub_app.add_stage(MyStage, SystemStage::parallel()); App::new().add_sub_app(MySubApp, sub_app, move |main_world, sub_app| { extract(app_world, render_app); render_app.app.schedule.run(); }); } // after fn main() { let sub_app = App::empty(); sub_app.add_stage(MyStage, SystemStage::parallel()); App::new().add_sub_app(MySubApp, sub_app, move |main_world, sub_app| { extract(app_world, render_app); // schedule is automatically called for you after extract is run }); } ``` --- crates/bevy_app/src/app.rs | 31 +++++-- crates/bevy_app/src/plugin.rs | 9 ++ crates/bevy_ecs/src/schedule/mod.rs | 11 +++ crates/bevy_render/src/lib.rs | 135 +++++++++------------------- 4 files changed, 87 insertions(+), 99 deletions(-) diff --git a/crates/bevy_app/src/app.rs b/crates/bevy_app/src/app.rs index 16caea1be6c47..11d37d8cbe23e 100644 --- a/crates/bevy_app/src/app.rs +++ b/crates/bevy_app/src/app.rs @@ -90,7 +90,20 @@ impl Debug for App { /// Each `SubApp` has its own [`Schedule`] and [`World`], enabling a separation of concerns. struct SubApp { app: App, - runner: Box, + extract: Box, +} + +impl SubApp { + /// Runs the `SubApp`'s schedule. + pub fn run(&mut self) { + self.app.schedule.run(&mut self.app.world); + self.app.world.clear_trackers(); + } + + /// Extracts data from main world to this sub-app. + pub fn extract(&mut self, main_world: &mut World) { + (self.extract)(main_world, &mut self.app); + } } impl Debug for SubApp { @@ -153,8 +166,8 @@ impl App { self.schedule.run(&mut self.world); for sub_app in self.sub_apps.values_mut() { - (sub_app.runner)(&mut self.world, &mut sub_app.app); - sub_app.app.world.clear_trackers(); + sub_app.extract(&mut self.world); + sub_app.run(); } self.world.clear_trackers(); @@ -176,6 +189,14 @@ impl App { if app.is_building_plugin { panic!("App::run() was called from within Plugin::Build(), which is not allowed."); } + + // temporarily remove the plugin registry to run each plugin's setup function on app. + let mut plugin_registry = std::mem::take(&mut app.plugin_registry); + for plugin in &plugin_registry { + plugin.setup(&mut app); + } + std::mem::swap(&mut app.plugin_registry, &mut plugin_registry); + let runner = std::mem::replace(&mut app.runner, Box::new(run_once)); (runner)(app); } @@ -1004,13 +1025,13 @@ impl App { &mut self, label: impl AppLabel, app: App, - sub_app_runner: impl Fn(&mut World, &mut App) + 'static, + extract: impl Fn(&mut World, &mut App) + 'static, ) -> &mut Self { self.sub_apps.insert( label.as_label(), SubApp { app, - runner: Box::new(sub_app_runner), + extract: Box::new(extract), }, ); self diff --git a/crates/bevy_app/src/plugin.rs b/crates/bevy_app/src/plugin.rs index 7a88233947a19..22bc37e00eb2a 100644 --- a/crates/bevy_app/src/plugin.rs +++ b/crates/bevy_app/src/plugin.rs @@ -16,10 +16,19 @@ use std::any::Any; pub trait Plugin: Downcast + Any + Send + Sync { /// Configures the [`App`] to which this plugin is added. fn build(&self, app: &mut App); + + /// Runs after all plugins are built, but before the app runner is called. + /// This can be useful if you have some resource that other plugins need during their build step, + /// but after build you want to remove it and send it to another thread. + fn setup(&self, _app: &mut App) { + // do nothing + } + /// Configures a name for the [`Plugin`] which is primarily used for debugging. fn name(&self) -> &str { std::any::type_name::() } + /// If the plugin can be meaningfully instantiated several times in an [`App`](crate::App), /// override this method to return `false`. fn is_unique(&self) -> bool { diff --git a/crates/bevy_ecs/src/schedule/mod.rs b/crates/bevy_ecs/src/schedule/mod.rs index f7c8f78f458b6..7e9cf6e9099f0 100644 --- a/crates/bevy_ecs/src/schedule/mod.rs +++ b/crates/bevy_ecs/src/schedule/mod.rs @@ -361,6 +361,17 @@ impl Schedule { .and_then(|stage| stage.downcast_mut::()) } + /// Removes a [`Stage`] from the schedule. + pub fn remove_stage(&mut self, stage_label: impl StageLabel) -> Option> { + let label = stage_label.as_label(); + + let Some(index) = self.stage_order.iter().position(|x| *x == label) else { + return None; + }; + self.stage_order.remove(index); + self.stages.remove(&label) + } + /// Executes each [`Stage`] contained in the schedule, one at a time. pub fn run_once(&mut self, world: &mut World) { for label in &self.stage_order { diff --git a/crates/bevy_render/src/lib.rs b/crates/bevy_render/src/lib.rs index 715c1a1f9548b..0ba66415c35c8 100644 --- a/crates/bevy_render/src/lib.rs +++ b/crates/bevy_render/src/lib.rs @@ -92,6 +92,10 @@ pub enum RenderStage { Cleanup, } +/// Resource for holding the extract stage of the rendering schedule. +#[derive(Resource)] +pub struct ExtractStage(pub SystemStage); + /// The simulation [`World`] of the application, stored as a resource. /// This resource is only available during [`RenderStage::Extract`] and not /// during command application of that stage. @@ -198,7 +202,10 @@ impl Plugin for RenderPlugin { .with_system(PipelineCache::process_pipeline_queue_system) .with_system(render_system.at_end()), ) - .add_stage(RenderStage::Cleanup, SystemStage::parallel()) + .add_stage( + RenderStage::Cleanup, + SystemStage::parallel().with_system(World::clear_entities.at_end()), + ) .init_resource::() .insert_resource(RenderInstance(instance)) .insert_resource(device) @@ -248,78 +255,6 @@ impl Plugin for RenderPlugin { // extract extract(app_world, render_app); } - - { - #[cfg(feature = "trace")] - let _stage_span = - bevy_utils::tracing::info_span!("stage", name = "prepare").entered(); - - // prepare - let prepare = render_app - .schedule - .get_stage_mut::(RenderStage::Prepare) - .unwrap(); - prepare.run(&mut render_app.world); - } - - { - #[cfg(feature = "trace")] - let _stage_span = - bevy_utils::tracing::info_span!("stage", name = "queue").entered(); - - // queue - let queue = render_app - .schedule - .get_stage_mut::(RenderStage::Queue) - .unwrap(); - queue.run(&mut render_app.world); - } - - { - #[cfg(feature = "trace")] - let _stage_span = - bevy_utils::tracing::info_span!("stage", name = "sort").entered(); - - // phase sort - let phase_sort = render_app - .schedule - .get_stage_mut::(RenderStage::PhaseSort) - .unwrap(); - phase_sort.run(&mut render_app.world); - } - - { - #[cfg(feature = "trace")] - let _stage_span = - bevy_utils::tracing::info_span!("stage", name = "render").entered(); - - // render - let render = render_app - .schedule - .get_stage_mut::(RenderStage::Render) - .unwrap(); - render.run(&mut render_app.world); - } - - { - #[cfg(feature = "trace")] - let _stage_span = - bevy_utils::tracing::info_span!("stage", name = "cleanup").entered(); - - // cleanup - let cleanup = render_app - .schedule - .get_stage_mut::(RenderStage::Cleanup) - .unwrap(); - cleanup.run(&mut render_app.world); - } - { - #[cfg(feature = "trace")] - let _stage_span = - bevy_utils::tracing::info_span!("stage", name = "clear_entities").entered(); - - render_app.world.clear_entities(); - } }); } @@ -335,6 +270,20 @@ impl Plugin for RenderPlugin { .register_type::() .register_type::(); } + + fn setup(&self, app: &mut App) { + if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { + // move the extract stage to a resource so render_app.run() does not run it. + let stage = render_app + .schedule + .remove_stage(RenderStage::Extract) + .unwrap() + .downcast::() + .unwrap(); + + render_app.world.insert_resource(ExtractStage(*stage)); + } + } } /// A "scratch" world used to avoid allocating new worlds every frame when @@ -345,25 +294,23 @@ struct ScratchMainWorld(World); /// Executes the [`Extract`](RenderStage::Extract) stage of the renderer. /// This updates the render world with the extracted ECS data of the current frame. fn extract(app_world: &mut World, render_app: &mut App) { - let extract = render_app - .schedule - .get_stage_mut::(RenderStage::Extract) - .unwrap(); - - // temporarily add the app world to the render world as a resource - let scratch_world = app_world.remove_resource::().unwrap(); - let inserted_world = std::mem::replace(app_world, scratch_world.0); - let running_world = &mut render_app.world; - running_world.insert_resource(MainWorld(inserted_world)); - - extract.run(running_world); - // move the app world back, as if nothing happened. - let inserted_world = running_world.remove_resource::().unwrap(); - let scratch_world = std::mem::replace(app_world, inserted_world.0); - app_world.insert_resource(ScratchMainWorld(scratch_world)); - - // Note: We apply buffers (read, Commands) after the `MainWorld` has been removed from the render app's world - // so that in future, pipelining will be able to do this too without any code relying on it. - // see - extract.apply_buffers(running_world); + render_app + .world + .resource_scope(|render_world, mut extract_stage: Mut| { + // temporarily add the app world to the render world as a resource + let scratch_world = app_world.remove_resource::().unwrap(); + let inserted_world = std::mem::replace(app_world, scratch_world.0); + render_world.insert_resource(MainWorld(inserted_world)); + + extract_stage.0.run(render_world); + // move the app world back, as if nothing happened. + let inserted_world = render_world.remove_resource::().unwrap(); + let scratch_world = std::mem::replace(app_world, inserted_world.0); + app_world.insert_resource(ScratchMainWorld(scratch_world)); + + // Note: We apply buffers (read, Commands) after the `MainWorld` has been removed from the render app's world + // so that in future, pipelining will be able to do this too without any code relying on it. + // see + extract_stage.0.apply_buffers(render_world); + }); } From d823da04598587915cd906957feaa9cd03e1c96f Mon Sep 17 00:00:00 2001 From: James Liu Date: Mon, 9 Jan 2023 19:24:56 +0000 Subject: [PATCH 50/60] Reduce branching in TrackedRenderPass (#7053) # Objective Speed up the render phase for rendering. ## Solution - Follow up #6988 and make the internals of atomic IDs `NonZeroU32`. This niches the `Option`s of the IDs in draw state, which reduces the size and branching behavior when evaluating for equality. - Require `&RenderDevice` to get the device's `Limits` when initializing a `TrackedRenderPass` to preallocate the bind groups and vertex buffer state in `DrawState`, this removes the branch on needing to resize those `Vec`s. ## Performance This produces a similar speed up akin to that of #6885. This shows an approximate 6% speed up in `main_opaque_pass_3d` on `many_foxes` (408.79 us -> 388us). This should be orthogonal to the gains seen there. ![image](https://user-images.githubusercontent.com/3137680/209906239-e430f026-63c2-4b95-957e-a2045b810d79.png) --- ## Changelog Added: `RenderContext::begin_tracked_render_pass`. Changed: `TrackedRenderPass` now requires a `&RenderDevice` on construction. Removed: `bevy_render::render_phase::DrawState`. It was not usable in any form outside of `bevy_render`. ## Migration Guide TODO --- crates/bevy_core_pipeline/src/bloom/mod.rs | 83 +++++++++---------- .../src/core_2d/main_pass_2d_node.rs | 11 +-- .../src/core_3d/main_pass_3d_node.rs | 31 ++----- crates/bevy_pbr/src/render/light.rs | 28 +++---- crates/bevy_render/Cargo.toml | 1 + .../src/render_phase/draw_state.rs | 30 ++++--- .../src/render_resource/resource_macros.rs | 18 ++-- crates/bevy_render/src/renderer/mod.rs | 16 ++++ crates/bevy_ui/src/render/render_pass.rs | 9 +- 9 files changed, 105 insertions(+), 122 deletions(-) diff --git a/crates/bevy_core_pipeline/src/bloom/mod.rs b/crates/bevy_core_pipeline/src/bloom/mod.rs index 0700c3b16b3ae..7112ccba63d9d 100644 --- a/crates/bevy_core_pipeline/src/bloom/mod.rs +++ b/crates/bevy_core_pipeline/src/bloom/mod.rs @@ -17,7 +17,6 @@ use bevy_render::{ }, prelude::Camera, render_graph::{Node, NodeRunError, RenderGraph, RenderGraphContext, SlotInfo, SlotType}, - render_phase::TrackedRenderPass, render_resource::*, renderer::{RenderContext, RenderDevice}, texture::{CachedTexture, TextureCache}, @@ -232,17 +231,15 @@ impl Node for BloomNode { { let view = &BloomTextures::texture_view(&textures.texture_a, 0); let mut prefilter_pass = - TrackedRenderPass::new(render_context.command_encoder.begin_render_pass( - &RenderPassDescriptor { - label: Some("bloom_prefilter_pass"), - color_attachments: &[Some(RenderPassColorAttachment { - view, - resolve_target: None, - ops: Operations::default(), - })], - depth_stencil_attachment: None, - }, - )); + render_context.begin_tracked_render_pass(RenderPassDescriptor { + label: Some("bloom_prefilter_pass"), + color_attachments: &[Some(RenderPassColorAttachment { + view, + resolve_target: None, + ops: Operations::default(), + })], + depth_stencil_attachment: None, + }); prefilter_pass.set_render_pipeline(downsampling_prefilter_pipeline); prefilter_pass.set_bind_group( 0, @@ -258,17 +255,15 @@ impl Node for BloomNode { for mip in 1..textures.mip_count { let view = &BloomTextures::texture_view(&textures.texture_a, mip); let mut downsampling_pass = - TrackedRenderPass::new(render_context.command_encoder.begin_render_pass( - &RenderPassDescriptor { - label: Some("bloom_downsampling_pass"), - color_attachments: &[Some(RenderPassColorAttachment { - view, - resolve_target: None, - ops: Operations::default(), - })], - depth_stencil_attachment: None, - }, - )); + render_context.begin_tracked_render_pass(RenderPassDescriptor { + label: Some("bloom_downsampling_pass"), + color_attachments: &[Some(RenderPassColorAttachment { + view, + resolve_target: None, + ops: Operations::default(), + })], + depth_stencil_attachment: None, + }); downsampling_pass.set_render_pipeline(downsampling_pipeline); downsampling_pass.set_bind_group( 0, @@ -284,17 +279,15 @@ impl Node for BloomNode { for mip in (1..textures.mip_count).rev() { let view = &BloomTextures::texture_view(&textures.texture_b, mip - 1); let mut upsampling_pass = - TrackedRenderPass::new(render_context.command_encoder.begin_render_pass( - &RenderPassDescriptor { - label: Some("bloom_upsampling_pass"), - color_attachments: &[Some(RenderPassColorAttachment { - view, - resolve_target: None, - ops: Operations::default(), - })], - depth_stencil_attachment: None, - }, - )); + render_context.begin_tracked_render_pass(RenderPassDescriptor { + label: Some("bloom_upsampling_pass"), + color_attachments: &[Some(RenderPassColorAttachment { + view, + resolve_target: None, + ops: Operations::default(), + })], + depth_stencil_attachment: None, + }); upsampling_pass.set_render_pipeline(upsampling_pipeline); upsampling_pass.set_bind_group( 0, @@ -309,18 +302,16 @@ impl Node for BloomNode { { let mut upsampling_final_pass = - TrackedRenderPass::new(render_context.command_encoder.begin_render_pass( - &RenderPassDescriptor { - label: Some("bloom_upsampling_final_pass"), - color_attachments: &[Some(view_target.get_unsampled_color_attachment( - Operations { - load: LoadOp::Load, - store: true, - }, - ))], - depth_stencil_attachment: None, - }, - )); + render_context.begin_tracked_render_pass(RenderPassDescriptor { + label: Some("bloom_upsampling_final_pass"), + color_attachments: &[Some(view_target.get_unsampled_color_attachment( + Operations { + load: LoadOp::Load, + store: true, + }, + ))], + depth_stencil_attachment: None, + }); upsampling_final_pass.set_render_pipeline(upsampling_final_pipeline); upsampling_final_pass.set_bind_group( 0, diff --git a/crates/bevy_core_pipeline/src/core_2d/main_pass_2d_node.rs b/crates/bevy_core_pipeline/src/core_2d/main_pass_2d_node.rs index 2a262b426195b..da3cbf3c17242 100644 --- a/crates/bevy_core_pipeline/src/core_2d/main_pass_2d_node.rs +++ b/crates/bevy_core_pipeline/src/core_2d/main_pass_2d_node.rs @@ -3,7 +3,6 @@ use crate::{ core_2d::{camera_2d::Camera2d, Transparent2d}, }; use bevy_ecs::prelude::*; -use bevy_render::render_phase::TrackedRenderPass; use bevy_render::{ camera::ExtractedCamera, render_graph::{Node, NodeRunError, RenderGraphContext, SlotInfo, SlotType}, @@ -63,7 +62,8 @@ impl Node for MainPass2dNode { { #[cfg(feature = "trace")] let _main_pass_2d = info_span!("main_pass_2d").entered(); - let pass_descriptor = RenderPassDescriptor { + + let mut render_pass = render_context.begin_tracked_render_pass(RenderPassDescriptor { label: Some("main_pass_2d"), color_attachments: &[Some(target.get_color_attachment(Operations { load: match camera_2d.clear_color { @@ -76,12 +76,7 @@ impl Node for MainPass2dNode { store: true, }))], depth_stencil_attachment: None, - }; - - let render_pass = render_context - .command_encoder - .begin_render_pass(&pass_descriptor); - let mut render_pass = TrackedRenderPass::new(render_pass); + }); if let Some(viewport) = camera.viewport.as_ref() { render_pass.set_camera_viewport(viewport); diff --git a/crates/bevy_core_pipeline/src/core_3d/main_pass_3d_node.rs b/crates/bevy_core_pipeline/src/core_3d/main_pass_3d_node.rs index 1067643e03b12..a836e0bcbe450 100644 --- a/crates/bevy_core_pipeline/src/core_3d/main_pass_3d_node.rs +++ b/crates/bevy_core_pipeline/src/core_3d/main_pass_3d_node.rs @@ -3,7 +3,6 @@ use crate::{ core_3d::{AlphaMask3d, Camera3d, Opaque3d, Transparent3d}, }; use bevy_ecs::prelude::*; -use bevy_render::render_phase::TrackedRenderPass; use bevy_render::{ camera::ExtractedCamera, render_graph::{Node, NodeRunError, RenderGraphContext, SlotInfo, SlotType}, @@ -70,7 +69,8 @@ impl Node for MainPass3dNode { // NOTE: Scoped to drop the mutable borrow of render_context #[cfg(feature = "trace")] let _main_opaque_pass_3d_span = info_span!("main_opaque_pass_3d").entered(); - let pass_descriptor = RenderPassDescriptor { + + let mut render_pass = render_context.begin_tracked_render_pass(RenderPassDescriptor { label: Some("main_opaque_pass_3d"), // NOTE: The opaque pass loads the color // buffer as well as writing to it. @@ -94,12 +94,7 @@ impl Node for MainPass3dNode { }), stencil_ops: None, }), - }; - - let render_pass = render_context - .command_encoder - .begin_render_pass(&pass_descriptor); - let mut render_pass = TrackedRenderPass::new(render_pass); + }); if let Some(viewport) = camera.viewport.as_ref() { render_pass.set_camera_viewport(viewport); @@ -113,7 +108,8 @@ impl Node for MainPass3dNode { // NOTE: Scoped to drop the mutable borrow of render_context #[cfg(feature = "trace")] let _main_alpha_mask_pass_3d_span = info_span!("main_alpha_mask_pass_3d").entered(); - let pass_descriptor = RenderPassDescriptor { + + let mut render_pass = render_context.begin_tracked_render_pass(RenderPassDescriptor { label: Some("main_alpha_mask_pass_3d"), // NOTE: The alpha_mask pass loads the color buffer as well as overwriting it where appropriate. color_attachments: &[Some(target.get_color_attachment(Operations { @@ -129,12 +125,7 @@ impl Node for MainPass3dNode { }), stencil_ops: None, }), - }; - - let render_pass = render_context - .command_encoder - .begin_render_pass(&pass_descriptor); - let mut render_pass = TrackedRenderPass::new(render_pass); + }); if let Some(viewport) = camera.viewport.as_ref() { render_pass.set_camera_viewport(viewport); @@ -148,7 +139,8 @@ impl Node for MainPass3dNode { // NOTE: Scoped to drop the mutable borrow of render_context #[cfg(feature = "trace")] let _main_transparent_pass_3d_span = info_span!("main_transparent_pass_3d").entered(); - let pass_descriptor = RenderPassDescriptor { + + let mut render_pass = render_context.begin_tracked_render_pass(RenderPassDescriptor { label: Some("main_transparent_pass_3d"), // NOTE: The transparent pass loads the color buffer as well as overwriting it where appropriate. color_attachments: &[Some(target.get_color_attachment(Operations { @@ -169,12 +161,7 @@ impl Node for MainPass3dNode { }), stencil_ops: None, }), - }; - - let render_pass = render_context - .command_encoder - .begin_render_pass(&pass_descriptor); - let mut render_pass = TrackedRenderPass::new(render_pass); + }); if let Some(viewport) = camera.viewport.as_ref() { render_pass.set_camera_viewport(viewport); diff --git a/crates/bevy_pbr/src/render/light.rs b/crates/bevy_pbr/src/render/light.rs index 9cd2bf0bbd83c..38bab358d7c71 100644 --- a/crates/bevy_pbr/src/render/light.rs +++ b/crates/bevy_pbr/src/render/light.rs @@ -1770,23 +1770,19 @@ impl Node for ShadowPassNode { continue; } - let pass_descriptor = RenderPassDescriptor { - label: Some(&view_light.pass_name), - color_attachments: &[], - depth_stencil_attachment: Some(RenderPassDepthStencilAttachment { - view: &view_light.depth_texture_view, - depth_ops: Some(Operations { - load: LoadOp::Clear(0.0), - store: true, + let mut render_pass = + render_context.begin_tracked_render_pass(RenderPassDescriptor { + label: Some(&view_light.pass_name), + color_attachments: &[], + depth_stencil_attachment: Some(RenderPassDepthStencilAttachment { + view: &view_light.depth_texture_view, + depth_ops: Some(Operations { + load: LoadOp::Clear(0.0), + store: true, + }), + stencil_ops: None, }), - stencil_ops: None, - }), - }; - - let render_pass = render_context - .command_encoder - .begin_render_pass(&pass_descriptor); - let mut render_pass = TrackedRenderPass::new(render_pass); + }); shadow_phase.render(&mut render_pass, world, view_light_entity); } diff --git a/crates/bevy_render/Cargo.toml b/crates/bevy_render/Cargo.toml index 605871172c2ec..eaa4d09af6bac 100644 --- a/crates/bevy_render/Cargo.toml +++ b/crates/bevy_render/Cargo.toml @@ -50,6 +50,7 @@ image = { version = "0.24", default-features = false } # misc wgpu = { version = "0.14.0", features = ["spirv"] } +wgpu-hal = "0.14.1" codespan-reporting = "0.11.0" naga = { version = "0.10.0", features = ["glsl-in", "spv-in", "spv-out", "wgsl-in", "wgsl-out"] } serde = { version = "1", features = ["derive"] } diff --git a/crates/bevy_render/src/render_phase/draw_state.rs b/crates/bevy_render/src/render_phase/draw_state.rs index 907712e6788a6..6d6795fe9a936 100644 --- a/crates/bevy_render/src/render_phase/draw_state.rs +++ b/crates/bevy_render/src/render_phase/draw_state.rs @@ -5,14 +5,16 @@ use crate::{ BindGroup, BindGroupId, Buffer, BufferId, BufferSlice, RenderPipeline, RenderPipelineId, ShaderStages, }, + renderer::RenderDevice, }; -use bevy_utils::tracing::trace; +use bevy_utils::{default, tracing::trace}; use std::ops::Range; use wgpu::{IndexFormat, RenderPass}; +use wgpu_hal::{MAX_BIND_GROUPS, MAX_VERTEX_BUFFERS}; /// Tracks the current [`TrackedRenderPass`] state to ensure draw calls are valid. #[derive(Debug, Default)] -pub struct DrawState { +struct DrawState { pipeline: Option, bind_groups: Vec<(Option, Vec)>, vertex_buffers: Vec>, @@ -26,12 +28,10 @@ impl DrawState { bind_group: BindGroupId, dynamic_indices: &[u32], ) { - if index >= self.bind_groups.len() { - self.bind_groups.resize(index + 1, (None, Vec::new())); - } - self.bind_groups[index].0 = Some(bind_group); - self.bind_groups[index].1.clear(); - self.bind_groups[index].1.extend(dynamic_indices); + let group = &mut self.bind_groups[index]; + group.0 = Some(bind_group); + group.1.clear(); + group.1.extend(dynamic_indices); } pub fn is_bind_group_set( @@ -48,9 +48,6 @@ impl DrawState { } pub fn set_vertex_buffer(&mut self, index: usize, buffer: BufferId, offset: u64) { - if index >= self.vertex_buffers.len() { - self.vertex_buffers.resize(index + 1, None); - } self.vertex_buffers[index] = Some((buffer, offset)); } @@ -98,9 +95,16 @@ pub struct TrackedRenderPass<'a> { impl<'a> TrackedRenderPass<'a> { /// Tracks the supplied render pass. - pub fn new(pass: RenderPass<'a>) -> Self { + pub fn new(device: &RenderDevice, pass: RenderPass<'a>) -> Self { + let limits = device.limits(); + let max_bind_groups = limits.max_bind_groups as usize; + let max_vertex_buffers = limits.max_vertex_buffers as usize; Self { - state: DrawState::default(), + state: DrawState { + bind_groups: vec![(None, Vec::new()); max_bind_groups.min(MAX_BIND_GROUPS)], + vertex_buffers: vec![None; max_vertex_buffers.min(MAX_VERTEX_BUFFERS)], + ..default() + }, pass, } } diff --git a/crates/bevy_render/src/render_resource/resource_macros.rs b/crates/bevy_render/src/render_resource/resource_macros.rs index e27380426a169..26aaceb4fc92f 100644 --- a/crates/bevy_render/src/render_resource/resource_macros.rs +++ b/crates/bevy_render/src/render_resource/resource_macros.rs @@ -124,7 +124,7 @@ macro_rules! render_resource_wrapper { macro_rules! define_atomic_id { ($atomic_id_type:ident) => { #[derive(Copy, Clone, Hash, Eq, PartialEq, Debug)] - pub struct $atomic_id_type(u32); + pub struct $atomic_id_type(core::num::NonZeroU32); // We use new instead of default to indicate that each ID created will be unique. #[allow(clippy::new_without_default)] @@ -134,15 +134,13 @@ macro_rules! define_atomic_id { static COUNTER: AtomicU32 = AtomicU32::new(1); - match COUNTER.fetch_add(1, Ordering::Relaxed) { - 0 => { - panic!( - "The system ran out of unique `{}`s.", - stringify!($atomic_id_type) - ); - } - id => Self(id), - } + let counter = COUNTER.fetch_add(1, Ordering::Relaxed); + Self(core::num::NonZeroU32::new(counter).unwrap_or_else(|| { + panic!( + "The system ran out of unique `{}`s.", + stringify!($atomic_id_type) + ); + })) } } }; diff --git a/crates/bevy_render/src/renderer/mod.rs b/crates/bevy_render/src/renderer/mod.rs index aa362a3d9b2ed..19cee9c9585a5 100644 --- a/crates/bevy_render/src/renderer/mod.rs +++ b/crates/bevy_render/src/renderer/mod.rs @@ -8,6 +8,8 @@ pub use render_device::*; use crate::{ render_graph::RenderGraph, + render_phase::TrackedRenderPass, + render_resource::RenderPassDescriptor, settings::{WgpuSettings, WgpuSettingsPriority}, view::{ExtractedWindows, ViewTarget}, }; @@ -279,3 +281,17 @@ pub struct RenderContext { pub render_device: RenderDevice, pub command_encoder: CommandEncoder, } + +impl RenderContext { + /// Creates a new [`TrackedRenderPass`] for the context, + /// configured using the provided `descriptor`. + pub fn begin_tracked_render_pass<'a>( + &'a mut self, + descriptor: RenderPassDescriptor<'a, '_>, + ) -> TrackedRenderPass<'a> { + TrackedRenderPass::new( + &self.render_device, + self.command_encoder.begin_render_pass(&descriptor), + ) + } +} diff --git a/crates/bevy_ui/src/render/render_pass.rs b/crates/bevy_ui/src/render/render_pass.rs index ee05cf6f8a21b..63ff0a47f989c 100644 --- a/crates/bevy_ui/src/render/render_pass.rs +++ b/crates/bevy_ui/src/render/render_pass.rs @@ -76,19 +76,14 @@ impl Node for UiPassNode { } else { input_view_entity }; - let pass_descriptor = RenderPassDescriptor { + let mut render_pass = render_context.begin_tracked_render_pass(RenderPassDescriptor { label: Some("ui_pass"), color_attachments: &[Some(target.get_unsampled_color_attachment(Operations { load: LoadOp::Load, store: true, }))], depth_stencil_attachment: None, - }; - - let render_pass = render_context - .command_encoder - .begin_render_pass(&pass_descriptor); - let mut render_pass = TrackedRenderPass::new(render_pass); + }); transparent_phase.render(&mut render_pass, world, view_entity); From 4343f2e2a33dbaeb99d00b8fc5dec7ae4920270c Mon Sep 17 00:00:00 2001 From: radiish Date: Mon, 9 Jan 2023 19:47:07 +0000 Subject: [PATCH 51/60] reflect: add `insert` and `remove` methods to `List` (#7063) # Objective - Fixes #7061 ## Solution - Add and implement `insert` and `remove` methods for `List`. --- ## Changelog - Added `insert` and `remove` methods to `List`. - Changed the `push` and `pop` methods on `List` to have default implementations. ## Migration Guide - Manual implementors of `List` need to implement the new methods `insert` and `remove` and consider whether to use the new default implementation of `push` and `pop`. Co-authored-by: radiish --- crates/bevy_reflect/src/impls/smallvec.rs | 16 +++++++++ crates/bevy_reflect/src/impls/std.rs | 22 ++++++++++-- crates/bevy_reflect/src/list.rs | 41 ++++++++++++++++++++--- 3 files changed, 73 insertions(+), 6 deletions(-) diff --git a/crates/bevy_reflect/src/impls/smallvec.rs b/crates/bevy_reflect/src/impls/smallvec.rs index f44957175f384..4a4c64e4ff9dd 100644 --- a/crates/bevy_reflect/src/impls/smallvec.rs +++ b/crates/bevy_reflect/src/impls/smallvec.rs @@ -49,6 +49,22 @@ impl List for SmallVec where T::Item: FromReflect, { + fn insert(&mut self, index: usize, value: Box) { + let value = value.take::().unwrap_or_else(|value| { + ::Item::from_reflect(&*value).unwrap_or_else(|| { + panic!( + "Attempted to insert invalid value of type {}.", + value.type_name() + ) + }) + }); + SmallVec::insert(self, index, value); + } + + fn remove(&mut self, index: usize) -> Box { + Box::new(self.remove(index)) + } + fn push(&mut self, value: Box) { let value = value.take::().unwrap_or_else(|value| { ::Item::from_reflect(&*value).unwrap_or_else(|| { diff --git a/crates/bevy_reflect/src/impls/std.rs b/crates/bevy_reflect/src/impls/std.rs index ca7ad9c8710e9..e80859ddbd286 100644 --- a/crates/bevy_reflect/src/impls/std.rs +++ b/crates/bevy_reflect/src/impls/std.rs @@ -179,7 +179,7 @@ impl_from_reflect_value!(NonZeroU8); impl_from_reflect_value!(NonZeroI8); macro_rules! impl_reflect_for_veclike { - ($ty:ty, $push:expr, $pop:expr, $sub:ty) => { + ($ty:ty, $insert:expr, $remove:expr, $push:expr, $pop:expr, $sub:ty) => { impl Array for $ty { #[inline] fn get(&self, index: usize) -> Option<&dyn Reflect> { @@ -213,6 +213,22 @@ macro_rules! impl_reflect_for_veclike { } impl List for $ty { + fn insert(&mut self, index: usize, value: Box) { + let value = value.take::().unwrap_or_else(|value| { + T::from_reflect(&*value).unwrap_or_else(|| { + panic!( + "Attempted to insert invalid value of type {}.", + value.type_name() + ) + }) + }); + $insert(self, index, value); + } + + fn remove(&mut self, index: usize) -> Box { + Box::new($remove(self, index)) + } + fn push(&mut self, value: Box) { let value = value.take::().unwrap_or_else(|value| { T::from_reflect(&*value).unwrap_or_else(|| { @@ -328,9 +344,11 @@ macro_rules! impl_reflect_for_veclike { }; } -impl_reflect_for_veclike!(Vec, Vec::push, Vec::pop, [T]); +impl_reflect_for_veclike!(Vec, Vec::insert, Vec::remove, Vec::push, Vec::pop, [T]); impl_reflect_for_veclike!( VecDeque, + VecDeque::insert, + VecDeque::remove, VecDeque::push_back, VecDeque::pop_back, VecDeque:: diff --git a/crates/bevy_reflect/src/list.rs b/crates/bevy_reflect/src/list.rs index 6a2c2a8767381..2d67a62f24140 100644 --- a/crates/bevy_reflect/src/list.rs +++ b/crates/bevy_reflect/src/list.rs @@ -9,18 +9,43 @@ use crate::{ /// An ordered, mutable list of [Reflect] items. This corresponds to types like [`std::vec::Vec`]. /// -/// This is a sub-trait of [`Array`] as it implements a [`push`](List::push) function, allowing -/// it's internal size to grow. +/// This is a sub-trait of [`Array`], however as it implements [insertion](List::insert) and [removal](List::remove), +/// it's internal size may change. /// /// This trait expects index 0 to contain the _front_ element. /// The _back_ element must refer to the element with the largest index. /// These two rules above should be upheld by manual implementors. +/// +/// [`push`](List::push) and [`pop`](List::pop) have default implementations, +/// however it may be faster to implement them manually. pub trait List: Reflect + Array { + /// Inserts an element at position `index` within the list, + /// shifting all elements after it towards the back of the list. + /// + /// # Panics + /// Panics if `index > len`. + fn insert(&mut self, index: usize, element: Box); + + /// Removes and returns the element at position `index` within the list, + /// shifting all elements before it towards the front of the list. + /// + /// # Panics + /// Panics if `index` is out of bounds. + fn remove(&mut self, index: usize) -> Box; + /// Appends an element to the _back_ of the list. - fn push(&mut self, value: Box); + fn push(&mut self, value: Box) { + self.insert(self.len(), value); + } /// Removes the _back_ element from the list and returns it, or [`None`] if it is empty. - fn pop(&mut self) -> Option>; + fn pop(&mut self) -> Option> { + if self.is_empty() { + None + } else { + Some(self.remove(self.len() - 1)) + } + } /// Clones the list, producing a [`DynamicList`]. fn clone_dynamic(&self) -> DynamicList { @@ -174,6 +199,14 @@ impl Array for DynamicList { } impl List for DynamicList { + fn insert(&mut self, index: usize, element: Box) { + self.values.insert(index, element); + } + + fn remove(&mut self, index: usize) -> Box { + self.values.remove(index) + } + fn push(&mut self, value: Box) { DynamicList::push_box(self, value); } From 845b9565abc927db4246eac6cdac97bdeff95833 Mon Sep 17 00:00:00 2001 From: James Liu Date: Mon, 9 Jan 2023 20:40:34 +0000 Subject: [PATCH 52/60] Panic on dropping NonSend in non-origin thread. (#6534) # Objective Fixes #3310. Fixes #6282. Fixes #6278. Fixes #3666. ## Solution Split out `!Send` resources into `NonSendResources`. Add a `origin_thread_id` to all `!Send` Resources, check it on dropping `NonSendResourceData`, if there's a mismatch, panic. Moved all of the checks that `MainThreadValidator` would do into `NonSendResources` instead. All `!Send` resources now individually track which thread they were inserted from. This is validated against for every access, mutation, and drop that could be done against the value. A regression test using an altered version of the example from #3310 has been added. This is a stopgap solution for the current status quo. A full solution may involve fully removing `!Send` resources/components from `World`, which will likely require a much more thorough design on how to handle the existing in-engine and ecosystem use cases. This PR also introduces another breaking change: ```rust use bevy_ecs::prelude::*; #[derive(Resource)] struct Resource(u32); fn main() { let mut world = World::new(); world.insert_resource(Resource(1)); world.insert_non_send_resource(Resource(2)); let res = world.get_resource_mut::().unwrap(); assert_eq!(res.0, 2); } ``` This code will run correctly on 0.9.1 but not with this PR, since NonSend resources and normal resources have become actual distinct concepts storage wise. ## Changelog Changed: Fix soundness bug with `World: Send`. Dropping a `World` that contains a `!Send` resource on the wrong thread will now panic. ## Migration Guide Normal resources and `NonSend` resources no longer share the same backing storage. If `R: Resource`, then `NonSend` and `Res` will return different instances from each other. If you are using both `Res` and `NonSend` (or their mutable variants), to fetch the same resources, it's strongly advised to use `Res`. --- crates/bevy_ecs/src/lib.rs | 37 +- .../src/schedule/ambiguity_detection.rs | 4 + crates/bevy_ecs/src/storage/mod.rs | 3 +- crates/bevy_ecs/src/storage/resource.rs | 152 +++++--- crates/bevy_ecs/src/system/system_param.rs | 16 +- crates/bevy_ecs/src/world/mod.rs | 335 ++++++++++++------ crates/bevy_ecs/src/world/world_cell.rs | 4 +- crates/bevy_render/src/view/window.rs | 2 +- 8 files changed, 369 insertions(+), 184 deletions(-) diff --git a/crates/bevy_ecs/src/lib.rs b/crates/bevy_ecs/src/lib.rs index 46f1a8dc4281c..02780adff3318 100644 --- a/crates/bevy_ecs/src/lib.rs +++ b/crates/bevy_ecs/src/lib.rs @@ -1267,6 +1267,15 @@ mod tests { assert_eq!(*world.non_send_resource_mut::(), 456); } + #[test] + fn non_send_resource_points_to_distinct_data() { + let mut world = World::default(); + world.insert_resource(A(123)); + world.insert_non_send_resource(A(456)); + assert_eq!(*world.resource::(), A(123)); + assert_eq!(*world.non_send_resource::(), A(456)); + } + #[test] #[should_panic] fn non_send_resource_panic() { @@ -1406,31 +1415,18 @@ mod tests { assert_eq!(world.resource::().0, 1); } - #[test] - fn non_send_resource_scope() { - let mut world = World::default(); - world.insert_non_send_resource(NonSendA::default()); - world.resource_scope(|world: &mut World, mut value: Mut| { - value.0 += 1; - assert!(!world.contains_resource::()); - }); - assert_eq!(world.non_send_resource::().0, 1); - } - #[test] #[should_panic( - expected = "attempted to access NonSend resource bevy_ecs::tests::NonSendA off of the main thread" + expected = "Attempted to access or drop non-send resource bevy_ecs::tests::NonSendA from thread" )] - fn non_send_resource_scope_from_different_thread() { + fn non_send_resource_drop_from_different_thread() { let mut world = World::default(); world.insert_non_send_resource(NonSendA::default()); let thread = std::thread::spawn(move || { - // Accessing the non-send resource on a different thread + // Dropping the non-send resource on a different thread // Should result in a panic - world.resource_scope(|_: &mut World, mut value: Mut| { - value.0 += 1; - }); + drop(world); }); if let Err(err) = thread.join() { @@ -1438,6 +1434,13 @@ mod tests { } } + #[test] + fn non_send_resource_drop_from_same_thread() { + let mut world = World::default(); + world.insert_non_send_resource(NonSendA::default()); + drop(world); + } + #[test] fn insert_overwrite_drop() { let (dropck1, dropped1) = DropCk::new_pair(); diff --git a/crates/bevy_ecs/src/schedule/ambiguity_detection.rs b/crates/bevy_ecs/src/schedule/ambiguity_detection.rs index 558754c9dfcd5..5e1269309154d 100644 --- a/crates/bevy_ecs/src/schedule/ambiguity_detection.rs +++ b/crates/bevy_ecs/src/schedule/ambiguity_detection.rs @@ -335,6 +335,7 @@ mod tests { fn read_only() { let mut world = World::new(); world.insert_resource(R); + world.insert_non_send_resource(R); world.spawn(A); world.init_resource::>(); @@ -394,6 +395,7 @@ mod tests { fn nonsend() { let mut world = World::new(); world.insert_resource(R); + world.insert_non_send_resource(R); let mut test_stage = SystemStage::parallel(); test_stage @@ -497,6 +499,7 @@ mod tests { fn ignore_all_ambiguities() { let mut world = World::new(); world.insert_resource(R); + world.insert_non_send_resource(R); let mut test_stage = SystemStage::parallel(); test_stage @@ -513,6 +516,7 @@ mod tests { fn ambiguous_with_label() { let mut world = World::new(); world.insert_resource(R); + world.insert_non_send_resource(R); #[derive(SystemLabel)] struct IgnoreMe; diff --git a/crates/bevy_ecs/src/storage/mod.rs b/crates/bevy_ecs/src/storage/mod.rs index 6e848a042b492..b2fab3fdcb590 100644 --- a/crates/bevy_ecs/src/storage/mod.rs +++ b/crates/bevy_ecs/src/storage/mod.rs @@ -14,5 +14,6 @@ pub use table::*; pub struct Storages { pub sparse_sets: SparseSets, pub tables: Tables, - pub resources: Resources, + pub resources: Resources, + pub non_send_resources: Resources, } diff --git a/crates/bevy_ecs/src/storage/resource.rs b/crates/bevy_ecs/src/storage/resource.rs index 01fa742e06362..40f5554591337 100644 --- a/crates/bevy_ecs/src/storage/resource.rs +++ b/crates/bevy_ecs/src/storage/resource.rs @@ -2,19 +2,61 @@ use crate::archetype::ArchetypeComponentId; use crate::component::{ComponentId, ComponentTicks, Components, TickCells}; use crate::storage::{Column, SparseSet, TableRow}; use bevy_ptr::{OwningPtr, Ptr, UnsafeCellDeref}; +use std::{mem::ManuallyDrop, thread::ThreadId}; /// The type-erased backing storage and metadata for a single resource within a [`World`]. /// +/// If `SEND` is false, values of this type will panic if dropped from a different thread. +/// /// [`World`]: crate::world::World -pub struct ResourceData { - column: Column, +pub struct ResourceData { + column: ManuallyDrop, + type_name: String, id: ArchetypeComponentId, + origin_thread_id: Option, +} + +impl Drop for ResourceData { + fn drop(&mut self) { + if self.is_present() { + // If this thread is already panicking, panicking again will cause + // the entire process to abort. In this case we choose to avoid + // dropping or checking this altogether and just leak the column. + if std::thread::panicking() { + return; + } + self.validate_access(); + } + // SAFETY: Drop is only called once upon dropping the ResourceData + // and is inaccessible after this as the parent ResourceData has + // been dropped. The validate_access call above will check that the + // data is dropped on the thread it was inserted from. + unsafe { + ManuallyDrop::drop(&mut self.column); + } + } } -impl ResourceData { +impl ResourceData { /// The only row in the underlying column. const ROW: TableRow = TableRow::new(0); + #[inline] + fn validate_access(&self) { + if SEND { + return; + } + if self.origin_thread_id != Some(std::thread::current().id()) { + // Panic in tests, as testing for aborting is nearly impossible + panic!( + "Attempted to access or drop non-send resource {} from thread {:?} on a thread {:?}. This is not allowed. Aborting.", + self.type_name, + self.origin_thread_id, + std::thread::current().id() + ); + } + } + /// Returns true if the resource is populated. #[inline] pub fn is_present(&self) -> bool { @@ -28,9 +70,16 @@ impl ResourceData { } /// Gets a read-only pointer to the underlying resource, if available. + /// + /// # Panics + /// If `SEND` is false, this will panic if a value is present and is not accessed from the + /// original thread it was inserted from. #[inline] pub fn get_data(&self) -> Option> { - self.column.get_data(Self::ROW) + self.column.get_data(Self::ROW).map(|res| { + self.validate_access(); + res + }) } /// Gets a read-only reference to the change ticks of the underlying resource, if available. @@ -39,26 +88,35 @@ impl ResourceData { self.column.get_ticks(Self::ROW) } + /// # Panics + /// If `SEND` is false, this will panic if a value is present and is not accessed from the + /// original thread it was inserted in. #[inline] pub(crate) fn get_with_ticks(&self) -> Option<(Ptr<'_>, TickCells<'_>)> { - self.column.get(Self::ROW) + self.column.get(Self::ROW).map(|res| { + self.validate_access(); + res + }) } /// Inserts a value into the resource. If a value is already present /// it will be replaced. /// - /// # Safety - /// `value` must be valid for the underlying type for the resource. - /// - /// The underlying type must be [`Send`] or be inserted from the main thread. - /// This can be validated with [`World::validate_non_send_access_untyped`]. + /// # Panics + /// If `SEND` is false, this will panic if a value is present and is not replaced from + /// the original thread it was inserted in. /// - /// [`World::validate_non_send_access_untyped`]: crate::world::World::validate_non_send_access_untyped + /// # Safety + /// - `value` must be valid for the underlying type for the resource. #[inline] pub(crate) unsafe fn insert(&mut self, value: OwningPtr<'_>, change_tick: u32) { if self.is_present() { + self.validate_access(); self.column.replace(Self::ROW, value, change_tick); } else { + if !SEND { + self.origin_thread_id = Some(std::thread::current().id()); + } self.column.push(value, ComponentTicks::new(change_tick)); } } @@ -66,13 +124,12 @@ impl ResourceData { /// Inserts a value into the resource with a pre-existing change tick. If a /// value is already present it will be replaced. /// - /// # Safety - /// `value` must be valid for the underlying type for the resource. - /// - /// The underlying type must be [`Send`] or be inserted from the main thread. - /// This can be validated with [`World::validate_non_send_access_untyped`]. + /// # Panics + /// If `SEND` is false, this will panic if a value is present and is not replaced from + /// the original thread it was inserted in. /// - /// [`World::validate_non_send_access_untyped`]: crate::world::World::validate_non_send_access_untyped + /// # Safety + /// - `value` must be valid for the underlying type for the resource. #[inline] pub(crate) unsafe fn insert_with_ticks( &mut self, @@ -80,6 +137,7 @@ impl ResourceData { change_ticks: ComponentTicks, ) { if self.is_present() { + self.validate_access(); self.column.replace_untracked(Self::ROW, value); *self.column.get_added_ticks_unchecked(Self::ROW).deref_mut() = change_ticks.added; *self @@ -87,35 +145,41 @@ impl ResourceData { .get_changed_ticks_unchecked(Self::ROW) .deref_mut() = change_ticks.changed; } else { + if !SEND { + self.origin_thread_id = Some(std::thread::current().id()); + } self.column.push(value, change_ticks); } } /// Removes a value from the resource, if present. /// - /// # Safety - /// The underlying type must be [`Send`] or be removed from the main thread. - /// This can be validated with [`World::validate_non_send_access_untyped`]. - /// - /// The removed value must be used or dropped. - /// - /// [`World::validate_non_send_access_untyped`]: crate::world::World::validate_non_send_access_untyped + /// # Panics + /// If `SEND` is false, this will panic if a value is present and is not removed from the + /// original thread it was inserted from. #[inline] #[must_use = "The returned pointer to the removed component should be used or dropped"] - pub(crate) unsafe fn remove(&mut self) -> Option<(OwningPtr<'_>, ComponentTicks)> { - self.column.swap_remove_and_forget(Self::ROW) + pub(crate) fn remove(&mut self) -> Option<(OwningPtr<'_>, ComponentTicks)> { + if SEND { + self.column.swap_remove_and_forget(Self::ROW) + } else { + self.is_present() + .then(|| self.validate_access()) + .and_then(|_| self.column.swap_remove_and_forget(Self::ROW)) + } } /// Removes a value from the resource, if present, and drops it. /// - /// # Safety - /// The underlying type must be [`Send`] or be removed from the main thread. - /// This can be validated with [`World::validate_non_send_access_untyped`]. - /// - /// [`World::validate_non_send_access_untyped`]: crate::world::World::validate_non_send_access_untyped + /// # Panics + /// If `SEND` is false, this will panic if a value is present and is not + /// accessed from the original thread it was inserted in. #[inline] - pub(crate) unsafe fn remove_and_drop(&mut self) { - self.column.clear(); + pub(crate) fn remove_and_drop(&mut self) { + if self.is_present() { + self.validate_access(); + self.column.clear(); + } } } @@ -124,11 +188,11 @@ impl ResourceData { /// [`Resource`]: crate::system::Resource /// [`World`]: crate::world::World #[derive(Default)] -pub struct Resources { - resources: SparseSet, +pub struct Resources { + resources: SparseSet>, } -impl Resources { +impl Resources { /// The total number of resources stored in the [`World`] /// /// [`World`]: crate::world::World @@ -138,7 +202,7 @@ impl Resources { } /// Iterate over all resources that have been initialized, i.e. given a [`ComponentId`] - pub fn iter(&self) -> impl Iterator { + pub fn iter(&self) -> impl Iterator)> { self.resources.iter().map(|(id, data)| (*id, data)) } @@ -153,13 +217,13 @@ impl Resources { /// Gets read-only access to a resource, if it exists. #[inline] - pub fn get(&self, component_id: ComponentId) -> Option<&ResourceData> { + pub fn get(&self, component_id: ComponentId) -> Option<&ResourceData> { self.resources.get(component_id) } /// Gets mutable access to a resource, if it exists. #[inline] - pub(crate) fn get_mut(&mut self, component_id: ComponentId) -> Option<&mut ResourceData> { + pub(crate) fn get_mut(&mut self, component_id: ComponentId) -> Option<&mut ResourceData> { self.resources.get_mut(component_id) } @@ -167,17 +231,23 @@ impl Resources { /// /// # Panics /// Will panic if `component_id` is not valid for the provided `components` + /// If `SEND` is false, this will panic if `component_id`'s `ComponentInfo` is not registered as being `Send` + `Sync`. pub(crate) fn initialize_with( &mut self, component_id: ComponentId, components: &Components, f: impl FnOnce() -> ArchetypeComponentId, - ) -> &mut ResourceData { + ) -> &mut ResourceData { self.resources.get_or_insert_with(component_id, || { let component_info = components.get_info(component_id).unwrap(); + if SEND { + assert!(component_info.is_send_and_sync()); + } ResourceData { - column: Column::with_capacity(component_info, 1), + column: ManuallyDrop::new(Column::with_capacity(component_info, 1)), + type_name: String::from(component_info.name()), id: f(), + origin_thread_id: None, } }) } diff --git a/crates/bevy_ecs/src/system/system_param.rs b/crates/bevy_ecs/src/system/system_param.rs index 814ac6316e441..11605b6bfbcd6 100644 --- a/crates/bevy_ecs/src/system/system_param.rs +++ b/crates/bevy_ecs/src/system/system_param.rs @@ -1038,7 +1038,7 @@ unsafe impl<'a, T: 'static> SystemParam for NonSend<'a, T> { .add_unfiltered_read(component_id); let archetype_component_id = world - .get_resource_archetype_component_id(component_id) + .get_non_send_archetype_component_id(component_id) .unwrap(); system_meta .archetype_component_access @@ -1054,9 +1054,8 @@ unsafe impl<'a, T: 'static> SystemParam for NonSend<'a, T> { world: &'w World, change_tick: u32, ) -> Self::Item<'w, 's> { - world.validate_non_send_access::(); let (ptr, ticks) = world - .get_resource_with_ticks(component_id) + .get_non_send_with_ticks(component_id) .unwrap_or_else(|| { panic!( "Non-send resource requested by {} does not exist: {}", @@ -1090,9 +1089,8 @@ unsafe impl SystemParam for Option> { world: &'w World, change_tick: u32, ) -> Self::Item<'w, 's> { - world.validate_non_send_access::(); world - .get_resource_with_ticks(component_id) + .get_non_send_with_ticks(component_id) .map(|(ptr, ticks)| NonSend { value: ptr.deref(), ticks: ticks.read(), @@ -1130,7 +1128,7 @@ unsafe impl<'a, T: 'static> SystemParam for NonSendMut<'a, T> { .add_unfiltered_write(component_id); let archetype_component_id = world - .get_resource_archetype_component_id(component_id) + .get_non_send_archetype_component_id(component_id) .unwrap(); system_meta .archetype_component_access @@ -1146,9 +1144,8 @@ unsafe impl<'a, T: 'static> SystemParam for NonSendMut<'a, T> { world: &'w World, change_tick: u32, ) -> Self::Item<'w, 's> { - world.validate_non_send_access::(); let (ptr, ticks) = world - .get_resource_with_ticks(component_id) + .get_non_send_with_ticks(component_id) .unwrap_or_else(|| { panic!( "Non-send resource requested by {} does not exist: {}", @@ -1179,9 +1176,8 @@ unsafe impl<'a, T: 'static> SystemParam for Option> { world: &'w World, change_tick: u32, ) -> Self::Item<'w, 's> { - world.validate_non_send_access::(); world - .get_resource_with_ticks(component_id) + .get_non_send_with_ticks(component_id) .map(|(ptr, ticks)| NonSendMut { value: ptr.assert_unique().deref_mut(), ticks: Ticks::from_tick_cells(ticks, system_meta.last_change_tick, change_tick), diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index dd754cfa13307..0cdca5826a205 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -16,6 +16,7 @@ use crate::{ }, entity::{AllocAtWithoutReplacement, Entities, Entity, EntityLocation}, event::{Event, Events}, + ptr::UnsafeCellDeref, query::{QueryState, ReadOnlyWorldQuery, WorldQuery}, storage::{ResourceData, SparseSet, Storages}, system::Resource, @@ -60,7 +61,6 @@ pub struct World { pub(crate) removed_components: SparseSet>, /// Access cache used by [WorldCell]. pub(crate) archetype_component_access: ArchetypeComponentAccess, - main_thread_validator: MainThreadValidator, pub(crate) change_tick: AtomicU32, pub(crate) last_change_tick: u32, } @@ -76,7 +76,6 @@ impl Default for World { bundles: Default::default(), removed_components: Default::default(), archetype_component_access: Default::default(), - main_thread_validator: Default::default(), // Default value is `1`, and `last_change_tick`s default to `0`, such that changes // are detected on first system runs and for direct world queries. change_tick: AtomicU32::new(1), @@ -803,7 +802,7 @@ impl World { /// Panics if called from a thread other than the main thread. #[inline] pub fn init_non_send_resource(&mut self) { - if !self.contains_resource::() { + if !self.contains_non_send::() { let resource = R::from_world(self); self.insert_non_send_resource(resource); } @@ -816,16 +815,15 @@ impl World { /// Systems with `NonSend` resources are always scheduled on the main thread. /// /// # Panics - /// - /// Panics if called from a thread other than the main thread. + /// If a value is already present, this function will panic if called + /// from a different thread than where the original value was inserted from. #[inline] pub fn insert_non_send_resource(&mut self, value: R) { - self.validate_non_send_access::(); let component_id = self.components.init_non_send::(); OwningPtr::make(value, |ptr| { // SAFETY: component_id was just initialized and corresponds to resource of type R unsafe { - self.insert_resource_by_id(component_id, ptr); + self.insert_non_send_by_id(component_id, ptr); } }); } @@ -833,37 +831,51 @@ impl World { /// Removes the resource of a given type and returns it, if it exists. Otherwise returns [None]. #[inline] pub fn remove_resource(&mut self) -> Option { - // SAFETY: R is Send + Sync - unsafe { self.remove_resource_unchecked() } + let component_id = self.components.get_resource_id(TypeId::of::())?; + let (ptr, _) = self.storages.resources.get_mut(component_id)?.remove()?; + // SAFETY: `component_id` was gotten via looking up the `R` type + unsafe { Some(ptr.read::()) } } + /// Removes a `!Send` resource from the world and returns it, if present. + /// + /// `NonSend` resources cannot be sent across threads, + /// and do not need the `Send + Sync` bounds. + /// Systems with `NonSend` resources are always scheduled on the main thread. + /// + /// Returns `None` if a value was not previously present. + /// + /// # Panics + /// If a value is present, this function will panic if called from a different + /// thread than where the value was inserted from. #[inline] pub fn remove_non_send_resource(&mut self) -> Option { - self.validate_non_send_access::(); - // SAFETY: we are on main thread - unsafe { self.remove_resource_unchecked() } + let component_id = self.components.get_resource_id(TypeId::of::())?; + let (ptr, _) = self + .storages + .non_send_resources + .get_mut(component_id)? + .remove()?; + // SAFETY: `component_id` was gotten via looking up the `R` type + unsafe { Some(ptr.read::()) } } + /// Returns `true` if a resource of type `R` exists. Otherwise returns `false`. #[inline] - /// # Safety - /// Only remove `NonSend` resources from the main thread - /// as they cannot be sent across threads - #[allow(unused_unsafe)] - pub unsafe fn remove_resource_unchecked(&mut self) -> Option { - let component_id = self.components.get_resource_id(TypeId::of::())?; - // SAFETY: the resource is of type R and the value is returned back to the caller. - unsafe { - let (ptr, _) = self.storages.resources.get_mut(component_id)?.remove()?; - Some(ptr.read::()) - } + pub fn contains_resource(&self) -> bool { + self.components + .get_resource_id(TypeId::of::()) + .and_then(|component_id| self.storages.resources.get(component_id)) + .map(|info| info.is_present()) + .unwrap_or(false) } /// Returns `true` if a resource of type `R` exists. Otherwise returns `false`. #[inline] - pub fn contains_resource(&self) -> bool { + pub fn contains_non_send(&self) -> bool { self.components .get_resource_id(TypeId::of::()) - .and_then(|component_id| self.storages.resources.get(component_id)) + .and_then(|component_id| self.storages.non_send_resources.get(component_id)) .map(|info| info.is_present()) .unwrap_or(false) } @@ -979,6 +991,8 @@ impl World { /// /// Panics if the resource does not exist. /// Use [`get_non_send_resource`](World::get_non_send_resource) instead if you want to handle this case. + /// + /// This function will panic if it isn't called from the same thread that the resource was inserted from. #[inline] #[track_caller] pub fn non_send_resource(&self) -> &R { @@ -999,6 +1013,8 @@ impl World { /// /// Panics if the resource does not exist. /// Use [`get_non_send_resource_mut`](World::get_non_send_resource_mut) instead if you want to handle this case. + /// + /// This function will panic if it isn't called from the same thread that the resource was inserted from. #[inline] #[track_caller] pub fn non_send_resource_mut(&mut self) -> Mut<'_, R> { @@ -1014,7 +1030,10 @@ impl World { } /// Gets a reference to the non-send resource of the given type, if it exists. - /// Otherwise returns [None] + /// Otherwise returns [None]. + /// + /// # Panics + /// This function will panic if it isn't called from the same thread that the resource was inserted from. #[inline] pub fn get_non_send_resource(&self) -> Option<&R> { let component_id = self.components.get_resource_id(TypeId::of::())?; @@ -1024,6 +1043,9 @@ impl World { /// Gets a mutable reference to the non-send resource of the given type, if it exists. /// Otherwise returns [None] + /// + /// # Panics + /// This function will panic if it isn't called from the same thread that the resource was inserted from. #[inline] pub fn get_non_send_resource_mut(&mut self) -> Option> { // SAFETY: unique world access @@ -1033,6 +1055,9 @@ impl World { /// Gets a mutable reference to the non-send resource of the given type, if it exists. /// Otherwise returns [None] /// + /// # Panics + /// This function will panic if it isn't called from the same thread that the resource was inserted from. + /// /// # Safety /// This will allow aliased mutable access to the given non-send resource type. The caller must /// ensure that there is either only one mutable access or multiple immutable accesses at a time. @@ -1051,6 +1076,21 @@ impl World { self.storages.resources.get(component_id)?.get_with_ticks() } + // Shorthand helper function for getting the data and change ticks for a resource. + /// + /// # Panics + /// This function will panic if it isn't called from the same thread that the resource was inserted from. + #[inline] + pub(crate) fn get_non_send_with_ticks( + &self, + component_id: ComponentId, + ) -> Option<(Ptr<'_>, TickCells<'_>)> { + self.storages + .non_send_resources + .get(component_id)? + .get_with_ticks() + } + // Shorthand helper function for getting the [`ArchetypeComponentId`] for a resource. #[inline] pub(crate) fn get_resource_archetype_component_id( @@ -1061,6 +1101,16 @@ impl World { Some(resource.id()) } + // Shorthand helper function for getting the [`ArchetypeComponentId`] for a resource. + #[inline] + pub(crate) fn get_non_send_archetype_component_id( + &self, + component_id: ComponentId, + ) -> Option { + let resource = self.storages.non_send_resources.get(component_id)?; + Some(resource.id()) + } + /// For a given batch of ([Entity], [Bundle]) pairs, either spawns each [Entity] with the given /// bundle (if the entity does not exist), or inserts the [Bundle] (if the entity already exists). /// This is faster than doing equivalent operations one-by-one. @@ -1207,13 +1257,7 @@ impl World { /// }); /// assert_eq!(world.get_resource::().unwrap().0, 2); /// ``` - pub fn resource_scope< - R: 'static, /* The resource doesn't need to be Send nor Sync. */ - U, - >( - &mut self, - f: impl FnOnce(&mut World, Mut) -> U, - ) -> U { + pub fn resource_scope(&mut self, f: impl FnOnce(&mut World, Mut) -> U) -> U { let last_change_tick = self.last_change_tick(); let change_tick = self.change_tick(); @@ -1222,17 +1266,11 @@ impl World { .get_resource_id(TypeId::of::()) .unwrap_or_else(|| panic!("resource does not exist: {}", std::any::type_name::())); // If the resource isn't send and sync, validate that we are on the main thread, so that we can access it. - let component_info = self.components().get_info(component_id).unwrap(); - if !component_info.is_send_and_sync() { - self.validate_non_send_access::(); - } - let (ptr, mut ticks) = self .storages .resources .get_mut(component_id) - // SAFETY: The type R is Send and Sync or we've already validated that we're on the main thread. - .and_then(|info| unsafe { info.remove() }) + .and_then(|info| info.remove()) .unwrap_or_else(|| panic!("resource does not exist: {}", std::any::type_name::())); // Read the value onto the stack to avoid potential mut aliasing. // SAFETY: pointer is of type R @@ -1331,8 +1369,13 @@ impl World { &self, component_id: ComponentId, ) -> Option<&R> { - self.validate_non_send_access::(); - self.get_resource_with_id(component_id) + Some( + self.storages + .non_send_resources + .get(component_id)? + .get_data()? + .deref::(), + ) } /// # Safety @@ -1343,8 +1386,20 @@ impl World { &self, component_id: ComponentId, ) -> Option> { - self.validate_non_send_access::(); - self.get_resource_unchecked_mut_with_id(component_id) + let (ptr, ticks) = self + .storages + .non_send_resources + .get(component_id)? + .get_with_ticks()?; + Some(Mut { + value: ptr.assert_unique().deref_mut(), + ticks: Ticks { + added: ticks.added.deref_mut(), + changed: ticks.changed.deref_mut(), + last_change_tick: self.last_change_tick(), + change_tick: self.read_change_tick(), + }, + }) } /// Inserts a new resource with the given `value`. Will replace the value if it already existed. @@ -1353,8 +1408,7 @@ impl World { /// use this in cases where the actual types are not known at compile time.** /// /// # Safety - /// The value referenced by `value` must be valid for the given [`ComponentId`] of this world - /// `component_id` must exist in this [`World`] + /// The value referenced by `value` must be valid for the given [`ComponentId`] of this world. #[inline] pub unsafe fn insert_resource_by_id( &mut self, @@ -1363,18 +1417,43 @@ impl World { ) { let change_tick = self.change_tick(); - // SAFETY: component_id is valid, ensured by caller + // SAFETY: value is valid for component_id, ensured by caller self.initialize_resource_internal(component_id) .insert(value, change_tick); } + /// Inserts a new `!Send` resource with the given `value`. Will replace the value if it already + /// existed. + /// + /// **You should prefer to use the typed API [`World::insert_non_send_resource`] where possible and only + /// use this in cases where the actual types are not known at compile time.** + /// + /// # Panics + /// If a value is already present, this function will panic if not called from the same + /// thread that the original value was inserted from. + /// /// # Safety - /// `component_id` must be valid for this world + /// The value referenced by `value` must be valid for the given [`ComponentId`] of this world. #[inline] - unsafe fn initialize_resource_internal( + pub unsafe fn insert_non_send_by_id( &mut self, component_id: ComponentId, - ) -> &mut ResourceData { + value: OwningPtr<'_>, + ) { + let change_tick = self.change_tick(); + + // SAFETY: value is valid for component_id, ensured by caller + self.initialize_non_send_internal(component_id) + .insert(value, change_tick); + } + + /// # Panics + /// Panics if `component_id` is not registered as a `Send` component type in this `World` + #[inline] + fn initialize_resource_internal( + &mut self, + component_id: ComponentId, + ) -> &mut ResourceData { let archetype_component_count = &mut self.archetypes.archetype_component_count; self.storages .resources @@ -1385,36 +1464,35 @@ impl World { }) } + /// # Panics + /// panics if `component_id` is not registered in this world + #[inline] + fn initialize_non_send_internal( + &mut self, + component_id: ComponentId, + ) -> &mut ResourceData { + let archetype_component_count = &mut self.archetypes.archetype_component_count; + self.storages + .non_send_resources + .initialize_with(component_id, &self.components, || { + let id = ArchetypeComponentId::new(*archetype_component_count); + *archetype_component_count += 1; + id + }) + } + pub(crate) fn initialize_resource(&mut self) -> ComponentId { let component_id = self.components.init_resource::(); - // SAFETY: resource initialized above - unsafe { self.initialize_resource_internal(component_id) }; + self.initialize_resource_internal(component_id); component_id } pub(crate) fn initialize_non_send_resource(&mut self) -> ComponentId { let component_id = self.components.init_non_send::(); - // SAFETY: resource initialized above - unsafe { self.initialize_resource_internal(component_id) }; + self.initialize_non_send_internal(component_id); component_id } - pub(crate) fn validate_non_send_access(&self) { - assert!( - self.main_thread_validator.is_main_thread(), - "attempted to access NonSend resource {} off of the main thread", - std::any::type_name::(), - ); - } - - pub(crate) fn validate_non_send_access_untyped(&self, name: &str) { - assert!( - self.main_thread_validator.is_main_thread(), - "attempted to access NonSend resource {} off of the main thread", - name - ); - } - /// Empties queued entities and adds them to the empty [Archetype](crate::archetype::Archetype). /// This should be called before doing operations that might operate on queued entities, /// such as inserting a [Component]. @@ -1464,9 +1542,16 @@ impl World { // Iterate over all component change ticks, clamping their age to max age // PERF: parallelize let change_tick = self.change_tick(); - self.storages.tables.check_change_ticks(change_tick); - self.storages.sparse_sets.check_change_ticks(change_tick); - self.storages.resources.check_change_ticks(change_tick); + let Storages { + ref mut tables, + ref mut sparse_sets, + ref mut resources, + ref mut non_send_resources, + } = self.storages; + tables.check_change_ticks(change_tick); + sparse_sets.check_change_ticks(change_tick); + resources.check_change_ticks(change_tick); + non_send_resources.check_change_ticks(change_tick); } pub fn clear_entities(&mut self) { @@ -1486,10 +1571,6 @@ impl World { /// use this in cases where the actual types are not known at compile time.** #[inline] pub fn get_resource_by_id(&self, component_id: ComponentId) -> Option> { - let info = self.components.get_info(component_id)?; - if !info.is_send_and_sync() { - self.validate_non_send_access_untyped(info.name()); - } self.storages.resources.get(component_id)?.get_data() } @@ -1501,13 +1582,7 @@ impl World { /// use this in cases where the actual types are not known at compile time.** #[inline] pub fn get_resource_mut_by_id(&mut self, component_id: ComponentId) -> Option> { - let info = self.components.get_info(component_id)?; - if !info.is_send_and_sync() { - self.validate_non_send_access_untyped(info.name()); - } - let change_tick = self.change_tick(); - let (ptr, ticks) = self.get_resource_with_ticks(component_id)?; // SAFETY: This function has exclusive access to the world so nothing aliases `ticks`. @@ -1522,22 +1597,73 @@ impl World { }) } + /// Gets a `!Send` resource to the resource with the id [`ComponentId`] if it exists. + /// The returned pointer must not be used to modify the resource, and must not be + /// dereferenced after the immutable borrow of the [`World`] ends. + /// + /// **You should prefer to use the typed API [`World::get_resource`] where possible and only + /// use this in cases where the actual types are not known at compile time.** + /// + /// # Panics + /// This function will panic if it isn't called from the same thread that the resource was inserted from. + #[inline] + pub fn get_non_send_by_id(&self, component_id: ComponentId) -> Option> { + self.storages + .non_send_resources + .get(component_id)? + .get_data() + } + + /// Gets a `!Send` resource to the resource with the id [`ComponentId`] if it exists. + /// The returned pointer may be used to modify the resource, as long as the mutable borrow + /// of the [`World`] is still valid. + /// + /// **You should prefer to use the typed API [`World::get_resource_mut`] where possible and only + /// use this in cases where the actual types are not known at compile time.** + /// + /// # Panics + /// This function will panic if it isn't called from the same thread that the resource was inserted from. + #[inline] + pub fn get_non_send_mut_by_id(&mut self, component_id: ComponentId) -> Option> { + let change_tick = self.change_tick(); + let (ptr, ticks) = self.get_non_send_with_ticks(component_id)?; + + // SAFETY: This function has exclusive access to the world so nothing aliases `ticks`. + // - index is in-bounds because the column is initialized and non-empty + // - no other reference to the ticks of the same row can exist at the same time + let ticks = unsafe { Ticks::from_tick_cells(ticks, self.last_change_tick(), change_tick) }; + + Some(MutUntyped { + // SAFETY: This function has exclusive access to the world so nothing aliases `ptr`. + value: unsafe { ptr.assert_unique() }, + ticks, + }) + } + /// Removes the resource of a given type, if it exists. Otherwise returns [None]. /// /// **You should prefer to use the typed API [`World::remove_resource`] where possible and only /// use this in cases where the actual types are not known at compile time.** pub fn remove_resource_by_id(&mut self, component_id: ComponentId) -> Option<()> { - let info = self.components.get_info(component_id)?; - if !info.is_send_and_sync() { - self.validate_non_send_access_untyped(info.name()); - } - // SAFETY: The underlying type is Send and Sync or we've already validated we're on the main thread - unsafe { - self.storages - .resources - .get_mut(component_id)? - .remove_and_drop(); - } + self.storages + .resources + .get_mut(component_id)? + .remove_and_drop(); + Some(()) + } + + /// Removes the resource of a given type, if it exists. Otherwise returns [None]. + /// + /// **You should prefer to use the typed API [`World::remove_resource`] where possible and only + /// use this in cases where the actual types are not known at compile time.** + /// + /// # Panics + /// This function will panic if it isn't called from the same thread that the resource was inserted from. + pub fn remove_non_send_by_id(&mut self, component_id: ComponentId) -> Option<()> { + self.storages + .non_send_resources + .get_mut(component_id)? + .remove_and_drop(); Some(()) } @@ -1546,6 +1672,9 @@ impl World { /// /// **You should prefer to use the typed API [`World::get_mut`] where possible and only /// use this in cases where the actual types are not known at compile time.** + /// + /// # Panics + /// This function will panic if it isn't called from the same thread that the resource was inserted from. #[inline] pub fn get_by_id(&self, entity: Entity, component_id: ComponentId) -> Option> { let info = self.components().get_info(component_id)?; @@ -1622,24 +1751,6 @@ impl FromWorld for T { } } -struct MainThreadValidator { - main_thread: std::thread::ThreadId, -} - -impl MainThreadValidator { - fn is_main_thread(&self) -> bool { - self.main_thread == std::thread::current().id() - } -} - -impl Default for MainThreadValidator { - fn default() -> Self { - Self { - main_thread: std::thread::current().id(), - } - } -} - #[cfg(test)] mod tests { use super::World; diff --git a/crates/bevy_ecs/src/world/world_cell.rs b/crates/bevy_ecs/src/world/world_cell.rs index 5f0fb5aaa5a74..8e6699e076694 100644 --- a/crates/bevy_ecs/src/world/world_cell.rs +++ b/crates/bevy_ecs/src/world/world_cell.rs @@ -256,7 +256,7 @@ impl<'w> WorldCell<'w> { let component_id = self.world.components.get_resource_id(TypeId::of::())?; let archetype_component_id = self .world - .get_resource_archetype_component_id(component_id)?; + .get_non_send_archetype_component_id(component_id)?; WorldBorrow::try_new( // SAFETY: ComponentId matches TypeId || unsafe { self.world.get_non_send_with_id(component_id) }, @@ -289,7 +289,7 @@ impl<'w> WorldCell<'w> { let component_id = self.world.components.get_resource_id(TypeId::of::())?; let archetype_component_id = self .world - .get_resource_archetype_component_id(component_id)?; + .get_non_send_archetype_component_id(component_id)?; WorldBorrowMut::try_new( // SAFETY: ComponentId matches TypeId and access is checked by WorldBorrowMut || unsafe { self.world.get_non_send_unchecked_mut_with_id(component_id) }, diff --git a/crates/bevy_render/src/view/window.rs b/crates/bevy_render/src/view/window.rs index 8e6f82c66c911..c5b0499b08534 100644 --- a/crates/bevy_render/src/view/window.rs +++ b/crates/bevy_render/src/view/window.rs @@ -29,7 +29,7 @@ impl Plugin for WindowRenderPlugin { render_app .init_resource::() .init_resource::() - .init_resource::() + .init_non_send_resource::() .add_system_to_stage(RenderStage::Extract, extract_windows) .add_system_to_stage( RenderStage::Prepare, From a151fbff5c19b91f5a079433574d2e28746a8eb8 Mon Sep 17 00:00:00 2001 From: JoJoJet <21144246+JoJoJet@users.noreply.github.com> Date: Mon, 9 Jan 2023 20:56:06 +0000 Subject: [PATCH 53/60] Relax `Sync` bound on `Local as ExclusiveSystemParam` (#7040) # Objective The type `Local` unnecessarily has the bound `T: Sync` when the local is used in an exclusive system. ## Solution Lift the bound. --- ## Changelog Removed the bound `T: Sync` from `Local` when used as an `ExclusiveSystemParam`. --- crates/bevy_ecs/src/system/exclusive_system_param.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_ecs/src/system/exclusive_system_param.rs b/crates/bevy_ecs/src/system/exclusive_system_param.rs index 4b6342660439e..7f5b25f77fba3 100644 --- a/crates/bevy_ecs/src/system/exclusive_system_param.rs +++ b/crates/bevy_ecs/src/system/exclusive_system_param.rs @@ -48,7 +48,7 @@ impl<'a, P: SystemParam + 'static> ExclusiveSystemParam for &'a mut SystemState< } } -impl<'_s, T: FromWorld + Send + Sync + 'static> ExclusiveSystemParam for Local<'_s, T> { +impl<'_s, T: FromWorld + Send + 'static> ExclusiveSystemParam for Local<'_s, T> { type State = SyncCell; type Item<'s> = Local<'s, T>; From 1ad542a21d7ab0c612b36b26113557ef74659427 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois?= Date: Mon, 9 Jan 2023 21:19:48 +0000 Subject: [PATCH 54/60] add rust-version for MSRV and CI job to check (#6852) # Objective - Fixes #6777, fixes #2998, replaces #5518 - Help avoid confusing error message when using an older version of Rust ## Solution - Add the `rust-version` field to `Cargo.toml` - Add a CI job checking the MSRV - Add the job to bors --- .github/bors.toml | 1 + .github/workflows/ci.yml | 26 ++++++++++++++++++++++++++ Cargo.toml | 1 + 3 files changed, 28 insertions(+) diff --git a/.github/bors.toml b/.github/bors.toml index 7c237230f15ca..087a395e95150 100644 --- a/.github/bors.toml +++ b/.github/bors.toml @@ -17,6 +17,7 @@ status = [ "build-without-default-features (bevy)", "build-without-default-features (bevy_ecs)", "build-without-default-features (bevy_reflect)", + "msrv", ] use_squash_merge = true diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c5b282b74a27c..eafdcfebbde87 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -275,3 +275,29 @@ jobs: run: sudo apt-get update; sudo apt-get install --no-install-recommends libasound2-dev libudev-dev - name: Run cargo udeps run: cargo udeps + + msrv: + runs-on: ubuntu-latest + needs: build + steps: + - uses: actions/checkout@v3 + - uses: actions/cache@v3 + with: + path: | + ~/.cargo/bin/ + ~/.cargo/registry/index/ + ~/.cargo/registry/cache/ + ~/.cargo/git/db/ + target/ + key: ${{ runner.os }}-cargo-msrv-${{ hashFiles('**/Cargo.toml') }} + - name: get MSRV + run: | + msrv=`cargo metadata --no-deps --format-version 1 | jq --raw-output '.packages[] | select(.name=="bevy") | .rust_version'` + echo "MSRV=$msrv" >> $GITHUB_ENV + - uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{ env.MSRV }} + - name: Install alsa and udev + run: sudo apt-get update; sudo apt-get install --no-install-recommends libasound2-dev libudev-dev + - name: Run cargo check + run: cargo check diff --git a/Cargo.toml b/Cargo.toml index 2cd39656e74a1..d6e9a2e9bea69 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,7 @@ keywords = ["game", "engine", "gamedev", "graphics", "bevy"] license = "MIT OR Apache-2.0" readme = "README.md" repository = "https://github.com/bevyengine/bevy" +rust-version = "1.66.0" [workspace] exclude = ["benches", "crates/bevy_ecs_compile_fail_tests", "crates/bevy_reflect_compile_fail_tests"] From dc857c08b1c31b7b45e1e1a8cae7ef9a314b8bf0 Mon Sep 17 00:00:00 2001 From: 2ne1ugly <47616772+2ne1ugly@users.noreply.github.com> Date: Mon, 9 Jan 2023 21:43:27 +0000 Subject: [PATCH 55/60] Implement `SparseSetIndex` for `WorldId` (#7125) # Objective - Fixes #7124 ## Solution - Add Hash Derive on `WorldId` - Add `SparseSetIndex` impl --- crates/bevy_ecs/src/world/identifier.rs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/crates/bevy_ecs/src/world/identifier.rs b/crates/bevy_ecs/src/world/identifier.rs index 6e2b8efc0f222..c65da1b81a7e1 100644 --- a/crates/bevy_ecs/src/world/identifier.rs +++ b/crates/bevy_ecs/src/world/identifier.rs @@ -1,6 +1,7 @@ +use crate::storage::SparseSetIndex; use std::sync::atomic::{AtomicUsize, Ordering}; -#[derive(Copy, Clone, PartialEq, Eq, Debug)] +#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)] // We use usize here because that is the largest `Atomic` we want to require /// A unique identifier for a [`super::World`]. // Note that this *is* used by external crates as well as for internal safety checks @@ -26,6 +27,17 @@ impl WorldId { } } +impl SparseSetIndex for WorldId { + #[inline] + fn sparse_set_index(&self) -> usize { + self.0 + } + + fn get_sparse_set_index(value: usize) -> Self { + Self(value) + } +} + #[cfg(test)] mod tests { use super::*; From 6c4acf9e8bbf5cca5c1bcdaac991e9bcb9c67b17 Mon Sep 17 00:00:00 2001 From: 2ne1ugly <47616772+2ne1ugly@users.noreply.github.com> Date: Mon, 9 Jan 2023 21:43:29 +0000 Subject: [PATCH 56/60] Fix doc in `App::add_sub_app` (#7139) # Objective - Fix the name of function parameter name in docs ## Solution - Change `f` to `sub_app_runner` --- It confused me a bit when I was reading the docs in the autocomplete hint. Hesitated about filing a PR since it's just a one single word change in the comment. Is this the right process to change these docs? --- crates/bevy_app/src/app.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_app/src/app.rs b/crates/bevy_app/src/app.rs index 11d37d8cbe23e..d55cdca7eb61a 100644 --- a/crates/bevy_app/src/app.rs +++ b/crates/bevy_app/src/app.rs @@ -1018,7 +1018,7 @@ impl App { /// Adds an [`App`] as a child of the current one. /// - /// The provided function `f` is called by the [`update`](Self::update) method. The [`World`] + /// The provided function `sub_app_runner` is called by the [`update`](Self::update) method. The [`World`] /// parameter represents the main app world, while the [`App`] parameter is just a mutable /// reference to the `SubApp` itself. pub fn add_sub_app( From 1246fdfc6d440d866868cec9646d893a4f534df0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois?= Date: Mon, 9 Jan 2023 21:43:30 +0000 Subject: [PATCH 57/60] Fix overflow scaling for images (#7142) # Objective - Fixes #4057 - Do not multiply position by scale factor --- crates/bevy_ui/src/render/mod.rs | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/crates/bevy_ui/src/render/mod.rs b/crates/bevy_ui/src/render/mod.rs index 595896c1fdfec..0a6d93285ac4e 100644 --- a/crates/bevy_ui/src/render/mod.rs +++ b/crates/bevy_ui/src/render/mod.rs @@ -176,7 +176,6 @@ pub struct ExtractedUiNode { pub clip: Option, pub flip_x: bool, pub flip_y: bool, - pub scale_factor: f32, } #[derive(Resource, Default)] @@ -188,7 +187,6 @@ pub fn extract_uinodes( mut extracted_uinodes: ResMut, images: Extract>>, ui_stack: Extract>, - windows: Extract>, uinode_query: Extract< Query<( &Node, @@ -200,7 +198,6 @@ pub fn extract_uinodes( )>, >, ) { - let scale_factor = windows.scale_factor(WindowId::primary()) as f32; extracted_uinodes.uinodes.clear(); for (stack_index, entity) in ui_stack.uinodes.iter().enumerate() { if let Ok((uinode, transform, color, maybe_image, visibility, clip)) = @@ -236,7 +233,6 @@ pub fn extract_uinodes( clip: clip.map(|clip| clip.clip), flip_x, flip_y, - scale_factor, }); } } @@ -364,7 +360,6 @@ pub fn extract_text_uinodes( clip: clip.map(|clip| clip.clip), flip_x: false, flip_y: false, - scale_factor, }); } } @@ -500,20 +495,20 @@ pub fn prepare_uinodes( let atlas_extent = extracted_uinode.atlas_size.unwrap_or(uinode_rect.max); let mut uvs = [ Vec2::new( - uinode_rect.min.x + positions_diff[0].x * extracted_uinode.scale_factor, - uinode_rect.min.y + positions_diff[0].y * extracted_uinode.scale_factor, + uinode_rect.min.x + positions_diff[0].x, + uinode_rect.min.y + positions_diff[0].y, ), Vec2::new( - uinode_rect.max.x + positions_diff[1].x * extracted_uinode.scale_factor, - uinode_rect.min.y + positions_diff[1].y * extracted_uinode.scale_factor, + uinode_rect.max.x + positions_diff[1].x, + uinode_rect.min.y + positions_diff[1].y, ), Vec2::new( - uinode_rect.max.x + positions_diff[2].x * extracted_uinode.scale_factor, - uinode_rect.max.y + positions_diff[2].y * extracted_uinode.scale_factor, + uinode_rect.max.x + positions_diff[2].x, + uinode_rect.max.y + positions_diff[2].y, ), Vec2::new( - uinode_rect.min.x + positions_diff[3].x * extracted_uinode.scale_factor, - uinode_rect.max.y + positions_diff[3].y * extracted_uinode.scale_factor, + uinode_rect.min.x + positions_diff[3].x, + uinode_rect.max.y + positions_diff[3].y, ), ] .map(|pos| pos / atlas_extent); From 5cda42682aa650b59f8eeccc46ad295a3b1b73bb Mon Sep 17 00:00:00 2001 From: Giacomo Stevanato Date: Mon, 9 Jan 2023 21:57:14 +0000 Subject: [PATCH 58/60] Add `TypeRegistrationDeserializer` and remove `BorrowedStr` (#7094) # Objective This a follow-up to #6894, see https://github.com/bevyengine/bevy/pull/6894#discussion_r1045203113 The goal is to avoid cloning any string when getting a `&TypeRegistration` corresponding to a string which is being deserialized. As a bonus code duplication is also reduced. ## Solution The manual deserialization of a string and lookup into the type registry has been moved into a separate `TypeRegistrationDeserializer` type, which implements `DeserializeSeed` with a `Visitor` that accepts any string with `visit_str`, even ones that may not live longer than that function call. `BorrowedStr` has been removed since it's no longer used. --- ## Changelog - The type `TypeRegistrationDeserializer` has been added, which simplifies getting a `&TypeRegistration` while deserializing a string. --- crates/bevy_reflect/Cargo.toml | 2 +- crates/bevy_reflect/src/serde/de.rs | 67 ++++++++++++++++++++++------- crates/bevy_scene/src/serde.rs | 28 ++++++------ 3 files changed, 64 insertions(+), 33 deletions(-) diff --git a/crates/bevy_reflect/Cargo.toml b/crates/bevy_reflect/Cargo.toml index c7d54e9964837..f74889c5ab06b 100644 --- a/crates/bevy_reflect/Cargo.toml +++ b/crates/bevy_reflect/Cargo.toml @@ -29,7 +29,7 @@ downcast-rs = "1.2" parking_lot = "0.12.1" thiserror = "1.0" once_cell = "1.11" -serde = { version = "1", features = ["derive"] } +serde = "1" smallvec = { version = "1.6", features = ["serde", "union", "const_generics"], optional = true } glam = { version = "0.22", features = ["serde"], optional = true } diff --git a/crates/bevy_reflect/src/serde/de.rs b/crates/bevy_reflect/src/serde/de.rs index 91021d33d68b8..799ccf33550e2 100644 --- a/crates/bevy_reflect/src/serde/de.rs +++ b/crates/bevy_reflect/src/serde/de.rs @@ -12,7 +12,6 @@ use serde::de::{ }; use serde::Deserialize; use std::any::TypeId; -use std::borrow::Cow; use std::fmt; use std::fmt::{Debug, Display, Formatter}; use std::slice::Iter; @@ -211,13 +210,6 @@ impl<'de> Visitor<'de> for U32Visitor { } } -/// Helper struct for deserializing strings without allocating (when possible). -/// -/// Based on [this comment](https://github.com/bevyengine/bevy/pull/6894#discussion_r1045069010). -#[derive(Deserialize)] -#[serde(transparent)] -struct BorrowableCowStr<'a>(#[serde(borrow)] Cow<'a, str>); - /// A general purpose deserializer for reflected types. /// /// This will return a [`Box`] containing the deserialized data. @@ -265,6 +257,54 @@ impl<'a, 'de> DeserializeSeed<'de> for UntypedReflectDeserializer<'a> { } } +/// A deserializer for type registrations. +/// +/// This will return a [`&TypeRegistration`] corresponding to the given type. +/// This deserializer expects a string containing the _full_ [type name] of the +/// type to find the `TypeRegistration` of. +/// +/// [`&TypeRegistration`]: crate::TypeRegistration +/// [type name]: std::any::type_name +pub struct TypeRegistrationDeserializer<'a> { + registry: &'a TypeRegistry, +} + +impl<'a> TypeRegistrationDeserializer<'a> { + pub fn new(registry: &'a TypeRegistry) -> Self { + Self { registry } + } +} + +impl<'a, 'de> DeserializeSeed<'de> for TypeRegistrationDeserializer<'a> { + type Value = &'a TypeRegistration; + + fn deserialize(self, deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + struct TypeRegistrationVisitor<'a>(&'a TypeRegistry); + + impl<'de, 'a> Visitor<'de> for TypeRegistrationVisitor<'a> { + type Value = &'a TypeRegistration; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("string containing `type` entry for the reflected value") + } + + fn visit_str(self, type_name: &str) -> Result + where + E: Error, + { + self.0.get_with_name(type_name).ok_or_else(|| { + Error::custom(format_args!("No registration found for `{type_name}`")) + }) + } + } + + deserializer.deserialize_str(TypeRegistrationVisitor(self.registry)) + } +} + struct UntypedReflectDeserializerVisitor<'a> { registry: &'a TypeRegistry, } @@ -280,14 +320,9 @@ impl<'a, 'de> Visitor<'de> for UntypedReflectDeserializerVisitor<'a> { where A: MapAccess<'de>, { - let type_name = map - .next_key::()? - .ok_or_else(|| Error::invalid_length(0, &"at least one entry"))? - .0; - - let registration = self.registry.get_with_name(&type_name).ok_or_else(|| { - Error::custom(format_args!("No registration found for `{type_name}`")) - })?; + let registration = map + .next_key_seed(TypeRegistrationDeserializer::new(self.registry))? + .ok_or_else(|| Error::invalid_length(0, &"at least one entry"))?; let value = map.next_value_seed(TypedReflectDeserializer { registration, registry: self.registry, diff --git a/crates/bevy_scene/src/serde.rs b/crates/bevy_scene/src/serde.rs index e1896d988c206..873015105d1f3 100644 --- a/crates/bevy_scene/src/serde.rs +++ b/crates/bevy_scene/src/serde.rs @@ -1,7 +1,10 @@ use crate::{DynamicEntity, DynamicScene}; use anyhow::Result; use bevy_reflect::serde::{TypedReflectDeserializer, TypedReflectSerializer}; -use bevy_reflect::{serde::UntypedReflectDeserializer, Reflect, TypeRegistry, TypeRegistryArc}; +use bevy_reflect::{ + serde::{TypeRegistrationDeserializer, UntypedReflectDeserializer}, + Reflect, TypeRegistry, TypeRegistryArc, +}; use bevy_utils::HashSet; use serde::ser::SerializeMap; use serde::{ @@ -9,7 +12,6 @@ use serde::{ ser::SerializeStruct, Deserialize, Deserializer, Serialize, Serializer, }; -use std::borrow::Cow; use std::fmt::Formatter; pub const SCENE_STRUCT: &str = "Scene"; @@ -353,15 +355,16 @@ impl<'a, 'de> Visitor<'de> for ComponentVisitor<'a> { { let mut added = HashSet::new(); let mut components = Vec::new(); - while let Some(BorrowableCowStr(key)) = map.next_key()? { - if !added.insert(key.clone()) { - return Err(Error::custom(format!("duplicate component: `{key}`"))); + while let Some(registration) = + map.next_key_seed(TypeRegistrationDeserializer::new(self.registry))? + { + if !added.insert(registration.type_id()) { + return Err(Error::custom(format_args!( + "duplicate component: `{}`", + registration.type_name() + ))); } - let registration = self - .registry - .get_with_name(&key) - .ok_or_else(|| Error::custom(format!("no registration found for `{key}`")))?; components.push( map.next_value_seed(TypedReflectDeserializer::new(registration, self.registry))?, ); @@ -385,13 +388,6 @@ impl<'a, 'de> Visitor<'de> for ComponentVisitor<'a> { } } -/// Helper struct for deserializing strings without allocating (when possible). -/// -/// Based on [this comment](https://github.com/bevyengine/bevy/pull/6894#discussion_r1045069010). -#[derive(Deserialize)] -#[serde(transparent)] -struct BorrowableCowStr<'a>(#[serde(borrow)] Cow<'a, str>); - #[cfg(test)] mod tests { use crate::serde::{SceneDeserializer, SceneSerializer}; From b7f14210a313f4afaf8b0ff245344e748bee8568 Mon Sep 17 00:00:00 2001 From: JoJoJet <21144246+JoJoJet@users.noreply.github.com> Date: Mon, 9 Jan 2023 22:20:10 +0000 Subject: [PATCH 59/60] Add `Mut::reborrow` (#7114) # Objective - In some cases, you need a `Mut` pointer, but you only have a mutable reference to one. There is no easy way of converting `&'a mut Mut<'_, T>` -> `Mut<'a, T>` outside of the engine. ### Example (Before) ```rust fn do_with_mut(val: Mut) { ... } for x: Mut in &mut query { // The function expects a `Mut`, so `x` gets moved here. do_with_mut(x); // Error: use of moved value. do_a_thing(&x); } ``` ## Solution - Add the function `reborrow`, which performs the mapping. This is analogous to `PtrMut::reborrow`. ### Example (After) ```rust fn do_with_mut(val: Mut) { ... } for x: Mut in &mut query { // We reborrow `x`, so the original does not get moved. do_with_mut(x.reborrow()); // Works fine. do_a_thing(&x); } ``` --- ## Changelog - Added the method `reborrow` to `Mut`, `ResMut`, `NonSendMut`, and `MutUntyped`. --- crates/bevy_ecs/src/change_detection.rs | 37 +++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/crates/bevy_ecs/src/change_detection.rs b/crates/bevy_ecs/src/change_detection.rs index d9ce86e60294d..ac28a8c29a11b 100644 --- a/crates/bevy_ecs/src/change_detection.rs +++ b/crates/bevy_ecs/src/change_detection.rs @@ -209,6 +209,25 @@ macro_rules! impl_methods { self.value } + /// Returns a `Mut<>` with a smaller lifetime. + /// This is useful if you have `&mut + #[doc = stringify!($name)] + /// `, but you need a `Mut`. + /// + /// Note that calling [`DetectChanges::set_last_changed`] on the returned value + /// will not affect the original. + pub fn reborrow(&mut self) -> Mut<'_, $target> { + Mut { + value: self.value, + ticks: Ticks { + added: self.ticks.added, + changed: self.ticks.changed, + last_change_tick: self.ticks.last_change_tick, + change_tick: self.ticks.change_tick, + } + } + } + /// Maps to an inner value by applying a function to the contained reference, without flagging a change. /// /// You should never modify the argument passed to the closure -- if you want to modify the data @@ -427,6 +446,24 @@ impl<'a> MutUntyped<'a> { self.value } + /// Returns a [`MutUntyped`] with a smaller lifetime. + /// This is useful if you have `&mut MutUntyped`, but you need a `MutUntyped`. + /// + /// Note that calling [`DetectChanges::set_last_changed`] on the returned value + /// will not affect the original. + #[inline] + pub fn reborrow(&mut self) -> MutUntyped { + MutUntyped { + value: self.value.reborrow(), + ticks: Ticks { + added: self.ticks.added, + changed: self.ticks.changed, + last_change_tick: self.ticks.last_change_tick, + change_tick: self.ticks.change_tick, + }, + } + } + /// Returns a pointer to the value without taking ownership of this smart pointer, marking it as changed. /// /// In order to avoid marking the value as changed, you need to call [`bypass_change_detection`](DetectChanges::bypass_change_detection). From 1029b0b468047663a55835fdd63a24d3a78ca54b Mon Sep 17 00:00:00 2001 From: james7132 Date: Sat, 21 Jan 2023 12:15:14 -0800 Subject: [PATCH 60/60] Fix build --- crates/bevy_ecs/src/world/identifier.rs | 11 +++++++ crates/bevy_ecs/src/world/mod.rs | 43 ------------------------- 2 files changed, 11 insertions(+), 43 deletions(-) diff --git a/crates/bevy_ecs/src/world/identifier.rs b/crates/bevy_ecs/src/world/identifier.rs index c65da1b81a7e1..0ce5467b2e4ee 100644 --- a/crates/bevy_ecs/src/world/identifier.rs +++ b/crates/bevy_ecs/src/world/identifier.rs @@ -28,6 +28,9 @@ impl WorldId { } impl SparseSetIndex for WorldId { + type Repr = usize; + const MAX_SIZE: usize = usize::MAX; + #[inline] fn sparse_set_index(&self) -> usize { self.0 @@ -36,6 +39,14 @@ impl SparseSetIndex for WorldId { fn get_sparse_set_index(value: usize) -> Self { Self(value) } + + unsafe fn repr_from_index(index: usize) -> Self::Repr { + index + } + + fn repr_to_index(repr: &usize) -> usize { + *repr + } } #[cfg(test)] diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index 9baed2753ea70..446e3a957f23a 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -1734,49 +1734,6 @@ impl World { }) } - /// Gets a `!Send` resource to the resource with the id [`ComponentId`] if it exists. - /// The returned pointer must not be used to modify the resource, and must not be - /// dereferenced after the immutable borrow of the [`World`] ends. - /// - /// **You should prefer to use the typed API [`World::get_resource`] where possible and only - /// use this in cases where the actual types are not known at compile time.** - /// - /// # Panics - /// This function will panic if it isn't called from the same thread that the resource was inserted from. - #[inline] - pub fn get_non_send_by_id(&self, component_id: ComponentId) -> Option> { - self.storages - .non_send_resources - .get(component_id)? - .get_data() - } - - /// Gets a `!Send` resource to the resource with the id [`ComponentId`] if it exists. - /// The returned pointer may be used to modify the resource, as long as the mutable borrow - /// of the [`World`] is still valid. - /// - /// **You should prefer to use the typed API [`World::get_resource_mut`] where possible and only - /// use this in cases where the actual types are not known at compile time.** - /// - /// # Panics - /// This function will panic if it isn't called from the same thread that the resource was inserted from. - #[inline] - pub fn get_non_send_mut_by_id(&mut self, component_id: ComponentId) -> Option> { - let change_tick = self.change_tick(); - let (ptr, ticks) = self.get_non_send_with_ticks(component_id)?; - - // SAFETY: This function has exclusive access to the world so nothing aliases `ticks`. - // - index is in-bounds because the column is initialized and non-empty - // - no other reference to the ticks of the same row can exist at the same time - let ticks = unsafe { Ticks::from_tick_cells(ticks, self.last_change_tick(), change_tick) }; - - Some(MutUntyped { - // SAFETY: This function has exclusive access to the world so nothing aliases `ptr`. - value: unsafe { ptr.assert_unique() }, - ticks, - }) - } - /// Removes the resource of a given type, if it exists. Otherwise returns [None]. /// /// **You should prefer to use the typed API [`World::remove_resource`] where possible and only