diff --git a/crates/bevy_ecs/src/resource.rs b/crates/bevy_ecs/src/resource.rs index a94fa13277efc..5bf2846d4a16a 100644 --- a/crates/bevy_ecs/src/resource.rs +++ b/crates/bevy_ecs/src/resource.rs @@ -1,13 +1,12 @@ //! Resources are unique, singleton-like data types that can be accessed from systems and stored in the [`World`](crate::world::World). -use core::ops::{Deref, DerefMut}; use log::warn; use crate::{ component::{Component, ComponentId, Mutable}, entity::Entity, lifecycle::HookContext, - storage::SparseSet, + storage::SparseArray, world::DeferredWorld, }; #[cfg(feature = "bevy_reflect")] @@ -89,12 +88,33 @@ pub trait Resource: Component {} /// A cache that links each `ComponentId` from a resource to the corresponding entity. #[derive(Default)] -pub struct ResourceEntities(SyncUnsafeCell>); +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() + } -impl Deref for ResourceEntities { - type Target = SparseSet; + /// Removes the entry for the given resource component. + /// Returns the entity that was removed, or `None` if there was no entity. + #[inline] + pub(crate) fn remove(&mut self, id: ComponentId) -> Option { + self.0.get_mut().remove(id) + } - fn deref(&self) -> &Self::Target { + #[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. @@ -104,12 +124,6 @@ impl Deref for ResourceEntities { } } -impl DerefMut for ResourceEntities { - fn deref_mut(&mut self) -> &mut Self::Target { - self.0.get_mut() - } -} - /// A marker component for entities that have a Resource component. #[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Component, Debug))] #[derive(Component, Debug)] @@ -134,7 +148,7 @@ impl IsResource { .unwrap() .resource_component_id(); - if let Some(&original_entity) = world.resource_entities.get(resource_component_id) { + if let Some(original_entity) = world.resource_entities.get(resource_component_id) { if !world.entities().contains(original_entity) { let name = world .components() @@ -182,7 +196,7 @@ impl IsResource { .resource_component_id(); if let Some(resource_entity) = world.resource_entities.get(resource_component_id) - && *resource_entity == context.entity + && 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() }; diff --git a/crates/bevy_ecs/src/storage/sparse_set.rs b/crates/bevy_ecs/src/storage/sparse_set.rs index e4ad05134532c..a836cf9984bc1 100644 --- a/crates/bevy_ecs/src/storage/sparse_set.rs +++ b/crates/bevy_ecs/src/storage/sparse_set.rs @@ -112,6 +112,19 @@ impl SparseArray { marker: PhantomData, } } + + /// 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] + 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)) + }) + } } /// 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 23cb6a48395c5..a391968e3c41b 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -1860,7 +1860,7 @@ impl World { ) -> (ComponentId, EntityWorldMut<'_>) { let resource_id = self.register_resource::(); - if let Some(&entity) = self.resource_entities.get(resource_id) { + if let Some(entity) = self.resource_entities.get(resource_id) { let entity_ref = self.get_entity(entity).expect("ResourceCache is in sync"); if !entity_ref.contains_id(resource_id) { let resource = func(self); @@ -1995,7 +1995,7 @@ 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 entity = self.resource_entities.get(resource_id)?; let value = self .get_entity_mut(entity) .expect("ResourceCache is in sync") @@ -2040,7 +2040,7 @@ impl World { #[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) + && let Ok(entity_ref) = self.get_entity(entity) { return entity_ref.contains_id(component_id); } @@ -2130,7 +2130,7 @@ impl World { 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(entity).ok()?; entity_ref.get_change_ticks_by_id(component_id) } @@ -2781,7 +2781,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 = self.resource_entities.get(component_id)?; let mut entity_mut = self.get_entity_mut(entity).ok()?; let mut ticks = entity_mut.get_change_ticks::()?; @@ -2974,7 +2974,7 @@ impl World { ) { // 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) + self.get_entity_mut(entity) .expect("ResourceCache is in sync") } else { self.spawn_empty() @@ -3303,11 +3303,7 @@ 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() - .map(|(id, entity)| (*id, *entity)) - .collect(); + 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); } @@ -3509,11 +3505,11 @@ impl World { #[inline] pub fn iter_resources(&self) -> impl Iterator)> { self.resource_entities - .indices() .iter() - .filter_map(|component_id| { - let component_info = self.components().get_info(*component_id)?; - let resource = self.get_resource_by_id(*component_id)?; + .filter_map(|(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)) }) } @@ -3591,7 +3587,6 @@ impl World { resource_entities .iter() - .map(|(component_id, entity)| (*component_id, *entity)) .filter_map(move |(component_id, entity)| { // SAFETY: If a resource has been initialized, a corresponding ComponentInfo must exist with its ID. let component_info = @@ -3867,7 +3862,6 @@ impl fmt::Debug for World { .field("entity_count", &self.entities.count_spawned()) .field("archetype_count", &self.archetypes.len()) .field("component_count", &self.components.len()) - .field("resource_count", &self.resource_entities.len()) .finish() } } diff --git a/crates/bevy_ecs/src/world/reflect.rs b/crates/bevy_ecs/src/world/reflect.rs index 4ebad2db519ae..0c7909d5bc4aa 100644 --- a/crates/bevy_ecs/src/world/reflect.rs +++ b/crates/bevy_ecs/src/world/reflect.rs @@ -200,7 +200,7 @@ impl World { reflected_resource: Box, ) { if let Some(entity) = self.resource_entities().get(resource_id) { - self.entity_mut(*entity).insert_reflect(reflected_resource); + self.entity_mut(entity).insert_reflect(reflected_resource); } else { self.spawn_empty().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 89574f67cf26a..7a9c7a8fa3757 100644 --- a/crates/bevy_ecs/src/world/unsafe_world_cell.rs +++ b/crates/bevy_ecs/src/world/unsafe_world_cell.rs @@ -466,7 +466,7 @@ impl<'w> UnsafeWorldCell<'w> { 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_cell = self.get_entity(*entity).ok()?; + let entity_cell = self.get_entity(entity).ok()?; entity_cell.get_by_id(component_id) } @@ -575,7 +575,7 @@ impl<'w> UnsafeWorldCell<'w> { 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_cell = self.get_entity(*entity).ok()?; + let entity_cell = self.get_entity(entity).ok()?; entity_cell.get_mut_by_id(component_id).ok() } @@ -680,13 +680,13 @@ impl<'w> UnsafeWorldCell<'w> { // SAFETY: We have permission to access the resource of `component_id`. let entity = unsafe { self.resource_entities() }.get(component_id)?; let storage_type = self.components().get_info(component_id)?.storage_type(); - let location = self.get_entity(*entity).ok()?.location(); + let location = self.get_entity(entity).ok()?.location(); // SAFETY: // - caller ensures there is no `&mut World` // - caller ensures there are no mutable borrows of this resource // - caller ensures that we have permission to access this resource // - storage_type and location are valid - get_component_and_ticks(self, component_id, storage_type, *entity, location) + get_component_and_ticks(self, component_id, storage_type, entity, location) } // Shorthand helper function for getting the data and change ticks for a resource. diff --git a/crates/bevy_remote/src/builtin_methods.rs b/crates/bevy_remote/src/builtin_methods.rs index 4a252892c7540..f4ba201acf22d 100644 --- a/crates/bevy_remote/src/builtin_methods.rs +++ b/crates/bevy_remote/src/builtin_methods.rs @@ -1893,7 +1893,7 @@ fn get_resource_entity_pair( .resource_entities() .get(component_id) .ok_or(anyhow!("Resource entity does not exist."))?; - Ok((*entity, component_id)) + Ok((entity, component_id)) } #[cfg(test)] diff --git a/crates/bevy_settings/src/lib.rs b/crates/bevy_settings/src/lib.rs index 7f2c7684c984e..a5956f58dac29 100644 --- a/crates/bevy_settings/src/lib.rs +++ b/crates/bevy_settings/src/lib.rs @@ -326,7 +326,7 @@ fn resources_to_toml( let Some(res_entity) = world.resource_entities().get(component_id) else { continue; }; - let res_entity_ref = world.entity(*res_entity); + let res_entity_ref = world.entity(res_entity); let Some(reflect) = cmp.reflect(res_entity_ref) else { continue; }; @@ -441,7 +441,7 @@ fn apply_settings_to_world( if let Some(res_entity) = res_entity { // Resource already exists, so apply toml properties to it. - let res_entity_mut = world.entity_mut(*res_entity); + let res_entity_mut = world.entity_mut(res_entity); let Some(mut reflect) = reflect_component.reflect_mut(res_entity_mut) else { continue; }; diff --git a/crates/bevy_world_serialization/src/dynamic_world.rs b/crates/bevy_world_serialization/src/dynamic_world.rs index f41cf8aa1e19e..87bbdcbb8c154 100644 --- a/crates/bevy_world_serialization/src/dynamic_world.rs +++ b/crates/bevy_world_serialization/src/dynamic_world.rs @@ -184,7 +184,7 @@ impl DynamicWorld { // 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 + entity } else { world.spawn_empty().id() }; diff --git a/crates/bevy_world_serialization/src/dynamic_world_builder.rs b/crates/bevy_world_serialization/src/dynamic_world_builder.rs index ff078fb071612..727d7adc9fecc 100644 --- a/crates/bevy_world_serialization/src/dynamic_world_builder.rs +++ b/crates/bevy_world_serialization/src/dynamic_world_builder.rs @@ -378,14 +378,14 @@ impl<'w> DynamicWorldBuilder<'w> { .get_valid_id(TypeId::of::()); for (component_id, entity) in self.original_world.resource_entities().iter() { - if Some(*component_id) == original_world_dqf_id { + if Some(component_id) == original_world_dqf_id { continue; } let mut extract_and_push = || { let type_id = self .original_world .components() - .get_info(*component_id)? + .get_info(component_id)? .type_id()?; let is_denied = self.resource_filter.is_denied_by_id(type_id); @@ -400,12 +400,12 @@ impl<'w> DynamicWorldBuilder<'w> { type_registration.data::()?; let component = type_registration .data::()? - .reflect(self.original_world.entity(*entity))?; + .reflect(self.original_world.entity(entity))?; let component = clone_reflect_value(component.as_partial_reflect(), type_registration); - self.extracted_resources.insert(*component_id, component); + self.extracted_resources.insert(component_id, component); Some(()) }; extract_and_push(); diff --git a/crates/bevy_world_serialization/src/world_asset.rs b/crates/bevy_world_serialization/src/world_asset.rs index 72bd157831634..458974e898533 100644 --- a/crates/bevy_world_serialization/src/world_asset.rs +++ b/crates/bevy_world_serialization/src/world_asset.rs @@ -76,13 +76,13 @@ impl WorldAsset { // Resources archetype for (component_id, source_entity) in self.world.resource_entities().iter() { - if Some(*component_id) == self_dqf_id { + if Some(component_id) == self_dqf_id { continue; } if !world - .get_entity(*source_entity) + .get_entity(source_entity) .ok() - .is_some_and(|entity_ref| entity_ref.contains_id(*component_id)) + .is_some_and(|entity_ref| entity_ref.contains_id(component_id)) { continue; } @@ -90,7 +90,7 @@ impl WorldAsset { let component_info = self .world .components() - .get_info(*component_id) + .get_info(component_id) .expect("component_ids in archetypes should have ComponentInfo"); let type_id = component_info @@ -114,8 +114,8 @@ impl WorldAsset { // 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 + if let Some(entity) = world.resource_entities().get(component_id) { + entity } else { world.spawn_empty().id() }; @@ -123,7 +123,7 @@ impl WorldAsset { reflect_component.copy( &self.world, world, - *source_entity, + source_entity, destination_entity, &type_registry, );