Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 28 additions & 14 deletions crates/bevy_ecs/src/resource.rs
Original file line number Diff line number Diff line change
@@ -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")]
Expand Down Expand Up @@ -89,12 +88,33 @@ pub trait Resource: Component<Mutability = Mutable> {}

/// A cache that links each `ComponentId` from a resource to the corresponding entity.
#[derive(Default)]
pub struct ResourceEntities(SyncUnsafeCell<SparseSet<ComponentId, Entity>>);
pub struct ResourceEntities(SyncUnsafeCell<SparseArray<ComponentId, Entity>>);

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<Item = (ComponentId, Entity)> {
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<Entity> {
self.deref().get(id).copied()
}

impl Deref for ResourceEntities {
type Target = SparseSet<ComponentId, Entity>;
/// 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<Entity> {
self.0.get_mut().remove(id)
}

fn deref(&self) -> &Self::Target {
#[inline]
fn deref(&self) -> &SparseArray<ComponentId, Entity> {
// 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.
Expand All @@ -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)]
Expand All @@ -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()
Expand Down Expand Up @@ -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() };
Expand Down
13 changes: 13 additions & 0 deletions crates/bevy_ecs/src/storage/sparse_set.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,19 @@ impl<I: SparseSetIndex, V> SparseArray<I, V> {
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<Item = (I, &V)> {
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.
Expand Down
28 changes: 11 additions & 17 deletions crates/bevy_ecs/src/world/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1860,7 +1860,7 @@ impl World {
) -> (ComponentId, EntityWorldMut<'_>) {
let resource_id = self.register_resource::<R>();

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);
Expand Down Expand Up @@ -1995,7 +1995,7 @@ impl World {
#[inline]
pub fn remove_resource<R: Resource>(&mut self) -> Option<R> {
let resource_id = self.component_id::<R>()?;
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")
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -2130,7 +2130,7 @@ impl World {
component_id: ComponentId,
) -> Option<ComponentTicks> {
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)
}

Expand Down Expand Up @@ -2781,7 +2781,7 @@ impl World {
let change_tick = self.change_tick();

let component_id = self.components.valid_component_id::<R>()?;
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::<R>()?;
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -3509,11 +3505,11 @@ impl World {
#[inline]
pub fn iter_resources(&self) -> impl Iterator<Item = (&ComponentInfo, Ptr<'_>)> {
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))
})
}
Expand Down Expand Up @@ -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 =
Expand Down Expand Up @@ -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()
}
}
Expand Down
2 changes: 1 addition & 1 deletion crates/bevy_ecs/src/world/reflect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ impl World {
reflected_resource: Box<dyn PartialReflect>,
) {
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);
}
Expand Down
8 changes: 4 additions & 4 deletions crates/bevy_ecs/src/world/unsafe_world_cell.rs
Original file line number Diff line number Diff line change
Expand Up @@ -466,7 +466,7 @@ impl<'w> UnsafeWorldCell<'w> {
pub unsafe fn get_resource_by_id(self, component_id: ComponentId) -> Option<Ptr<'w>> {
// 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)
}

Expand Down Expand Up @@ -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()
}

Expand Down Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion crates/bevy_remote/src/builtin_methods.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down
4 changes: 2 additions & 2 deletions crates/bevy_settings/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
};
Expand Down Expand Up @@ -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;
};
Expand Down
2 changes: 1 addition & 1 deletion crates/bevy_world_serialization/src/dynamic_world.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
};
Expand Down
8 changes: 4 additions & 4 deletions crates/bevy_world_serialization/src/dynamic_world_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -378,14 +378,14 @@ impl<'w> DynamicWorldBuilder<'w> {
.get_valid_id(TypeId::of::<DefaultQueryFilters>());

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);
Expand All @@ -400,12 +400,12 @@ impl<'w> DynamicWorldBuilder<'w> {
type_registration.data::<ReflectResource>()?;
let component = type_registration
.data::<ReflectComponent>()?
.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();
Expand Down
14 changes: 7 additions & 7 deletions crates/bevy_world_serialization/src/world_asset.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,21 +76,21 @@ 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;
}

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
Expand All @@ -114,16 +114,16 @@ 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()
};

reflect_component.copy(
&self.world,
world,
*source_entity,
source_entity,
destination_entity,
&type_registry,
);
Expand Down