use log::warn;
use crate::{
component::{Component, ComponentId},
entity::Entity,
lifecycle::HookContext,
storage::SparseArray,
world::DeferredWorld,
};
#[cfg(feature = "bevy_reflect")]
use {crate::reflect::ReflectComponent, bevy_reflect::Reflect};
pub use bevy_ecs_macros::Resource;
use bevy_platform::cell::SyncUnsafeCell;
#[diagnostic::on_unimplemented(
message = "`{Self}` is not a `Resource`",
label = "invalid `Resource`",
note = "consider annotating `{Self}` with `#[derive(Resource)]`"
)]
pub trait Resource: Component {}
#[derive(Default)]
pub struct ResourceEntities(SyncUnsafeCell<SparseArray<ComponentId, Entity>>);
impl ResourceEntities {
#[inline]
pub fn iter(&self) -> impl Iterator<Item = (ComponentId, Entity)> {
self.deref().iter().map(|(id, entity)| (id, *entity))
}
#[inline]
pub fn get(&self, id: ComponentId) -> Option<Entity> {
self.deref().get(id).copied()
}
#[inline]
fn deref(&self) -> &SparseArray<ComponentId, Entity> {
unsafe { &*self.0.get() }
}
}
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Component, Debug))]
#[derive(Component, Debug)]
#[component(on_insert, on_discard, on_despawn)]
pub struct IsResource(ComponentId);
impl IsResource {
pub fn new(component_id: ComponentId) -> Self {
Self(component_id)
}
pub fn resource_component_id(&self) -> ComponentId {
self.0
}
pub(crate) fn on_insert(mut world: DeferredWorld, context: HookContext) {
let resource_component_id = world
.entity(context.entity)
.get::<Self>()
.unwrap()
.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()
.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 {
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 {
let cache = unsafe { world.as_unsafe_world_cell().resource_entities() };
unsafe { &mut *cache.0.get() }.insert(resource_component_id, context.entity);
}
}
pub(crate) fn on_discard(mut world: DeferredWorld, context: HookContext) {
let resource_component_id = world
.entity(context.entity)
.get::<Self>()
.unwrap()
.resource_component_id();
if let Some(resource_entity) = world.resource_entities.get(resource_component_id)
&& resource_entity == context.entity
{
let cache = unsafe { world.as_unsafe_world_cell().resource_entities() };
unsafe { &mut *cache.0.get() }.remove(resource_component_id);
world
.commands()
.entity(context.entity)
.remove_by_id(resource_component_id);
}
}
pub(crate) fn on_despawn(_world: DeferredWorld, _context: HookContext) {
warn!("Resource entities are not supposed to be despawned.");
}
}
pub use crate::component::IS_RESOURCE;
#[cfg(test)]
mod tests {
use core::sync::atomic::{AtomicBool, Ordering::Relaxed};
use crate::{
change_detection::MaybeLocation,
component::Components,
entity::Entity,
lifecycle::HookContext,
prelude::{EntityRef, Query, SystemParamBuilder},
ptr::OwningPtr,
resource::{IsResource, Resource, ResourceEntities},
system::{ParamBuilder, QueryParamBuilder, RunSystemOnce},
world::{DeferredWorld, FilteredEntityRef, World},
};
use alloc::vec::Vec;
use bevy_ecs_macros::Component;
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().count_spawned();
let id1 = world.init_resource::<TestResource1>();
assert_eq!(world.entities().count_spawned(), start + 1);
world.insert_resource(TestResource2(String::from("Foo")));
assert_eq!(world.entities().count_spawned(), start + 2);
let id3 = world.register_component::<TestResource3>();
assert_eq!(world.entities().count_spawned(), start + 2);
OwningPtr::make(20_u8, |ptr| {
unsafe {
world.insert_resource_by_id(id3, ptr, MaybeLocation::caller());
}
});
assert_eq!(world.entities().count_spawned(), start + 3);
let e3 = world.resource_entities().get(id3).unwrap();
assert!(world.remove_resource_by_id(id3));
assert_eq!(world.entities().count_spawned(), start + 3);
OwningPtr::make(20_u8, |ptr| {
unsafe {
world.insert_resource_by_id(id3, ptr, MaybeLocation::caller());
}
});
assert_eq!(e3, world.resource_entities().get(id3).unwrap());
let e1 = world.resource_entities().get(id1).unwrap();
world.remove_resource::<TestResource1>();
assert_eq!(world.entities().count_spawned(), start + 3);
world.init_resource::<TestResource1>();
assert_eq!(e1, world.resource_entities().get(id1).unwrap());
world.insert_resource(TestResource2(String::from("Bar")));
assert_eq!(world.entities().count_spawned(), start + 3);
}
#[test]
fn is_resource_presence() {
#[derive(Default, Resource)]
struct TestResource;
let mut world = World::new();
let id = world.init_resource::<TestResource>();
assert!(world.get_resource::<TestResource>().is_some());
let mut query = world.query::<(Entity, &TestResource, &IsResource)>();
let first_entity = {
let resources = query.iter(&world).collect::<Vec<_>>();
assert_eq!(resources.len(), 1);
let (entity, _test_resource, is_resource) = resources[0];
assert_eq!(is_resource.resource_component_id(), id);
entity
};
world.entity_mut(first_entity).remove::<IsResource>();
assert!(world.get_resource::<TestResource>().is_none());
assert!(
!world.entity(first_entity).contains::<TestResource>(),
"Removing IsResource should also remove the Resource component it corresponds to"
);
world.init_resource::<TestResource>();
let second_entity = {
let resources = query.iter(&world).collect::<Vec<_>>();
assert_eq!(resources.len(), 1);
let (entity, _test_resource, is_resource) = resources[0];
assert_eq!(is_resource.resource_component_id(), id);
entity
};
assert_ne!(
first_entity, second_entity,
"The first resource entity was invalidated, so the second initialization should be new"
);
let id = world.spawn(TestResource).id();
assert!(world.entity(id).get::<TestResource>().is_none());
assert!(world.entity(id).get::<IsResource>().is_none());
assert!(world.entity(second_entity).get::<TestResource>().is_some());
assert!(world.entity(second_entity).get::<IsResource>().is_some());
}
#[test]
fn derive_resource_component_features() {
static ON_ADD_CALLED: AtomicBool = AtomicBool::new(false);
#[derive(Resource)]
#[component(immutable, on_add)]
struct TestResource;
impl TestResource {
fn on_add(_: DeferredWorld, _: HookContext) {
ON_ADD_CALLED.store(true, Relaxed);
}
}
let mut world = World::new();
world.insert_resource(TestResource);
assert!(ON_ADD_CALLED.load(Relaxed));
assert!(world.get_resource::<TestResource>().is_some());
}
#[test]
fn derive_resource_require_features() {
#[derive(Component, Default)]
struct RequiredComponent;
#[derive(Resource)]
#[require(RequiredComponent)]
struct TestResource;
let mut world = World::new();
world.insert_resource(TestResource);
assert_eq!(
world
.query::<(&TestResource, &RequiredComponent)>()
.iter(&world)
.count(),
1
);
}
#[test]
fn get_multiple_dynamic_resources() {
use std::any::TypeId;
#[derive(Resource)]
struct ResA(u8);
#[derive(Resource)]
struct ResB(u8);
let mut world = World::default();
world.insert_resource(ResA(12));
world.insert_resource(ResB(34));
let system = (
QueryParamBuilder::new(|builder| {
builder.data::<EntityRef>();
builder.with::<IsResource>();
builder.or(|builder| {
builder.with::<ResA>();
builder.with::<ResB>();
});
}),
ParamBuilder,
ParamBuilder,
)
.build_state(&mut world)
.build_system(resource_system);
fn resource_system(
query: Query<FilteredEntityRef>,
resource_entities: &ResourceEntities,
components: &Components,
) {
let component_id_a = components.get_id(TypeId::of::<ResA>()).unwrap();
let component_id_b = components.get_id(TypeId::of::<ResB>()).unwrap();
let entity_a = resource_entities.get(component_id_a).unwrap();
let entity_b = resource_entities.get(component_id_b).unwrap();
let entity_ref_a: FilteredEntityRef = query.get(entity_a).unwrap();
assert_eq!(entity_ref_a.get::<ResA>().unwrap().0, 12);
let entity_ref_b: FilteredEntityRef = query.get(entity_b).unwrap();
assert_eq!(entity_ref_b.get::<ResB>().unwrap().0, 34);
}
let _ = world.run_system_once(system);
}
}