From 67092ddb225c2cc44f44b685fc911e690b155598 Mon Sep 17 00:00:00 2001 From: Trashtalk Date: Tue, 21 Apr 2026 01:09:02 +0200 Subject: [PATCH 01/22] make RequiredComponentConstructor Send and Sync --- crates/bevy_ecs/src/component/required.rs | 35 ++++++++++++++--------- 1 file changed, 22 insertions(+), 13 deletions(-) diff --git a/crates/bevy_ecs/src/component/required.rs b/crates/bevy_ecs/src/component/required.rs index c1a9f06b8cf8f..673d683d96e23 100644 --- a/crates/bevy_ecs/src/component/required.rs +++ b/crates/bevy_ecs/src/component/required.rs @@ -15,7 +15,7 @@ use crate::{ }; /// Metadata associated with a required component. See [`Component`] for details. -#[derive(Clone)] +#[derive(Clone, Component)] pub struct RequiredComponent { /// The constructor used for the required component. pub constructor: RequiredComponentConstructor, @@ -25,7 +25,12 @@ pub struct RequiredComponent { #[derive(Clone)] pub struct RequiredComponentConstructor( // Note: this function makes `unsafe` assumptions, so it cannot be public. - Arc, + Arc< + dyn Fn(&mut Table, &mut SparseSets, Tick, TableRow, Entity, MaybeLocation) + + Send + + Sync + + 'static, + >, ); impl RequiredComponentConstructor { @@ -36,7 +41,7 @@ impl RequiredComponentConstructor { /// - `component_id` must be a valid component for type `C`. pub unsafe fn new( component_id: ComponentId, - constructor: impl Fn() -> C + 'static, + constructor: impl Fn() -> C + Send + Sync + 'static, ) -> Self { RequiredComponentConstructor({ // `portable-atomic-util` `Arc` is not able to coerce an unsized @@ -49,13 +54,14 @@ impl RequiredComponentConstructor { use alloc::boxed::Box; type Constructor = dyn for<'a, 'b> Fn( - &'a mut Table, - &'b mut SparseSets, - Tick, - TableRow, - Entity, - MaybeLocation, - ); + &'a mut Table, + &'b mut SparseSets, + Tick, + TableRow, + Entity, + MaybeLocation, + ) + Send + + Sync; #[cfg(not(target_has_atomic = "ptr"))] type Intermediate = Box; @@ -155,7 +161,7 @@ impl RequiredComponents { unsafe fn register( &mut self, components: &mut ComponentsRegistrator<'_>, - constructor: impl Fn() -> C + 'static, + constructor: impl Fn() -> C + Send + Sync + 'static, ) { let id = components.register_component::(); // SAFETY: @@ -177,7 +183,7 @@ impl RequiredComponents { &mut self, component_id: ComponentId, components: &Components, - constructor: impl Fn() -> C + 'static, + constructor: impl Fn() -> C + Send + Sync + 'static, ) { // SAFETY: the caller guarantees that `component_id` is valid for the type `C`. let constructor = @@ -588,7 +594,10 @@ impl<'a, 'w> RequiredComponentsRegistrator<'a, 'w> { /// /// If the component was not already registered as an explicit required component then it is added /// as one, potentially overriding the constructor of a inherited required component, otherwise panics. - pub fn register_required(&mut self, constructor: impl Fn() -> C + 'static) { + pub fn register_required( + &mut self, + constructor: impl Fn() -> C + Send + Sync + 'static, + ) { // SAFETY: we internally guarantee that all components in `required_components` // are registered in `components` unsafe { From 361525ecccbcc9f33bec95c481097f36e34e3fc7 Mon Sep 17 00:00:00 2001 From: Trashtalk Date: Tue, 21 Apr 2026 03:52:03 +0200 Subject: [PATCH 02/22] made RelationshipAccessorInitializer Sync and Send --- crates/bevy_ecs/src/relationship/mod.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/bevy_ecs/src/relationship/mod.rs b/crates/bevy_ecs/src/relationship/mod.rs index 184b126407daa..8ae18dc0fe8bf 100644 --- a/crates/bevy_ecs/src/relationship/mod.rs +++ b/crates/bevy_ecs/src/relationship/mod.rs @@ -523,7 +523,7 @@ pub enum RelationshipAccessorInitializer { allow_self_referential: bool, /// Getter for [`ComponentId`] of the [`RelationshipTarget`] counterpart. /// Should return `None` if [`RelationshipTarget`] isn't registered yet. - relationship_target_getter: Arc Option>, + relationship_target_getter: Arc Option + Send + Sync>, }, /// Describes a [`RelationshipTarget`] component. RelationshipTarget { @@ -539,7 +539,7 @@ pub enum RelationshipAccessorInitializer { allow_self_referential: bool, /// Getter for [`ComponentId`] of the [`Relationship`] counterpart. /// Should return `None` if [`Relationship`] isn't registered yet. - relationship_getter: Arc Option>, + relationship_getter: Arc Option + Send + Sync>, }, } @@ -741,7 +741,7 @@ impl ComponentRelationshipAccessor { C: Relationship, { // Due to https://github.com/taiki-e/portable-atomic/issues/143 we have to box this first, and then get the Arc from the box - let getter: Box Option> = + let getter: Box Option + Send + Sync> = Box::new(|components| components.get_id(TypeId::of::())); Self { initializer: RelationshipAccessorInitializer::Relationship { @@ -760,7 +760,7 @@ impl ComponentRelationshipAccessor { C: RelationshipTarget, { // Due to https://github.com/taiki-e/portable-atomic/issues/143 we have to box this first, and then get the Arc from the box - let getter: Box Option> = + let getter: Box Option + Send + Sync> = Box::new(|components| components.get_id(TypeId::of::())); Self { initializer: RelationshipAccessorInitializer::RelationshipTarget { From ba5d0953c4cde6ae3c741c179f1b4ea3d3c446a3 Mon Sep 17 00:00:00 2001 From: Trashtalk Date: Tue, 21 Apr 2026 03:52:37 +0200 Subject: [PATCH 03/22] derive Component for ComponentInfo --- crates/bevy_ecs/src/component/info.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_ecs/src/component/info.rs b/crates/bevy_ecs/src/component/info.rs index 567cbb7bb77c4..1f8df3144e47f 100644 --- a/crates/bevy_ecs/src/component/info.rs +++ b/crates/bevy_ecs/src/component/info.rs @@ -28,7 +28,7 @@ use crate::{ }; /// Stores metadata for a type of component or resource stored in a specific [`World`](crate::world::World). -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Component)] pub struct ComponentInfo { pub(super) id: ComponentId, pub(super) descriptor: ComponentDescriptor, From 382d79dfefcd2b35a8c0e576d9ba558176d32249 Mon Sep 17 00:00:00 2001 From: Trashtalk Date: Tue, 21 Apr 2026 16:41:28 +0200 Subject: [PATCH 04/22] made ComponentId(usize) into ComponentId(Entity) --- crates/bevy_ecs/src/component/constants.rs | 12 +- crates/bevy_ecs/src/component/info.rs | 88 +++--- crates/bevy_ecs/src/component/register.rs | 72 +---- crates/bevy_ecs/src/entity_disabling.rs | 22 +- crates/bevy_ecs/src/lifecycle.rs | 10 +- .../src/observer/centralized_storage.rs | 60 ++-- crates/bevy_ecs/src/query/access.rs | 257 +++++++++--------- crates/bevy_ecs/src/resource.rs | 2 +- crates/bevy_ecs/src/storage/sparse_set.rs | 6 +- crates/bevy_ecs/src/storage/table/mod.rs | 8 +- .../bevy_ecs/src/world/entity_access/mod.rs | 4 +- crates/bevy_ecs/src/world/mod.rs | 12 +- 12 files changed, 269 insertions(+), 284 deletions(-) diff --git a/crates/bevy_ecs/src/component/constants.rs b/crates/bevy_ecs/src/component/constants.rs index 3f582545f85b9..1783eac5bc117 100644 --- a/crates/bevy_ecs/src/component/constants.rs +++ b/crates/bevy_ecs/src/component/constants.rs @@ -1,14 +1,14 @@ //! Constant components included in every world. /// `usize` for the [`Add`](crate::lifecycle::Add) component used in lifecycle observers. -pub const ADD: usize = 0; +pub const ADD: u32 = 0; /// `usize` for the [`Insert`](crate::lifecycle::Insert) component used in lifecycle observers. -pub const INSERT: usize = 1; +pub const INSERT: u32 = 1; /// `usize` for the [`Discard`](crate::lifecycle::Discard) component used in lifecycle observers. -pub const DISCARD: usize = 2; +pub const DISCARD: u32 = 2; /// `usize` for the [`Remove`](crate::lifecycle::Remove) component used in lifecycle observers. -pub const REMOVE: usize = 3; +pub const REMOVE: u32 = 3; /// `usize` for [`Despawn`](crate::lifecycle::Despawn) component used in lifecycle observers. -pub const DESPAWN: usize = 4; +pub const DESPAWN: u32 = 4; /// `usize` of the [`IsResource`](crate::resource::IsResource) component used to mark entities with resources. -pub const IS_RESOURCE: usize = 5; +pub const IS_RESOURCE: u32 = 5; diff --git a/crates/bevy_ecs/src/component/info.rs b/crates/bevy_ecs/src/component/info.rs index 1f8df3144e47f..4d94cf5e9b2b6 100644 --- a/crates/bevy_ecs/src/component/info.rs +++ b/crates/bevy_ecs/src/component/info.rs @@ -1,4 +1,5 @@ -use alloc::{borrow::Cow, vec::Vec}; +use alloc::borrow::Cow; +use bevy_ecs_macros::MapEntities; use bevy_platform::{hash::FixedHasher, sync::PoisonError}; use bevy_ptr::OwningPtr; #[cfg(feature = "bevy_reflect")] @@ -18,6 +19,7 @@ use crate::{ Component, ComponentCloneBehavior, ComponentMutability, QueuedComponents, RequiredComponents, StorageType, }, + entity::{Entity, EntityHashMap}, lifecycle::ComponentHooks, query::DebugCheckedUnwrap as _, relationship::{ @@ -172,13 +174,13 @@ impl ComponentInfo { /// /// Given a type `T` which implements [`Component`] (including [`Resource`]), the `ComponentId` for `T` can be retrieved /// from a `World` using [`World::component_id()`](crate::world::World::component_id) or via [`Components::component_id()`]. -#[derive(Debug, Copy, Clone, Hash, Ord, PartialOrd, Eq, PartialEq)] +#[derive(Debug, Copy, Clone, Hash, Ord, PartialOrd, Eq, PartialEq, MapEntities)] #[cfg_attr( feature = "bevy_reflect", derive(Reflect), reflect(Debug, Hash, PartialEq, Clone) )] -pub struct ComponentId(pub(super) usize); +pub struct ComponentId(pub(super) Entity); impl ComponentId { /// Creates a new [`ComponentId`]. @@ -186,26 +188,36 @@ impl ComponentId { /// The `index` is a unique value associated with each type of component in a given world. /// Usually, this value is taken from a counter incremented for each type of component registered with the world. #[inline] - pub const fn new(index: usize) -> ComponentId { + pub const fn new(index: Entity) -> ComponentId { ComponentId(index) } + /// Creates a new [`ComponentId`]. + /// + /// Panics if the index is `u32::MAX`. + #[inline] + pub(crate) const fn from_u32(index: u32) -> ComponentId { + ComponentId(Entity::from_raw_u32(index).unwrap()) + } + /// Returns the index of the current component. + // TODO: Track down all uses and improve data structures for performance. #[inline] pub fn index(self) -> usize { - self.0 + self.0.index_u32() as usize } } +// Identical implementation as Entity impl SparseSetIndex for ComponentId { #[inline] fn sparse_set_index(&self) -> usize { - self.index() + self.0.index().sparse_set_index() } #[inline] fn get_sparse_set_index(value: usize) -> Self { - Self(value) + Self(Entity::get_sparse_set_index(value)) } } @@ -355,7 +367,7 @@ impl ComponentDescriptor { /// Stores metadata associated with each kind of [`Component`] in a given [`World`](crate::world::World). #[derive(Debug, Default)] pub struct Components { - pub(super) components: Vec>, + pub(super) components: EntityHashMap, pub(super) indices: TypeIdMap, // This is kept internal and local to verify that no deadlocks can occur. pub(super) queued: bevy_platform::sync::RwLock, @@ -375,15 +387,10 @@ impl Components { ) { descriptor.initialize(id, self); let info = ComponentInfo::new(id, descriptor); - let least_len = id.0 + 1; - if self.components.len() < least_len { - self.components.resize_with(least_len, || None); + // SAFETY: The id has never been registered before. + unsafe { + self.components.insert_unique_unchecked(id.0, info); } - // SAFETY: We just extended the vec to make this index valid. - let slot = unsafe { self.components.get_mut(id.0).debug_checked_unwrap() }; - // Caller ensures id is unique - debug_assert!(slot.is_none()); - *slot = Some(info); } /// Returns the number of components registered or queued with this instance. @@ -445,7 +452,7 @@ impl Components { /// This will return an incorrect result if `id` did not come from the same world as `self`. It may return `None` or a garbage value. #[inline] pub fn get_info(&self, id: ComponentId) -> Option<&ComponentInfo> { - self.components.get(id.0).and_then(|info| info.as_ref()) + self.components.get(&id.0).and_then(|info| Some(info)) } /// Gets the [`ComponentDescriptor`] of the component with this [`ComponentId`] if it is present. @@ -457,8 +464,8 @@ impl Components { #[inline] pub fn get_descriptor<'a>(&'a self, id: ComponentId) -> Option> { self.components - .get(id.0) - .and_then(|info| info.as_ref().map(|info| Cow::Borrowed(&info.descriptor))) + .get(&id.0) + .and_then(|info| Some(Cow::Borrowed(&info.descriptor))) .or_else(|| { let queued = self.queued.read().unwrap_or_else(PoisonError::into_inner); // first check components, then resources, then dynamic @@ -478,8 +485,8 @@ impl Components { #[inline] pub fn get_name<'a>(&'a self, id: ComponentId) -> Option { self.components - .get(id.0) - .and_then(|info| info.as_ref().map(|info| info.descriptor.name())) + .get(&id.0) + .and_then(|info| Some(info.descriptor.name())) .or_else(|| { let queued = self.queued.read().unwrap_or_else(PoisonError::into_inner); // first check components, then resources, then dynamic @@ -499,27 +506,21 @@ impl Components { #[inline] pub unsafe fn get_info_unchecked(&self, id: ComponentId) -> &ComponentInfo { // SAFETY: The caller ensures `id` is valid. - unsafe { - self.components - .get(id.0) - .debug_checked_unwrap() - .as_ref() - .debug_checked_unwrap() - } + unsafe { self.components.get(&id.0).debug_checked_unwrap() } } #[inline] pub(crate) fn get_hooks_mut(&mut self, id: ComponentId) -> Option<&mut ComponentHooks> { self.components - .get_mut(id.0) - .and_then(|info| info.as_mut().map(|info| &mut info.hooks)) + .get_mut(&id.0) + .and_then(|info| Some(&mut info.hooks)) } #[inline] pub(crate) fn get_required_components(&self, id: ComponentId) -> Option<&RequiredComponents> { self.components - .get(id.0) - .and_then(|info| info.as_ref().map(|info| &info.required_components)) + .get(&id.0) + .and_then(|info| Some(&info.required_components)) } #[inline] @@ -528,8 +529,8 @@ impl Components { id: ComponentId, ) -> Option<&mut RequiredComponents> { self.components - .get_mut(id.0) - .and_then(|info| info.as_mut().map(|info| &mut info.required_components)) + .get_mut(&id.0) + .and_then(|info| Some(&mut info.required_components)) } #[inline] @@ -538,8 +539,8 @@ impl Components { id: ComponentId, ) -> Option<&IndexSet> { self.components - .get(id.0) - .and_then(|info| info.as_ref().map(|info| &info.required_by)) + .get(&id.0) + .and_then(|info| Some(&info.required_by)) } #[inline] @@ -548,8 +549,8 @@ impl Components { id: ComponentId, ) -> Option<&mut IndexSet> { self.components - .get_mut(id.0) - .and_then(|info| info.as_mut().map(|info| &mut info.required_by)) + .get_mut(&id.0) + .and_then(|info| Some(&mut info.required_by)) } /// Returns true if the [`ComponentId`] is fully registered and valid. @@ -557,7 +558,7 @@ impl Components { /// Those ids are still correct, but they are not usable in every context yet. #[inline] pub fn is_id_valid(&self, id: ComponentId) -> bool { - self.components.get(id.0).is_some_and(Option::is_some) + self.components.get(&id.0).is_some() } /// Type-erased equivalent of [`Components::valid_component_id()`]. @@ -739,7 +740,7 @@ impl Components { /// Gets an iterator over all components fully registered with this instance. pub fn iter_registered(&self) -> impl Iterator + '_ { - self.components.iter().filter_map(Option::as_ref) + self.components.values() } pub(crate) fn get_relationship_accessor_mut( @@ -747,10 +748,7 @@ impl Components { component_id: ComponentId, ) -> Option<&mut MaybeRelationshipAccessor> { self.components - .get_mut(component_id.index()) - .and_then(|info| { - info.as_mut() - .map(|info| &mut info.descriptor.relationship_accessor) - }) + .get_mut(&component_id.0) + .and_then(|info| Some(&mut info.descriptor.relationship_accessor)) } } diff --git a/crates/bevy_ecs/src/component/register.rs b/crates/bevy_ecs/src/component/register.rs index 8f5f175efc1c9..f52754e9841f9 100644 --- a/crates/bevy_ecs/src/component/register.rs +++ b/crates/bevy_ecs/src/component/register.rs @@ -5,6 +5,7 @@ use core::any::Any; use core::{any::TypeId, fmt::Debug, ops::Deref}; use crate::component::{enforce_no_required_components_recursion, RequiredComponentsRegistrator}; +use crate::entity::EntityAllocator; use crate::lifecycle::ComponentHooks; use crate::{ component::{ @@ -14,57 +15,10 @@ use crate::{ resource::Resource, }; -/// Generates [`ComponentId`]s. -#[derive(Debug, Default)] -pub struct ComponentIds { - next: bevy_platform::sync::atomic::AtomicUsize, -} - -impl ComponentIds { - /// Peeks the next [`ComponentId`] to be generated without generating it. - pub fn peek(&self) -> ComponentId { - ComponentId( - self.next - .load(bevy_platform::sync::atomic::Ordering::Relaxed), - ) - } - - /// Generates and returns the next [`ComponentId`]. - pub fn next(&self) -> ComponentId { - ComponentId( - self.next - .fetch_add(1, bevy_platform::sync::atomic::Ordering::Relaxed), - ) - } - - /// Peeks the next [`ComponentId`] to be generated without generating it. - pub fn peek_mut(&mut self) -> ComponentId { - ComponentId(*self.next.get_mut()) - } - - /// Generates and returns the next [`ComponentId`]. - pub fn next_mut(&mut self) -> ComponentId { - let id = self.next.get_mut(); - let result = ComponentId(*id); - *id += 1; - result - } - - /// Returns the number of [`ComponentId`]s generated. - pub fn len(&self) -> usize { - self.peek().0 - } - - /// Returns true if and only if no ids have been generated. - pub fn is_empty(&self) -> bool { - self.len() == 0 - } -} - /// A [`Components`] wrapper that enables additional features, like registration. pub struct ComponentsRegistrator<'w> { pub(super) components: &'w mut Components, - pub(super) ids: &'w mut ComponentIds, + pub(super) ids: &'w mut EntityAllocator, pub(super) recursion_check_stack: Vec, } @@ -83,7 +37,7 @@ impl<'w> ComponentsRegistrator<'w> { /// /// The [`Components`] and [`ComponentIds`] must match. /// For example, they must be from the same world. - pub unsafe fn new(components: &'w mut Components, ids: &'w mut ComponentIds) -> Self { + pub unsafe fn new(components: &'w mut Components, ids: &'w mut EntityAllocator) -> Self { Self { components, ids, @@ -196,7 +150,7 @@ impl<'w> ComponentsRegistrator<'w> { return registrator.register(self); } - let id = self.ids.next_mut(); + let id = ComponentId::new(self.ids.alloc()); // SAFETY: The component is not currently registered, and the id is fresh. unsafe { self.register_component_unchecked( @@ -250,9 +204,7 @@ impl<'w> ComponentsRegistrator<'w> { &mut self .components .components - .get_mut(id.0) - .debug_checked_unwrap() - .as_mut() + .get_mut(&id.0) .debug_checked_unwrap() }; @@ -283,7 +235,7 @@ impl<'w> ComponentsRegistrator<'w> { &mut self, descriptor: ComponentDescriptor, ) -> ComponentId { - let id = self.ids.next_mut(); + let id = ComponentId::new(self.ids.alloc()); // SAFETY: The id is fresh. unsafe { self.components.register_component_inner(id, descriptor); @@ -345,7 +297,7 @@ impl<'w> ComponentsRegistrator<'w> { return registrator.register(self); } - let id = self.ids.next_mut(); + let id = ComponentId::new(self.ids.alloc()); // SAFETY: The resource is not currently registered, the id is fresh, and the [`ComponentDescriptor`] matches the [`TypeId`] unsafe { self.components @@ -442,7 +394,7 @@ impl Debug for QueuedComponents { #[derive(Clone, Copy)] pub struct ComponentsQueuedRegistrator<'w> { components: &'w Components, - ids: &'w ComponentIds, + ids: &'w EntityAllocator, } impl Deref for ComponentsQueuedRegistrator<'_> { @@ -460,7 +412,7 @@ impl<'w> ComponentsQueuedRegistrator<'w> { /// /// The [`Components`] and [`ComponentIds`] must match. /// For example, they must be from the same world. - pub unsafe fn new(components: &'w Components, ids: &'w ComponentIds) -> Self { + pub unsafe fn new(components: &'w Components, ids: &'w EntityAllocator) -> Self { Self { components, ids } } @@ -484,7 +436,9 @@ impl<'w> ComponentsQueuedRegistrator<'w> { .entry(type_id) .or_insert_with(|| { // SAFETY: The id was just generated. - unsafe { QueuedRegistration::new(self.ids.next(), descriptor, func) } + unsafe { + QueuedRegistration::new(ComponentId::new(self.ids.alloc()), descriptor, func) + } }) .id } @@ -495,7 +449,7 @@ impl<'w> ComponentsQueuedRegistrator<'w> { descriptor: ComponentDescriptor, func: fn(&mut ComponentsRegistrator, ComponentId, ComponentDescriptor), ) -> ComponentId { - let id = self.ids.next(); + let id = ComponentId::new(self.ids.alloc()); self.components .queued .write() diff --git a/crates/bevy_ecs/src/entity_disabling.rs b/crates/bevy_ecs/src/entity_disabling.rs index 1e751070ac2df..46ff6237ee2ab 100644 --- a/crates/bevy_ecs/src/entity_disabling.rs +++ b/crates/bevy_ecs/src/entity_disabling.rs @@ -253,42 +253,44 @@ mod tests { #[test] fn filters_modify_access() { let mut filters = DefaultQueryFilters::empty(); - filters.register_disabling_component(ComponentId::new(1)); + filters.register_disabling_component(ComponentId::from_u32(1)); // A component access with an unrelated component let mut component_access = FilteredAccess::default(); - component_access.access_mut().add_read(ComponentId::new(2)); + component_access + .access_mut() + .add_read(ComponentId::from_u32(2)); let mut applied_access = component_access.clone(); filters.modify_access(&mut applied_access); assert_eq!(0, applied_access.with_filters().count()); assert_eq!( - vec![ComponentId::new(1)], + vec![ComponentId::from_u32(1)], applied_access.without_filters().collect::>() ); // We add a with filter, now we expect to see both filters - component_access.and_with(ComponentId::new(4)); + component_access.and_with(ComponentId::from_u32(4)); let mut applied_access = component_access.clone(); filters.modify_access(&mut applied_access); assert_eq!( - vec![ComponentId::new(4)], + vec![ComponentId::from_u32(4)], applied_access.with_filters().collect::>() ); assert_eq!( - vec![ComponentId::new(1)], + vec![ComponentId::from_u32(1)], applied_access.without_filters().collect::>() ); let copy = component_access.clone(); // We add a rule targeting a default component, that filter should no longer be added - component_access.and_with(ComponentId::new(1)); + component_access.and_with(ComponentId::from_u32(1)); let mut applied_access = component_access.clone(); filters.modify_access(&mut applied_access); assert_eq!( - vec![ComponentId::new(1), ComponentId::new(4)], + vec![ComponentId::from_u32(1), ComponentId::from_u32(4)], applied_access.with_filters().collect::>() ); assert_eq!(0, applied_access.without_filters().count()); @@ -297,12 +299,12 @@ mod tests { component_access = copy.clone(); component_access .access_mut() - .add_archetypal(ComponentId::new(1)); + .add_archetypal(ComponentId::from_u32(1)); let mut applied_access = component_access.clone(); filters.modify_access(&mut applied_access); assert_eq!( - vec![ComponentId::new(4)], + vec![ComponentId::from_u32(4)], applied_access.with_filters().collect::>() ); assert_eq!(0, applied_access.without_filters().count()); diff --git a/crates/bevy_ecs/src/lifecycle.rs b/crates/bevy_ecs/src/lifecycle.rs index 13685cf9ce933..26eb8000524d4 100644 --- a/crates/bevy_ecs/src/lifecycle.rs +++ b/crates/bevy_ecs/src/lifecycle.rs @@ -315,15 +315,15 @@ impl ComponentHooks { } /// [`EventKey`] for [`Add`] -pub const ADD: EventKey = EventKey(ComponentId::new(crate::component::ADD)); +pub const ADD: EventKey = EventKey(ComponentId::from_u32(crate::component::ADD)); /// [`EventKey`] for [`Insert`] -pub const INSERT: EventKey = EventKey(ComponentId::new(crate::component::INSERT)); +pub const INSERT: EventKey = EventKey(ComponentId::from_u32(crate::component::INSERT)); /// [`EventKey`] for [`Discard`] -pub const DISCARD: EventKey = EventKey(ComponentId::new(crate::component::DISCARD)); +pub const DISCARD: EventKey = EventKey(ComponentId::from_u32(crate::component::DISCARD)); /// [`EventKey`] for [`Remove`] -pub const REMOVE: EventKey = EventKey(ComponentId::new(crate::component::REMOVE)); +pub const REMOVE: EventKey = EventKey(ComponentId::from_u32(crate::component::REMOVE)); /// [`EventKey`] for [`Despawn`] -pub const DESPAWN: EventKey = EventKey(ComponentId::new(crate::component::DESPAWN)); +pub const DESPAWN: EventKey = EventKey(ComponentId::from_u32(crate::component::DESPAWN)); /// Trigger emitted when a component is inserted onto an entity that does not already have that /// component. Runs before `Insert`. diff --git a/crates/bevy_ecs/src/observer/centralized_storage.rs b/crates/bevy_ecs/src/observer/centralized_storage.rs index 8b09e95a0ec90..2c712abdda8f6 100644 --- a/crates/bevy_ecs/src/observer/centralized_storage.rs +++ b/crates/bevy_ecs/src/observer/centralized_storage.rs @@ -39,14 +39,20 @@ impl Observers { pub(crate) fn get_observers_mut(&mut self, event_key: EventKey) -> &mut CachedObservers { use crate::lifecycle::*; - match event_key { - ADD => &mut self.add, - INSERT => &mut self.insert, - DISCARD => &mut self.discard, - REMOVE => &mut self.remove, - DESPAWN => &mut self.despawn, - _ => self.cache.entry(event_key).or_default(), + // We can't use a constant match expression due to Entity having a manual `PartialEq` impl. + // see https://doc.rust-lang.org/stable/std/marker/trait.StructuralPartialEq.html + if event_key == ADD { + return &mut self.add; + } else if event_key == INSERT { + return &mut self.insert; + } else if event_key == DISCARD { + return &mut self.discard; + } else if event_key == REMOVE { + return &mut self.remove; + } else if event_key == DESPAWN { + return &mut self.despawn; } + self.cache.entry(event_key).or_default() } /// Attempts to get the observers for the given `event_key`. @@ -62,27 +68,39 @@ impl Observers { pub fn try_get_observers(&self, event_key: EventKey) -> Option<&CachedObservers> { use crate::lifecycle::*; - match event_key { - ADD => Some(&self.add), - INSERT => Some(&self.insert), - DISCARD => Some(&self.discard), - REMOVE => Some(&self.remove), - DESPAWN => Some(&self.despawn), - _ => self.cache.get(&event_key), + // We can't use a constant match expression due to Entity having a manual `PartialEq` impl. + // see https://doc.rust-lang.org/stable/std/marker/trait.StructuralPartialEq.html + if event_key == ADD { + return Some(&self.add); + } else if event_key == INSERT { + return Some(&self.insert); + } else if event_key == DISCARD { + return Some(&self.discard); + } else if event_key == REMOVE { + return Some(&self.remove); + } else if event_key == DESPAWN { + return Some(&self.despawn); } + self.cache.get(&event_key) } pub(crate) fn is_archetype_cached(event_key: EventKey) -> Option { use crate::lifecycle::*; - match event_key { - ADD => Some(ArchetypeFlags::ON_ADD_OBSERVER), - INSERT => Some(ArchetypeFlags::ON_INSERT_OBSERVER), - DISCARD => Some(ArchetypeFlags::ON_DISCARD_OBSERVER), - REMOVE => Some(ArchetypeFlags::ON_REMOVE_OBSERVER), - DESPAWN => Some(ArchetypeFlags::ON_DESPAWN_OBSERVER), - _ => None, + // We can't use a constant match expression due to Entity having a manual `PartialEq` impl. + // see https://doc.rust-lang.org/stable/std/marker/trait.StructuralPartialEq.html + if event_key == ADD { + return Some(ArchetypeFlags::ON_ADD_OBSERVER); + } else if event_key == INSERT { + return Some(ArchetypeFlags::ON_INSERT_OBSERVER); + } else if event_key == DISCARD { + return Some(ArchetypeFlags::ON_DISCARD_OBSERVER); + } else if event_key == REMOVE { + return Some(ArchetypeFlags::ON_REMOVE_OBSERVER); + } else if event_key == DESPAWN { + return Some(ArchetypeFlags::ON_DESPAWN_OBSERVER); } + None } pub(crate) fn update_archetype_flags( diff --git a/crates/bevy_ecs/src/query/access.rs b/crates/bevy_ecs/src/query/access.rs index f16e318bf7b5f..3c7d38ce6c698 100644 --- a/crates/bevy_ecs/src/query/access.rs +++ b/crates/bevy_ecs/src/query/access.rs @@ -581,9 +581,9 @@ impl Access { /// # use bevy_ecs::component::ComponentId; /// let mut access = Access::default(); /// - /// access.add_read(ComponentId::new(1)); - /// access.add_write(ComponentId::new(2)); - /// access.add_archetypal(ComponentId::new(3)); + /// access.add_read(ComponentId::from_u32(1)); + /// access.add_write(ComponentId::from_u32(2)); + /// access.add_archetypal(ComponentId::from_u32(3)); /// /// let result = access /// .try_iter_access() @@ -592,9 +592,9 @@ impl Access { /// assert_eq!( /// result, /// Ok(vec![ - /// ComponentAccessKind::Shared(ComponentId::new(1)), - /// ComponentAccessKind::Exclusive(ComponentId::new(2)), - /// ComponentAccessKind::Archetypal(ComponentId::new(3)), + /// ComponentAccessKind::Shared(ComponentId::from_u32(1)), + /// ComponentAccessKind::Exclusive(ComponentId::from_u32(2)), + /// ComponentAccessKind::Archetypal(ComponentId::from_u32(3)), /// ]), /// ); /// ``` @@ -1465,8 +1465,9 @@ impl Extend for ComponentIdSet { /// An iterator of [`ComponentId`]s. /// -/// This is equivalent to `map(ComponentId::new)`, +/// This is equivalent to `map(ComponentId::from_u32)`, /// but is a named type to allow it to be used in associated types. +// TODO: Maybe remove this entirely #[repr(transparent)] pub struct ComponentIdIter(I); @@ -1475,7 +1476,7 @@ impl> Iterator for ComponentIdIter { #[inline] fn next(&mut self) -> Option { - self.0.next().map(ComponentId::new) + self.0.next().map(|id| ComponentId::from_u32(id as u32)) } #[inline] @@ -1487,7 +1488,9 @@ impl> Iterator for ComponentIdIter { impl> DoubleEndedIterator for ComponentIdIter { #[inline] fn next_back(&mut self) -> Option { - self.0.next_back().map(ComponentId::new) + self.0 + .next_back() + .map(|id| ComponentId::from_u32(id as u32)) } } @@ -1509,10 +1512,10 @@ mod tests { fn create_sample_access() -> Access { let mut access = Access::default(); - access.add_read(ComponentId::new(1)); - access.add_read(ComponentId::new(2)); - access.add_write(ComponentId::new(3)); - access.add_archetypal(ComponentId::new(5)); + access.add_read(ComponentId::from_u32(1)); + access.add_read(ComponentId::from_u32(2)); + access.add_write(ComponentId::from_u32(3)); + access.add_archetypal(ComponentId::from_u32(5)); access.read_all(); access @@ -1521,10 +1524,10 @@ mod tests { fn create_sample_filtered_access() -> FilteredAccess { let mut filtered_access = FilteredAccess::default(); - filtered_access.add_write(ComponentId::new(1)); - filtered_access.add_read(ComponentId::new(2)); - filtered_access.add_required(ComponentId::new(3)); - filtered_access.and_with(ComponentId::new(4)); + filtered_access.add_write(ComponentId::from_u32(1)); + filtered_access.add_read(ComponentId::from_u32(2)); + filtered_access.add_required(ComponentId::from_u32(3)); + filtered_access.and_with(ComponentId::from_u32(4)); filtered_access } @@ -1532,8 +1535,8 @@ mod tests { fn create_sample_access_filters() -> AccessFilters { let mut access_filters = AccessFilters::default(); - access_filters.with.insert(ComponentId::new(3)); - access_filters.without.insert(ComponentId::new(5)); + access_filters.with.insert(ComponentId::from_u32(3)); + access_filters.without.insert(ComponentId::from_u32(5)); access_filters } @@ -1541,8 +1544,8 @@ mod tests { fn create_sample_filtered_access_set() -> FilteredAccessSet { let mut filtered_access_set = FilteredAccessSet::default(); - filtered_access_set.add_unfiltered_component_read(ComponentId::new(2)); - filtered_access_set.add_unfiltered_component_write(ComponentId::new(4)); + filtered_access_set.add_unfiltered_component_read(ComponentId::from_u32(2)); + filtered_access_set.add_unfiltered_component_write(ComponentId::from_u32(4)); filtered_access_set.read_all(); filtered_access_set @@ -1561,9 +1564,9 @@ mod tests { let original = create_sample_access(); let mut cloned = Access::default(); - cloned.add_write(ComponentId::new(7)); - cloned.add_read(ComponentId::new(4)); - cloned.add_archetypal(ComponentId::new(8)); + cloned.add_write(ComponentId::from_u32(7)); + cloned.add_read(ComponentId::from_u32(4)); + cloned.add_archetypal(ComponentId::from_u32(8)); cloned.write_all(); cloned.clone_from(&original); @@ -1584,8 +1587,8 @@ mod tests { let original = create_sample_filtered_access(); let mut cloned = FilteredAccess::default(); - cloned.add_write(ComponentId::new(7)); - cloned.add_read(ComponentId::new(4)); + cloned.add_write(ComponentId::from_u32(7)); + cloned.add_read(ComponentId::from_u32(4)); cloned.append_or(&FilteredAccess::default()); cloned.clone_from(&original); @@ -1606,8 +1609,8 @@ mod tests { let original = create_sample_access_filters(); let mut cloned = AccessFilters::default(); - cloned.with.insert(ComponentId::new(1)); - cloned.without.insert(ComponentId::new(2)); + cloned.with.insert(ComponentId::from_u32(1)); + cloned.without.insert(ComponentId::from_u32(2)); cloned.clone_from(&original); @@ -1627,8 +1630,8 @@ mod tests { let original = create_sample_filtered_access_set(); let mut cloned = FilteredAccessSet::default(); - cloned.add_unfiltered_component_read(ComponentId::new(7)); - cloned.add_unfiltered_component_write(ComponentId::new(9)); + cloned.add_unfiltered_component_read(ComponentId::from_u32(7)); + cloned.add_unfiltered_component_write(ComponentId::from_u32(9)); cloned.write_all(); cloned.clone_from(&original); @@ -1640,7 +1643,7 @@ mod tests { fn read_all_access_conflicts() { // read_all / single write let mut access_a = Access::default(); - access_a.add_write(ComponentId::new(0)); + access_a.add_write(ComponentId::from_u32(0)); let mut access_b = Access::default(); access_b.read_all(); @@ -1660,54 +1663,54 @@ mod tests { #[test] fn access_get_conflicts() { let mut access_a = Access::default(); - access_a.add_read(ComponentId::new(0)); - access_a.add_read(ComponentId::new(1)); + access_a.add_read(ComponentId::from_u32(0)); + access_a.add_read(ComponentId::from_u32(1)); let mut access_b = Access::default(); - access_b.add_read(ComponentId::new(0)); - access_b.add_write(ComponentId::new(1)); + access_b.add_read(ComponentId::from_u32(0)); + access_b.add_write(ComponentId::from_u32(1)); assert_eq!( access_a.get_conflicts(&access_b), - vec![ComponentId::new(1)].into() + vec![ComponentId::from_u32(1)].into() ); let mut access_c = Access::default(); - access_c.add_write(ComponentId::new(0)); - access_c.add_write(ComponentId::new(1)); + access_c.add_write(ComponentId::from_u32(0)); + access_c.add_write(ComponentId::from_u32(1)); assert_eq!( access_a.get_conflicts(&access_c), - vec![ComponentId::new(0), ComponentId::new(1)].into() + vec![ComponentId::from_u32(0), ComponentId::from_u32(1)].into() ); assert_eq!( access_b.get_conflicts(&access_c), - vec![ComponentId::new(0), ComponentId::new(1)].into() + vec![ComponentId::from_u32(0), ComponentId::from_u32(1)].into() ); let mut access_d = Access::default(); - access_d.add_read(ComponentId::new(0)); + access_d.add_read(ComponentId::from_u32(0)); assert_eq!(access_d.get_conflicts(&access_a), AccessConflicts::empty()); assert_eq!(access_d.get_conflicts(&access_b), AccessConflicts::empty()); assert_eq!( access_d.get_conflicts(&access_c), - vec![ComponentId::new(0)].into() + vec![ComponentId::from_u32(0)].into() ); } #[test] fn filtered_combined_access() { let mut access_a = FilteredAccessSet::default(); - access_a.add_unfiltered_component_read(ComponentId::new(1)); + access_a.add_unfiltered_component_read(ComponentId::from_u32(1)); let mut filter_b = FilteredAccess::default(); - filter_b.add_write(ComponentId::new(1)); + filter_b.add_write(ComponentId::from_u32(1)); let conflicts = access_a.get_conflicts_single(&filter_b); assert_eq!( &conflicts, - &AccessConflicts::from(vec![ComponentId::new(1)]), + &AccessConflicts::from(vec![ComponentId::from_u32(1)]), "access_a: {access_a:?}, filter_b: {filter_b:?}" ); } @@ -1715,23 +1718,23 @@ mod tests { #[test] fn filtered_access_extend() { let mut access_a = FilteredAccess::default(); - access_a.add_read(ComponentId::new(0)); - access_a.add_read(ComponentId::new(1)); - access_a.and_with(ComponentId::new(2)); + access_a.add_read(ComponentId::from_u32(0)); + access_a.add_read(ComponentId::from_u32(1)); + access_a.and_with(ComponentId::from_u32(2)); let mut access_b = FilteredAccess::default(); - access_b.add_read(ComponentId::new(0)); - access_b.add_write(ComponentId::new(3)); - access_b.and_without(ComponentId::new(4)); + access_b.add_read(ComponentId::from_u32(0)); + access_b.add_write(ComponentId::from_u32(3)); + access_b.and_without(ComponentId::from_u32(4)); access_a.extend(&access_b); let mut expected = FilteredAccess::default(); - expected.add_read(ComponentId::new(0)); - expected.add_read(ComponentId::new(1)); - expected.and_with(ComponentId::new(2)); - expected.add_write(ComponentId::new(3)); - expected.and_without(ComponentId::new(4)); + expected.add_read(ComponentId::from_u32(0)); + expected.add_read(ComponentId::from_u32(1)); + expected.and_with(ComponentId::from_u32(2)); + expected.add_write(ComponentId::from_u32(3)); + expected.and_without(ComponentId::from_u32(4)); assert!(access_a.eq(&expected)); } @@ -1740,17 +1743,17 @@ mod tests { fn filtered_access_extend_or() { let mut access_a = FilteredAccess::default(); // Exclusive access to `(&mut A, &mut B)`. - access_a.add_write(ComponentId::new(0)); - access_a.add_write(ComponentId::new(1)); + access_a.add_write(ComponentId::from_u32(0)); + access_a.add_write(ComponentId::from_u32(1)); // Filter by `With`. let mut access_b = FilteredAccess::default(); - access_b.and_with(ComponentId::new(2)); + access_b.and_with(ComponentId::from_u32(2)); // Filter by `(With, Without)`. let mut access_c = FilteredAccess::default(); - access_c.and_with(ComponentId::new(3)); - access_c.and_without(ComponentId::new(4)); + access_c.and_with(ComponentId::from_u32(3)); + access_c.and_without(ComponentId::from_u32(4)); // Turns `access_b` into `Or<(With, (With, Without))>`. access_b.append_or(&access_c); @@ -1762,8 +1765,8 @@ mod tests { // The intention here is to test that exclusive access implied by `add_write` // forms correct normalized access structs when extended with `Or` filters. let mut expected = FilteredAccess::default(); - expected.add_write(ComponentId::new(0)); - expected.add_write(ComponentId::new(1)); + expected.add_write(ComponentId::from_u32(0)); + expected.add_write(ComponentId::from_u32(1)); // The resulted access is expected to represent `Or<((With, With, With), (With, With, With, Without))>`. expected.filter_sets = vec![ AccessFilters { @@ -1786,20 +1789,20 @@ mod tests { fn try_iter_component_access_simple() { let mut access = Access::default(); - access.add_read(ComponentId::new(1)); - access.add_read(ComponentId::new(2)); - access.add_write(ComponentId::new(3)); - access.add_archetypal(ComponentId::new(5)); + access.add_read(ComponentId::from_u32(1)); + access.add_read(ComponentId::from_u32(2)); + access.add_write(ComponentId::from_u32(3)); + access.add_archetypal(ComponentId::from_u32(5)); let result = access.try_iter_access().map(Iterator::collect::>); assert_eq!( result, Ok(vec![ - ComponentAccessKind::Shared(ComponentId::new(1)), - ComponentAccessKind::Shared(ComponentId::new(2)), - ComponentAccessKind::Exclusive(ComponentId::new(3)), - ComponentAccessKind::Archetypal(ComponentId::new(5)), + ComponentAccessKind::Shared(ComponentId::from_u32(1)), + ComponentAccessKind::Shared(ComponentId::from_u32(2)), + ComponentAccessKind::Exclusive(ComponentId::from_u32(3)), + ComponentAccessKind::Archetypal(ComponentId::from_u32(5)), ]), ); } @@ -1808,8 +1811,8 @@ mod tests { fn try_iter_component_access_unbounded_write_all() { let mut access = Access::default(); - access.add_read(ComponentId::new(1)); - access.add_read(ComponentId::new(2)); + access.add_read(ComponentId::from_u32(1)); + access.add_read(ComponentId::from_u32(2)); access.write_all(); let result = access.try_iter_access().map(Iterator::collect::>); @@ -1827,8 +1830,8 @@ mod tests { fn try_iter_component_access_unbounded_read_all() { let mut access = Access::default(); - access.add_read(ComponentId::new(1)); - access.add_read(ComponentId::new(2)); + access.add_read(ComponentId::from_u32(1)); + access.add_read(ComponentId::from_u32(2)); access.read_all(); let result = access.try_iter_access().map(Iterator::collect::>); @@ -1940,48 +1943,48 @@ mod tests { #[test] fn component_id_set_insert_remove_clear() { let mut set = ComponentIdSet::new(); - assert!(!set.contains(ComponentId::new(0))); - assert!(!set.contains(ComponentId::new(1))); - assert!(!set.contains(ComponentId::new(2))); + assert!(!set.contains(ComponentId::from_u32(0))); + assert!(!set.contains(ComponentId::from_u32(1))); + assert!(!set.contains(ComponentId::from_u32(2))); assert!(set.is_clear()); - set.insert(ComponentId::new(2)); - set.insert(ComponentId::new(1)); - assert!(!set.contains(ComponentId::new(0))); - assert!(set.contains(ComponentId::new(1))); - assert!(set.contains(ComponentId::new(2))); + set.insert(ComponentId::from_u32(2)); + set.insert(ComponentId::from_u32(1)); + assert!(!set.contains(ComponentId::from_u32(0))); + assert!(set.contains(ComponentId::from_u32(1))); + assert!(set.contains(ComponentId::from_u32(2))); assert!(!set.is_clear()); - set.remove(ComponentId::new(1)); - assert!(!set.contains(ComponentId::new(0))); - assert!(!set.contains(ComponentId::new(1))); - assert!(set.contains(ComponentId::new(2))); + set.remove(ComponentId::from_u32(1)); + assert!(!set.contains(ComponentId::from_u32(0))); + assert!(!set.contains(ComponentId::from_u32(1))); + assert!(set.contains(ComponentId::from_u32(2))); assert!(!set.is_clear()); - set.insert(ComponentId::new(2)); - set.insert(ComponentId::new(1)); - assert!(!set.contains(ComponentId::new(0))); - assert!(set.contains(ComponentId::new(1))); - assert!(set.contains(ComponentId::new(2))); + set.insert(ComponentId::from_u32(2)); + set.insert(ComponentId::from_u32(1)); + assert!(!set.contains(ComponentId::from_u32(0))); + assert!(set.contains(ComponentId::from_u32(1))); + assert!(set.contains(ComponentId::from_u32(2))); assert!(!set.is_clear()); set.clear(); - assert!(!set.contains(ComponentId::new(0))); - assert!(!set.contains(ComponentId::new(1))); - assert!(!set.contains(ComponentId::new(2))); + assert!(!set.contains(ComponentId::from_u32(0))); + assert!(!set.contains(ComponentId::from_u32(1))); + assert!(!set.contains(ComponentId::from_u32(2))); assert!(set.is_clear()); } #[test] fn component_id_set_remove_out_of_range() { let mut set = ComponentIdSet::new(); - set.remove(ComponentId::new(3)); - set.insert(ComponentId::new(1)); - set.remove(ComponentId::new(4)); - assert!(set.iter().eq([1].map(ComponentId::new))); + set.remove(ComponentId::from_u32(3)); + set.insert(ComponentId::from_u32(1)); + set.remove(ComponentId::from_u32(4)); + assert!(set.iter().eq([1].map(ComponentId::from_u32))); } #[test] fn component_id_set_is_subset_is_disjoint() { - let set_1234 = ComponentIdSet::from_iter([1, 2, 3, 4].map(ComponentId::new)); - let set_23 = ComponentIdSet::from_iter([2, 3].map(ComponentId::new)); - let set_45 = ComponentIdSet::from_iter([4, 5].map(ComponentId::new)); + let set_1234 = ComponentIdSet::from_iter([1, 2, 3, 4].map(ComponentId::from_u32)); + let set_23 = ComponentIdSet::from_iter([2, 3].map(ComponentId::from_u32)); + let set_45 = ComponentIdSet::from_iter([4, 5].map(ComponentId::from_u32)); assert!(set_23.is_subset(&set_1234)); assert!(!set_1234.is_subset(&set_23)); assert!(set_23.is_disjoint(&set_45)); @@ -1992,52 +1995,64 @@ mod tests { #[test] fn component_id_set_union_intersection_difference() { - let set_13 = ComponentIdSet::from_iter([1, 3].map(ComponentId::new)); - let set_23 = ComponentIdSet::from_iter([2, 3].map(ComponentId::new)); - - assert!(set_13.union(&set_23).eq([1, 3, 2].map(ComponentId::new))); - assert!(set_23.union(&set_13).eq([2, 3, 1].map(ComponentId::new))); - assert!(set_13.intersection(&set_23).eq([3].map(ComponentId::new))); - assert!(set_23.intersection(&set_13).eq([3].map(ComponentId::new))); - assert!(set_13.difference(&set_23).eq([1].map(ComponentId::new))); - assert!(set_23.difference(&set_13).eq([2].map(ComponentId::new))); + let set_13 = ComponentIdSet::from_iter([1, 3].map(ComponentId::from_u32)); + let set_23 = ComponentIdSet::from_iter([2, 3].map(ComponentId::from_u32)); + + assert!(set_13 + .union(&set_23) + .eq([1, 3, 2].map(ComponentId::from_u32))); + assert!(set_23 + .union(&set_13) + .eq([2, 3, 1].map(ComponentId::from_u32))); + assert!(set_13 + .intersection(&set_23) + .eq([3].map(ComponentId::from_u32))); + assert!(set_23 + .intersection(&set_13) + .eq([3].map(ComponentId::from_u32))); + assert!(set_13 + .difference(&set_23) + .eq([1].map(ComponentId::from_u32))); + assert!(set_23 + .difference(&set_13) + .eq([2].map(ComponentId::from_u32))); } #[test] fn component_id_set_union_intersection_difference_with() { - let set_13 = ComponentIdSet::from_iter([1, 3].map(ComponentId::new)); - let set_23 = ComponentIdSet::from_iter([2, 3].map(ComponentId::new)); + let set_13 = ComponentIdSet::from_iter([1, 3].map(ComponentId::from_u32)); + let set_23 = ComponentIdSet::from_iter([2, 3].map(ComponentId::from_u32)); let mut s = set_13.clone(); s.union_with(&set_23); - assert!(s.iter().eq([1, 2, 3].map(ComponentId::new))); + assert!(s.iter().eq([1, 2, 3].map(ComponentId::from_u32))); let mut s = set_23.clone(); s.union_with(&set_13); - assert!(s.iter().eq([1, 2, 3].map(ComponentId::new))); + assert!(s.iter().eq([1, 2, 3].map(ComponentId::from_u32))); let mut s = set_13.clone(); s.intersect_with(&set_23); - assert!(s.iter().eq([3].map(ComponentId::new))); + assert!(s.iter().eq([3].map(ComponentId::from_u32))); let mut s = set_23.clone(); s.intersect_with(&set_13); - assert!(s.iter().eq([3].map(ComponentId::new))); + assert!(s.iter().eq([3].map(ComponentId::from_u32))); let mut s = set_13.clone(); s.difference_with(&set_23); - assert!(s.iter().eq([1].map(ComponentId::new))); + assert!(s.iter().eq([1].map(ComponentId::from_u32))); let mut s = set_23.clone(); s.difference_with(&set_13); - assert!(s.iter().eq([2].map(ComponentId::new))); + assert!(s.iter().eq([2].map(ComponentId::from_u32))); let mut s = set_13.clone(); s.difference_from(&set_23); - assert!(s.iter().eq([2].map(ComponentId::new))); + assert!(s.iter().eq([2].map(ComponentId::from_u32))); let mut s = set_23.clone(); s.difference_from(&set_13); - assert!(s.iter().eq([1].map(ComponentId::new))); + assert!(s.iter().eq([1].map(ComponentId::from_u32))); } } diff --git a/crates/bevy_ecs/src/resource.rs b/crates/bevy_ecs/src/resource.rs index 3e8099f53b057..fd28d5b1eb0ed 100644 --- a/crates/bevy_ecs/src/resource.rs +++ b/crates/bevy_ecs/src/resource.rs @@ -211,7 +211,7 @@ impl IsResource { } /// [`ComponentId`] of the [`IsResource`] component. -pub const IS_RESOURCE: ComponentId = ComponentId::new(crate::component::IS_RESOURCE); +pub const IS_RESOURCE: ComponentId = ComponentId::from_u32(crate::component::IS_RESOURCE); #[cfg(test)] mod tests { diff --git a/crates/bevy_ecs/src/storage/sparse_set.rs b/crates/bevy_ecs/src/storage/sparse_set.rs index 13460e2defd1e..c92d5fb2c9d20 100644 --- a/crates/bevy_ecs/src/storage/sparse_set.rs +++ b/crates/bevy_ecs/src/storage/sparse_set.rs @@ -937,12 +937,12 @@ mod tests { collected_sets.sort(); assert_eq!( collected_sets, - vec![(ComponentId::new(1), 0), (ComponentId::new(2), 0),] + vec![(ComponentId::from_u32(1), 0), (ComponentId::from_u32(2), 0),] ); - fn register_component(sets: &mut SparseSets, id: usize) { + fn register_component(sets: &mut SparseSets, id: u32) { let descriptor = ComponentDescriptor::new::(); - let id = ComponentId::new(id); + let id = ComponentId::from_u32(id); let info = ComponentInfo::new(id, descriptor); sets.get_or_insert(&info); } diff --git a/crates/bevy_ecs/src/storage/table/mod.rs b/crates/bevy_ecs/src/storage/table/mod.rs index 494260c463b34..4a5b1e847ec9b 100644 --- a/crates/bevy_ecs/src/storage/table/mod.rs +++ b/crates/bevy_ecs/src/storage/table/mod.rs @@ -857,8 +857,8 @@ impl Drop for Table { mod tests { use crate::{ change_detection::{MaybeLocation, Tick}, - component::{Component, ComponentIds, Components, ComponentsRegistrator}, - entity::{Entity, EntityIndex}, + component::{Component, Components, ComponentsRegistrator}, + entity::{Entity, EntityAllocator, EntityIndex}, ptr::OwningPtr, storage::{TableBuilder, TableId, TableRow, Tables}, }; @@ -882,10 +882,10 @@ mod tests { #[test] fn table() { let mut components = Components::default(); - let mut componentids = ComponentIds::default(); + let mut allocator = EntityAllocator::default(); // SAFETY: They are both new. let mut registrator = - unsafe { ComponentsRegistrator::new(&mut components, &mut componentids) }; + unsafe { ComponentsRegistrator::new(&mut components, &mut allocator) }; let component_id = registrator.register_component::>(); let columns = &[component_id]; let mut table = TableBuilder::with_capacity(0, columns.len()) diff --git a/crates/bevy_ecs/src/world/entity_access/mod.rs b/crates/bevy_ecs/src/world/entity_access/mod.rs index a4a63e76d2cf3..7e2b1465fcf40 100644 --- a/crates/bevy_ecs/src/world/entity_access/mod.rs +++ b/crates/bevy_ecs/src/world/entity_access/mod.rs @@ -99,7 +99,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::from_u32(u32::MAX - 1); let mut world = World::new(); let entity = world.spawn_empty().id(); @@ -109,7 +109,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::from_u32(u32::MAX - 1); let mut world = World::new(); let mut entity = world.spawn_empty(); diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index 8ee75de95f147..47f8f545fa706 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -44,7 +44,7 @@ use crate::{ CheckChangeTicks, ComponentTicks, ComponentTicksMut, MaybeLocation, MutUntyped, Tick, }, component::{ - Component, ComponentDescriptor, ComponentId, ComponentIds, ComponentInfo, Components, + Component, ComponentDescriptor, ComponentId, ComponentInfo, Components, ComponentsQueuedRegistrator, ComponentsRegistrator, Mutable, RequiredComponents, RequiredComponentsError, }, @@ -100,7 +100,6 @@ pub struct World { pub(crate) entities: Entities, pub(crate) entity_allocator: EntityAllocator, pub(crate) components: Components, - pub(crate) component_ids: ComponentIds, pub(crate) resource_entities: ResourceEntities, pub(crate) archetypes: Archetypes, pub(crate) storages: Storages, @@ -134,7 +133,6 @@ impl Default for World { last_check_tick: Tick::new(0), last_trigger_id: 0, command_queue: RawCommandQueue::new(), - component_ids: ComponentIds::default(), }; world.bootstrap(); world @@ -271,14 +269,14 @@ impl World { #[inline] pub fn components_queue(&self) -> ComponentsQueuedRegistrator<'_> { // SAFETY: These are from the same world. - unsafe { ComponentsQueuedRegistrator::new(&self.components, &self.component_ids) } + unsafe { ComponentsQueuedRegistrator::new(&self.components, &self.entity_allocator) } } /// Prepares a [`ComponentsRegistrator`] for the world. #[inline] pub fn components_registrator(&mut self) -> ComponentsRegistrator<'_> { // SAFETY: These are from the same world. - unsafe { ComponentsRegistrator::new(&mut self.components, &mut self.component_ids) } + unsafe { ComponentsRegistrator::new(&mut self.components, &mut self.entity_allocator) } } /// Retrieves this world's [`Storages`] collection. @@ -3330,7 +3328,7 @@ impl World { pub(crate) fn register_bundle_info(&mut self) -> BundleId { // SAFETY: These come from the same world. `Self.components_registrator` can't be used since we borrow other fields too. let mut registrator = - unsafe { ComponentsRegistrator::new(&mut self.components, &mut self.component_ids) }; + unsafe { ComponentsRegistrator::new(&mut self.components, &mut self.entity_allocator) }; // SAFETY: `registrator`, `self.storages` and `self.bundles` all come from this world. unsafe { @@ -3342,7 +3340,7 @@ impl World { pub(crate) fn register_contributed_bundle_info(&mut self) -> BundleId { // SAFETY: These come from the same world. `Self.components_registrator` can't be used since we borrow other fields too. let mut registrator = - unsafe { ComponentsRegistrator::new(&mut self.components, &mut self.component_ids) }; + unsafe { ComponentsRegistrator::new(&mut self.components, &mut self.entity_allocator) }; // SAFETY: `registrator`, `self.bundles` and `self.storages` are all from this world. unsafe { From 064e48109372a3c8a9fbaea120d24f84924f5012 Mon Sep 17 00:00:00 2001 From: Trashtalk Date: Fri, 24 Apr 2026 19:57:09 +0200 Subject: [PATCH 05/22] Revert "derive Component for ComponentInfo" This reverts commit ba5d0953c4cde6ae3c741c179f1b4ea3d3c446a3. --- crates/bevy_ecs/src/component/info.rs | 2 +- crates/bevy_ecs/src/component/required.rs | 35 +++++++++-------------- crates/bevy_ecs/src/relationship/mod.rs | 8 +++--- 3 files changed, 18 insertions(+), 27 deletions(-) diff --git a/crates/bevy_ecs/src/component/info.rs b/crates/bevy_ecs/src/component/info.rs index 4d94cf5e9b2b6..bb21893247af0 100644 --- a/crates/bevy_ecs/src/component/info.rs +++ b/crates/bevy_ecs/src/component/info.rs @@ -30,7 +30,7 @@ use crate::{ }; /// Stores metadata for a type of component or resource stored in a specific [`World`](crate::world::World). -#[derive(Debug, Clone, Component)] +#[derive(Debug, Clone)] pub struct ComponentInfo { pub(super) id: ComponentId, pub(super) descriptor: ComponentDescriptor, diff --git a/crates/bevy_ecs/src/component/required.rs b/crates/bevy_ecs/src/component/required.rs index 673d683d96e23..c1a9f06b8cf8f 100644 --- a/crates/bevy_ecs/src/component/required.rs +++ b/crates/bevy_ecs/src/component/required.rs @@ -15,7 +15,7 @@ use crate::{ }; /// Metadata associated with a required component. See [`Component`] for details. -#[derive(Clone, Component)] +#[derive(Clone)] pub struct RequiredComponent { /// The constructor used for the required component. pub constructor: RequiredComponentConstructor, @@ -25,12 +25,7 @@ pub struct RequiredComponent { #[derive(Clone)] pub struct RequiredComponentConstructor( // Note: this function makes `unsafe` assumptions, so it cannot be public. - Arc< - dyn Fn(&mut Table, &mut SparseSets, Tick, TableRow, Entity, MaybeLocation) - + Send - + Sync - + 'static, - >, + Arc, ); impl RequiredComponentConstructor { @@ -41,7 +36,7 @@ impl RequiredComponentConstructor { /// - `component_id` must be a valid component for type `C`. pub unsafe fn new( component_id: ComponentId, - constructor: impl Fn() -> C + Send + Sync + 'static, + constructor: impl Fn() -> C + 'static, ) -> Self { RequiredComponentConstructor({ // `portable-atomic-util` `Arc` is not able to coerce an unsized @@ -54,14 +49,13 @@ impl RequiredComponentConstructor { use alloc::boxed::Box; type Constructor = dyn for<'a, 'b> Fn( - &'a mut Table, - &'b mut SparseSets, - Tick, - TableRow, - Entity, - MaybeLocation, - ) + Send - + Sync; + &'a mut Table, + &'b mut SparseSets, + Tick, + TableRow, + Entity, + MaybeLocation, + ); #[cfg(not(target_has_atomic = "ptr"))] type Intermediate = Box; @@ -161,7 +155,7 @@ impl RequiredComponents { unsafe fn register( &mut self, components: &mut ComponentsRegistrator<'_>, - constructor: impl Fn() -> C + Send + Sync + 'static, + constructor: impl Fn() -> C + 'static, ) { let id = components.register_component::(); // SAFETY: @@ -183,7 +177,7 @@ impl RequiredComponents { &mut self, component_id: ComponentId, components: &Components, - constructor: impl Fn() -> C + Send + Sync + 'static, + constructor: impl Fn() -> C + 'static, ) { // SAFETY: the caller guarantees that `component_id` is valid for the type `C`. let constructor = @@ -594,10 +588,7 @@ impl<'a, 'w> RequiredComponentsRegistrator<'a, 'w> { /// /// If the component was not already registered as an explicit required component then it is added /// as one, potentially overriding the constructor of a inherited required component, otherwise panics. - pub fn register_required( - &mut self, - constructor: impl Fn() -> C + Send + Sync + 'static, - ) { + pub fn register_required(&mut self, constructor: impl Fn() -> C + 'static) { // SAFETY: we internally guarantee that all components in `required_components` // are registered in `components` unsafe { diff --git a/crates/bevy_ecs/src/relationship/mod.rs b/crates/bevy_ecs/src/relationship/mod.rs index 8ae18dc0fe8bf..184b126407daa 100644 --- a/crates/bevy_ecs/src/relationship/mod.rs +++ b/crates/bevy_ecs/src/relationship/mod.rs @@ -523,7 +523,7 @@ pub enum RelationshipAccessorInitializer { allow_self_referential: bool, /// Getter for [`ComponentId`] of the [`RelationshipTarget`] counterpart. /// Should return `None` if [`RelationshipTarget`] isn't registered yet. - relationship_target_getter: Arc Option + Send + Sync>, + relationship_target_getter: Arc Option>, }, /// Describes a [`RelationshipTarget`] component. RelationshipTarget { @@ -539,7 +539,7 @@ pub enum RelationshipAccessorInitializer { allow_self_referential: bool, /// Getter for [`ComponentId`] of the [`Relationship`] counterpart. /// Should return `None` if [`Relationship`] isn't registered yet. - relationship_getter: Arc Option + Send + Sync>, + relationship_getter: Arc Option>, }, } @@ -741,7 +741,7 @@ impl ComponentRelationshipAccessor { C: Relationship, { // Due to https://github.com/taiki-e/portable-atomic/issues/143 we have to box this first, and then get the Arc from the box - let getter: Box Option + Send + Sync> = + let getter: Box Option> = Box::new(|components| components.get_id(TypeId::of::())); Self { initializer: RelationshipAccessorInitializer::Relationship { @@ -760,7 +760,7 @@ impl ComponentRelationshipAccessor { C: RelationshipTarget, { // Due to https://github.com/taiki-e/portable-atomic/issues/143 we have to box this first, and then get the Arc from the box - let getter: Box Option + Send + Sync> = + let getter: Box Option> = Box::new(|components| components.get_id(TypeId::of::())); Self { initializer: RelationshipAccessorInitializer::RelationshipTarget { From b246b70cb7e7877bd142a70918b0f9be35f72832 Mon Sep 17 00:00:00 2001 From: Trashtalk Date: Fri, 24 Apr 2026 22:42:57 +0200 Subject: [PATCH 06/22] removed ResourceEntities --- crates/bevy_ecs/src/component/info.rs | 6 + crates/bevy_ecs/src/resource.rs | 116 +++++------------- crates/bevy_ecs/src/storage/sparse_set.rs | 2 + crates/bevy_ecs/src/world/mod.rs | 62 +++++----- crates/bevy_ecs/src/world/reflect.rs | 9 +- .../bevy_ecs/src/world/unsafe_world_cell.rs | 19 +-- 6 files changed, 83 insertions(+), 131 deletions(-) diff --git a/crates/bevy_ecs/src/component/info.rs b/crates/bevy_ecs/src/component/info.rs index bb21893247af0..360a3f6782993 100644 --- a/crates/bevy_ecs/src/component/info.rs +++ b/crates/bevy_ecs/src/component/info.rs @@ -206,6 +206,12 @@ impl ComponentId { pub fn index(self) -> usize { self.0.index_u32() as usize } + + /// Returns the inner entity from the ComponentId + #[inline] + pub fn entity(self) -> Entity { + self.0 + } } // Identical implementation as Entity diff --git a/crates/bevy_ecs/src/resource.rs b/crates/bevy_ecs/src/resource.rs index fd28d5b1eb0ed..4fefaba63de09 100644 --- a/crates/bevy_ecs/src/resource.rs +++ b/crates/bevy_ecs/src/resource.rs @@ -4,16 +4,13 @@ use log::warn; use crate::{ component::{Component, ComponentId, Mutable}, - entity::Entity, lifecycle::HookContext, - storage::SparseArray, world::DeferredWorld, }; #[cfg(feature = "bevy_reflect")] use {crate::reflect::ReflectComponent, bevy_reflect::Reflect}; // The derive macro for the `Resource` trait pub use bevy_ecs_macros::Resource; -use bevy_platform::cell::SyncUnsafeCell; /// A type that can be inserted into a [`World`] as a singleton. /// @@ -86,37 +83,6 @@ use bevy_platform::cell::SyncUnsafeCell; )] pub trait Resource: Component {} -/// A cache that links each `ComponentId` from a resource to the corresponding entity. -#[derive(Default)] -pub struct ResourceEntities(SyncUnsafeCell>); - -impl ResourceEntities { - /// Returns an iterator over all registered resource components and their corresponding entity. - /// - /// This must scan the entire array of components to find non-empty values, - /// which may be slow even if there are few resources. - #[inline] - pub fn iter(&self) -> impl Iterator { - self.deref().iter().map(|(id, entity)| (id, *entity)) - } - - /// Returns the entity for the given resource component, or `None` if there is no entity. - #[inline] - pub fn get(&self, id: ComponentId) -> Option { - self.deref().get(id).copied() - } - - #[inline] - fn deref(&self) -> &SparseArray { - // SAFETY: There are no other mutable references to the map. - // The underlying `SyncUnsafeCell` is never exposed outside this module, - // so mutable references are only created by the resource hooks. - // We only expose `&ResourceCache` to code with access to a resource (such as `&World`), - // and that would conflict with the `DeferredWorld` passed to the resource hook. - unsafe { &*self.0.get() } - } -} - /// A marker component for entities that have a Resource component. #[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Component, Debug))] #[derive(Component, Debug)] @@ -140,44 +106,36 @@ impl IsResource { .get::() .unwrap() .resource_component_id(); + let original_entity = resource_component_id.entity(); + + if !world.entities().contains(original_entity) { + let name = world + .components() + .get_name(resource_component_id) + .expect("resource is registered"); + panic!( + "Resource entity {} of {} has been despawned, when it's not supposed to be.", + original_entity, name + ); + } - if let Some(original_entity) = world.resource_entities.get(resource_component_id) { - if !world.entities().contains(original_entity) { - let name = world - .components() - .get_name(resource_component_id) - .expect("resource is registered"); - panic!( - "Resource entity {} of {} has been despawned, when it's not supposed to be.", - original_entity, name - ); - } - - if original_entity != context.entity { - // the resource already exists and the new one should be removed - world - .commands() - .entity(context.entity) - .remove_by_id(resource_component_id); - world - .commands() - .entity(context.entity) - .remove_by_id(context.component_id); - let name = world - .components() - .get_name(resource_component_id) - .expect("resource is registered"); - warn!("Tried inserting the resource {} while one already exists. + if original_entity != context.entity { + // the resource already exists and the new one should be removed + world + .commands() + .entity(context.entity) + .remove_by_id(resource_component_id); + world + .commands() + .entity(context.entity) + .remove_by_id(context.component_id); + let name = world + .components() + .get_name(resource_component_id) + .expect("resource is registered"); + warn!("Tried inserting the resource {} while one already exists. Resources are unique components stored on a single entity. Inserting on a different entity, when one already exists, causes the new value to be removed.", name); - } - } else { - // SAFETY: We have exclusive world access (as long as we don't make structural changes). - let cache = unsafe { world.as_unsafe_world_cell().resource_entities() }; - // SAFETY: There are no shared references to the map. - // We only expose `&ResourceCache` to code with access to a resource (such as `&World`), - // and that would conflict with the `DeferredWorld` passed to the resource hook. - unsafe { &mut *cache.0.get() }.insert(resource_component_id, context.entity); } } @@ -187,17 +145,9 @@ impl IsResource { .get::() .unwrap() .resource_component_id(); + let original_entity = resource_component_id.entity(); - if let Some(resource_entity) = world.resource_entities.get(resource_component_id) - && resource_entity == context.entity - { - // SAFETY: We have exclusive world access (as long as we don't make structural changes). - let cache = unsafe { world.as_unsafe_world_cell().resource_entities() }; - // SAFETY: There are no shared references to the map. - // We only expose `&ResourceCache` to code with access to a resource (such as `&World`), - // and that would conflict with the `DeferredWorld` passed to the resource hook. - unsafe { &mut *cache.0.get() }.remove(resource_component_id); - + if original_entity == context.entity { world .commands() .entity(context.entity) @@ -255,7 +205,7 @@ mod tests { } }); assert_eq!(world.entities().count_spawned(), start + 3); - let e3 = world.resource_entities().get(id3).unwrap(); + let e3 = id3.entity(); assert!(world.remove_resource_by_id(id3)); // the entity is stable: removing the resource should only remove the component from the entity, not despawn the entity assert_eq!(world.entities().count_spawned(), start + 3); @@ -265,13 +215,13 @@ mod tests { world.insert_resource_by_id(id3, ptr, MaybeLocation::caller()); } }); - assert_eq!(e3, world.resource_entities().get(id3).unwrap()); + assert_eq!(e3, id3.entity()); // again, the entity is stable: see previous explanation - let e1 = world.resource_entities().get(id1).unwrap(); + let e1 = id1.entity(); world.remove_resource::(); assert_eq!(world.entities().count_spawned(), start + 3); world.init_resource::(); - assert_eq!(e1, world.resource_entities().get(id1).unwrap()); + assert_eq!(e1, id1.entity()); // make sure that trying to add a resource twice results, doesn't change the entity count world.insert_resource(TestResource2(String::from("Bar"))); assert_eq!(world.entities().count_spawned(), start + 3); diff --git a/crates/bevy_ecs/src/storage/sparse_set.rs b/crates/bevy_ecs/src/storage/sparse_set.rs index c92d5fb2c9d20..b18394d13b80f 100644 --- a/crates/bevy_ecs/src/storage/sparse_set.rs +++ b/crates/bevy_ecs/src/storage/sparse_set.rs @@ -136,6 +136,7 @@ impl SparseArray { } } + /* TODO: Maybe change the data structure in Components /// Returns an iterator over the non-empty values in the array. /// /// This must scan the entire array to find non-empty values, @@ -148,6 +149,7 @@ impl SparseArray { .map(|value| (SparseSetIndex::get_sparse_set_index(index), value)) }) } + */ } /// A sparse data structure of [`Component`](crate::component::Component)s. diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index 47f8f545fa706..d90600a9df0df 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -57,7 +57,7 @@ use crate::{ prelude::{Add, Despawn, DetectChangesMut, Discard, Insert, Remove}, query::{DebugCheckedUnwrap, QueryData, QueryFilter, QueryState}, relationship::RelationshipHookMode, - resource::{IsResource, Resource, ResourceEntities, IS_RESOURCE}, + resource::{IsResource, Resource, IS_RESOURCE}, schedule::{Schedule, ScheduleLabel, Schedules}, storage::{NonSendData, Storages}, system::Commands, @@ -100,7 +100,6 @@ pub struct World { pub(crate) entities: Entities, pub(crate) entity_allocator: EntityAllocator, pub(crate) components: Components, - pub(crate) resource_entities: ResourceEntities, pub(crate) archetypes: Archetypes, pub(crate) storages: Storages, pub(crate) bundles: Bundles, @@ -120,7 +119,6 @@ impl Default for World { entities: Entities::new(), entity_allocator: EntityAllocator::default(), components: Default::default(), - resource_entities: Default::default(), archetypes: Archetypes::new(), storages: Default::default(), bundles: Default::default(), @@ -257,12 +255,6 @@ impl World { &self.components } - /// Retrieves this world's [`ResourceEntities`]. - #[inline] - pub fn resource_entities(&self) -> &ResourceEntities { - &self.resource_entities - } - /// Prepares a [`ComponentsQueuedRegistrator`] for the world. /// **NOTE:** [`ComponentsQueuedRegistrator`] is easily misused. /// See its docs for important notes on when and how it should be used. @@ -1856,10 +1848,10 @@ impl World { func: impl FnOnce(&mut World) -> R, caller: MaybeLocation, ) -> (ComponentId, EntityWorldMut<'_>) { - let resource_id = self.register_resource::(); + let resource_id: ComponentId = self.register_resource::(); + let entity = resource_id.entity(); - if let Some(entity) = self.resource_entities.get(resource_id) { - let entity_ref = self.get_entity(entity).expect("ResourceCache is in sync"); + if let Ok(entity_ref) = self.get_entity(entity) { if !entity_ref.contains_id(resource_id) { let resource = func(self); move_as_ptr!(resource); @@ -1875,7 +1867,7 @@ impl World { let resource = func(self); move_as_ptr!(resource); - let entity_mut = self.spawn_with_caller(resource, caller); // ResourceCache is updated automatically + let entity_mut = self.spawn_at_unchecked(entity, resource, caller); (resource_id, entity_mut) } @@ -1993,9 +1985,8 @@ impl World { #[inline] pub fn remove_resource(&mut self) -> Option { let resource_id = self.component_id::()?; - let entity = self.resource_entities.get(resource_id)?; let value = self - .get_entity_mut(entity) + .get_entity_mut(resource_id.entity()) .expect("ResourceCache is in sync") .take::()?; Some(value) @@ -2037,9 +2028,7 @@ impl World { /// Returns `true` if a resource with provided `component_id` exists. Otherwise returns `false`. #[inline] pub fn contains_resource_by_id(&self, component_id: ComponentId) -> bool { - if let Some(entity) = self.resource_entities.get(component_id) - && let Ok(entity_ref) = self.get_entity(entity) - { + if let Ok(entity_ref) = self.get_entity(component_id.entity()) { return entity_ref.contains_id(component_id); } false @@ -2127,8 +2116,7 @@ impl World { &self, component_id: ComponentId, ) -> Option { - let entity = self.resource_entities.get(component_id)?; - let entity_ref = self.get_entity(entity).ok()?; + let entity_ref = self.get_entity(component_id.entity()).ok()?; entity_ref.get_change_ticks_by_id(component_id) } @@ -2779,7 +2767,7 @@ impl World { let change_tick = self.change_tick(); let component_id = self.components.valid_component_id::()?; - let entity = self.resource_entities.get(component_id)?; + let entity = component_id.entity(); let mut entity_mut = self.get_entity_mut(entity).ok()?; let mut ticks = entity_mut.get_change_ticks::()?; @@ -2970,13 +2958,14 @@ impl World { value: OwningPtr<'_>, caller: MaybeLocation, ) { + let entity = component_id.entity(); // if the resource already exists, we replace it on the same entity - let mut entity_mut = if let Some(entity) = self.resource_entities.get(component_id) { - self.get_entity_mut(entity) - .expect("ResourceCache is in sync") + let mut entity_mut = if let Ok(entity_mut) = self.get_entity_mut(entity) { + entity_mut } else { - self.spawn_empty() + self.spawn_empty_at(entity).unwrap() // doesn't already exist }; + entity_mut.insert_by_id_with_caller( component_id, value, @@ -3301,9 +3290,16 @@ impl World { /// This can easily cause systems expecting certain resources to immediately start panicking. /// Use with caution. pub fn clear_resources(&mut self) { - let pairs: Vec<(ComponentId, Entity)> = self.resource_entities().iter().collect(); - for (component_id, entity) in pairs { - self.entity_mut(entity).remove_by_id(component_id); + let ids: Vec = self + .components + .iter_registered() + .map(ComponentInfo::id) + .collect(); + for component_id in ids { + let entity = component_id.entity(); + if let Ok(mut entity_mut) = self.get_entity_mut(entity) { + entity_mut.remove_by_id(component_id); + } } } @@ -3502,6 +3498,7 @@ impl World { /// ``` #[inline] pub fn iter_resources(&self) -> impl Iterator)> { + /* self.resource_entities .iter() .filter_map(|(component_id, entity)| { @@ -3510,6 +3507,8 @@ impl World { let resource = entity_cell.get_by_id(component_id).ok()?; Some((component_info, resource)) }) + */ + core::iter::empty() // TODO: Fix iter_resources } /// Mutably iterates over all resources in the world. @@ -3578,6 +3577,7 @@ impl World { /// # assert_eq!(world.resource::().0, 3); /// ``` pub fn iter_resources_mut(&mut self) -> impl Iterator)> { + /* let unsafe_world = self.as_unsafe_world_cell(); // SAFETY: exclusive world access to all resources let resource_entities = unsafe { unsafe_world.resource_entities() }; @@ -3602,6 +3602,8 @@ impl World { Some((component_info, mut_untyped)) }) + */ + core::iter::empty() // TODO: Fix iter_resources_mut } /// Gets a pointer to `!Send` data with the id [`ComponentId`] if it exists. @@ -3651,8 +3653,8 @@ impl World { /// **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) -> bool { - if let Some(entity) = self.resource_entities.get(component_id) - && let Ok(mut entity_mut) = self.get_entity_mut(entity) + let entity = component_id.entity(); + if let Ok(mut entity_mut) = self.get_entity_mut(entity) && entity_mut.contains_id(component_id) { entity_mut.remove_by_id(component_id); diff --git a/crates/bevy_ecs/src/world/reflect.rs b/crates/bevy_ecs/src/world/reflect.rs index 0c7909d5bc4aa..d94bcf5245dc0 100644 --- a/crates/bevy_ecs/src/world/reflect.rs +++ b/crates/bevy_ecs/src/world/reflect.rs @@ -199,10 +199,13 @@ impl World { resource_id: ComponentId, reflected_resource: Box, ) { - if let Some(entity) = self.resource_entities().get(resource_id) { - self.entity_mut(entity).insert_reflect(reflected_resource); + let entity = resource_id.entity(); + if let Ok(mut entity_mut) = self.get_entity_mut(entity) { + entity_mut.insert_reflect(reflected_resource); } else { - self.spawn_empty().insert_reflect(reflected_resource); + self.spawn_empty_at(entity) + .unwrap() + .insert_reflect(reflected_resource); } } } diff --git a/crates/bevy_ecs/src/world/unsafe_world_cell.rs b/crates/bevy_ecs/src/world/unsafe_world_cell.rs index 7a9c7a8fa3757..98adb7a42bb7d 100644 --- a/crates/bevy_ecs/src/world/unsafe_world_cell.rs +++ b/crates/bevy_ecs/src/world/unsafe_world_cell.rs @@ -17,7 +17,7 @@ use crate::{ observer::Observers, prelude::Component, query::{DebugCheckedUnwrap, QueryAccessError, ReleaseStateQueryData, SingleEntityQueryData}, - resource::{Resource, ResourceEntities}, + resource::Resource, storage::{ComponentSparseSet, Storages, Table}, world::RawCommandQueue, }; @@ -288,17 +288,6 @@ impl<'w> UnsafeWorldCell<'w> { &unsafe { self.world_metadata() }.components } - /// Retrieves this world's resource-entity map. - /// - /// # Safety - /// The caller must have exclusive read or write access to the resources that are updated in the cache. - #[inline] - pub unsafe fn resource_entities(self) -> &'w ResourceEntities { - // SAFETY: - // - we only access world metadata - &unsafe { self.world_metadata() }.resource_entities - } - /// Retrieves this world's collection of [removed components](RemovedComponentMessages). pub fn removed_components(self) -> &'w RemovedComponentMessages { // SAFETY: @@ -465,7 +454,7 @@ impl<'w> UnsafeWorldCell<'w> { #[inline] pub unsafe fn get_resource_by_id(self, component_id: ComponentId) -> Option> { // SAFETY: We have permission to access the resource of `component_id`. - let entity = unsafe { self.resource_entities() }.get(component_id)?; + let entity = component_id.entity(); let entity_cell = self.get_entity(entity).ok()?; entity_cell.get_by_id(component_id) } @@ -574,7 +563,7 @@ impl<'w> UnsafeWorldCell<'w> { ) -> Option> { self.assert_allows_mutable_access(); // SAFETY: We have permission to access the resource of `component_id`. - let entity = unsafe { self.resource_entities() }.get(component_id)?; + let entity = component_id.entity(); let entity_cell = self.get_entity(entity).ok()?; entity_cell.get_mut_by_id(component_id).ok() } @@ -678,7 +667,7 @@ impl<'w> UnsafeWorldCell<'w> { component_id: ComponentId, ) -> Option<(Ptr<'w>, ComponentTickCells<'w>)> { // SAFETY: We have permission to access the resource of `component_id`. - let entity = unsafe { self.resource_entities() }.get(component_id)?; + let entity = component_id.entity(); let storage_type = self.components().get_info(component_id)?.storage_type(); let location = self.get_entity(entity).ok()?.location(); // SAFETY: From 505dec44e6f144424311adc8c3ae52d139d6eec4 Mon Sep 17 00:00:00 2001 From: Trashtalk Date: Sat, 25 Apr 2026 02:41:23 +0200 Subject: [PATCH 07/22] fixed some tests --- crates/bevy_ecs/src/component/info.rs | 35 +++++++++++++---------- crates/bevy_ecs/src/component/register.rs | 2 +- crates/bevy_ecs/src/name.rs | 2 +- crates/bevy_ecs/src/observer/mod.rs | 2 +- crates/bevy_ecs/src/resource.rs | 12 +++++--- crates/bevy_ecs/src/storage/sparse_set.rs | 2 +- crates/bevy_ecs/src/world/mod.rs | 21 +++++++------- 7 files changed, 42 insertions(+), 34 deletions(-) diff --git a/crates/bevy_ecs/src/component/info.rs b/crates/bevy_ecs/src/component/info.rs index 360a3f6782993..7a891279f636a 100644 --- a/crates/bevy_ecs/src/component/info.rs +++ b/crates/bevy_ecs/src/component/info.rs @@ -1,6 +1,6 @@ use alloc::borrow::Cow; use bevy_ecs_macros::MapEntities; -use bevy_platform::{hash::FixedHasher, sync::PoisonError}; +use bevy_platform::{collections::HashMap, hash::FixedHasher, sync::PoisonError}; use bevy_ptr::OwningPtr; #[cfg(feature = "bevy_reflect")] use bevy_reflect::Reflect; @@ -19,7 +19,7 @@ use crate::{ Component, ComponentCloneBehavior, ComponentMutability, QueuedComponents, RequiredComponents, StorageType, }, - entity::{Entity, EntityHashMap}, + entity::Entity, lifecycle::ComponentHooks, query::DebugCheckedUnwrap as _, relationship::{ @@ -373,7 +373,7 @@ impl ComponentDescriptor { /// Stores metadata associated with each kind of [`Component`] in a given [`World`](crate::world::World). #[derive(Debug, Default)] pub struct Components { - pub(super) components: EntityHashMap, + pub(super) components: HashMap, pub(super) indices: TypeIdMap, // This is kept internal and local to verify that no deadlocks can occur. pub(super) queued: bevy_platform::sync::RwLock, @@ -395,7 +395,7 @@ impl Components { let info = ComponentInfo::new(id, descriptor); // SAFETY: The id has never been registered before. unsafe { - self.components.insert_unique_unchecked(id.0, info); + self.components.insert_unique_unchecked(id, info); } } @@ -458,7 +458,7 @@ impl Components { /// This will return an incorrect result if `id` did not come from the same world as `self`. It may return `None` or a garbage value. #[inline] pub fn get_info(&self, id: ComponentId) -> Option<&ComponentInfo> { - self.components.get(&id.0).and_then(|info| Some(info)) + self.components.get(&id).and_then(|info| Some(info)) } /// Gets the [`ComponentDescriptor`] of the component with this [`ComponentId`] if it is present. @@ -470,7 +470,7 @@ impl Components { #[inline] pub fn get_descriptor<'a>(&'a self, id: ComponentId) -> Option> { self.components - .get(&id.0) + .get(&id) .and_then(|info| Some(Cow::Borrowed(&info.descriptor))) .or_else(|| { let queued = self.queued.read().unwrap_or_else(PoisonError::into_inner); @@ -491,7 +491,7 @@ impl Components { #[inline] pub fn get_name<'a>(&'a self, id: ComponentId) -> Option { self.components - .get(&id.0) + .get(&id) .and_then(|info| Some(info.descriptor.name())) .or_else(|| { let queued = self.queued.read().unwrap_or_else(PoisonError::into_inner); @@ -512,20 +512,20 @@ impl Components { #[inline] pub unsafe fn get_info_unchecked(&self, id: ComponentId) -> &ComponentInfo { // SAFETY: The caller ensures `id` is valid. - unsafe { self.components.get(&id.0).debug_checked_unwrap() } + unsafe { self.components.get(&id).debug_checked_unwrap() } } #[inline] pub(crate) fn get_hooks_mut(&mut self, id: ComponentId) -> Option<&mut ComponentHooks> { self.components - .get_mut(&id.0) + .get_mut(&id) .and_then(|info| Some(&mut info.hooks)) } #[inline] pub(crate) fn get_required_components(&self, id: ComponentId) -> Option<&RequiredComponents> { self.components - .get(&id.0) + .get(&id) .and_then(|info| Some(&info.required_components)) } @@ -535,7 +535,7 @@ impl Components { id: ComponentId, ) -> Option<&mut RequiredComponents> { self.components - .get_mut(&id.0) + .get_mut(&id) .and_then(|info| Some(&mut info.required_components)) } @@ -545,7 +545,7 @@ impl Components { id: ComponentId, ) -> Option<&IndexSet> { self.components - .get(&id.0) + .get(&id) .and_then(|info| Some(&info.required_by)) } @@ -555,7 +555,7 @@ impl Components { id: ComponentId, ) -> Option<&mut IndexSet> { self.components - .get_mut(&id.0) + .get_mut(&id) .and_then(|info| Some(&mut info.required_by)) } @@ -564,7 +564,7 @@ impl Components { /// Those ids are still correct, but they are not usable in every context yet. #[inline] pub fn is_id_valid(&self, id: ComponentId) -> bool { - self.components.get(&id.0).is_some() + self.components.get(&id).is_some() } /// Type-erased equivalent of [`Components::valid_component_id()`]. @@ -749,12 +749,17 @@ impl Components { self.components.values() } + /// Gets an iterator over all component ids fully registered with this instance. + pub(crate) fn iter_registered_ids(&self) -> impl Iterator + '_ { + self.components.keys().copied() + } + pub(crate) fn get_relationship_accessor_mut( &mut self, component_id: ComponentId, ) -> Option<&mut MaybeRelationshipAccessor> { self.components - .get_mut(&component_id.0) + .get_mut(&component_id) .and_then(|info| Some(&mut info.descriptor.relationship_accessor)) } } diff --git a/crates/bevy_ecs/src/component/register.rs b/crates/bevy_ecs/src/component/register.rs index f52754e9841f9..fc8c1e79dab3b 100644 --- a/crates/bevy_ecs/src/component/register.rs +++ b/crates/bevy_ecs/src/component/register.rs @@ -204,7 +204,7 @@ impl<'w> ComponentsRegistrator<'w> { &mut self .components .components - .get_mut(&id.0) + .get_mut(&id) .debug_checked_unwrap() }; diff --git a/crates/bevy_ecs/src/name.rs b/crates/bevy_ecs/src/name.rs index 721881542858f..6ac8c72fbe400 100644 --- a/crates/bevy_ecs/src/name.rs +++ b/crates/bevy_ecs/src/name.rs @@ -293,7 +293,7 @@ mod tests { let mut query = world.query::(); let d1 = query.get(&world, e1).unwrap(); // NameOrEntity Display for entities without a Name should be {index}v{generation} - assert_eq!(d1.to_string(), "1v0"); + assert_eq!(d1.to_string(), "9v0"); let d2 = query.get(&world, e2).unwrap(); // NameOrEntity Display for entities with a Name should be the Name assert_eq!(d2.to_string(), "MyName"); diff --git a/crates/bevy_ecs/src/observer/mod.rs b/crates/bevy_ecs/src/observer/mod.rs index a1e1b9217e70c..f920769a0e810 100644 --- a/crates/bevy_ecs/src/observer/mod.rs +++ b/crates/bevy_ecs/src/observer/mod.rs @@ -440,7 +440,7 @@ mod tests { world.add_observer(|_: On, mut res: ResMut| res.observed("add_2")); world.spawn(A).flush(); - assert_eq!(vec!["add_2", "add_1"], world.resource::().0); + assert_eq!(vec!["add_1", "add_2"], world.resource::().0); // we have one A entity and two observers assert_eq!(world.query::<&A>().query(&world).count(), 1); assert_eq!(world.query::<&Observer>().query(&world).count(), 2); diff --git a/crates/bevy_ecs/src/resource.rs b/crates/bevy_ecs/src/resource.rs index 4fefaba63de09..cabad31564e3a 100644 --- a/crates/bevy_ecs/src/resource.rs +++ b/crates/bevy_ecs/src/resource.rs @@ -242,7 +242,9 @@ mod tests { let resources = query.iter(&world).collect::>(); assert_eq!(resources.len(), 1); let (entity, _test_resource, is_resource) = resources[0]; - assert_eq!(is_resource.resource_component_id(), id); + let resource_id = is_resource.resource_component_id(); + assert_eq!(resource_id, id); + assert_eq!(resource_id.entity(), entity); entity }; @@ -261,13 +263,15 @@ mod tests { let resources = query.iter(&world).collect::>(); assert_eq!(resources.len(), 1); let (entity, _test_resource, is_resource) = resources[0]; - assert_eq!(is_resource.resource_component_id(), id); + let resource_id = is_resource.resource_component_id(); + assert_eq!(resource_id, id); + assert_eq!(resource_id.entity(), entity); entity }; - assert_ne!( + assert_eq!( first_entity, second_entity, - "The first resource entity was invalidated, so the second initialization should be new" + "The entity on which a resource is spawned is always the same." ); let id = world.spawn(TestResource).id(); diff --git a/crates/bevy_ecs/src/storage/sparse_set.rs b/crates/bevy_ecs/src/storage/sparse_set.rs index b18394d13b80f..5be514ddb870c 100644 --- a/crates/bevy_ecs/src/storage/sparse_set.rs +++ b/crates/bevy_ecs/src/storage/sparse_set.rs @@ -939,7 +939,7 @@ mod tests { collected_sets.sort(); assert_eq!( collected_sets, - vec![(ComponentId::from_u32(1), 0), (ComponentId::from_u32(2), 0),] + vec![(ComponentId::from_u32(2), 0), (ComponentId::from_u32(1), 0),] ); fn register_component(sets: &mut SparseSets, id: u32) { diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index d90600a9df0df..6a124d5cd8cff 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -3498,17 +3498,15 @@ impl World { /// ``` #[inline] pub fn iter_resources(&self) -> impl Iterator)> { - /* - self.resource_entities - .iter() - .filter_map(|(component_id, entity)| { + self.components + .iter_registered_ids() + .filter_map(|component_id| { + let entity = component_id.entity(); let component_info = self.components().get_info(component_id)?; let entity_cell = self.get_entity(entity).ok()?; let resource = entity_cell.get_by_id(component_id).ok()?; Some((component_info, resource)) }) - */ - core::iter::empty() // TODO: Fix iter_resources } /// Mutably iterates over all resources in the world. @@ -4126,11 +4124,7 @@ mod tests { let mut iter = world.iter_resources(); - let (info, ptr) = iter.next().unwrap(); - assert_eq!(info.name(), DebugName::type_name::()); - // SAFETY: We know that the resource is of type `TestResource` - assert_eq!(unsafe { ptr.deref::().0 }, 42); - + // The resources are given back in reverse order of component registration let (info, ptr) = iter.next().unwrap(); assert_eq!(info.name(), DebugName::type_name::()); assert_eq!( @@ -4139,6 +4133,11 @@ mod tests { &"Hello, world!".to_string() ); + let (info, ptr) = iter.next().unwrap(); + assert_eq!(info.name(), DebugName::type_name::()); + // SAFETY: We know that the resource is of type `TestResource` + assert_eq!(unsafe { ptr.deref::().0 }, 42); + assert!(iter.next().is_none()); } From 1a33441ab639948ec42430fd880ae23df096a372 Mon Sep 17 00:00:00 2001 From: Trashtalk Date: Sat, 25 Apr 2026 14:38:59 +0200 Subject: [PATCH 08/22] fix more tests and other crates --- crates/bevy_ecs/src/component/info.rs | 8 ++++- crates/bevy_ecs/src/world/mod.rs | 30 +++++++------------ crates/bevy_remote/src/builtin_methods.rs | 8 ++--- crates/bevy_settings/src/lib.rs | 7 ++--- .../src/dynamic_world.rs | 11 +++---- .../src/dynamic_world_builder.rs | 11 ++++--- crates/bevy_world_serialization/src/serde.rs | 12 ++++---- .../src/world_asset.rs | 17 ++++++----- 8 files changed, 51 insertions(+), 53 deletions(-) diff --git a/crates/bevy_ecs/src/component/info.rs b/crates/bevy_ecs/src/component/info.rs index 7a891279f636a..653352cccd29b 100644 --- a/crates/bevy_ecs/src/component/info.rs +++ b/crates/bevy_ecs/src/component/info.rs @@ -750,10 +750,16 @@ impl Components { } /// Gets an iterator over all component ids fully registered with this instance. - pub(crate) fn iter_registered_ids(&self) -> impl Iterator + '_ { + pub fn iter_registered_ids(&self) -> impl Iterator + '_ { self.components.keys().copied() } + pub(crate) fn iter_registered_pairs( + &self, + ) -> impl Iterator + '_ { + self.components.iter() + } + pub(crate) fn get_relationship_accessor_mut( &mut self, component_id: ComponentId, diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index 6a124d5cd8cff..195d4418f5858 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -3575,19 +3575,13 @@ impl World { /// # assert_eq!(world.resource::().0, 3); /// ``` pub fn iter_resources_mut(&mut self) -> impl Iterator)> { - /* let unsafe_world = self.as_unsafe_world_cell(); - // SAFETY: exclusive world access to all resources - let resource_entities = unsafe { unsafe_world.resource_entities() }; let components = unsafe_world.components(); - resource_entities - .iter() - .filter_map(move |(component_id, entity)| { - // SAFETY: If a resource has been initialized, a corresponding ComponentInfo must exist with its ID. - let component_info = - unsafe { components.get_info(component_id).debug_checked_unwrap() }; - + components + .iter_registered_pairs() + .filter_map(move |(component_id, component_info)| { + let entity = component_id.entity(); let entity_cell = unsafe_world.get_entity(entity).ok()?; // SAFETY: @@ -3596,12 +3590,10 @@ impl World { // or resource_entities mutably // - `resource_entities` doesn't contain duplicate entities, so // no duplicate references are created - let mut_untyped = unsafe { entity_cell.get_mut_by_id(component_id).ok()? }; + let mut_untyped = unsafe { entity_cell.get_mut_by_id(*component_id).ok()? }; Some((component_info, mut_untyped)) }) - */ - core::iter::empty() // TODO: Fix iter_resources_mut } /// Gets a pointer to `!Send` data with the id [`ComponentId`] if it exists. @@ -4154,17 +4146,17 @@ mod tests { let mut iter = world.iter_resources_mut(); let (info, mut mut_untyped) = iter.next().unwrap(); - assert_eq!(info.name(), DebugName::type_name::()); - // SAFETY: We know that the resource is of type `TestResource` + assert_eq!(info.name(), DebugName::type_name::()); + // SAFETY: We know that the resource is of type `TestResource2` unsafe { - mut_untyped.as_mut().deref_mut::().0 = 43; + mut_untyped.as_mut().deref_mut::().0 = "Hello, world?".to_string(); }; let (info, mut mut_untyped) = iter.next().unwrap(); - assert_eq!(info.name(), DebugName::type_name::()); - // SAFETY: We know that the resource is of type `TestResource2` + assert_eq!(info.name(), DebugName::type_name::()); + // SAFETY: We know that the resource is of type `TestResource` unsafe { - mut_untyped.as_mut().deref_mut::().0 = "Hello, world?".to_string(); + mut_untyped.as_mut().deref_mut::().0 = 43; }; assert!(iter.next().is_none()); diff --git a/crates/bevy_remote/src/builtin_methods.rs b/crates/bevy_remote/src/builtin_methods.rs index 2f10c3397d6a0..a65ce7abbf700 100644 --- a/crates/bevy_remote/src/builtin_methods.rs +++ b/crates/bevy_remote/src/builtin_methods.rs @@ -2014,10 +2014,10 @@ fn get_resource_entity_pair( .components() .get_id(type_id) .ok_or(anyhow!("Resource not registered: `{}`", resource_path))?; - let entity = world - .resource_entities() - .get(component_id) - .ok_or(anyhow!("Resource entity does not exist."))?; + let entity = component_id.entity(); + if !world.entities().contains_spawned(entity) { + return Err(anyhow!("Resource entity does not exist.")); + } Ok((entity, component_id)) } diff --git a/crates/bevy_settings/src/lib.rs b/crates/bevy_settings/src/lib.rs index 4b9c4eff84fae..6c109fae8f4c6 100644 --- a/crates/bevy_settings/src/lib.rs +++ b/crates/bevy_settings/src/lib.rs @@ -20,6 +20,7 @@ use std::collections::HashMap; use bevy_app::{App, Plugin, PostUpdate}; use bevy_ecs::{ change_detection::Tick, + component::ComponentId, reflect::{AppTypeRegistry, ReflectComponent, ReflectResource}, resource::Resource, system::{Command, Commands, Res, ResMut}, @@ -341,9 +342,7 @@ fn resources_to_toml( continue; }; - let Some(res_entity) = world.resource_entities().get(component_id) else { - continue; - }; + let res_entity = component_id.entity(); let res_entity_ref = world.entity(res_entity); let Some(reflect) = cmp.reflect(res_entity_ref) else { continue; @@ -470,7 +469,7 @@ fn apply_settings_to_world( let reflect_component = ty.data::().unwrap(); let component_id = world.components().get_id(*tid); - let res_entity = component_id.and_then(|cid| world.resource_entities().get(cid)); + let res_entity = component_id.map(ComponentId::entity); if let Some(res_entity) = res_entity { // Resource already exists, so apply toml properties to it. diff --git a/crates/bevy_world_serialization/src/dynamic_world.rs b/crates/bevy_world_serialization/src/dynamic_world.rs index 87bbdcbb8c154..e116d1b400840 100644 --- a/crates/bevy_world_serialization/src/dynamic_world.rs +++ b/crates/bevy_world_serialization/src/dynamic_world.rs @@ -181,13 +181,10 @@ impl DynamicWorld { .expect("ReflectComponent is depended on ReflectResource"); let resource_id = reflect_component.register_component(world); - - // check if the resource already exists, if not spawn it, otherwise override the value - let entity = if let Some(entity) = world.resource_entities().get(resource_id) { - entity - } else { - world.spawn_empty().id() - }; + let entity = resource_id.entity(); + if !world.entities().contains_spawned(entity) { + let _ = world.spawn_empty_at(entity); + } SceneEntityMapper::world_scope(entity_map, world, |world, mapper| { reflect_component.apply_or_insert_mapped( diff --git a/crates/bevy_world_serialization/src/dynamic_world_builder.rs b/crates/bevy_world_serialization/src/dynamic_world_builder.rs index 727d7adc9fecc..831a759bce77b 100644 --- a/crates/bevy_world_serialization/src/dynamic_world_builder.rs +++ b/crates/bevy_world_serialization/src/dynamic_world_builder.rs @@ -377,8 +377,11 @@ impl<'w> DynamicWorldBuilder<'w> { .components() .get_valid_id(TypeId::of::()); - for (component_id, entity) in self.original_world.resource_entities().iter() { - if Some(component_id) == original_world_dqf_id { + for component_id in self.original_world.components().iter_registered_ids() { + let entity = component_id.entity(); + if Some(component_id) == original_world_dqf_id + || !self.original_world.entities().contains_spawned(entity) + { continue; } let mut extract_and_push = || { @@ -501,8 +504,8 @@ mod tests { assert_eq!(dynamic_world.entities.len(), 1); assert_eq!(dynamic_world.entities[0].entity, entity); assert_eq!(dynamic_world.entities[0].components.len(), 2); - assert!(dynamic_world.entities[0].components[0].represents::()); - assert!(dynamic_world.entities[0].components[1].represents::()); + assert!(dynamic_world.entities[0].components[0].represents::()); + assert!(dynamic_world.entities[0].components[1].represents::()); } #[test] diff --git a/crates/bevy_world_serialization/src/serde.rs b/crates/bevy_world_serialization/src/serde.rs index 64505eda4b8ee..ab8cdde410dfb 100644 --- a/crates/bevy_world_serialization/src/serde.rs +++ b/crates/bevy_world_serialization/src/serde.rs @@ -694,25 +694,25 @@ mod tests { ), }, entities: { - 4294967290: ( + 4294967279: ( components: { "bevy_world_serialization::serde::tests::FakeMesh3d": (Uuid("00000000-0000-0000-0000-000000000001")), }, ), - 4294967291: ( + 4294967281: ( components: { "bevy_world_serialization::serde::tests::Bar": (345), "bevy_world_serialization::serde::tests::Baz": (789), "bevy_world_serialization::serde::tests::Foo": (123), }, ), - 4294967292: ( + 4294967283: ( components: { "bevy_world_serialization::serde::tests::Bar": (345), "bevy_world_serialization::serde::tests::Foo": (123), }, ), - 4294967293: ( + 4294967285: ( components: { "bevy_world_serialization::serde::tests::Foo": (123), }, @@ -920,7 +920,7 @@ mod tests { assert_eq!( vec![ - 0, 1, 253, 255, 255, 255, 15, 1, 51, 98, 101, 118, 121, 95, 119, 111, 114, 108, + 0, 1, 245, 255, 255, 255, 15, 1, 51, 98, 101, 118, 121, 95, 119, 111, 114, 108, 100, 95, 115, 101, 114, 105, 97, 108, 105, 122, 97, 116, 105, 111, 110, 58, 58, 115, 101, 114, 100, 101, 58, 58, 116, 101, 115, 116, 115, 58, 58, 77, 121, 67, 111, 109, 112, 111, 110, 101, 110, 116, 1, 2, 3, 102, 102, 166, 63, 205, 204, 108, 64, @@ -963,7 +963,7 @@ mod tests { assert_eq!( vec![ - 146, 128, 129, 206, 255, 255, 255, 253, 145, 129, 217, 51, 98, 101, 118, 121, 95, + 146, 128, 129, 206, 255, 255, 255, 245, 145, 129, 217, 51, 98, 101, 118, 121, 95, 119, 111, 114, 108, 100, 95, 115, 101, 114, 105, 97, 108, 105, 122, 97, 116, 105, 111, 110, 58, 58, 115, 101, 114, 100, 101, 58, 58, 116, 101, 115, 116, 115, 58, 58, 77, 121, 67, 111, 109, 112, 111, 110, 101, 110, 116, 147, 147, 1, 2, 3, 146, 202, diff --git a/crates/bevy_world_serialization/src/world_asset.rs b/crates/bevy_world_serialization/src/world_asset.rs index 458974e898533..6f7a2fb5ab066 100644 --- a/crates/bevy_world_serialization/src/world_asset.rs +++ b/crates/bevy_world_serialization/src/world_asset.rs @@ -75,8 +75,11 @@ impl WorldAsset { .get_id(TypeId::of::()); // Resources archetype - for (component_id, source_entity) in self.world.resource_entities().iter() { - if Some(component_id) == self_dqf_id { + for component_id in self.world.components().iter_registered_ids() { + let source_entity = component_id.entity(); + if Some(component_id) == self_dqf_id + || !self.world.entities().contains_spawned(source_entity) + { continue; } if !world @@ -113,12 +116,10 @@ impl WorldAsset { .expect("ReflectComponent is depended on ReflectResource"); // check if the resource already exists in the other world, if not spawn it - let destination_entity = - if let Some(entity) = world.resource_entities().get(component_id) { - entity - } else { - world.spawn_empty().id() - }; + let destination_entity = component_id.entity(); + if !world.entities().contains_spawned(destination_entity) { + let _ = world.spawn_empty_at(destination_entity); + } reflect_component.copy( &self.world, From e85625a80ad9fab1f7804a239e453876a2febabe Mon Sep 17 00:00:00 2001 From: Trashtalk Date: Sun, 26 Apr 2026 19:26:30 +0200 Subject: [PATCH 09/22] fix review comments --- crates/bevy_ecs/src/component/constants.rs | 12 ++++++------ crates/bevy_ecs/src/component/info.rs | 20 +++++--------------- crates/bevy_ecs/src/storage/sparse_set.rs | 3 +-- crates/bevy_ecs/src/world/mod.rs | 13 +++++++------ 4 files changed, 19 insertions(+), 29 deletions(-) diff --git a/crates/bevy_ecs/src/component/constants.rs b/crates/bevy_ecs/src/component/constants.rs index 1783eac5bc117..69f37f6db9020 100644 --- a/crates/bevy_ecs/src/component/constants.rs +++ b/crates/bevy_ecs/src/component/constants.rs @@ -1,14 +1,14 @@ //! Constant components included in every world. -/// `usize` for the [`Add`](crate::lifecycle::Add) component used in lifecycle observers. +/// `u32` for the [`Add`](crate::lifecycle::Add) component used in lifecycle observers. pub const ADD: u32 = 0; -/// `usize` for the [`Insert`](crate::lifecycle::Insert) component used in lifecycle observers. +/// `u32` for the [`Insert`](crate::lifecycle::Insert) component used in lifecycle observers. pub const INSERT: u32 = 1; -/// `usize` for the [`Discard`](crate::lifecycle::Discard) component used in lifecycle observers. +/// `u32` for the [`Discard`](crate::lifecycle::Discard) component used in lifecycle observers. pub const DISCARD: u32 = 2; -/// `usize` for the [`Remove`](crate::lifecycle::Remove) component used in lifecycle observers. +/// `u32` for the [`Remove`](crate::lifecycle::Remove) component used in lifecycle observers. pub const REMOVE: u32 = 3; -/// `usize` for [`Despawn`](crate::lifecycle::Despawn) component used in lifecycle observers. +/// `u32` for [`Despawn`](crate::lifecycle::Despawn) component used in lifecycle observers. pub const DESPAWN: u32 = 4; -/// `usize` of the [`IsResource`](crate::resource::IsResource) component used to mark entities with resources. +/// `u32` of the [`IsResource`](crate::resource::IsResource) component used to mark entities with resources. pub const IS_RESOURCE: u32 = 5; diff --git a/crates/bevy_ecs/src/component/info.rs b/crates/bevy_ecs/src/component/info.rs index 653352cccd29b..9774bb0fbecf4 100644 --- a/crates/bevy_ecs/src/component/info.rs +++ b/crates/bevy_ecs/src/component/info.rs @@ -19,7 +19,7 @@ use crate::{ Component, ComponentCloneBehavior, ComponentMutability, QueuedComponents, RequiredComponents, StorageType, }, - entity::Entity, + entity::{Entity, EntityHash}, lifecycle::ComponentHooks, query::DebugCheckedUnwrap as _, relationship::{ @@ -218,7 +218,7 @@ impl ComponentId { impl SparseSetIndex for ComponentId { #[inline] fn sparse_set_index(&self) -> usize { - self.0.index().sparse_set_index() + self.0.sparse_set_index() } #[inline] @@ -373,7 +373,7 @@ impl ComponentDescriptor { /// Stores metadata associated with each kind of [`Component`] in a given [`World`](crate::world::World). #[derive(Debug, Default)] pub struct Components { - pub(super) components: HashMap, + pub(super) components: HashMap, pub(super) indices: TypeIdMap, // This is kept internal and local to verify that no deadlocks can occur. pub(super) queued: bevy_platform::sync::RwLock, @@ -393,6 +393,7 @@ impl Components { ) { descriptor.initialize(id, self); let info = ComponentInfo::new(id, descriptor); + debug_assert!(!self.components.contains_key(&id)); // SAFETY: The id has never been registered before. unsafe { self.components.insert_unique_unchecked(id, info); @@ -458,7 +459,7 @@ impl Components { /// This will return an incorrect result if `id` did not come from the same world as `self`. It may return `None` or a garbage value. #[inline] pub fn get_info(&self, id: ComponentId) -> Option<&ComponentInfo> { - self.components.get(&id).and_then(|info| Some(info)) + self.components.get(&id) } /// Gets the [`ComponentDescriptor`] of the component with this [`ComponentId`] if it is present. @@ -749,17 +750,6 @@ impl Components { self.components.values() } - /// Gets an iterator over all component ids fully registered with this instance. - pub fn iter_registered_ids(&self) -> impl Iterator + '_ { - self.components.keys().copied() - } - - pub(crate) fn iter_registered_pairs( - &self, - ) -> impl Iterator + '_ { - self.components.iter() - } - pub(crate) fn get_relationship_accessor_mut( &mut self, component_id: ComponentId, diff --git a/crates/bevy_ecs/src/storage/sparse_set.rs b/crates/bevy_ecs/src/storage/sparse_set.rs index 5be514ddb870c..e745569499915 100644 --- a/crates/bevy_ecs/src/storage/sparse_set.rs +++ b/crates/bevy_ecs/src/storage/sparse_set.rs @@ -136,12 +136,12 @@ impl SparseArray { } } - /* TODO: Maybe change the data structure in Components /// Returns an iterator over the non-empty values in the array. /// /// This must scan the entire array to find non-empty values, /// which may be slow even if the array is sparsely populated. #[inline] + #[expect(dead_code)] pub(crate) fn iter(&self) -> impl Iterator { self.values.iter().enumerate().filter_map(|(index, value)| { value @@ -149,7 +149,6 @@ impl SparseArray { .map(|value| (SparseSetIndex::get_sparse_set_index(index), value)) }) } - */ } /// A sparse data structure of [`Component`](crate::component::Component)s. diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index 195d4418f5858..902a1293c892a 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -3499,10 +3499,10 @@ impl World { #[inline] pub fn iter_resources(&self) -> impl Iterator)> { self.components - .iter_registered_ids() - .filter_map(|component_id| { + .iter_registered() + .filter_map(|component_info| { + let component_id = component_info.id(); let entity = component_id.entity(); - let component_info = self.components().get_info(component_id)?; let entity_cell = self.get_entity(entity).ok()?; let resource = entity_cell.get_by_id(component_id).ok()?; Some((component_info, resource)) @@ -3579,8 +3579,9 @@ impl World { let components = unsafe_world.components(); components - .iter_registered_pairs() - .filter_map(move |(component_id, component_info)| { + .iter_registered() + .filter_map(move |component_info| { + let component_id = component_info.id(); let entity = component_id.entity(); let entity_cell = unsafe_world.get_entity(entity).ok()?; @@ -3590,7 +3591,7 @@ impl World { // or resource_entities mutably // - `resource_entities` doesn't contain duplicate entities, so // no duplicate references are created - let mut_untyped = unsafe { entity_cell.get_mut_by_id(*component_id).ok()? }; + let mut_untyped = unsafe { entity_cell.get_mut_by_id(component_id).ok()? }; Some((component_info, mut_untyped)) }) From 9238025ee09d758ee5466f13f9e69787fba0b8b2 Mon Sep 17 00:00:00 2001 From: Trashtalk Date: Sun, 26 Apr 2026 19:42:45 +0200 Subject: [PATCH 10/22] fixed clippy mistakes --- crates/bevy_ecs/src/component/info.rs | 22 +++++++++------------- crates/bevy_ecs/src/storage/sparse_set.rs | 2 +- 2 files changed, 10 insertions(+), 14 deletions(-) diff --git a/crates/bevy_ecs/src/component/info.rs b/crates/bevy_ecs/src/component/info.rs index 9774bb0fbecf4..4856cb27bb6ec 100644 --- a/crates/bevy_ecs/src/component/info.rs +++ b/crates/bevy_ecs/src/component/info.rs @@ -207,7 +207,7 @@ impl ComponentId { self.0.index_u32() as usize } - /// Returns the inner entity from the ComponentId + /// Returns the inner entity from the `ComponentId` #[inline] pub fn entity(self) -> Entity { self.0 @@ -472,7 +472,7 @@ impl Components { pub fn get_descriptor<'a>(&'a self, id: ComponentId) -> Option> { self.components .get(&id) - .and_then(|info| Some(Cow::Borrowed(&info.descriptor))) + .map(|info| Cow::Borrowed(&info.descriptor)) .or_else(|| { let queued = self.queued.read().unwrap_or_else(PoisonError::into_inner); // first check components, then resources, then dynamic @@ -493,7 +493,7 @@ impl Components { pub fn get_name<'a>(&'a self, id: ComponentId) -> Option { self.components .get(&id) - .and_then(|info| Some(info.descriptor.name())) + .map(|info| info.descriptor.name()) .or_else(|| { let queued = self.queued.read().unwrap_or_else(PoisonError::into_inner); // first check components, then resources, then dynamic @@ -518,16 +518,14 @@ impl Components { #[inline] pub(crate) fn get_hooks_mut(&mut self, id: ComponentId) -> Option<&mut ComponentHooks> { - self.components - .get_mut(&id) - .and_then(|info| Some(&mut info.hooks)) + self.components.get_mut(&id).map(|info| &mut info.hooks) } #[inline] pub(crate) fn get_required_components(&self, id: ComponentId) -> Option<&RequiredComponents> { self.components .get(&id) - .and_then(|info| Some(&info.required_components)) + .map(|info| &info.required_components) } #[inline] @@ -537,7 +535,7 @@ impl Components { ) -> Option<&mut RequiredComponents> { self.components .get_mut(&id) - .and_then(|info| Some(&mut info.required_components)) + .map(|info| &mut info.required_components) } #[inline] @@ -545,9 +543,7 @@ impl Components { &self, id: ComponentId, ) -> Option<&IndexSet> { - self.components - .get(&id) - .and_then(|info| Some(&info.required_by)) + self.components.get(&id).map(|info| &info.required_by) } #[inline] @@ -557,7 +553,7 @@ impl Components { ) -> Option<&mut IndexSet> { self.components .get_mut(&id) - .and_then(|info| Some(&mut info.required_by)) + .map(|info| &mut info.required_by) } /// Returns true if the [`ComponentId`] is fully registered and valid. @@ -756,6 +752,6 @@ impl Components { ) -> Option<&mut MaybeRelationshipAccessor> { self.components .get_mut(&component_id) - .and_then(|info| Some(&mut info.descriptor.relationship_accessor)) + .map(|info| &mut info.descriptor.relationship_accessor) } } diff --git a/crates/bevy_ecs/src/storage/sparse_set.rs b/crates/bevy_ecs/src/storage/sparse_set.rs index e745569499915..6e7f86daca051 100644 --- a/crates/bevy_ecs/src/storage/sparse_set.rs +++ b/crates/bevy_ecs/src/storage/sparse_set.rs @@ -141,7 +141,7 @@ impl SparseArray { /// This must scan the entire array to find non-empty values, /// which may be slow even if the array is sparsely populated. #[inline] - #[expect(dead_code)] + #[expect(dead_code, reason = "May need this for later.")] pub(crate) fn iter(&self) -> impl Iterator { self.values.iter().enumerate().filter_map(|(index, value)| { value From d686995c07759d823314854c9130928af24382fd Mon Sep 17 00:00:00 2001 From: Trashtalk Date: Sun, 26 Apr 2026 20:44:38 +0200 Subject: [PATCH 11/22] fix other crates --- crates/bevy_settings/src/lib.rs | 3 ++- .../src/dynamic_world_builder.rs | 9 +++++++-- crates/bevy_world_serialization/src/world_asset.rs | 9 +++++++-- 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/crates/bevy_settings/src/lib.rs b/crates/bevy_settings/src/lib.rs index 6c109fae8f4c6..ab78da6225847 100644 --- a/crates/bevy_settings/src/lib.rs +++ b/crates/bevy_settings/src/lib.rs @@ -496,7 +496,8 @@ fn apply_settings_to_world( // The resource does not exist, so create a default. let reflect_default = ty.data::().unwrap(); let mut default_value = reflect_default.default(); - let mut res_entity = world.spawn_empty(); + let new_component_id = reflect_component.register_component(world); + let mut res_entity = world.spawn_empty_at(new_component_id.entity()).unwrap(); if let Some(toml) = toml && let Some(value) = toml.get(settings_group) diff --git a/crates/bevy_world_serialization/src/dynamic_world_builder.rs b/crates/bevy_world_serialization/src/dynamic_world_builder.rs index 831a759bce77b..2ad15b9a344c3 100644 --- a/crates/bevy_world_serialization/src/dynamic_world_builder.rs +++ b/crates/bevy_world_serialization/src/dynamic_world_builder.rs @@ -5,7 +5,7 @@ use crate::{DynamicEntity, DynamicWorld, WorldFilter}; use alloc::collections::BTreeMap; use bevy_ecs::resource::IS_RESOURCE; use bevy_ecs::{ - component::{Component, ComponentId}, + component::{Component, ComponentId, ComponentInfo}, entity_disabling::DefaultQueryFilters, prelude::Entity, reflect::{ReflectComponent, ReflectResource}, @@ -377,7 +377,12 @@ impl<'w> DynamicWorldBuilder<'w> { .components() .get_valid_id(TypeId::of::()); - for component_id in self.original_world.components().iter_registered_ids() { + for component_id in self + .original_world + .components() + .iter_registered() + .map(ComponentInfo::id) + { let entity = component_id.entity(); if Some(component_id) == original_world_dqf_id || !self.original_world.entities().contains_spawned(entity) diff --git a/crates/bevy_world_serialization/src/world_asset.rs b/crates/bevy_world_serialization/src/world_asset.rs index 6f7a2fb5ab066..4414b0d30101a 100644 --- a/crates/bevy_world_serialization/src/world_asset.rs +++ b/crates/bevy_world_serialization/src/world_asset.rs @@ -5,7 +5,7 @@ use crate::{DynamicWorld, WorldInstanceSpawnError}; use bevy_asset::Asset; use bevy_ecs::resource::IS_RESOURCE; use bevy_ecs::{ - component::ComponentCloneBehavior, + component::{ComponentCloneBehavior, ComponentInfo}, entity::{Entity, EntityHashMap, SceneEntityMapper}, entity_disabling::DefaultQueryFilters, reflect::{AppTypeRegistry, ReflectComponent, ReflectResource}, @@ -75,7 +75,12 @@ impl WorldAsset { .get_id(TypeId::of::()); // Resources archetype - for component_id in self.world.components().iter_registered_ids() { + for component_id in self + .world + .components() + .iter_registered() + .map(ComponentInfo::id) + { let source_entity = component_id.entity(); if Some(component_id) == self_dqf_id || !self.world.entities().contains_spawned(source_entity) From 46cd5bd62042b8fbfed42f832d1ef260dac6398b Mon Sep 17 00:00:00 2001 From: Trashtalk Date: Mon, 27 Apr 2026 23:02:34 +0200 Subject: [PATCH 12/22] renamed .entity() to .id() for ComponentId --- crates/bevy_ecs/src/component/info.rs | 2 +- crates/bevy_ecs/src/resource.rs | 16 +++++++-------- crates/bevy_ecs/src/world/mod.rs | 20 +++++++++---------- crates/bevy_ecs/src/world/reflect.rs | 2 +- .../bevy_ecs/src/world/unsafe_world_cell.rs | 6 +++--- crates/bevy_remote/src/builtin_methods.rs | 2 +- crates/bevy_settings/src/lib.rs | 6 +++--- .../src/dynamic_world.rs | 2 +- .../src/dynamic_world_builder.rs | 2 +- .../src/world_asset.rs | 4 ++-- 10 files changed, 31 insertions(+), 31 deletions(-) diff --git a/crates/bevy_ecs/src/component/info.rs b/crates/bevy_ecs/src/component/info.rs index 4856cb27bb6ec..0c973dee670d1 100644 --- a/crates/bevy_ecs/src/component/info.rs +++ b/crates/bevy_ecs/src/component/info.rs @@ -209,7 +209,7 @@ impl ComponentId { /// Returns the inner entity from the `ComponentId` #[inline] - pub fn entity(self) -> Entity { + pub fn id(self) -> Entity { self.0 } } diff --git a/crates/bevy_ecs/src/resource.rs b/crates/bevy_ecs/src/resource.rs index cabad31564e3a..7fccf21455fcc 100644 --- a/crates/bevy_ecs/src/resource.rs +++ b/crates/bevy_ecs/src/resource.rs @@ -106,7 +106,7 @@ impl IsResource { .get::() .unwrap() .resource_component_id(); - let original_entity = resource_component_id.entity(); + let original_entity = resource_component_id.id(); if !world.entities().contains(original_entity) { let name = world @@ -145,7 +145,7 @@ impl IsResource { .get::() .unwrap() .resource_component_id(); - let original_entity = resource_component_id.entity(); + let original_entity = resource_component_id.id(); if original_entity == context.entity { world @@ -205,7 +205,7 @@ mod tests { } }); assert_eq!(world.entities().count_spawned(), start + 3); - let e3 = id3.entity(); + let e3 = id3.id(); assert!(world.remove_resource_by_id(id3)); // the entity is stable: removing the resource should only remove the component from the entity, not despawn the entity assert_eq!(world.entities().count_spawned(), start + 3); @@ -215,13 +215,13 @@ mod tests { world.insert_resource_by_id(id3, ptr, MaybeLocation::caller()); } }); - assert_eq!(e3, id3.entity()); + assert_eq!(e3, id3.id()); // again, the entity is stable: see previous explanation - let e1 = id1.entity(); + let e1 = id1.id(); world.remove_resource::(); assert_eq!(world.entities().count_spawned(), start + 3); world.init_resource::(); - assert_eq!(e1, id1.entity()); + assert_eq!(e1, id1.id()); // make sure that trying to add a resource twice results, doesn't change the entity count world.insert_resource(TestResource2(String::from("Bar"))); assert_eq!(world.entities().count_spawned(), start + 3); @@ -244,7 +244,7 @@ mod tests { let (entity, _test_resource, is_resource) = resources[0]; let resource_id = is_resource.resource_component_id(); assert_eq!(resource_id, id); - assert_eq!(resource_id.entity(), entity); + assert_eq!(resource_id.id(), entity); entity }; @@ -265,7 +265,7 @@ mod tests { let (entity, _test_resource, is_resource) = resources[0]; let resource_id = is_resource.resource_component_id(); assert_eq!(resource_id, id); - assert_eq!(resource_id.entity(), entity); + assert_eq!(resource_id.id(), entity); entity }; diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index 902a1293c892a..851daf5eaad77 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -1849,7 +1849,7 @@ impl World { caller: MaybeLocation, ) -> (ComponentId, EntityWorldMut<'_>) { let resource_id: ComponentId = self.register_resource::(); - let entity = resource_id.entity(); + let entity = resource_id.id(); if let Ok(entity_ref) = self.get_entity(entity) { if !entity_ref.contains_id(resource_id) { @@ -1986,7 +1986,7 @@ impl World { pub fn remove_resource(&mut self) -> Option { let resource_id = self.component_id::()?; let value = self - .get_entity_mut(resource_id.entity()) + .get_entity_mut(resource_id.id()) .expect("ResourceCache is in sync") .take::()?; Some(value) @@ -2028,7 +2028,7 @@ impl World { /// Returns `true` if a resource with provided `component_id` exists. Otherwise returns `false`. #[inline] pub fn contains_resource_by_id(&self, component_id: ComponentId) -> bool { - if let Ok(entity_ref) = self.get_entity(component_id.entity()) { + if let Ok(entity_ref) = self.get_entity(component_id.id()) { return entity_ref.contains_id(component_id); } false @@ -2116,7 +2116,7 @@ impl World { &self, component_id: ComponentId, ) -> Option { - let entity_ref = self.get_entity(component_id.entity()).ok()?; + let entity_ref = self.get_entity(component_id.id()).ok()?; entity_ref.get_change_ticks_by_id(component_id) } @@ -2767,7 +2767,7 @@ impl World { let change_tick = self.change_tick(); let component_id = self.components.valid_component_id::()?; - let entity = component_id.entity(); + let entity = component_id.id(); let mut entity_mut = self.get_entity_mut(entity).ok()?; let mut ticks = entity_mut.get_change_ticks::()?; @@ -2958,7 +2958,7 @@ impl World { value: OwningPtr<'_>, caller: MaybeLocation, ) { - let entity = component_id.entity(); + let entity = component_id.id(); // if the resource already exists, we replace it on the same entity let mut entity_mut = if let Ok(entity_mut) = self.get_entity_mut(entity) { entity_mut @@ -3296,7 +3296,7 @@ impl World { .map(ComponentInfo::id) .collect(); for component_id in ids { - let entity = component_id.entity(); + let entity = component_id.id(); if let Ok(mut entity_mut) = self.get_entity_mut(entity) { entity_mut.remove_by_id(component_id); } @@ -3502,7 +3502,7 @@ impl World { .iter_registered() .filter_map(|component_info| { let component_id = component_info.id(); - let entity = component_id.entity(); + let entity = component_id.id(); let entity_cell = self.get_entity(entity).ok()?; let resource = entity_cell.get_by_id(component_id).ok()?; Some((component_info, resource)) @@ -3582,7 +3582,7 @@ impl World { .iter_registered() .filter_map(move |component_info| { let component_id = component_info.id(); - let entity = component_id.entity(); + let entity = component_id.id(); let entity_cell = unsafe_world.get_entity(entity).ok()?; // SAFETY: @@ -3644,7 +3644,7 @@ impl World { /// **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) -> bool { - let entity = component_id.entity(); + let entity = component_id.id(); if let Ok(mut entity_mut) = self.get_entity_mut(entity) && entity_mut.contains_id(component_id) { diff --git a/crates/bevy_ecs/src/world/reflect.rs b/crates/bevy_ecs/src/world/reflect.rs index d94bcf5245dc0..464d5bc4c2c41 100644 --- a/crates/bevy_ecs/src/world/reflect.rs +++ b/crates/bevy_ecs/src/world/reflect.rs @@ -199,7 +199,7 @@ impl World { resource_id: ComponentId, reflected_resource: Box, ) { - let entity = resource_id.entity(); + let entity = resource_id.id(); if let Ok(mut entity_mut) = self.get_entity_mut(entity) { entity_mut.insert_reflect(reflected_resource); } else { diff --git a/crates/bevy_ecs/src/world/unsafe_world_cell.rs b/crates/bevy_ecs/src/world/unsafe_world_cell.rs index 98adb7a42bb7d..7c9bd247bd074 100644 --- a/crates/bevy_ecs/src/world/unsafe_world_cell.rs +++ b/crates/bevy_ecs/src/world/unsafe_world_cell.rs @@ -454,7 +454,7 @@ impl<'w> UnsafeWorldCell<'w> { #[inline] pub unsafe fn get_resource_by_id(self, component_id: ComponentId) -> Option> { // SAFETY: We have permission to access the resource of `component_id`. - let entity = component_id.entity(); + let entity = component_id.id(); let entity_cell = self.get_entity(entity).ok()?; entity_cell.get_by_id(component_id) } @@ -563,7 +563,7 @@ impl<'w> UnsafeWorldCell<'w> { ) -> Option> { self.assert_allows_mutable_access(); // SAFETY: We have permission to access the resource of `component_id`. - let entity = component_id.entity(); + let entity = component_id.id(); let entity_cell = self.get_entity(entity).ok()?; entity_cell.get_mut_by_id(component_id).ok() } @@ -667,7 +667,7 @@ impl<'w> UnsafeWorldCell<'w> { component_id: ComponentId, ) -> Option<(Ptr<'w>, ComponentTickCells<'w>)> { // SAFETY: We have permission to access the resource of `component_id`. - let entity = component_id.entity(); + let entity = component_id.id(); let storage_type = self.components().get_info(component_id)?.storage_type(); let location = self.get_entity(entity).ok()?.location(); // SAFETY: diff --git a/crates/bevy_remote/src/builtin_methods.rs b/crates/bevy_remote/src/builtin_methods.rs index a65ce7abbf700..d38fe3baa123f 100644 --- a/crates/bevy_remote/src/builtin_methods.rs +++ b/crates/bevy_remote/src/builtin_methods.rs @@ -2014,7 +2014,7 @@ fn get_resource_entity_pair( .components() .get_id(type_id) .ok_or(anyhow!("Resource not registered: `{}`", resource_path))?; - let entity = component_id.entity(); + let entity = component_id.id(); if !world.entities().contains_spawned(entity) { return Err(anyhow!("Resource entity does not exist.")); } diff --git a/crates/bevy_settings/src/lib.rs b/crates/bevy_settings/src/lib.rs index ab78da6225847..4a217284fb523 100644 --- a/crates/bevy_settings/src/lib.rs +++ b/crates/bevy_settings/src/lib.rs @@ -342,7 +342,7 @@ fn resources_to_toml( continue; }; - let res_entity = component_id.entity(); + let res_entity = component_id.id(); let res_entity_ref = world.entity(res_entity); let Some(reflect) = cmp.reflect(res_entity_ref) else { continue; @@ -469,7 +469,7 @@ fn apply_settings_to_world( let reflect_component = ty.data::().unwrap(); let component_id = world.components().get_id(*tid); - let res_entity = component_id.map(ComponentId::entity); + let res_entity = component_id.map(ComponentId::id); if let Some(res_entity) = res_entity { // Resource already exists, so apply toml properties to it. @@ -497,7 +497,7 @@ fn apply_settings_to_world( let reflect_default = ty.data::().unwrap(); let mut default_value = reflect_default.default(); let new_component_id = reflect_component.register_component(world); - let mut res_entity = world.spawn_empty_at(new_component_id.entity()).unwrap(); + let mut res_entity = world.spawn_empty_at(new_component_id.id()).unwrap(); if let Some(toml) = toml && let Some(value) = toml.get(settings_group) diff --git a/crates/bevy_world_serialization/src/dynamic_world.rs b/crates/bevy_world_serialization/src/dynamic_world.rs index e116d1b400840..e506190c5d9ad 100644 --- a/crates/bevy_world_serialization/src/dynamic_world.rs +++ b/crates/bevy_world_serialization/src/dynamic_world.rs @@ -181,7 +181,7 @@ impl DynamicWorld { .expect("ReflectComponent is depended on ReflectResource"); let resource_id = reflect_component.register_component(world); - let entity = resource_id.entity(); + let entity = resource_id.id(); if !world.entities().contains_spawned(entity) { let _ = world.spawn_empty_at(entity); } diff --git a/crates/bevy_world_serialization/src/dynamic_world_builder.rs b/crates/bevy_world_serialization/src/dynamic_world_builder.rs index 2ad15b9a344c3..6670017edaeba 100644 --- a/crates/bevy_world_serialization/src/dynamic_world_builder.rs +++ b/crates/bevy_world_serialization/src/dynamic_world_builder.rs @@ -383,7 +383,7 @@ impl<'w> DynamicWorldBuilder<'w> { .iter_registered() .map(ComponentInfo::id) { - let entity = component_id.entity(); + let entity = component_id.id(); if Some(component_id) == original_world_dqf_id || !self.original_world.entities().contains_spawned(entity) { diff --git a/crates/bevy_world_serialization/src/world_asset.rs b/crates/bevy_world_serialization/src/world_asset.rs index 4414b0d30101a..043f025a636d2 100644 --- a/crates/bevy_world_serialization/src/world_asset.rs +++ b/crates/bevy_world_serialization/src/world_asset.rs @@ -81,7 +81,7 @@ impl WorldAsset { .iter_registered() .map(ComponentInfo::id) { - let source_entity = component_id.entity(); + let source_entity = component_id.id(); if Some(component_id) == self_dqf_id || !self.world.entities().contains_spawned(source_entity) { @@ -121,7 +121,7 @@ impl WorldAsset { .expect("ReflectComponent is depended on ReflectResource"); // check if the resource already exists in the other world, if not spawn it - let destination_entity = component_id.entity(); + let destination_entity = component_id.id(); if !world.entities().contains_spawned(destination_entity) { let _ = world.spawn_empty_at(destination_entity); } From f93a9b86c31d09400835284ad6ec6a3bb6e430dc Mon Sep 17 00:00:00 2001 From: Trashtalk Date: Mon, 27 Apr 2026 23:03:02 +0200 Subject: [PATCH 13/22] changed access to use HashSets --- crates/bevy_ecs/src/query/access.rs | 135 +++++++++++++---------- crates/bevy_ecs/src/query/access_iter.rs | 2 +- 2 files changed, 77 insertions(+), 60 deletions(-) diff --git a/crates/bevy_ecs/src/query/access.rs b/crates/bevy_ecs/src/query/access.rs index 3c7d38ce6c698..699054f0e92a1 100644 --- a/crates/bevy_ecs/src/query/access.rs +++ b/crates/bevy_ecs/src/query/access.rs @@ -1,7 +1,10 @@ +use crate::entity::EntityHash; use crate::world::unsafe_world_cell::UnsafeWorldCell; use crate::{component::ComponentId, resource::IS_RESOURCE}; use alloc::{format, string::String, vec, vec::Vec}; +use bevy_platform::collections::HashSet; use core::iter::FusedIterator; +use core::ops::{BitAndAssign, BitOrAssign, SubAssign}; use core::{fmt, fmt::Debug}; use derive_more::From; use fixedbitset::{Difference, FixedBitSet, Intersection, IntoOnes, Ones, Union}; @@ -11,14 +14,14 @@ use thiserror::Error; /// /// Used internally to ensure soundness during system initialization and execution. /// See the [`is_compatible`](Access::is_compatible) and [`get_conflicts`](Access::get_conflicts) functions. -#[derive(Eq, PartialEq, Default, Hash, Debug)] +#[derive(Eq, PartialEq, Default, Debug)] pub struct Access { /// All accessed components, or forbidden components if /// `Self::component_read_and_writes_inverted` is set. - read_and_writes: ComponentIdSet, + read_and_writes: HashSet, /// All exclusively-accessed components, or components that may not be /// exclusively accessed if `Self::component_writes_inverted` is set. - writes: ComponentIdSet, + writes: HashSet, /// Is `true` if this component can read all components *except* those /// present in `Self::read_and_writes`. read_and_writes_inverted: bool, @@ -26,7 +29,7 @@ pub struct Access { /// present in `Self::writes`. writes_inverted: bool, // Components that are not accessed, but whose presence in an archetype affect query results. - archetypal: ComponentIdSet, + archetypal: HashSet, } // This is needed since `#[derive(Clone)]` does not generate optimized `clone_from`. @@ -56,9 +59,9 @@ impl Access { Self { read_and_writes_inverted: false, writes_inverted: false, - read_and_writes: ComponentIdSet::new(), - writes: ComponentIdSet::new(), - archetypal: ComponentIdSet::new(), + read_and_writes: HashSet::with_hasher(EntityHash), + writes: HashSet::with_hasher(EntityHash), + archetypal: HashSet::with_hasher(EntityHash), } } @@ -96,7 +99,7 @@ impl Access { if !self.read_and_writes_inverted { self.read_and_writes.insert(index); } else { - self.read_and_writes.remove(index); + self.read_and_writes.remove(&index); } } @@ -112,7 +115,7 @@ impl Access { if !self.writes_inverted { self.writes.insert(index); } else { - self.writes.remove(index); + self.writes.remove(&index); } } @@ -153,7 +156,7 @@ impl Access { if self.read_and_writes_inverted { self.read_and_writes.insert(index); } else { - self.read_and_writes.remove(index); + self.read_and_writes.remove(&index); } } @@ -175,7 +178,7 @@ impl Access { if self.writes_inverted { self.writes.insert(index); } else { - self.writes.remove(index); + self.writes.remove(&index); } } @@ -200,7 +203,7 @@ impl Access { /// Returns `true` if this can access the component given by `index`. pub fn has_read(&self, index: ComponentId) -> bool { - self.read_and_writes_inverted ^ self.read_and_writes.contains(index) + self.read_and_writes_inverted ^ self.read_and_writes.contains(&index) } /// Returns `true` if this can access any component. @@ -211,7 +214,7 @@ impl Access { /// Returns `true` if this can access any component. pub fn has_any_read(&self) -> bool { - self.read_and_writes_inverted || !self.read_and_writes.is_clear() + self.read_and_writes_inverted || !self.read_and_writes.is_empty() } /// Returns `true` if this can exclusively access the component given by `index`. @@ -222,7 +225,7 @@ impl Access { /// Returns `true` if this can exclusively access the component given by `index`. pub fn has_write(&self, index: ComponentId) -> bool { - self.writes_inverted ^ self.writes.contains(index) + self.writes_inverted ^ self.writes.contains(&index) } /// Returns `true` if this accesses any component mutably. @@ -233,7 +236,7 @@ impl Access { /// Returns `true` if this accesses any component mutably. pub fn has_any_write(&self) -> bool { - self.writes_inverted || !self.writes.is_clear() + self.writes_inverted || !self.writes.is_empty() } /// Returns `true` if this can access the resource given by `index`. @@ -269,7 +272,7 @@ impl Access { /// /// [`Has`]: crate::query::Has pub fn has_archetypal(&self, index: ComponentId) -> bool { - self.archetypal.contains(index) + self.archetypal.contains(&index) } /// Sets this as having access to all components (i.e. `EntityRef`). @@ -308,7 +311,7 @@ impl Access { /// Returns `true` if this has access to all components (i.e. `EntityRef` and `&World`). #[inline] pub fn has_read_all(&self) -> bool { - self.read_and_writes_inverted && self.read_and_writes.is_clear() + self.read_and_writes_inverted && self.read_and_writes.is_empty() } /// Returns `true` if this has write access to all components (i.e. `EntityMut` and `&mut World`). @@ -320,7 +323,7 @@ impl Access { /// Returns `true` if this has write access to all components (i.e. `EntityMut` and `&mut World`). #[inline] pub fn has_write_all(&self) -> bool { - self.writes_inverted && self.writes.is_clear() + self.writes_inverted && self.writes.is_empty() } /// Removes all writes. @@ -351,7 +354,7 @@ impl Access { &other.writes, other.writes_inverted, ); - self.archetypal.union_with(&other.archetypal); + self.archetypal.bitor_assign(&other.archetypal); } /// Removes any access from `self` that would conflict with `other`. @@ -512,9 +515,18 @@ impl Access { let temp_conflicts: ComponentIdSet = match (lhs_writes_inverted, rhs_reads_and_writes_inverted) { (true, true) => return AccessConflicts::All, - (false, true) => lhs_writes.difference(rhs_reads_and_writes).collect(), - (true, false) => rhs_reads_and_writes.difference(lhs_writes).collect(), - (false, false) => lhs_writes.intersection(rhs_reads_and_writes).collect(), + (false, true) => lhs_writes + .difference(rhs_reads_and_writes) + .copied() + .collect(), + (true, false) => rhs_reads_and_writes + .difference(lhs_writes) + .copied() + .collect(), + (false, false) => lhs_writes + .intersection(rhs_reads_and_writes) + .copied() + .collect(), }; conflicts.union_with(&temp_conflicts); } @@ -530,13 +542,15 @@ impl Access { /// Currently, this is only used for [`Has`]. /// /// [`Has`]: crate::query::Has - pub fn archetypal(&self) -> &ComponentIdSet { + pub fn archetypal(&self) -> &HashSet { &self.archetypal } /// Returns the set of components with read or write access, /// or an error if the access is unbounded. - pub fn try_reads_and_writes(&self) -> Result<&ComponentIdSet, UnboundedAccessError> { + pub fn try_reads_and_writes( + &self, + ) -> Result<&HashSet, UnboundedAccessError> { // writes_inverted is only ever true when read_and_writes_inverted is // also true. Therefore it is sufficient to check just read_and_writes_inverted. if self.read_and_writes_inverted { @@ -550,7 +564,7 @@ impl Access { /// Returns the set of components with write access, /// or an error if the access is unbounded. - pub fn try_writes(&self) -> Result<&ComponentIdSet, UnboundedAccessError> { + pub fn try_writes(&self) -> Result<&HashSet, UnboundedAccessError> { if self.writes_inverted { return Err(UnboundedAccessError { writes_inverted: self.writes_inverted, @@ -603,16 +617,16 @@ impl Access { ) -> Result + '_, UnboundedAccessError> { let reads_and_writes = self.try_reads_and_writes()?.iter().map(|index| { if self.writes.contains(index) { - ComponentAccessKind::Exclusive(index) + ComponentAccessKind::Exclusive(*index) } else { - ComponentAccessKind::Shared(index) + ComponentAccessKind::Shared(*index) } }); let archetypal = self .archetypal .difference(&self.read_and_writes) - .map(ComponentAccessKind::Archetypal); + .map(|id| ComponentAccessKind::Archetypal(*id)); Ok(reads_and_writes.chain(archetypal)) } @@ -627,19 +641,19 @@ impl Access { /// Note that this may change `self_inverted` to `true` if we add an infinite /// set to a finite one, resulting in a new infinite set. fn invertible_union_with( - self_set: &mut ComponentIdSet, + self_set: &mut HashSet, self_inverted: &mut bool, - other_set: &ComponentIdSet, + other_set: &HashSet, other_inverted: bool, ) { match (*self_inverted, other_inverted) { - (true, true) => self_set.intersect_with(other_set), - (true, false) => self_set.difference_with(other_set), + (true, true) => self_set.bitand_assign(other_set), + (true, false) => self_set.sub_assign(other_set), (false, true) => { *self_inverted = true; - self_set.difference_from(other_set); + self_set.clone_from(&(other_set - self_set)); } - (false, false) => self_set.union_with(other_set), + (false, false) => self_set.bitor_assign(other_set), } } @@ -652,9 +666,9 @@ fn invertible_union_with( /// Note that this may change `self_inverted` to `false` if we remove an /// infinite set from another infinite one, resulting in a finite difference. fn invertible_difference_with( - self_set: &mut ComponentIdSet, + self_set: &mut HashSet, self_inverted: &mut bool, - other_set: &ComponentIdSet, + other_set: &HashSet, other_inverted: bool, ) { // We can share the implementation of `invertible_union_with` with some algebra: @@ -1501,12 +1515,14 @@ mod tests { use super::{invertible_difference_with, invertible_union_with}; use crate::{ component::ComponentId, + entity::EntityHash, query::{ access::AccessFilters, Access, AccessConflicts, ComponentAccessKind, ComponentIdSet, FilteredAccess, FilteredAccessSet, UnboundedAccessError, }, }; use alloc::{vec, vec::Vec}; + use bevy_platform::collections::HashSet; use fixedbitset::FixedBitSet; fn create_sample_access() -> Access { @@ -1799,9 +1815,9 @@ mod tests { assert_eq!( result, Ok(vec![ - ComponentAccessKind::Shared(ComponentId::from_u32(1)), - ComponentAccessKind::Shared(ComponentId::from_u32(2)), ComponentAccessKind::Exclusive(ComponentId::from_u32(3)), + ComponentAccessKind::Shared(ComponentId::from_u32(2)), + ComponentAccessKind::Shared(ComponentId::from_u32(1)), ComponentAccessKind::Archetypal(ComponentId::from_u32(5)), ]), ); @@ -1845,20 +1861,21 @@ mod tests { ); } - /// Create a `ComponentIdSet` with a given number of total bits and a given list of bits to set. - /// Setting the number of bits is important in tests since the `PartialEq` impl checks that the length matches. - fn bit_set(bits: usize, iter: impl IntoIterator) -> ComponentIdSet { - let mut result = FixedBitSet::with_capacity(bits); - result.extend(iter); - ComponentIdSet::from_bits(result) + /// Create a `HashSet` with a given list of `ComponentId`s to set. + fn hashset_from_u32s(array: [u32; N]) -> HashSet { + let mut set = HashSet::with_hasher(EntityHash); + for index in array { + set.insert(ComponentId::from_u32(index)); + } + set } #[test] fn invertible_union_with_tests() { let invertible_union = |mut self_inverted: bool, other_inverted: bool| { // Check all four possible bit states: In both sets, the first, the second, or neither - let mut self_set = bit_set(4, [0, 1]); - let other_set = bit_set(4, [0, 2]); + let mut self_set = hashset_from_u32s([0, 1]); + let other_set = hashset_from_u32s([0, 2]); invertible_union_with( &mut self_set, &mut self_inverted, @@ -1871,19 +1888,19 @@ mod tests { // Check each combination of `inverted` flags let (s, i) = invertible_union(false, false); // [0, 1] | [0, 2] = [0, 1, 2] - assert_eq!((s, i), (bit_set(4, [0, 1, 2]), false)); + assert_eq!((s, i), (hashset_from_u32s([0, 1, 2]), false)); let (s, i) = invertible_union(false, true); // [0, 1] | [1, 3, ...] = [0, 1, 3, ...] - assert_eq!((s, i), (bit_set(4, [2]), true)); + assert_eq!((s, i), (hashset_from_u32s([2]), true)); let (s, i) = invertible_union(true, false); // [2, 3, ...] | [0, 2] = [0, 2, 3, ...] - assert_eq!((s, i), (bit_set(4, [1]), true)); + assert_eq!((s, i), (hashset_from_u32s([1]), true)); let (s, i) = invertible_union(true, true); // [2, 3, ...] | [1, 3, ...] = [1, 2, 3, ...] - assert_eq!((s, i), (bit_set(4, [0]), true)); + assert_eq!((s, i), (hashset_from_u32s([0]), true)); } #[test] @@ -1892,9 +1909,9 @@ mod tests { // make sure we invert the bits beyond the original length. // Failing to call `grow` before `toggle_range` would cause bit 1 to be zero, // which would incorrectly treat it as included in the output set. - let mut self_set = bit_set(1, [0]); + let mut self_set = hashset_from_u32s([0]); let mut self_inverted = false; - let other_set = bit_set(3, [0, 1]); + let other_set = hashset_from_u32s([0, 1]); let other_inverted = true; invertible_union_with( &mut self_set, @@ -1904,15 +1921,15 @@ mod tests { ); // [0] | [2, ...] = [0, 2, ...] - assert_eq!((self_set, self_inverted), (bit_set(3, [1]), true)); + assert_eq!((self_set, self_inverted), (hashset_from_u32s([1]), true)); } #[test] fn invertible_difference_with_tests() { let invertible_difference = |mut self_inverted: bool, other_inverted: bool| { // Check all four possible bit states: In both sets, the first, the second, or neither - let mut self_set = bit_set(4, [0, 1]); - let other_set = bit_set(4, [0, 2]); + let mut self_set = hashset_from_u32s([0, 1]); + let other_set = hashset_from_u32s([0, 2]); invertible_difference_with( &mut self_set, &mut self_inverted, @@ -1925,19 +1942,19 @@ mod tests { // Check each combination of `inverted` flags let (s, i) = invertible_difference(false, false); // [0, 1] - [0, 2] = [1] - assert_eq!((s, i), (bit_set(4, [1]), false)); + assert_eq!((s, i), (hashset_from_u32s([1]), false)); let (s, i) = invertible_difference(false, true); // [0, 1] - [1, 3, ...] = [0] - assert_eq!((s, i), (bit_set(4, [0]), false)); + assert_eq!((s, i), (hashset_from_u32s([0]), false)); let (s, i) = invertible_difference(true, false); // [2, 3, ...] - [0, 2] = [3, ...] - assert_eq!((s, i), (bit_set(4, [0, 1, 2]), true)); + assert_eq!((s, i), (hashset_from_u32s([0, 1, 2]), true)); let (s, i) = invertible_difference(true, true); // [2, 3, ...] - [1, 3, ...] = [2] - assert_eq!((s, i), (bit_set(4, [2]), false)); + assert_eq!((s, i), (hashset_from_u32s([2]), false)); } #[test] diff --git a/crates/bevy_ecs/src/query/access_iter.rs b/crates/bevy_ecs/src/query/access_iter.rs index 0e8979e648eef..35e2b7019dc56 100644 --- a/crates/bevy_ecs/src/query/access_iter.rs +++ b/crates/bevy_ecs/src/query/access_iter.rs @@ -108,7 +108,7 @@ fn has_conflicts_large<'a, Q: QueryData>( } /// The data storage type that is being accessed. -#[derive(Copy, Clone, Debug, PartialEq, Hash)] +#[derive(Copy, Clone, Debug, PartialEq)] pub enum EcsAccessType<'a> { /// Accesses [`Component`](crate::prelude::Component) data Component(EcsAccessLevel), From 8694591b495f73612315f58ba2710031cc3de765 Mon Sep 17 00:00:00 2001 From: Trashtalk Date: Mon, 27 Apr 2026 23:10:50 +0200 Subject: [PATCH 14/22] made ComponentId::from_u32 public --- crates/bevy_ecs/src/component/info.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_ecs/src/component/info.rs b/crates/bevy_ecs/src/component/info.rs index 0c973dee670d1..45e6028191736 100644 --- a/crates/bevy_ecs/src/component/info.rs +++ b/crates/bevy_ecs/src/component/info.rs @@ -196,7 +196,7 @@ impl ComponentId { /// /// Panics if the index is `u32::MAX`. #[inline] - pub(crate) const fn from_u32(index: u32) -> ComponentId { + pub const fn from_u32(index: u32) -> ComponentId { ComponentId(Entity::from_raw_u32(index).unwrap()) } From 6448e4dd39408dece3418149ed7964aa0652a724 Mon Sep 17 00:00:00 2001 From: Trashtalk Date: Tue, 28 Apr 2026 17:38:15 +0200 Subject: [PATCH 15/22] fix doc tests and benches --- benches/benches/bevy_ecs/empty_archetypes.rs | 3 --- crates/bevy_ecs/src/query/access.rs | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/benches/benches/bevy_ecs/empty_archetypes.rs b/benches/benches/bevy_ecs/empty_archetypes.rs index 4938120ae9a12..dc40bca48f501 100644 --- a/benches/benches/bevy_ecs/empty_archetypes.rs +++ b/benches/benches/bevy_ecs/empty_archetypes.rs @@ -166,7 +166,6 @@ fn empty_archetypes(criterion: &mut Criterion) { schedule.add_systems(iter); }); add_archetypes(&mut world, archetype_count); - world.clear_entities(); let mut e = world.spawn_empty(); e.insert(A::<0>(1.0)); e.insert(A::<1>(1.0)); @@ -197,7 +196,6 @@ fn empty_archetypes(criterion: &mut Criterion) { schedule.add_systems(for_each); }); add_archetypes(&mut world, archetype_count); - world.clear_entities(); let mut e = world.spawn_empty(); e.insert(A::<0>(1.0)); e.insert(A::<1>(1.0)); @@ -228,7 +226,6 @@ fn empty_archetypes(criterion: &mut Criterion) { schedule.add_systems(par_for_each); }); add_archetypes(&mut world, archetype_count); - world.clear_entities(); let mut e = world.spawn_empty(); e.insert(A::<0>(1.0)); e.insert(A::<1>(1.0)); diff --git a/crates/bevy_ecs/src/query/access.rs b/crates/bevy_ecs/src/query/access.rs index 699054f0e92a1..f74330ca5c1b6 100644 --- a/crates/bevy_ecs/src/query/access.rs +++ b/crates/bevy_ecs/src/query/access.rs @@ -606,8 +606,8 @@ impl Access { /// assert_eq!( /// result, /// Ok(vec![ - /// ComponentAccessKind::Shared(ComponentId::from_u32(1)), /// ComponentAccessKind::Exclusive(ComponentId::from_u32(2)), + /// ComponentAccessKind::Shared(ComponentId::from_u32(1)), /// ComponentAccessKind::Archetypal(ComponentId::from_u32(3)), /// ]), /// ); From 6fcb4d8dffd2db599840b1bcc8599f0e093b0cd0 Mon Sep 17 00:00:00 2001 From: Trashtalk Date: Tue, 28 Apr 2026 17:53:45 +0200 Subject: [PATCH 16/22] fix doc comment --- crates/bevy_ecs/src/component/register.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/bevy_ecs/src/component/register.rs b/crates/bevy_ecs/src/component/register.rs index 9af6e2fff6c8a..4339ac701c93b 100644 --- a/crates/bevy_ecs/src/component/register.rs +++ b/crates/bevy_ecs/src/component/register.rs @@ -35,7 +35,7 @@ impl<'w> ComponentsRegistrator<'w> { /// /// # Safety /// - /// The [`Components`] and [`ComponentIds`] must match. + /// The [`Components`] and [`ComponentId`]s must match. /// For example, they must be from the same world. pub unsafe fn new(components: &'w mut Components, ids: &'w mut EntityAllocator) -> Self { Self { @@ -415,7 +415,7 @@ impl<'w> ComponentsQueuedRegistrator<'w> { /// /// # Safety /// - /// The [`Components`] and [`ComponentIds`] must match. + /// The [`Components`] and [`ComponentId`]s must match. /// For example, they must be from the same world. pub unsafe fn new(components: &'w Components, ids: &'w EntityAllocator) -> Self { Self { components, ids } From d072251dfae238c36a8600f7853ac708a6b92d9e Mon Sep 17 00:00:00 2001 From: Trashtalk Date: Sat, 2 May 2026 00:01:58 +0200 Subject: [PATCH 17/22] add ComponentIdMap as a hybrid map --- crates/bevy_ecs/src/component/identifier.rs | 150 ++++++++++++++++++++ crates/bevy_ecs/src/component/info.rs | 87 +----------- crates/bevy_ecs/src/component/mod.rs | 2 + crates/bevy_ecs/src/storage/sparse_set.rs | 5 +- 4 files changed, 160 insertions(+), 84 deletions(-) create mode 100644 crates/bevy_ecs/src/component/identifier.rs diff --git a/crates/bevy_ecs/src/component/identifier.rs b/crates/bevy_ecs/src/component/identifier.rs new file mode 100644 index 0000000000000..b10b84d70beed --- /dev/null +++ b/crates/bevy_ecs/src/component/identifier.rs @@ -0,0 +1,150 @@ +use crate::{ + entity::{Entity, EntityHashMap, EntityHashSet, EntityIndex}, + storage::{SparseArray, SparseSetIndex}, +}; +use bevy_ecs_macros::MapEntities; +#[cfg(feature = "bevy_reflect")] +use bevy_reflect::Reflect; +use fixedbitset::FixedBitSet; + +/// A value which uniquely identifies the type of a [`Component`] or [`Resource`] within a +/// [`World`](crate::world::World). +/// +/// Each time a new `Component` type is registered within a `World` using +/// e.g. [`World::register_component`](crate::world::World::register_component) or +/// [`World::register_component_with_descriptor`](crate::world::World::register_component_with_descriptor) +/// or a Resource with e.g. [`World::init_resource`](crate::world::World::init_resource), +/// a corresponding `ComponentId` is created to track it. +/// +/// While the distinction between `ComponentId` and [`TypeId`] may seem superficial, breaking them +/// into two separate but related concepts allows components to exist outside of Rust's type system. +/// Each Rust type registered as a `Component` will have a corresponding `ComponentId`, but additional +/// `ComponentId`s may exist in a `World` to track components which cannot be +/// represented as Rust types for scripting or other advanced use-cases. +/// +/// A `ComponentId` is tightly coupled to its parent `World`. Attempting to use a `ComponentId` from +/// one `World` to access the metadata of a `Component` in a different `World` is undefined behavior +/// and must not be attempted. +/// +/// Given a type `T` which implements [`Component`] (including [`Resource`]), the `ComponentId` for `T` can be retrieved +/// from a `World` using [`World::component_id()`](crate::world::World::component_id) or via [`Components::component_id()`]. +#[derive(Debug, Copy, Clone, Hash, Ord, PartialOrd, Eq, PartialEq, MapEntities)] +#[cfg_attr( + feature = "bevy_reflect", + derive(Reflect), + reflect(Debug, Hash, PartialEq, Clone) +)] +pub struct ComponentId(pub(super) Entity); + +impl ComponentId { + /// Creates a new [`ComponentId`]. + /// + /// The `index` is a unique value associated with each type of component in a given world. + /// Usually, this value is taken from a counter incremented for each type of component registered with the world. + #[inline] + pub const fn new(index: Entity) -> ComponentId { + ComponentId(index) + } + + /// Creates a new [`ComponentId`]. + /// + /// Panics if the index is `u32::MAX`. + #[inline] + pub const fn from_u32(index: u32) -> ComponentId { + ComponentId(Entity::from_raw_u32(index).unwrap()) + } + + /// Returns the index of the current component. + // TODO: Track down all uses and improve data structures for performance. + #[inline] + pub fn index(self) -> usize { + self.0.index_u32() as usize + } + + /// Returns the inner entity from the `ComponentId` + #[inline] + pub fn id(self) -> Entity { + self.0 + } +} + +// Identical implementation as Entity +impl SparseSetIndex for ComponentId { + #[inline] + fn sparse_set_index(&self) -> usize { + self.0.sparse_set_index() + } + + #[inline] + fn get_sparse_set_index(value: usize) -> Self { + Self(Entity::get_sparse_set_index(value)) + } +} + +#[derive(Debug)] +pub(crate) struct ComponentIdMap { + dense: SparseArray, + sparse: EntityHashMap, +} + +impl Default for ComponentIdMap { + fn default() -> ComponentIdMap { + ComponentIdMap { + dense: SparseArray::new(), + sparse: EntityHashMap::new(), + } + } +} + +impl ComponentIdMap { + pub(crate) fn contains_key(&self, id: &ComponentId) -> bool { + let index = id.id().index(); + if index.index() < 1024 { + self.dense.contains(index) + } else { + self.sparse.contains_key(&id.id()) + } + } + + pub(crate) fn get(&self, id: &ComponentId) -> Option<&V> { + let index = id.id().index(); + if index.index() < 1024 { + self.dense.get(index) + } else { + self.sparse.get(&id.id()) + } + } + + pub(crate) fn get_mut(&mut self, id: &ComponentId) -> Option<&mut V> { + let index = id.id().index(); + if index.index() < 1024 { + self.dense.get_mut(index) + } else { + self.sparse.get_mut(&id.id()) + } + } + + pub(crate) fn values(&self) -> impl Iterator + '_ { + self.dense + .iter() + .map(|(_, v)| v) + .chain(self.sparse.values()) + } + + pub(crate) fn len(&self) -> usize { + self.dense.len() + self.sparse.len() + } + + /// # Safety + /// + /// This operation is safe if a key does not exist in the map. + pub unsafe fn insert_unique_unchecked(&mut self, id: ComponentId, value: V) { + let index = id.id().index(); + if index.index() < 1024 { + self.dense.insert(index, value); + } else { + // SAFETY: safety contract is ensured by the caller. + unsafe { self.sparse.insert_unique_unchecked(id.id(), value) }; + } + } +} diff --git a/crates/bevy_ecs/src/component/info.rs b/crates/bevy_ecs/src/component/info.rs index 45e6028191736..ac4b4785eacfc 100644 --- a/crates/bevy_ecs/src/component/info.rs +++ b/crates/bevy_ecs/src/component/info.rs @@ -1,9 +1,6 @@ use alloc::borrow::Cow; -use bevy_ecs_macros::MapEntities; -use bevy_platform::{collections::HashMap, hash::FixedHasher, sync::PoisonError}; +use bevy_platform::{hash::FixedHasher, sync::PoisonError}; use bevy_ptr::OwningPtr; -#[cfg(feature = "bevy_reflect")] -use bevy_reflect::Reflect; use bevy_utils::{prelude::DebugName, TypeIdMap}; use core::{ alloc::Layout, @@ -16,17 +13,15 @@ use indexmap::IndexSet; use crate::{ archetype::ArchetypeFlags, component::{ - Component, ComponentCloneBehavior, ComponentMutability, QueuedComponents, - RequiredComponents, StorageType, + Component, ComponentCloneBehavior, ComponentId, ComponentIdMap, ComponentMutability, + QueuedComponents, RequiredComponents, StorageType, }, - entity::{Entity, EntityHash}, lifecycle::ComponentHooks, query::DebugCheckedUnwrap as _, relationship::{ MaybeRelationshipAccessor, RelationshipAccessor, RelationshipAccessorInitializer, }, resource::Resource, - storage::SparseSetIndex, }; /// Stores metadata for a type of component or resource stored in a specific [`World`](crate::world::World). @@ -153,80 +148,6 @@ impl ComponentInfo { } } -/// A value which uniquely identifies the type of a [`Component`] or [`Resource`] within a -/// [`World`](crate::world::World). -/// -/// Each time a new `Component` type is registered within a `World` using -/// e.g. [`World::register_component`](crate::world::World::register_component) or -/// [`World::register_component_with_descriptor`](crate::world::World::register_component_with_descriptor) -/// or a Resource with e.g. [`World::init_resource`](crate::world::World::init_resource), -/// a corresponding `ComponentId` is created to track it. -/// -/// While the distinction between `ComponentId` and [`TypeId`] may seem superficial, breaking them -/// into two separate but related concepts allows components to exist outside of Rust's type system. -/// Each Rust type registered as a `Component` will have a corresponding `ComponentId`, but additional -/// `ComponentId`s may exist in a `World` to track components which cannot be -/// represented as Rust types for scripting or other advanced use-cases. -/// -/// A `ComponentId` is tightly coupled to its parent `World`. Attempting to use a `ComponentId` from -/// one `World` to access the metadata of a `Component` in a different `World` is undefined behavior -/// and must not be attempted. -/// -/// Given a type `T` which implements [`Component`] (including [`Resource`]), the `ComponentId` for `T` can be retrieved -/// from a `World` using [`World::component_id()`](crate::world::World::component_id) or via [`Components::component_id()`]. -#[derive(Debug, Copy, Clone, Hash, Ord, PartialOrd, Eq, PartialEq, MapEntities)] -#[cfg_attr( - feature = "bevy_reflect", - derive(Reflect), - reflect(Debug, Hash, PartialEq, Clone) -)] -pub struct ComponentId(pub(super) Entity); - -impl ComponentId { - /// Creates a new [`ComponentId`]. - /// - /// The `index` is a unique value associated with each type of component in a given world. - /// Usually, this value is taken from a counter incremented for each type of component registered with the world. - #[inline] - pub const fn new(index: Entity) -> ComponentId { - ComponentId(index) - } - - /// Creates a new [`ComponentId`]. - /// - /// Panics if the index is `u32::MAX`. - #[inline] - pub const fn from_u32(index: u32) -> ComponentId { - ComponentId(Entity::from_raw_u32(index).unwrap()) - } - - /// Returns the index of the current component. - // TODO: Track down all uses and improve data structures for performance. - #[inline] - pub fn index(self) -> usize { - self.0.index_u32() as usize - } - - /// Returns the inner entity from the `ComponentId` - #[inline] - pub fn id(self) -> Entity { - self.0 - } -} - -// Identical implementation as Entity -impl SparseSetIndex for ComponentId { - #[inline] - fn sparse_set_index(&self) -> usize { - self.0.sparse_set_index() - } - - #[inline] - fn get_sparse_set_index(value: usize) -> Self { - Self(Entity::get_sparse_set_index(value)) - } -} - /// A value describing a component or resource, which may or may not correspond to a Rust type. #[derive(Clone)] pub struct ComponentDescriptor { @@ -373,7 +294,7 @@ impl ComponentDescriptor { /// Stores metadata associated with each kind of [`Component`] in a given [`World`](crate::world::World). #[derive(Debug, Default)] pub struct Components { - pub(super) components: HashMap, + pub(super) components: ComponentIdMap, pub(super) indices: TypeIdMap, // This is kept internal and local to verify that no deadlocks can occur. pub(super) queued: bevy_platform::sync::RwLock, diff --git a/crates/bevy_ecs/src/component/mod.rs b/crates/bevy_ecs/src/component/mod.rs index a30f7379787ae..6ef5fb660ffae 100644 --- a/crates/bevy_ecs/src/component/mod.rs +++ b/crates/bevy_ecs/src/component/mod.rs @@ -2,12 +2,14 @@ mod clone; mod constants; +mod identifier; mod info; mod register; mod required; pub use clone::*; pub use constants::*; +pub use identifier::*; pub use info::*; pub use register::*; pub use required::*; diff --git a/crates/bevy_ecs/src/storage/sparse_set.rs b/crates/bevy_ecs/src/storage/sparse_set.rs index 6e7f86daca051..a3621be14dacd 100644 --- a/crates/bevy_ecs/src/storage/sparse_set.rs +++ b/crates/bevy_ecs/src/storage/sparse_set.rs @@ -61,6 +61,10 @@ impl SparseArray { marker: PhantomData, } } + + pub fn len(&self) -> usize { + self.values.len() + } } macro_rules! impl_sparse_array { @@ -141,7 +145,6 @@ impl SparseArray { /// This must scan the entire array to find non-empty values, /// which may be slow even if the array is sparsely populated. #[inline] - #[expect(dead_code, reason = "May need this for later.")] pub(crate) fn iter(&self) -> impl Iterator { self.values.iter().enumerate().filter_map(|(index, value)| { value From 023893f3161d9dee5d9691f150af68b23cff313c Mon Sep 17 00:00:00 2001 From: Trashtalk Date: Sat, 2 May 2026 02:58:44 +0200 Subject: [PATCH 18/22] changed ComponentIdSet --- crates/bevy_ecs/src/component/identifier.rs | 236 ++++++++++++++- crates/bevy_ecs/src/query/access.rs | 307 ++++---------------- crates/bevy_ecs/src/storage/sparse_set.rs | 8 +- crates/bevy_ecs/src/world/mod.rs | 25 +- 4 files changed, 308 insertions(+), 268 deletions(-) diff --git a/crates/bevy_ecs/src/component/identifier.rs b/crates/bevy_ecs/src/component/identifier.rs index b10b84d70beed..1e4448d117e98 100644 --- a/crates/bevy_ecs/src/component/identifier.rs +++ b/crates/bevy_ecs/src/component/identifier.rs @@ -1,3 +1,5 @@ +use core::ops::{BitAndAssign, BitOrAssign, SubAssign}; + use crate::{ entity::{Entity, EntityHashMap, EntityHashSet, EntityIndex}, storage::{SparseArray, SparseSetIndex}, @@ -125,10 +127,7 @@ impl ComponentIdMap { } pub(crate) fn values(&self) -> impl Iterator + '_ { - self.dense - .iter() - .map(|(_, v)| v) - .chain(self.sparse.values()) + self.dense.values().chain(self.sparse.values()) } pub(crate) fn len(&self) -> usize { @@ -148,3 +147,232 @@ impl ComponentIdMap { } } } + +/// A set of [`ComponentId`]s. +#[derive(Default, PartialEq, Eq)] +pub struct ComponentIdSet { + dense: FixedBitSet, + sparse: EntityHashSet, +} + +impl ComponentIdSet { + /// Create a new empty `ComponentIdSet`. + #[inline] + pub const fn new() -> Self { + Self { + dense: FixedBitSet::new(), + sparse: EntityHashSet::new(), + } + } + + #[cfg(test)] + pub(crate) fn from_bits(bits: FixedBitSet) -> Self { + Self { + dense: bits, + sparse: EntityHashSet::new(), + } + } + + /// Adds a [`ComponentId`] to the set. + #[inline] + pub fn insert(&mut self, index: ComponentId) { + let entity = index.id(); + if entity.index_u32() < 1024 { + self.dense.grow_and_insert(index.index()); + } else { + self.sparse.insert(entity); + } + } + + /// Removes a [`ComponentId`] from the set. + #[inline] + pub fn remove(&mut self, index: ComponentId) { + let entity = index.id(); + if entity.index_u32() < 1024 { + if index.index() < self.dense.len() { + self.dense.remove(index.index()); + } + } else { + self.sparse.remove(&entity); + } + } + + /// Removes all [`ComponentId`]s from the set. + #[inline] + pub fn clear(&mut self) { + self.dense.clear(); + self.sparse.clear(); + } + + /// Returns `true` if the [`ComponentId`] is in the set. + #[inline] + pub fn contains(&self, index: ComponentId) -> bool { + let entity = index.id(); + if entity.index_u32() < 1024 { + self.dense.contains(index.index()) + } else { + self.sparse.contains(&entity) + } + } + + /// Returns `true` if `self` has no elements in common with `other`. This + /// is equivalent to checking for an empty intersection. + #[inline] + pub fn is_disjoint(&self, other: &ComponentIdSet) -> bool { + self.dense.is_disjoint(&other.dense) && self.sparse.is_disjoint(&other.sparse) + } + + /// Returns `true` if the set is a subset of another, i.e. `other` contains + /// at least all the values in `self`. + #[inline] + pub fn is_subset(&self, other: &ComponentIdSet) -> bool { + self.dense.is_subset(&other.dense) && self.sparse.is_subset(&other.sparse) + } + + /// Returns `true` if the set is empty. + #[inline] + pub fn is_clear(&self) -> bool { + self.dense.is_clear() && self.sparse.is_empty() + } + + /// Iterates the [`ComponentId`]s in the set. + #[inline] + pub fn iter(&self) -> impl Iterator { + self.dense + .ones() + .map(|id| ComponentId::from_u32(id as u32)) + .chain(self.sparse.iter().map(|entity| ComponentId::new(*entity))) + } + + /// Returns a lazy iterator over the union of two [`ComponentIdSet`]s. + #[inline] + pub fn union<'a>(&'a self, other: &'a ComponentIdSet) -> impl Iterator { + self.dense + .union(&other.dense) + .map(|id| ComponentId::from_u32(id as u32)) + .chain( + self.sparse + .union(&other.sparse) + .map(|entity| ComponentId::new(*entity)), + ) + } + + /// Returns a lazy iterator over the intersection of two [`ComponentIdSet`]s. + #[inline] + pub fn intersection<'a>( + &'a self, + other: &'a ComponentIdSet, + ) -> impl Iterator { + self.dense + .intersection(&other.dense) + .map(|id| ComponentId::from_u32(id as u32)) + .chain( + self.sparse + .intersection(&other.sparse) + .map(|entity| ComponentId::new(*entity)), + ) + } + + /// Returns a lazy iterator over the difference of two [`ComponentIdSet`]s. + #[inline] + pub fn difference<'a>( + &'a self, + other: &'a ComponentIdSet, + ) -> impl Iterator { + self.dense + .difference(&other.dense) + .map(|id| ComponentId::from_u32(id as u32)) + .chain( + self.sparse + .difference(&other.sparse) + .map(|entity| ComponentId::new(*entity)), + ) + } + + /// In-place union of two [`ComponentIdSet`]s. + #[inline] + pub fn union_with(&mut self, other: &ComponentIdSet) { + self.dense.union_with(&other.dense); + self.sparse.bitor_assign(&other.sparse); + } + + /// In-place intersection of two [`ComponentIdSet`]s. + #[inline] + pub fn intersect_with(&mut self, other: &ComponentIdSet) { + self.dense.intersect_with(&other.dense); + self.sparse.bitand_assign(&other.sparse) + } + + /// In-place difference of two [`ComponentIdSet`]s. + #[inline] + pub fn difference_with(&mut self, other: &ComponentIdSet) { + self.dense.difference_with(&other.dense); + self.sparse.sub_assign(&other.sparse); + } + + /// In-place reversed difference of two [`ComponentIdSet`]s. + /// This sets `self` to be `other.difference(self)`. + #[inline] + pub fn difference_from(&mut self, other: &ComponentIdSet) { + // Calculate `other - self` as `!self & other` + // We have to grow here because the new bits are going to get flipped to 1. + self.dense.grow(other.dense.len()); + self.dense.toggle_range(..); + self.dense.intersect_with(&other.dense); + self.sparse.clone_from(&(&other.sparse - &self.sparse)); + } +} + +impl Clone for ComponentIdSet { + #[inline] + fn clone(&self) -> Self { + ComponentIdSet { + dense: self.dense.clone(), + sparse: self.sparse.clone(), + } + } + + #[inline] + fn clone_from(&mut self, source: &Self) { + self.dense.clone_from(&source.dense); + self.sparse.clone_from(&source.sparse); + } +} + +impl alloc::fmt::Debug for ComponentIdSet { + fn fmt(&self, f: &mut alloc::fmt::Formatter<'_>) -> alloc::fmt::Result { + // `FixedBitSet` normally has a `Debug` output like: + // FixedBitSet { data: [ 160 ], length: 8 } + // Instead, print the list of set values, like: + // [ 5, 7 ] + // Don't wrap in `ComponentId`, since that would just output: + // [ ComponentId(5), ComponentId(7) ] + f.debug_list() + .entries( + self.dense + .ones() + .chain(self.sparse.iter().map(|index| index.index_u32() as usize)), + ) + .finish() + } +} + +impl FromIterator for ComponentIdSet { + #[inline] + fn from_iter>(iter: T) -> Self { + let mut set = ComponentIdSet::new(); + for index in iter { + set.insert(index); + } + set + } +} + +impl Extend for ComponentIdSet { + #[inline] + fn extend>(&mut self, iter: T) { + for index in iter { + self.insert(index); + } + } +} diff --git a/crates/bevy_ecs/src/query/access.rs b/crates/bevy_ecs/src/query/access.rs index f74330ca5c1b6..aa0aa1ff12fa8 100644 --- a/crates/bevy_ecs/src/query/access.rs +++ b/crates/bevy_ecs/src/query/access.rs @@ -1,13 +1,11 @@ -use crate::entity::EntityHash; use crate::world::unsafe_world_cell::UnsafeWorldCell; -use crate::{component::ComponentId, resource::IS_RESOURCE}; +use crate::{ + component::{ComponentId, ComponentIdSet}, + resource::IS_RESOURCE, +}; use alloc::{format, string::String, vec, vec::Vec}; -use bevy_platform::collections::HashSet; use core::iter::FusedIterator; -use core::ops::{BitAndAssign, BitOrAssign, SubAssign}; -use core::{fmt, fmt::Debug}; use derive_more::From; -use fixedbitset::{Difference, FixedBitSet, Intersection, IntoOnes, Ones, Union}; use thiserror::Error; /// Tracks read and write access to specific elements in a collection. @@ -18,10 +16,10 @@ use thiserror::Error; pub struct Access { /// All accessed components, or forbidden components if /// `Self::component_read_and_writes_inverted` is set. - read_and_writes: HashSet, + read_and_writes: ComponentIdSet, /// All exclusively-accessed components, or components that may not be /// exclusively accessed if `Self::component_writes_inverted` is set. - writes: HashSet, + writes: ComponentIdSet, /// Is `true` if this component can read all components *except* those /// present in `Self::read_and_writes`. read_and_writes_inverted: bool, @@ -29,7 +27,7 @@ pub struct Access { /// present in `Self::writes`. writes_inverted: bool, // Components that are not accessed, but whose presence in an archetype affect query results. - archetypal: HashSet, + archetypal: ComponentIdSet, } // This is needed since `#[derive(Clone)]` does not generate optimized `clone_from`. @@ -59,9 +57,9 @@ impl Access { Self { read_and_writes_inverted: false, writes_inverted: false, - read_and_writes: HashSet::with_hasher(EntityHash), - writes: HashSet::with_hasher(EntityHash), - archetypal: HashSet::with_hasher(EntityHash), + read_and_writes: ComponentIdSet::new(), + writes: ComponentIdSet::new(), + archetypal: ComponentIdSet::new(), } } @@ -99,7 +97,7 @@ impl Access { if !self.read_and_writes_inverted { self.read_and_writes.insert(index); } else { - self.read_and_writes.remove(&index); + self.read_and_writes.remove(index); } } @@ -115,7 +113,7 @@ impl Access { if !self.writes_inverted { self.writes.insert(index); } else { - self.writes.remove(&index); + self.writes.remove(index); } } @@ -156,7 +154,7 @@ impl Access { if self.read_and_writes_inverted { self.read_and_writes.insert(index); } else { - self.read_and_writes.remove(&index); + self.read_and_writes.remove(index); } } @@ -178,7 +176,7 @@ impl Access { if self.writes_inverted { self.writes.insert(index); } else { - self.writes.remove(&index); + self.writes.remove(index); } } @@ -203,7 +201,7 @@ impl Access { /// Returns `true` if this can access the component given by `index`. pub fn has_read(&self, index: ComponentId) -> bool { - self.read_and_writes_inverted ^ self.read_and_writes.contains(&index) + self.read_and_writes_inverted ^ self.read_and_writes.contains(index) } /// Returns `true` if this can access any component. @@ -214,7 +212,7 @@ impl Access { /// Returns `true` if this can access any component. pub fn has_any_read(&self) -> bool { - self.read_and_writes_inverted || !self.read_and_writes.is_empty() + self.read_and_writes_inverted || !self.read_and_writes.is_clear() } /// Returns `true` if this can exclusively access the component given by `index`. @@ -225,7 +223,7 @@ impl Access { /// Returns `true` if this can exclusively access the component given by `index`. pub fn has_write(&self, index: ComponentId) -> bool { - self.writes_inverted ^ self.writes.contains(&index) + self.writes_inverted ^ self.writes.contains(index) } /// Returns `true` if this accesses any component mutably. @@ -236,7 +234,7 @@ impl Access { /// Returns `true` if this accesses any component mutably. pub fn has_any_write(&self) -> bool { - self.writes_inverted || !self.writes.is_empty() + self.writes_inverted || !self.writes.is_clear() } /// Returns `true` if this can access the resource given by `index`. @@ -272,7 +270,7 @@ impl Access { /// /// [`Has`]: crate::query::Has pub fn has_archetypal(&self, index: ComponentId) -> bool { - self.archetypal.contains(&index) + self.archetypal.contains(index) } /// Sets this as having access to all components (i.e. `EntityRef`). @@ -311,7 +309,7 @@ impl Access { /// Returns `true` if this has access to all components (i.e. `EntityRef` and `&World`). #[inline] pub fn has_read_all(&self) -> bool { - self.read_and_writes_inverted && self.read_and_writes.is_empty() + self.read_and_writes_inverted && self.read_and_writes.is_clear() } /// Returns `true` if this has write access to all components (i.e. `EntityMut` and `&mut World`). @@ -323,7 +321,7 @@ impl Access { /// Returns `true` if this has write access to all components (i.e. `EntityMut` and `&mut World`). #[inline] pub fn has_write_all(&self) -> bool { - self.writes_inverted && self.writes.is_empty() + self.writes_inverted && self.writes.is_clear() } /// Removes all writes. @@ -354,7 +352,7 @@ impl Access { &other.writes, other.writes_inverted, ); - self.archetypal.bitor_assign(&other.archetypal); + self.archetypal.union_with(&other.archetypal); } /// Removes any access from `self` that would conflict with `other`. @@ -515,18 +513,9 @@ impl Access { let temp_conflicts: ComponentIdSet = match (lhs_writes_inverted, rhs_reads_and_writes_inverted) { (true, true) => return AccessConflicts::All, - (false, true) => lhs_writes - .difference(rhs_reads_and_writes) - .copied() - .collect(), - (true, false) => rhs_reads_and_writes - .difference(lhs_writes) - .copied() - .collect(), - (false, false) => lhs_writes - .intersection(rhs_reads_and_writes) - .copied() - .collect(), + (false, true) => lhs_writes.difference(rhs_reads_and_writes).collect(), + (true, false) => rhs_reads_and_writes.difference(lhs_writes).collect(), + (false, false) => lhs_writes.intersection(rhs_reads_and_writes).collect(), }; conflicts.union_with(&temp_conflicts); } @@ -542,15 +531,13 @@ impl Access { /// Currently, this is only used for [`Has`]. /// /// [`Has`]: crate::query::Has - pub fn archetypal(&self) -> &HashSet { + pub fn archetypal(&self) -> &ComponentIdSet { &self.archetypal } /// Returns the set of components with read or write access, /// or an error if the access is unbounded. - pub fn try_reads_and_writes( - &self, - ) -> Result<&HashSet, UnboundedAccessError> { + pub fn try_reads_and_writes(&self) -> Result<&ComponentIdSet, UnboundedAccessError> { // writes_inverted is only ever true when read_and_writes_inverted is // also true. Therefore it is sufficient to check just read_and_writes_inverted. if self.read_and_writes_inverted { @@ -564,7 +551,7 @@ impl Access { /// Returns the set of components with write access, /// or an error if the access is unbounded. - pub fn try_writes(&self) -> Result<&HashSet, UnboundedAccessError> { + pub fn try_writes(&self) -> Result<&ComponentIdSet, UnboundedAccessError> { if self.writes_inverted { return Err(UnboundedAccessError { writes_inverted: self.writes_inverted, @@ -617,16 +604,16 @@ impl Access { ) -> Result + '_, UnboundedAccessError> { let reads_and_writes = self.try_reads_and_writes()?.iter().map(|index| { if self.writes.contains(index) { - ComponentAccessKind::Exclusive(*index) + ComponentAccessKind::Exclusive(index) } else { - ComponentAccessKind::Shared(*index) + ComponentAccessKind::Shared(index) } }); let archetypal = self .archetypal .difference(&self.read_and_writes) - .map(|id| ComponentAccessKind::Archetypal(*id)); + .map(|id| ComponentAccessKind::Archetypal(id)); Ok(reads_and_writes.chain(archetypal)) } @@ -641,19 +628,19 @@ impl Access { /// Note that this may change `self_inverted` to `true` if we add an infinite /// set to a finite one, resulting in a new infinite set. fn invertible_union_with( - self_set: &mut HashSet, + self_set: &mut ComponentIdSet, self_inverted: &mut bool, - other_set: &HashSet, + other_set: &ComponentIdSet, other_inverted: bool, ) { match (*self_inverted, other_inverted) { - (true, true) => self_set.bitand_assign(other_set), - (true, false) => self_set.sub_assign(other_set), + (true, true) => self_set.intersect_with(other_set), + (true, false) => self_set.difference_with(other_set), (false, true) => { *self_inverted = true; - self_set.clone_from(&(other_set - self_set)); + self_set.difference_from(other_set) } - (false, false) => self_set.bitor_assign(other_set), + (false, false) => self_set.union_with(other_set), } } @@ -666,9 +653,9 @@ fn invertible_union_with( /// Note that this may change `self_inverted` to `false` if we remove an /// infinite set from another infinite one, resulting in a finite difference. fn invertible_difference_with( - self_set: &mut HashSet, + self_set: &mut ComponentIdSet, self_inverted: &mut bool, - other_set: &HashSet, + other_set: &ComponentIdSet, other_inverted: bool, ) { // We can share the implementation of `invertible_union_with` with some algebra: @@ -786,7 +773,7 @@ impl AccessConflicts { *s = AccessConflicts::All; } (AccessConflicts::Individual(this), AccessConflicts::Individual(other)) => { - this.extend(other); + this.extend(other.iter()); } _ => {} } @@ -1295,188 +1282,6 @@ impl FilteredAccessSet { } } -/// A set of [`ComponentId`]s. -#[derive(Default, Eq, PartialEq, Hash)] -#[repr(transparent)] -pub struct ComponentIdSet(FixedBitSet); - -impl ComponentIdSet { - /// Create a new empty `ComponentIdSet`. - #[inline] - pub const fn new() -> Self { - Self(FixedBitSet::new()) - } - - #[cfg(test)] - pub(crate) fn from_bits(bits: FixedBitSet) -> Self { - Self(bits) - } - - /// Adds a [`ComponentId`] to the set. - #[inline] - pub fn insert(&mut self, index: ComponentId) { - self.0.grow_and_insert(index.index()); - } - - /// Removes a [`ComponentId`] from the set. - #[inline] - pub fn remove(&mut self, index: ComponentId) { - if index.index() < self.0.len() { - self.0.remove(index.index()); - } - } - - /// Removes all [`ComponentId`]s from the set. - #[inline] - pub fn clear(&mut self) { - self.0.clear(); - } - - /// Returns `true` if the [`ComponentId`] is in the set. - #[inline] - pub fn contains(&self, index: ComponentId) -> bool { - self.0.contains(index.index()) - } - - /// Returns `true` if `self` has no elements in common with `other`. This - /// is equivalent to checking for an empty intersection. - #[inline] - pub fn is_disjoint(&self, other: &ComponentIdSet) -> bool { - self.0.is_disjoint(&other.0) - } - - /// Returns `true` if the set is a subset of another, i.e. `other` contains - /// at least all the values in `self`. - #[inline] - pub fn is_subset(&self, other: &ComponentIdSet) -> bool { - self.0.is_subset(&other.0) - } - - /// Returns `true` if the set is empty. - #[inline] - pub fn is_clear(&self) -> bool { - self.0.is_clear() - } - - /// Iterates the [`ComponentId`]s in the set. - #[inline] - pub fn iter(&self) -> ComponentIdIter> { - ComponentIdIter(self.0.ones()) - } - - /// Returns a lazy iterator over the union of two [`ComponentIdSet`]s. - #[inline] - pub fn union<'a>(&'a self, other: &'a ComponentIdSet) -> ComponentIdIter> { - ComponentIdIter(self.0.union(&other.0)) - } - - /// Returns a lazy iterator over the intersection of two [`ComponentIdSet`]s. - #[inline] - pub fn intersection<'a>( - &'a self, - other: &'a ComponentIdSet, - ) -> ComponentIdIter> { - ComponentIdIter(self.0.intersection(&other.0)) - } - - /// Returns a lazy iterator over the difference of two [`ComponentIdSet`]s. - #[inline] - pub fn difference<'a>(&'a self, other: &'a ComponentIdSet) -> ComponentIdIter> { - ComponentIdIter(self.0.difference(&other.0)) - } - - /// In-place union of two [`ComponentIdSet`]s. - #[inline] - pub fn union_with(&mut self, other: &ComponentIdSet) { - self.0.union_with(&other.0); - } - - /// In-place intersection of two [`ComponentIdSet`]s. - #[inline] - pub fn intersect_with(&mut self, other: &ComponentIdSet) { - self.0.intersect_with(&other.0); - } - - /// In-place difference of two [`ComponentIdSet`]s. - #[inline] - pub fn difference_with(&mut self, other: &ComponentIdSet) { - self.0.difference_with(&other.0); - } - - /// In-place reversed difference of two [`ComponentIdSet`]s. - /// This sets `self` to be `other.difference(self)`. - #[inline] - pub fn difference_from(&mut self, other: &ComponentIdSet) { - // Calculate `other - self` as `!self & other` - // We have to grow here because the new bits are going to get flipped to 1. - self.0.grow(other.0.len()); - self.0.toggle_range(..); - self.0.intersect_with(&other.0); - } -} - -impl Debug for ComponentIdSet { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - // `FixedBitSet` normally has a `Debug` output like: - // FixedBitSet { data: [ 160 ], length: 8 } - // Instead, print the list of set values, like: - // [ 5, 7 ] - // Don't wrap in `ComponentId`, since that would just output: - // [ ComponentId(5), ComponentId(7) ] - f.debug_list().entries(self.0.ones()).finish() - } -} - -impl Clone for ComponentIdSet { - #[inline] - fn clone(&self) -> Self { - Self(self.0.clone()) - } - - #[inline] - fn clone_from(&mut self, source: &Self) { - self.0.clone_from(&source.0); - } -} - -impl IntoIterator for ComponentIdSet { - type Item = ComponentId; - - type IntoIter = ComponentIdIter; - - #[inline] - fn into_iter(self) -> Self::IntoIter { - ComponentIdIter(self.0.into_ones()) - } -} - -impl<'a> IntoIterator for &'a ComponentIdSet { - type Item = ComponentId; - - type IntoIter = ComponentIdIter>; - - #[inline] - fn into_iter(self) -> Self::IntoIter { - self.iter() - } -} - -impl FromIterator for ComponentIdSet { - #[inline] - fn from_iter>(iter: T) -> Self { - Self(FixedBitSet::from_iter( - iter.into_iter().map(ComponentId::index), - )) - } -} - -impl Extend for ComponentIdSet { - #[inline] - fn extend>(&mut self, iter: T) { - self.0.extend(iter.into_iter().map(ComponentId::index)); - } -} - /// An iterator of [`ComponentId`]s. /// /// This is equivalent to `map(ComponentId::from_u32)`, @@ -1514,15 +1319,13 @@ impl> FusedIterator for ComponentIdIter {} mod tests { use super::{invertible_difference_with, invertible_union_with}; use crate::{ - component::ComponentId, - entity::EntityHash, + component::{ComponentId, ComponentIdSet}, query::{ - access::AccessFilters, Access, AccessConflicts, ComponentAccessKind, ComponentIdSet, - FilteredAccess, FilteredAccessSet, UnboundedAccessError, + access::AccessFilters, Access, AccessConflicts, ComponentAccessKind, FilteredAccess, + FilteredAccessSet, UnboundedAccessError, }, }; use alloc::{vec, vec::Vec}; - use bevy_platform::collections::HashSet; use fixedbitset::FixedBitSet; fn create_sample_access() -> Access { @@ -1815,9 +1618,9 @@ mod tests { assert_eq!( result, Ok(vec![ - ComponentAccessKind::Exclusive(ComponentId::from_u32(3)), - ComponentAccessKind::Shared(ComponentId::from_u32(2)), ComponentAccessKind::Shared(ComponentId::from_u32(1)), + ComponentAccessKind::Shared(ComponentId::from_u32(2)), + ComponentAccessKind::Exclusive(ComponentId::from_u32(3)), ComponentAccessKind::Archetypal(ComponentId::from_u32(5)), ]), ); @@ -1862,8 +1665,8 @@ mod tests { } /// Create a `HashSet` with a given list of `ComponentId`s to set. - fn hashset_from_u32s(array: [u32; N]) -> HashSet { - let mut set = HashSet::with_hasher(EntityHash); + fn hashset_from_u32s(array: [u32; N]) -> ComponentIdSet { + let mut set = ComponentIdSet::new(); for index in array { set.insert(ComponentId::from_u32(index)); } @@ -1900,7 +1703,13 @@ mod tests { let (s, i) = invertible_union(true, true); // [2, 3, ...] | [1, 3, ...] = [1, 2, 3, ...] - assert_eq!((s, i), (hashset_from_u32s([0]), true)); + assert_eq!( + (s, i), + ( + ComponentIdSet::from_bits(FixedBitSet::with_capacity_and_blocks(2, [0b01])), + true + ) + ); } #[test] @@ -1946,7 +1755,13 @@ mod tests { let (s, i) = invertible_difference(false, true); // [0, 1] - [1, 3, ...] = [0] - assert_eq!((s, i), (hashset_from_u32s([0]), false)); + assert_eq!( + (s, i), + ( + ComponentIdSet::from_bits(FixedBitSet::with_capacity_and_blocks(2, [0b01])), + false + ) + ); let (s, i) = invertible_difference(true, false); // [2, 3, ...] - [0, 2] = [3, ...] diff --git a/crates/bevy_ecs/src/storage/sparse_set.rs b/crates/bevy_ecs/src/storage/sparse_set.rs index a3621be14dacd..26fa261b20f0e 100644 --- a/crates/bevy_ecs/src/storage/sparse_set.rs +++ b/crates/bevy_ecs/src/storage/sparse_set.rs @@ -145,12 +145,8 @@ impl SparseArray { /// This must scan the entire array to find non-empty values, /// which may be slow even if the array is sparsely populated. #[inline] - pub(crate) fn iter(&self) -> impl Iterator { - self.values.iter().enumerate().filter_map(|(index, value)| { - value - .as_ref() - .map(|value| (SparseSetIndex::get_sparse_set_index(index), value)) - }) + pub(crate) fn values(&self) -> impl Iterator { + self.values.iter().filter_map(|a| a.as_ref()) } } diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index 851daf5eaad77..92ed07a2d2973 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -4117,7 +4117,13 @@ mod tests { let mut iter = world.iter_resources(); - // The resources are given back in reverse order of component registration + // The resources are given back in order of component registration + // but this is not always true + let (info, ptr) = iter.next().unwrap(); + assert_eq!(info.name(), DebugName::type_name::()); + // SAFETY: We know that the resource is of type `TestResource` + assert_eq!(unsafe { ptr.deref::().0 }, 42); + let (info, ptr) = iter.next().unwrap(); assert_eq!(info.name(), DebugName::type_name::()); assert_eq!( @@ -4126,11 +4132,6 @@ mod tests { &"Hello, world!".to_string() ); - let (info, ptr) = iter.next().unwrap(); - assert_eq!(info.name(), DebugName::type_name::()); - // SAFETY: We know that the resource is of type `TestResource` - assert_eq!(unsafe { ptr.deref::().0 }, 42); - assert!(iter.next().is_none()); } @@ -4147,17 +4148,17 @@ mod tests { let mut iter = world.iter_resources_mut(); let (info, mut mut_untyped) = iter.next().unwrap(); - assert_eq!(info.name(), DebugName::type_name::()); - // SAFETY: We know that the resource is of type `TestResource2` + assert_eq!(info.name(), DebugName::type_name::()); + // SAFETY: We know that the resource is of type `TestResource` unsafe { - mut_untyped.as_mut().deref_mut::().0 = "Hello, world?".to_string(); + mut_untyped.as_mut().deref_mut::().0 = 43; }; let (info, mut mut_untyped) = iter.next().unwrap(); - assert_eq!(info.name(), DebugName::type_name::()); - // SAFETY: We know that the resource is of type `TestResource` + assert_eq!(info.name(), DebugName::type_name::()); + // SAFETY: We know that the resource is of type `TestResource2` unsafe { - mut_untyped.as_mut().deref_mut::().0 = 43; + mut_untyped.as_mut().deref_mut::().0 = "Hello, world?".to_string(); }; assert!(iter.next().is_none()); From e4c91822fb43e018e6edba0d22ad54ddb386d1ba Mon Sep 17 00:00:00 2001 From: Trashtalk Date: Sat, 2 May 2026 12:44:52 +0200 Subject: [PATCH 19/22] cargo clippy --- crates/bevy_ecs/src/component/identifier.rs | 6 +++--- crates/bevy_ecs/src/query/access.rs | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/crates/bevy_ecs/src/component/identifier.rs b/crates/bevy_ecs/src/component/identifier.rs index 1e4448d117e98..97dcc92a76cc3 100644 --- a/crates/bevy_ecs/src/component/identifier.rs +++ b/crates/bevy_ecs/src/component/identifier.rs @@ -300,7 +300,7 @@ impl ComponentIdSet { #[inline] pub fn intersect_with(&mut self, other: &ComponentIdSet) { self.dense.intersect_with(&other.dense); - self.sparse.bitand_assign(&other.sparse) + self.sparse.bitand_assign(&other.sparse); } /// In-place difference of two [`ComponentIdSet`]s. @@ -339,8 +339,8 @@ impl Clone for ComponentIdSet { } } -impl alloc::fmt::Debug for ComponentIdSet { - fn fmt(&self, f: &mut alloc::fmt::Formatter<'_>) -> alloc::fmt::Result { +impl core::fmt::Debug for ComponentIdSet { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { // `FixedBitSet` normally has a `Debug` output like: // FixedBitSet { data: [ 160 ], length: 8 } // Instead, print the list of set values, like: diff --git a/crates/bevy_ecs/src/query/access.rs b/crates/bevy_ecs/src/query/access.rs index aa0aa1ff12fa8..a480c7e14fdeb 100644 --- a/crates/bevy_ecs/src/query/access.rs +++ b/crates/bevy_ecs/src/query/access.rs @@ -613,7 +613,7 @@ impl Access { let archetypal = self .archetypal .difference(&self.read_and_writes) - .map(|id| ComponentAccessKind::Archetypal(id)); + .map(ComponentAccessKind::Archetypal); Ok(reads_and_writes.chain(archetypal)) } @@ -638,7 +638,7 @@ fn invertible_union_with( (true, false) => self_set.difference_with(other_set), (false, true) => { *self_inverted = true; - self_set.difference_from(other_set) + self_set.difference_from(other_set); } (false, false) => self_set.union_with(other_set), } From baec7eb81222cae50d42625a83fdcb6fd1ce8716 Mon Sep 17 00:00:00 2001 From: Trashtalk Date: Sat, 2 May 2026 12:51:35 +0200 Subject: [PATCH 20/22] fix doc test --- crates/bevy_ecs/src/query/access.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_ecs/src/query/access.rs b/crates/bevy_ecs/src/query/access.rs index a480c7e14fdeb..de84bbf87cc37 100644 --- a/crates/bevy_ecs/src/query/access.rs +++ b/crates/bevy_ecs/src/query/access.rs @@ -593,8 +593,8 @@ impl Access { /// assert_eq!( /// result, /// Ok(vec![ - /// ComponentAccessKind::Exclusive(ComponentId::from_u32(2)), /// ComponentAccessKind::Shared(ComponentId::from_u32(1)), + /// ComponentAccessKind::Exclusive(ComponentId::from_u32(2)), /// ComponentAccessKind::Archetypal(ComponentId::from_u32(3)), /// ]), /// ); From 155b998e721c07cddb80ce73d642e10d187abf9f Mon Sep 17 00:00:00 2001 From: Trashtalk Date: Sat, 2 May 2026 14:38:25 +0200 Subject: [PATCH 21/22] add benchmark --- benches/benches/bevy_ecs/main.rs | 2 + benches/benches/bevy_ecs/resources.rs | 69 +++++++++++++++++++++++++++ 2 files changed, 71 insertions(+) create mode 100644 benches/benches/bevy_ecs/resources.rs diff --git a/benches/benches/bevy_ecs/main.rs b/benches/benches/bevy_ecs/main.rs index 9e59d31aca00c..bce529de8f5ea 100644 --- a/benches/benches/bevy_ecs/main.rs +++ b/benches/benches/bevy_ecs/main.rs @@ -15,6 +15,7 @@ mod fragmentation; mod iteration; mod observers; mod param; +mod resources; mod scheduling; mod world; @@ -22,6 +23,7 @@ criterion_main!( bundles::benches, change_detection::benches, components::benches, + resources::benches, empty_archetypes::benches, entity_cloning::benches, events::benches, diff --git a/benches/benches/bevy_ecs/resources.rs b/benches/benches/bevy_ecs/resources.rs new file mode 100644 index 0000000000000..0d34576066797 --- /dev/null +++ b/benches/benches/bevy_ecs/resources.rs @@ -0,0 +1,69 @@ +use std::{alloc::Layout, hint::black_box, ptr::NonNull}; + +use benches::bench; +use bevy_ecs::{ + change_detection::MaybeLocation, + component::{ComponentCloneBehavior, ComponentDescriptor, StorageType}, + prelude::*, + ptr::OwningPtr, +}; +use criterion::{criterion_group, Criterion}; + +criterion_group!(benches, get, get_mut, insert_remove); + +fn create_world() -> World { + let mut world = World::new(); + for _ in 0..500 { + // SAFETY: Uses zero-sized value, never drops + unsafe { + let resource_id = + world.register_component_with_descriptor(ComponentDescriptor::new_with_layout( + "", + StorageType::Table, + Layout::new::<()>(), + None, + true, + ComponentCloneBehavior::Default, + None, + )); + world.insert_resource_by_id( + resource_id, + OwningPtr::new(NonNull::dangling()), + MaybeLocation::caller(), + ); + } + } + world +} + +#[derive(Resource)] +struct R; + +pub fn get(criterion: &mut Criterion) { + let mut world = create_world(); + world.insert_resource(R); + criterion.bench_function(bench!("get"), |bencher| { + bencher.iter(|| world.get_resource::()); + }); +} + +pub fn get_mut(criterion: &mut Criterion) { + let mut world = create_world(); + world.insert_resource(R); + criterion.bench_function(bench!("get_mut"), |bencher| { + bencher.iter(|| { + black_box(world.get_resource_mut::()); + }); + }); +} + +pub fn insert_remove(criterion: &mut Criterion) { + let mut world = create_world(); + criterion.bench_function(bench!("insert_remove"), |bencher| { + bencher.iter(|| { + world.insert_resource(R); + black_box(&mut world); + world.remove_resource::() + }); + }); +} From 569112d95ce9157a42a3df4623a218e70cc2f543 Mon Sep 17 00:00:00 2001 From: Trashtalk Date: Sat, 2 May 2026 14:55:36 +0200 Subject: [PATCH 22/22] changed over to SparseSet --- benches/benches/bevy_ecs/resources.rs | 2 +- crates/bevy_ecs/macros/src/component.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/benches/benches/bevy_ecs/resources.rs b/benches/benches/bevy_ecs/resources.rs index 0d34576066797..736e5daaeeb50 100644 --- a/benches/benches/bevy_ecs/resources.rs +++ b/benches/benches/bevy_ecs/resources.rs @@ -19,7 +19,7 @@ fn create_world() -> World { let resource_id = world.register_component_with_descriptor(ComponentDescriptor::new_with_layout( "", - StorageType::Table, + StorageType::SparseSet, Layout::new::<()>(), None, true, diff --git a/crates/bevy_ecs/macros/src/component.rs b/crates/bevy_ecs/macros/src/component.rs index c37f48e65b519..48a42ce010cc6 100644 --- a/crates/bevy_ecs/macros/src/component.rs +++ b/crates/bevy_ecs/macros/src/component.rs @@ -47,7 +47,7 @@ pub fn derive_resource(input: TokenStream) -> TokenStream { } }); - let storage = storage_path(&bevy_ecs_path, StorageTy::Table); + let storage = storage_path(&bevy_ecs_path, StorageTy::SparseSet); let on_add_path = None; let on_remove_path = None;