Merge 9acf43fa51
into 877d278785
This commit is contained in:
commit
c1a615d15a
@ -51,9 +51,10 @@ pub fn world_entity(criterion: &mut Criterion) {
|
||||
for entity_count in RANGE.map(|i| i * 10_000) {
|
||||
group.bench_function(format!("{entity_count}_entities"), |bencher| {
|
||||
let world = setup::<Table>(entity_count);
|
||||
let offset = world.resource_count();
|
||||
|
||||
bencher.iter(|| {
|
||||
for i in 0..entity_count {
|
||||
for i in offset..(entity_count + offset) {
|
||||
let entity =
|
||||
// SAFETY: Range is exclusive.
|
||||
Entity::from_raw(EntityRow::new(unsafe { NonMaxU32::new_unchecked(i) }));
|
||||
@ -74,9 +75,10 @@ pub fn world_get(criterion: &mut Criterion) {
|
||||
for entity_count in RANGE.map(|i| i * 10_000) {
|
||||
group.bench_function(format!("{entity_count}_entities_table"), |bencher| {
|
||||
let world = setup::<Table>(entity_count);
|
||||
let offset = world.resource_count();
|
||||
|
||||
bencher.iter(|| {
|
||||
for i in 0..entity_count {
|
||||
for i in offset..(entity_count + offset) {
|
||||
let entity =
|
||||
// SAFETY: Range is exclusive.
|
||||
Entity::from_raw(EntityRow::new(unsafe { NonMaxU32::new_unchecked(i) }));
|
||||
@ -86,9 +88,10 @@ pub fn world_get(criterion: &mut Criterion) {
|
||||
});
|
||||
group.bench_function(format!("{entity_count}_entities_sparse"), |bencher| {
|
||||
let world = setup::<Sparse>(entity_count);
|
||||
let offset = world.resource_count();
|
||||
|
||||
bencher.iter(|| {
|
||||
for i in 0..entity_count {
|
||||
for i in offset..(entity_count + offset) {
|
||||
let entity =
|
||||
// SAFETY: Range is exclusive.
|
||||
Entity::from_raw(EntityRow::new(unsafe { NonMaxU32::new_unchecked(i) }));
|
||||
@ -109,10 +112,11 @@ pub fn world_query_get(criterion: &mut Criterion) {
|
||||
for entity_count in RANGE.map(|i| i * 10_000) {
|
||||
group.bench_function(format!("{entity_count}_entities_table"), |bencher| {
|
||||
let mut world = setup::<Table>(entity_count);
|
||||
let offset = world.resource_count();
|
||||
let mut query = world.query::<&Table>();
|
||||
|
||||
bencher.iter(|| {
|
||||
for i in 0..entity_count {
|
||||
for i in offset..(entity_count + offset) {
|
||||
let entity =
|
||||
// SAFETY: Range is exclusive.
|
||||
Entity::from_raw(EntityRow::new(unsafe { NonMaxU32::new_unchecked(i) }));
|
||||
@ -137,9 +141,10 @@ pub fn world_query_get(criterion: &mut Criterion) {
|
||||
&WideTable<4>,
|
||||
&WideTable<5>,
|
||||
)>();
|
||||
let offset = world.resource_count();
|
||||
|
||||
bencher.iter(|| {
|
||||
for i in 0..entity_count {
|
||||
for i in offset..(entity_count + offset) {
|
||||
let entity =
|
||||
// SAFETY: Range is exclusive.
|
||||
Entity::from_raw(EntityRow::new(unsafe { NonMaxU32::new_unchecked(i) }));
|
||||
@ -149,10 +154,11 @@ pub fn world_query_get(criterion: &mut Criterion) {
|
||||
});
|
||||
group.bench_function(format!("{entity_count}_entities_sparse"), |bencher| {
|
||||
let mut world = setup::<Sparse>(entity_count);
|
||||
let offset = world.resource_count();
|
||||
let mut query = world.query::<&Sparse>();
|
||||
|
||||
bencher.iter(|| {
|
||||
for i in 0..entity_count {
|
||||
for i in offset..(entity_count + offset) {
|
||||
let entity =
|
||||
// SAFETY: Range is exclusive.
|
||||
Entity::from_raw(EntityRow::new(unsafe { NonMaxU32::new_unchecked(i) }));
|
||||
@ -177,9 +183,10 @@ pub fn world_query_get(criterion: &mut Criterion) {
|
||||
&WideSparse<4>,
|
||||
&WideSparse<5>,
|
||||
)>();
|
||||
let offset = world.resource_count();
|
||||
|
||||
bencher.iter(|| {
|
||||
for i in 0..entity_count {
|
||||
for i in offset..(entity_count + offset) {
|
||||
// SAFETY: Range is exclusive.
|
||||
let entity =
|
||||
Entity::from_raw(EntityRow::new(unsafe { NonMaxU32::new_unchecked(i) }));
|
||||
|
@ -1,5 +1,5 @@
|
||||
use alloc::{borrow::Cow, vec::Vec};
|
||||
use bevy_platform::{collections::HashSet, sync::PoisonError};
|
||||
use bevy_platform::{collections::HashMap, collections::HashSet, sync::PoisonError};
|
||||
use bevy_ptr::OwningPtr;
|
||||
#[cfg(feature = "bevy_reflect")]
|
||||
use bevy_reflect::Reflect;
|
||||
@ -18,6 +18,7 @@ use crate::{
|
||||
RequiredComponents, StorageType,
|
||||
},
|
||||
lifecycle::ComponentHooks,
|
||||
prelude::Entity,
|
||||
query::DebugCheckedUnwrap as _,
|
||||
resource::Resource,
|
||||
storage::SparseSetIndex,
|
||||
@ -346,6 +347,9 @@ pub struct Components {
|
||||
pub(super) components: Vec<Option<ComponentInfo>>,
|
||||
pub(super) indices: TypeIdMap<ComponentId>,
|
||||
pub(super) resource_indices: TypeIdMap<ComponentId>,
|
||||
/// A lookup for the entities on which resources are stored.
|
||||
/// It uses `ComponentId`s instead of `TypeId`s for untyped APIs
|
||||
pub(crate) resource_entities: HashMap<ComponentId, Entity>,
|
||||
// This is kept internal and local to verify that no deadlocks can occor.
|
||||
pub(super) queued: bevy_platform::sync::RwLock<QueuedComponents>,
|
||||
}
|
||||
|
@ -70,6 +70,7 @@
|
||||
use crate::{
|
||||
component::{ComponentId, Components, StorageType},
|
||||
query::FilteredAccess,
|
||||
resource::IsResource,
|
||||
world::{FromWorld, World},
|
||||
};
|
||||
use bevy_ecs_macros::{Component, Resource};
|
||||
@ -143,6 +144,8 @@ impl FromWorld for DefaultQueryFilters {
|
||||
let mut filters = DefaultQueryFilters::empty();
|
||||
let disabled_component_id = world.register_component::<Disabled>();
|
||||
filters.register_disabling_component(disabled_component_id);
|
||||
let is_resource_component_id = world.register_component::<IsResource>();
|
||||
filters.register_disabling_component(is_resource_component_id);
|
||||
filters
|
||||
}
|
||||
}
|
||||
|
@ -378,9 +378,9 @@ mod tests {
|
||||
let mut world = World::new();
|
||||
let e = world.spawn((TableStored("abc"), A(123))).id();
|
||||
let f = world.spawn((TableStored("def"), A(456))).id();
|
||||
assert_eq!(world.entities.len(), 2);
|
||||
assert_eq!(world.entity_count(), 2);
|
||||
assert!(world.despawn(e));
|
||||
assert_eq!(world.entities.len(), 1);
|
||||
assert_eq!(world.entity_count(), 1);
|
||||
assert!(world.get::<TableStored>(e).is_none());
|
||||
assert!(world.get::<A>(e).is_none());
|
||||
assert_eq!(world.get::<TableStored>(f).unwrap().0, "def");
|
||||
@ -393,9 +393,9 @@ mod tests {
|
||||
|
||||
let e = world.spawn((TableStored("abc"), SparseStored(123))).id();
|
||||
let f = world.spawn((TableStored("def"), SparseStored(456))).id();
|
||||
assert_eq!(world.entities.len(), 2);
|
||||
assert_eq!(world.entity_count(), 2);
|
||||
assert!(world.despawn(e));
|
||||
assert_eq!(world.entities.len(), 1);
|
||||
assert_eq!(world.entity_count(), 1);
|
||||
assert!(world.get::<TableStored>(e).is_none());
|
||||
assert!(world.get::<SparseStored>(e).is_none());
|
||||
assert_eq!(world.get::<TableStored>(f).unwrap().0, "def");
|
||||
@ -1786,7 +1786,7 @@ mod tests {
|
||||
fn try_insert_batch() {
|
||||
let mut world = World::default();
|
||||
let e0 = world.spawn(A(0)).id();
|
||||
let e1 = Entity::from_raw_u32(1).unwrap();
|
||||
let e1 = Entity::from_raw_u32(2).unwrap();
|
||||
|
||||
let values = vec![(e0, (A(1), B(0))), (e1, (A(0), B(1)))];
|
||||
|
||||
@ -1810,7 +1810,7 @@ mod tests {
|
||||
fn try_insert_batch_if_new() {
|
||||
let mut world = World::default();
|
||||
let e0 = world.spawn(A(0)).id();
|
||||
let e1 = Entity::from_raw_u32(1).unwrap();
|
||||
let e1 = Entity::from_raw_u32(2).unwrap();
|
||||
|
||||
let values = vec![(e0, (A(1), B(0))), (e1, (A(0), B(1)))];
|
||||
|
||||
|
@ -278,7 +278,7 @@ mod tests {
|
||||
let mut query = world.query::<NameOrEntity>();
|
||||
let d1 = query.get(&world, e1).unwrap();
|
||||
// NameOrEntity Display for entities without a Name should be {index}v{generation}
|
||||
assert_eq!(d1.to_string(), "0v0");
|
||||
assert_eq!(d1.to_string(), "1v0");
|
||||
let d2 = query.get(&world, e2).unwrap();
|
||||
// NameOrEntity Display for entities with a Name should be the Name
|
||||
assert_eq!(d2.to_string(), "MyName");
|
||||
|
@ -275,6 +275,7 @@ impl<'w, D: QueryData, F: QueryFilter> QueryBuilder<'w, D, F> {
|
||||
mod tests {
|
||||
use crate::{
|
||||
prelude::*,
|
||||
resource::IsResource,
|
||||
world::{EntityMutExcept, EntityRefExcept, FilteredEntityMut, FilteredEntityRef},
|
||||
};
|
||||
use std::dbg;
|
||||
@ -332,6 +333,7 @@ mod tests {
|
||||
#[test]
|
||||
fn builder_or() {
|
||||
let mut world = World::new();
|
||||
|
||||
world.spawn((A(0), B(0)));
|
||||
world.spawn(B(0));
|
||||
world.spawn(C(0));
|
||||
@ -485,6 +487,7 @@ mod tests {
|
||||
|
||||
let mut query = QueryBuilder::<(FilteredEntityMut, EntityMutExcept<A>)>::new(&mut world)
|
||||
.data::<EntityMut>()
|
||||
.filter::<Without<IsResource>>()
|
||||
.build();
|
||||
|
||||
// Removing `EntityMutExcept<A>` just leaves A
|
||||
@ -496,6 +499,7 @@ mod tests {
|
||||
|
||||
let mut query = QueryBuilder::<(FilteredEntityMut, EntityRefExcept<A>)>::new(&mut world)
|
||||
.data::<EntityMut>()
|
||||
.filter::<Without<IsResource>>()
|
||||
.build();
|
||||
|
||||
// Removing `EntityRefExcept<A>` just leaves A, plus read access
|
||||
|
@ -2669,6 +2669,7 @@ mod tests {
|
||||
#[test]
|
||||
fn query_iter_sorts() {
|
||||
let mut world = World::new();
|
||||
|
||||
for i in 0..100 {
|
||||
world.spawn(A(i as f32));
|
||||
world.spawn((A(i as f32), Sparse(i)));
|
||||
|
@ -1850,6 +1850,7 @@ mod tests {
|
||||
#[test]
|
||||
fn can_transmute_empty_tuple() {
|
||||
let mut world = World::new();
|
||||
|
||||
world.register_component::<A>();
|
||||
let entity = world.spawn(A(10)).id();
|
||||
|
||||
@ -2172,9 +2173,7 @@ mod tests {
|
||||
world.spawn((B(0), C(0)));
|
||||
world.spawn(C(0));
|
||||
|
||||
let mut df = DefaultQueryFilters::empty();
|
||||
df.register_disabling_component(world.register_component::<C>());
|
||||
world.insert_resource(df);
|
||||
world.register_disabling_component::<C>();
|
||||
|
||||
// Without<C> only matches the first entity
|
||||
let mut query = QueryState::<()>::new(&mut world);
|
||||
@ -2216,9 +2215,9 @@ mod tests {
|
||||
assert!(query.is_dense);
|
||||
assert_eq!(3, query.iter(&world).count());
|
||||
|
||||
let mut df = DefaultQueryFilters::empty();
|
||||
df.register_disabling_component(world.register_component::<Sparse>());
|
||||
let df = DefaultQueryFilters::from_world(&mut world);
|
||||
world.insert_resource(df);
|
||||
world.register_disabling_component::<Sparse>();
|
||||
|
||||
let mut query = QueryState::<()>::new(&mut world);
|
||||
// The query doesn't ask for sparse components, but the default filters adds
|
||||
@ -2226,9 +2225,9 @@ mod tests {
|
||||
assert!(!query.is_dense);
|
||||
assert_eq!(1, query.iter(&world).count());
|
||||
|
||||
let mut df = DefaultQueryFilters::empty();
|
||||
df.register_disabling_component(world.register_component::<Table>());
|
||||
let df = DefaultQueryFilters::from_world(&mut world);
|
||||
world.insert_resource(df);
|
||||
world.register_disabling_component::<Table>();
|
||||
|
||||
let mut query = QueryState::<()>::new(&mut world);
|
||||
// If the filter is instead a table components, the query can still be dense
|
||||
|
@ -1,5 +1,10 @@
|
||||
//! Resources are unique, singleton-like data types that can be accessed from systems and stored in the [`World`](crate::world::World).
|
||||
|
||||
use crate::prelude::Component;
|
||||
use crate::prelude::ReflectComponent;
|
||||
use bevy_reflect::prelude::ReflectDefault;
|
||||
use bevy_reflect::Reflect;
|
||||
use core::marker::PhantomData;
|
||||
// The derive macro for the `Resource` trait
|
||||
pub use bevy_ecs_macros::Resource;
|
||||
|
||||
@ -73,3 +78,89 @@ pub use bevy_ecs_macros::Resource;
|
||||
note = "consider annotating `{Self}` with `#[derive(Resource)]`"
|
||||
)]
|
||||
pub trait Resource: Send + Sync + 'static {}
|
||||
|
||||
/// A marker component for the entity that stores the resource of type `T`.
|
||||
///
|
||||
/// This component is automatically inserted when a resource of type `T` is inserted into the world,
|
||||
/// and can be used to find the entity that stores a particular resource.
|
||||
///
|
||||
/// By contrast, the [`IsResource`] component is used to find all entities that store resources,
|
||||
/// regardless of the type of resource they store.
|
||||
///
|
||||
/// This component comes with a hook that ensures that at most one entity has this component for any given `R`:
|
||||
/// adding this component to an entity (or spawning an entity with this component) will despawn any other entity with this component.
|
||||
#[derive(Component, Debug)]
|
||||
#[require(IsResource)]
|
||||
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Component, Default))]
|
||||
pub struct ResourceEntity<R: Resource>(#[reflect(ignore)] PhantomData<R>);
|
||||
|
||||
impl<R: Resource> Default for ResourceEntity<R> {
|
||||
fn default() -> Self {
|
||||
ResourceEntity(PhantomData)
|
||||
}
|
||||
}
|
||||
|
||||
/// A marker component for entities which store resources.
|
||||
///
|
||||
/// By contrast, the [`ResourceEntity<R>`] component is used to find the entity that stores a particular resource.
|
||||
/// This component is required by the [`ResourceEntity<R>`] component, and will automatically be added.
|
||||
#[cfg_attr(
|
||||
feature = "bevy_reflect",
|
||||
derive(Reflect),
|
||||
reflect(Component, Default, Debug)
|
||||
)]
|
||||
#[derive(Component, Default, Debug)]
|
||||
pub struct IsResource;
|
||||
|
||||
/// Used in conjunction with [`ResourceEntity<R>`], when no type information is available.
|
||||
/// This is used by [`World::insert_resource_by_id`](crate::world::World).
|
||||
#[derive(Resource)]
|
||||
pub(crate) struct TypeErasedResource;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::change_detection::MaybeLocation;
|
||||
use crate::ptr::OwningPtr;
|
||||
use crate::resource::Resource;
|
||||
use crate::world::World;
|
||||
use bevy_platform::prelude::String;
|
||||
|
||||
#[test]
|
||||
fn unique_resource_entities() {
|
||||
#[derive(Default, Resource)]
|
||||
struct TestResource1;
|
||||
|
||||
#[derive(Resource)]
|
||||
#[expect(dead_code, reason = "field needed for testing")]
|
||||
struct TestResource2(String);
|
||||
|
||||
#[derive(Resource)]
|
||||
#[expect(dead_code, reason = "field needed for testing")]
|
||||
struct TestResource3(u8);
|
||||
|
||||
let mut world = World::new();
|
||||
let start = world.entities().len();
|
||||
world.init_resource::<TestResource1>();
|
||||
assert_eq!(world.entities().len(), start + 1);
|
||||
world.insert_resource(TestResource2(String::from("Foo")));
|
||||
assert_eq!(world.entities().len(), start + 2);
|
||||
// like component registration, which just makes it known to the world that a component exists,
|
||||
// registering a resource should not spawn an entity.
|
||||
let id = world.register_resource::<TestResource3>();
|
||||
assert_eq!(world.entities().len(), start + 2);
|
||||
OwningPtr::make(20_u8, |ptr| {
|
||||
// SAFETY: id was just initialized and corresponds to a resource.
|
||||
unsafe {
|
||||
world.insert_resource_by_id(id, ptr, MaybeLocation::caller());
|
||||
}
|
||||
});
|
||||
assert_eq!(world.entities().len(), start + 3);
|
||||
assert!(world.remove_resource_by_id(id).is_some());
|
||||
assert_eq!(world.entities().len(), start + 2);
|
||||
world.remove_resource::<TestResource1>();
|
||||
assert_eq!(world.entities().len(), start + 1);
|
||||
// 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().len(), start + 1);
|
||||
}
|
||||
}
|
||||
|
@ -501,9 +501,9 @@ mod tests {
|
||||
#[test]
|
||||
fn command_processing() {
|
||||
let mut world = World::new();
|
||||
assert_eq!(world.entities.len(), 0);
|
||||
assert_eq!(world.entity_count(), 0);
|
||||
world.run_system_once(spawn_entity).unwrap();
|
||||
assert_eq!(world.entities.len(), 1);
|
||||
assert_eq!(world.entity_count(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -4926,6 +4926,7 @@ mod tests {
|
||||
change_detection::{MaybeLocation, MutUntyped},
|
||||
component::ComponentId,
|
||||
prelude::*,
|
||||
resource::IsResource,
|
||||
system::{assert_is_system, RunSystemOnce as _},
|
||||
world::{error::EntityComponentError, DeferredWorld, FilteredEntityMut, FilteredEntityRef},
|
||||
};
|
||||
@ -5374,7 +5375,7 @@ mod tests {
|
||||
|
||||
world.spawn(TestComponent(0)).insert(TestComponent2(0));
|
||||
|
||||
let mut query = world.query::<EntityRefExcept<TestComponent>>();
|
||||
let mut query = world.query::<EntityRefExcept<(TestComponent, IsResource)>>();
|
||||
|
||||
let mut found = false;
|
||||
for entity_ref in query.iter_mut(&mut world) {
|
||||
@ -5432,7 +5433,10 @@ mod tests {
|
||||
|
||||
world.run_system_once(system).unwrap();
|
||||
|
||||
fn system(_: Query<&mut TestComponent>, query: Query<EntityRefExcept<TestComponent>>) {
|
||||
fn system(
|
||||
_: Query<&mut TestComponent>,
|
||||
query: Query<EntityRefExcept<(TestComponent, IsResource)>>,
|
||||
) {
|
||||
for entity_ref in query.iter() {
|
||||
assert!(matches!(
|
||||
entity_ref.get::<TestComponent2>(),
|
||||
@ -5449,7 +5453,7 @@ mod tests {
|
||||
let mut world = World::new();
|
||||
world.spawn(TestComponent(0)).insert(TestComponent2(0));
|
||||
|
||||
let mut query = world.query::<EntityMutExcept<TestComponent>>();
|
||||
let mut query = world.query::<EntityMutExcept<(TestComponent, IsResource)>>();
|
||||
|
||||
let mut found = false;
|
||||
for mut entity_mut in query.iter_mut(&mut world) {
|
||||
@ -5514,7 +5518,10 @@ mod tests {
|
||||
|
||||
world.run_system_once(system).unwrap();
|
||||
|
||||
fn system(_: Query<&mut TestComponent>, mut query: Query<EntityMutExcept<TestComponent>>) {
|
||||
fn system(
|
||||
_: Query<&mut TestComponent>,
|
||||
mut query: Query<EntityMutExcept<(TestComponent, IsResource)>>,
|
||||
) {
|
||||
for mut entity_mut in query.iter_mut() {
|
||||
assert!(entity_mut
|
||||
.get_mut::<TestComponent2>()
|
||||
|
@ -22,6 +22,7 @@ use crate::{
|
||||
event::BufferedEvent,
|
||||
lifecycle::{ComponentHooks, ADD, DESPAWN, INSERT, REMOVE, REPLACE},
|
||||
prelude::{Add, Despawn, Insert, Remove, Replace},
|
||||
resource::{ResourceEntity, TypeErasedResource},
|
||||
};
|
||||
pub use bevy_ecs_macros::FromWorld;
|
||||
use bevy_utils::prelude::DebugName;
|
||||
@ -216,12 +217,20 @@ impl World {
|
||||
&mut self.entities
|
||||
}
|
||||
|
||||
/// Retrieves the number of [`Entities`] in the world.
|
||||
/// Retrieves the number of [`Entities`] in the world. This count does not include resource entities.
|
||||
///
|
||||
/// This is helpful as a diagnostic, but it can also be used effectively in tests.
|
||||
#[inline]
|
||||
pub fn entity_count(&self) -> u32 {
|
||||
self.entities.len()
|
||||
self.entities
|
||||
.len()
|
||||
.saturating_sub(self.components.resource_entities.len() as u32)
|
||||
}
|
||||
|
||||
/// Retrieves the number of [`Resource`]s in the world.
|
||||
#[inline]
|
||||
pub fn resource_count(&self) -> u32 {
|
||||
self.components.resource_entities.len() as u32
|
||||
}
|
||||
|
||||
/// Retrieves this world's [`Archetypes`] collection.
|
||||
@ -1701,6 +1710,18 @@ impl World {
|
||||
pub fn init_resource<R: Resource + FromWorld>(&mut self) -> ComponentId {
|
||||
let caller = MaybeLocation::caller();
|
||||
let component_id = self.components_registrator().register_resource::<R>();
|
||||
|
||||
if !self
|
||||
.components
|
||||
.resource_entities
|
||||
.contains_key(&component_id)
|
||||
{
|
||||
let entity = self.spawn(ResourceEntity::<R>::default()).id();
|
||||
self.components
|
||||
.resource_entities
|
||||
.insert(component_id, entity);
|
||||
}
|
||||
|
||||
if self
|
||||
.storages
|
||||
.resources
|
||||
@ -1738,6 +1759,17 @@ impl World {
|
||||
caller: MaybeLocation,
|
||||
) {
|
||||
let component_id = self.components_registrator().register_resource::<R>();
|
||||
if !self
|
||||
.components
|
||||
.resource_entities
|
||||
.contains_key(&component_id)
|
||||
{
|
||||
let entity = self.spawn(ResourceEntity::<R>::default()).id();
|
||||
self.components
|
||||
.resource_entities
|
||||
.insert(component_id, entity);
|
||||
}
|
||||
|
||||
OwningPtr::make(value, |ptr| {
|
||||
// SAFETY: component_id was just initialized and corresponds to resource of type R.
|
||||
unsafe {
|
||||
@ -1805,6 +1837,10 @@ impl World {
|
||||
#[inline]
|
||||
pub fn remove_resource<R: Resource>(&mut self) -> Option<R> {
|
||||
let component_id = self.components.get_valid_resource_id(TypeId::of::<R>())?;
|
||||
if let Some(entity) = self.components.resource_entities.remove(&component_id) {
|
||||
self.despawn(entity);
|
||||
}
|
||||
|
||||
let (ptr, _, _) = self.storages.resources.get_mut(component_id)?.remove()?;
|
||||
// SAFETY: `component_id` was gotten via looking up the `R` type
|
||||
unsafe { Some(ptr.read::<R>()) }
|
||||
@ -2711,6 +2747,20 @@ impl World {
|
||||
) {
|
||||
let change_tick = self.change_tick();
|
||||
|
||||
if !self
|
||||
.components
|
||||
.resource_entities
|
||||
.contains_key(&component_id)
|
||||
{
|
||||
// Since we don't know the type, we use a placeholder type.
|
||||
let entity = self
|
||||
.spawn(ResourceEntity::<TypeErasedResource>::default())
|
||||
.id();
|
||||
self.components
|
||||
.resource_entities
|
||||
.insert(component_id, entity);
|
||||
}
|
||||
|
||||
let resource = self.initialize_resource_internal(component_id);
|
||||
// SAFETY: `value` is valid for `component_id`, ensured by caller
|
||||
unsafe {
|
||||
@ -3403,6 +3453,10 @@ 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) -> Option<()> {
|
||||
if let Some(entity) = self.components.resource_entities.remove(&component_id) {
|
||||
self.despawn(entity);
|
||||
}
|
||||
|
||||
self.storages
|
||||
.resources
|
||||
.get_mut(component_id)?
|
||||
@ -3676,7 +3730,7 @@ mod tests {
|
||||
entity::EntityHashSet,
|
||||
entity_disabling::{DefaultQueryFilters, Disabled},
|
||||
ptr::OwningPtr,
|
||||
resource::Resource,
|
||||
resource::{IsResource, Resource},
|
||||
world::{error::EntityMutableFetchError, DeferredWorld},
|
||||
};
|
||||
use alloc::{
|
||||
@ -4108,8 +4162,10 @@ mod tests {
|
||||
let iterate_and_count_entities = |world: &World, entity_counters: &mut HashMap<_, _>| {
|
||||
entity_counters.clear();
|
||||
for entity in world.iter_entities() {
|
||||
let counter = entity_counters.entry(entity.id()).or_insert(0);
|
||||
*counter += 1;
|
||||
if !entity.contains::<IsResource>() {
|
||||
let counter = entity_counters.entry(entity.id()).or_insert(0);
|
||||
*counter += 1;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@ -4206,9 +4262,9 @@ mod tests {
|
||||
|
||||
let mut entities = world.iter_entities_mut().collect::<Vec<_>>();
|
||||
entities.sort_by_key(|e| e.get::<A>().map(|a| a.0).or(e.get::<B>().map(|b| b.0)));
|
||||
let (a, b) = entities.split_at_mut(2);
|
||||
let (a, b) = entities.split_at_mut(3);
|
||||
core::mem::swap(
|
||||
&mut a[1].get_mut::<A>().unwrap().0,
|
||||
&mut a[2].get_mut::<A>().unwrap().0,
|
||||
&mut b[0].get_mut::<B>().unwrap().0,
|
||||
);
|
||||
assert_eq!(world.entity(a1).get(), Some(&A(0)));
|
||||
|
@ -49,7 +49,13 @@ pub mod prelude {
|
||||
use bevy_app::prelude::*;
|
||||
|
||||
#[cfg(feature = "serialize")]
|
||||
use {bevy_asset::AssetApp, bevy_ecs::schedule::IntoScheduleConfigs};
|
||||
use {
|
||||
bevy_asset::AssetApp,
|
||||
bevy_ecs::schedule::IntoScheduleConfigs,
|
||||
bevy_ecs::{
|
||||
entity_disabling::DefaultQueryFilters, resource::IsResource, resource::ResourceEntity,
|
||||
},
|
||||
};
|
||||
|
||||
/// Plugin that provides scene functionality to an [`App`].
|
||||
#[derive(Default)]
|
||||
@ -64,6 +70,8 @@ impl Plugin for ScenePlugin {
|
||||
.init_resource::<SceneSpawner>()
|
||||
.register_type::<SceneRoot>()
|
||||
.register_type::<DynamicSceneRoot>()
|
||||
.register_type::<IsResource>()
|
||||
.register_type::<ResourceEntity<DefaultQueryFilters>>()
|
||||
.add_systems(SpawnScene, (scene_spawner, scene_spawner_system).chain());
|
||||
|
||||
// Register component hooks for DynamicSceneRoot
|
||||
|
@ -610,7 +610,7 @@ mod tests {
|
||||
assert_eq!(scene_component_a.y, 4.0);
|
||||
assert_eq!(
|
||||
app.world().entity(entity).get::<Children>().unwrap().len(),
|
||||
1
|
||||
3 // two resources-as-entities are also counted
|
||||
);
|
||||
|
||||
// let's try to delete the scene
|
||||
|
@ -516,9 +516,11 @@ mod tests {
|
||||
};
|
||||
use bevy_ecs::{
|
||||
entity::{Entity, EntityHashMap},
|
||||
entity_disabling::DefaultQueryFilters,
|
||||
prelude::{Component, ReflectComponent, ReflectResource, Resource, World},
|
||||
query::{With, Without},
|
||||
reflect::AppTypeRegistry,
|
||||
resource::{IsResource, ResourceEntity},
|
||||
world::FromWorld,
|
||||
};
|
||||
use bevy_reflect::{Reflect, ReflectDeserialize, ReflectSerialize};
|
||||
@ -611,6 +613,8 @@ mod tests {
|
||||
registry.register::<MyEntityRef>();
|
||||
registry.register::<Entity>();
|
||||
registry.register::<MyResource>();
|
||||
registry.register::<IsResource>();
|
||||
registry.register::<ResourceEntity<DefaultQueryFilters>>();
|
||||
}
|
||||
world.insert_resource(registry);
|
||||
world
|
||||
@ -638,20 +642,20 @@ mod tests {
|
||||
),
|
||||
},
|
||||
entities: {
|
||||
4294967293: (
|
||||
4294967291: (
|
||||
components: {
|
||||
"bevy_scene::serde::tests::Bar": (345),
|
||||
"bevy_scene::serde::tests::Baz": (789),
|
||||
"bevy_scene::serde::tests::Foo": (123),
|
||||
},
|
||||
),
|
||||
4294967294: (
|
||||
4294967292: (
|
||||
components: {
|
||||
"bevy_scene::serde::tests::Bar": (345),
|
||||
"bevy_scene::serde::tests::Foo": (123),
|
||||
},
|
||||
),
|
||||
4294967295: (
|
||||
4294967293: (
|
||||
components: {
|
||||
"bevy_scene::serde::tests::Foo": (123),
|
||||
},
|
||||
@ -757,7 +761,7 @@ mod tests {
|
||||
.write_to_world(&mut dst_world, &mut map)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(2, deserialized_scene.entities.len());
|
||||
assert_eq!(4, deserialized_scene.entities.len());
|
||||
assert_scene_eq(&scene, &deserialized_scene);
|
||||
|
||||
let bar_to_foo = dst_world
|
||||
@ -785,7 +789,7 @@ mod tests {
|
||||
|
||||
let (scene, deserialized_scene) = roundtrip_ron(&world);
|
||||
|
||||
assert_eq!(1, deserialized_scene.entities.len());
|
||||
assert_eq!(3, deserialized_scene.entities.len());
|
||||
assert_scene_eq(&scene, &deserialized_scene);
|
||||
|
||||
let mut world = create_world();
|
||||
@ -815,10 +819,19 @@ mod tests {
|
||||
|
||||
assert_eq!(
|
||||
vec![
|
||||
0, 1, 255, 255, 255, 255, 15, 1, 37, 98, 101, 118, 121, 95, 115, 99, 101, 110, 101,
|
||||
0, 3, 253, 255, 255, 255, 15, 1, 37, 98, 101, 118, 121, 95, 115, 99, 101, 110, 101,
|
||||
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, 1, 12, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33
|
||||
108, 64, 1, 12, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 254, 255,
|
||||
255, 255, 15, 1, 30, 98, 101, 118, 121, 95, 101, 99, 115, 58, 58, 114, 101, 115,
|
||||
111, 117, 114, 99, 101, 58, 58, 73, 115, 82, 101, 115, 111, 117, 114, 99, 101, 255,
|
||||
255, 255, 255, 15, 2, 30, 98, 101, 118, 121, 95, 101, 99, 115, 58, 58, 114, 101,
|
||||
115, 111, 117, 114, 99, 101, 58, 58, 73, 115, 82, 101, 115, 111, 117, 114, 99, 101,
|
||||
83, 98, 101, 118, 121, 95, 101, 99, 115, 58, 58, 114, 101, 115, 111, 117, 114, 99,
|
||||
101, 58, 58, 82, 101, 115, 111, 117, 114, 99, 101, 69, 110, 116, 105, 116, 121, 60,
|
||||
98, 101, 118, 121, 95, 101, 99, 115, 58, 58, 101, 110, 116, 105, 116, 121, 95, 100,
|
||||
105, 115, 97, 98, 108, 105, 110, 103, 58, 58, 68, 101, 102, 97, 117, 108, 116, 81,
|
||||
117, 101, 114, 121, 70, 105, 108, 116, 101, 114, 115, 62
|
||||
],
|
||||
serialized_scene
|
||||
);
|
||||
@ -830,7 +843,7 @@ mod tests {
|
||||
.deserialize(&mut postcard::Deserializer::from_bytes(&serialized_scene))
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(1, deserialized_scene.entities.len());
|
||||
assert_eq!(3, deserialized_scene.entities.len());
|
||||
assert_scene_eq(&scene, &deserialized_scene);
|
||||
}
|
||||
|
||||
@ -856,11 +869,21 @@ mod tests {
|
||||
|
||||
assert_eq!(
|
||||
vec![
|
||||
146, 128, 129, 206, 255, 255, 255, 255, 145, 129, 217, 37, 98, 101, 118, 121, 95,
|
||||
146, 128, 131, 206, 255, 255, 255, 253, 145, 129, 217, 37, 98, 101, 118, 121, 95,
|
||||
115, 99, 101, 110, 101, 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, 63, 166, 102, 102, 202, 64, 108, 204, 205, 129, 165, 84, 117, 112,
|
||||
108, 101, 172, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33
|
||||
108, 101, 172, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 206, 255,
|
||||
255, 255, 254, 145, 129, 190, 98, 101, 118, 121, 95, 101, 99, 115, 58, 58, 114,
|
||||
101, 115, 111, 117, 114, 99, 101, 58, 58, 73, 115, 82, 101, 115, 111, 117, 114, 99,
|
||||
101, 144, 206, 255, 255, 255, 255, 145, 130, 190, 98, 101, 118, 121, 95, 101, 99,
|
||||
115, 58, 58, 114, 101, 115, 111, 117, 114, 99, 101, 58, 58, 73, 115, 82, 101, 115,
|
||||
111, 117, 114, 99, 101, 144, 217, 83, 98, 101, 118, 121, 95, 101, 99, 115, 58, 58,
|
||||
114, 101, 115, 111, 117, 114, 99, 101, 58, 58, 82, 101, 115, 111, 117, 114, 99,
|
||||
101, 69, 110, 116, 105, 116, 121, 60, 98, 101, 118, 121, 95, 101, 99, 115, 58, 58,
|
||||
101, 110, 116, 105, 116, 121, 95, 100, 105, 115, 97, 98, 108, 105, 110, 103, 58,
|
||||
58, 68, 101, 102, 97, 117, 108, 116, 81, 117, 101, 114, 121, 70, 105, 108, 116,
|
||||
101, 114, 115, 62, 144
|
||||
],
|
||||
buf
|
||||
);
|
||||
@ -874,7 +897,7 @@ mod tests {
|
||||
.deserialize(&mut rmp_serde::Deserializer::new(&mut reader))
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(1, deserialized_scene.entities.len());
|
||||
assert_eq!(3, deserialized_scene.entities.len());
|
||||
assert_scene_eq(&scene, &deserialized_scene);
|
||||
}
|
||||
|
||||
@ -899,13 +922,23 @@ mod tests {
|
||||
|
||||
assert_eq!(
|
||||
vec![
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 1,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 253, 255, 255, 255, 0, 0, 0, 0, 1,
|
||||
0, 0, 0, 0, 0, 0, 0, 37, 0, 0, 0, 0, 0, 0, 0, 98, 101, 118, 121, 95, 115, 99, 101,
|
||||
110, 101, 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, 0, 0, 0, 0, 0, 0, 0, 2, 0,
|
||||
0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 102, 102, 166, 63, 205, 204, 108, 64, 1,
|
||||
0, 0, 0, 12, 0, 0, 0, 0, 0, 0, 0, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108,
|
||||
100, 33
|
||||
100, 33, 254, 255, 255, 255, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 30, 0, 0, 0, 0, 0,
|
||||
0, 0, 98, 101, 118, 121, 95, 101, 99, 115, 58, 58, 114, 101, 115, 111, 117, 114,
|
||||
99, 101, 58, 58, 73, 115, 82, 101, 115, 111, 117, 114, 99, 101, 255, 255, 255, 255,
|
||||
0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 30, 0, 0, 0, 0, 0, 0, 0, 98, 101, 118, 121, 95,
|
||||
101, 99, 115, 58, 58, 114, 101, 115, 111, 117, 114, 99, 101, 58, 58, 73, 115, 82,
|
||||
101, 115, 111, 117, 114, 99, 101, 83, 0, 0, 0, 0, 0, 0, 0, 98, 101, 118, 121, 95,
|
||||
101, 99, 115, 58, 58, 114, 101, 115, 111, 117, 114, 99, 101, 58, 58, 82, 101, 115,
|
||||
111, 117, 114, 99, 101, 69, 110, 116, 105, 116, 121, 60, 98, 101, 118, 121, 95,
|
||||
101, 99, 115, 58, 58, 101, 110, 116, 105, 116, 121, 95, 100, 105, 115, 97, 98, 108,
|
||||
105, 110, 103, 58, 58, 68, 101, 102, 97, 117, 108, 116, 81, 117, 101, 114, 121, 70,
|
||||
105, 108, 116, 101, 114, 115, 62
|
||||
],
|
||||
serialized_scene
|
||||
);
|
||||
@ -918,7 +951,7 @@ mod tests {
|
||||
bincode::serde::seed_decode_from_slice(scene_deserializer, &serialized_scene, config)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(1, deserialized_scene.entities.len());
|
||||
assert_eq!(3, deserialized_scene.entities.len());
|
||||
assert_scene_eq(&scene, &deserialized_scene);
|
||||
}
|
||||
|
||||
|
25
release-content/migration-guides/resources_as_components.md
Normal file
25
release-content/migration-guides/resources_as_components.md
Normal file
@ -0,0 +1,25 @@
|
||||
---
|
||||
title: Resources as Components
|
||||
pull_requests: [19711]
|
||||
---
|
||||
|
||||
Resources are very similar to Components: they are both data that can be stored in the ECS and queried.
|
||||
The only real difference between them is that querying a resource will return either one or zero resources, whereas querying for a component can return any number of entities that match it.
|
||||
|
||||
Even so, resources and components have always been separate concepts within the ECS.
|
||||
This leads to some annoying restrictions.
|
||||
While components have [`ComponentHooks`](https://docs.rs/bevy/latest/bevy/ecs/component/struct.ComponentHooks.html), it's not possible to add lifecycle hooks to resources.
|
||||
Moreover, the engine internals contain a lot of duplication because of it.
|
||||
|
||||
This motivates us to transition resources to components, and while most of the public API will stay the same, some breaking changes are inevitable.
|
||||
|
||||
This PR adds a dummy entity alongside every resource. This entity is inserted and removed alongside resources and doesn't do anything (yet).
|
||||
|
||||
This changes `World::entities().len()` as there are more entities than you might expect there to be. For example, a new world, no longer contains zero entities. This is mostly important for unit tests.
|
||||
|
||||
Two methods have been added `World::entity_count()` and `World::resource_count()`. The former returns the number of entities without the resource entities, while the latter returns the number of resources in the world.
|
||||
|
||||
While the marker component `IsResource` is added to [default query filters](https://docs.rs/bevy/latest/bevy/ecs/entity_disabling/struct.DefaultQueryFilters.html), during world creation, resource entities might still show up in broad queries with [`EntityMutExcept`](https://docs.rs/bevy/latest/bevy/ecs/world/struct.EntityMutExcept.html) and [`EntityRefExcept`](https://docs.rs/bevy/latest/bevy/ecs/world/struct.EntityRefExcept.html).
|
||||
They also show up in `World::iter_entities()`, `World::iter_entities_mut()` and [`QueryBuilder`](https://docs.rs/bevy/latest/bevy/ecs/prelude/struct.QueryBuilder.html).
|
||||
|
||||
Lastly, because of the entity bump, the input and output of the `bevy_scene` crate is not equivalent to the previous version, meaning that it's unadvisable to read in scenes from the previous version into the current one.
|
Loading…
Reference in New Issue
Block a user