1use log::warn;
4
5use crate::{
6 component::{Component, ComponentId},
7 entity::Entity,
8 lifecycle::HookContext,
9 storage::SparseArray,
10 world::DeferredWorld,
11};
12#[cfg(feature = "bevy_reflect")]
13use {crate::reflect::ReflectComponent, bevy_reflect::Reflect};
14pub use bevy_ecs_macros::Resource;
16use bevy_platform::cell::SyncUnsafeCell;
17
18#[diagnostic::on_unimplemented(
83 message = "`{Self}` is not a `Resource`",
84 label = "invalid `Resource`",
85 note = "consider annotating `{Self}` with `#[derive(Resource)]`"
86)]
87pub trait Resource: Component {}
88
89#[derive(Default)]
91pub struct ResourceEntities(SyncUnsafeCell<SparseArray<ComponentId, Entity>>);
92
93impl ResourceEntities {
94 #[inline]
99 pub fn iter(&self) -> impl Iterator<Item = (ComponentId, Entity)> {
100 self.deref().iter().map(|(id, entity)| (id, *entity))
101 }
102
103 #[inline]
105 pub fn get(&self, id: ComponentId) -> Option<Entity> {
106 self.deref().get(id).copied()
107 }
108
109 #[inline]
110 fn deref(&self) -> &SparseArray<ComponentId, Entity> {
111 unsafe { &*self.0.get() }
117 }
118}
119
120#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Component, Debug))]
122#[derive(Component, Debug)]
123#[component(on_insert, on_discard, on_despawn)]
124pub struct IsResource(ComponentId);
125
126impl IsResource {
127 pub fn new(component_id: ComponentId) -> Self {
129 Self(component_id)
130 }
131
132 pub fn resource_component_id(&self) -> ComponentId {
134 self.0
135 }
136
137 pub(crate) fn on_insert(mut world: DeferredWorld, context: HookContext) {
138 let resource_component_id = world
139 .entity(context.entity)
140 .get::<Self>()
141 .unwrap()
142 .resource_component_id();
143
144 if let Some(original_entity) = world.resource_entities.get(resource_component_id) {
145 if !world.entities().contains(original_entity) {
146 let name = world
147 .components()
148 .get_name(resource_component_id)
149 .expect("resource is registered");
150 panic!(
151 "Resource entity {} of {} has been despawned, when it's not supposed to be.",
152 original_entity, name
153 );
154 }
155
156 if original_entity != context.entity {
157 world
159 .commands()
160 .entity(context.entity)
161 .remove_by_id(resource_component_id);
162 world
163 .commands()
164 .entity(context.entity)
165 .remove_by_id(context.component_id);
166 let name = world
167 .components()
168 .get_name(resource_component_id)
169 .expect("resource is registered");
170 warn!("Tried inserting the resource {} while one already exists. \
171 Resources are unique components stored on a single entity. \
172 Inserting on a different entity, when one already exists, causes the new value to be removed.", name);
173 }
174 } else {
175 let cache = unsafe { world.as_unsafe_world_cell().resource_entities() };
177 unsafe { &mut *cache.0.get() }.insert(resource_component_id, context.entity);
181 }
182 }
183
184 pub(crate) fn on_discard(mut world: DeferredWorld, context: HookContext) {
185 let resource_component_id = world
186 .entity(context.entity)
187 .get::<Self>()
188 .unwrap()
189 .resource_component_id();
190
191 if let Some(resource_entity) = world.resource_entities.get(resource_component_id)
192 && resource_entity == context.entity
193 {
194 let cache = unsafe { world.as_unsafe_world_cell().resource_entities() };
196 unsafe { &mut *cache.0.get() }.remove(resource_component_id);
200
201 world
202 .commands()
203 .entity(context.entity)
204 .remove_by_id(resource_component_id);
205 }
206 }
207
208 pub(crate) fn on_despawn(_world: DeferredWorld, _context: HookContext) {
209 warn!("Resource entities are not supposed to be despawned.");
210 }
211}
212
213pub use crate::component::IS_RESOURCE;
214
215#[cfg(test)]
216mod tests {
217 use core::sync::atomic::{AtomicBool, Ordering::Relaxed};
218
219 use crate::{
220 change_detection::MaybeLocation,
221 component::Components,
222 entity::Entity,
223 lifecycle::HookContext,
224 prelude::{EntityRef, Query, SystemParamBuilder},
225 ptr::OwningPtr,
226 resource::{IsResource, Resource, ResourceEntities},
227 system::{ParamBuilder, QueryParamBuilder, RunSystemOnce},
228 world::{DeferredWorld, FilteredEntityRef, World},
229 };
230 use alloc::vec::Vec;
231 use bevy_ecs_macros::Component;
232 use bevy_platform::prelude::String;
233
234 #[test]
235 fn unique_resource_entities() {
236 #[derive(Default, Resource)]
237 struct TestResource1;
238
239 #[derive(Resource)]
240 #[expect(dead_code, reason = "field needed for testing")]
241 struct TestResource2(String);
242
243 #[derive(Resource)]
244 #[expect(dead_code, reason = "field needed for testing")]
245 struct TestResource3(u8);
246
247 let mut world = World::new();
248 let start = world.entities().count_spawned();
249 let id1 = world.init_resource::<TestResource1>();
250 assert_eq!(world.entities().count_spawned(), start + 1);
251 world.insert_resource(TestResource2(String::from("Foo")));
252 assert_eq!(world.entities().count_spawned(), start + 2);
253 let id3 = world.register_component::<TestResource3>();
256 assert_eq!(world.entities().count_spawned(), start + 2);
257 OwningPtr::make(20_u8, |ptr| {
258 unsafe {
260 world.insert_resource_by_id(id3, ptr, MaybeLocation::caller());
261 }
262 });
263 assert_eq!(world.entities().count_spawned(), start + 3);
264 let e3 = world.resource_entities().get(id3).unwrap();
265 assert!(world.remove_resource_by_id(id3));
266 assert_eq!(world.entities().count_spawned(), start + 3);
268 OwningPtr::make(20_u8, |ptr| {
269 unsafe {
271 world.insert_resource_by_id(id3, ptr, MaybeLocation::caller());
272 }
273 });
274 assert_eq!(e3, world.resource_entities().get(id3).unwrap());
275 let e1 = world.resource_entities().get(id1).unwrap();
277 world.remove_resource::<TestResource1>();
278 assert_eq!(world.entities().count_spawned(), start + 3);
279 world.init_resource::<TestResource1>();
280 assert_eq!(e1, world.resource_entities().get(id1).unwrap());
281 world.insert_resource(TestResource2(String::from("Bar")));
283 assert_eq!(world.entities().count_spawned(), start + 3);
284 }
285
286 #[test]
287 fn is_resource_presence() {
288 #[derive(Default, Resource)]
289 struct TestResource;
290
291 let mut world = World::new();
292 let id = world.init_resource::<TestResource>();
293
294 assert!(world.get_resource::<TestResource>().is_some());
295
296 let mut query = world.query::<(Entity, &TestResource, &IsResource)>();
297 let first_entity = {
298 let resources = query.iter(&world).collect::<Vec<_>>();
299 assert_eq!(resources.len(), 1);
300 let (entity, _test_resource, is_resource) = resources[0];
301 assert_eq!(is_resource.resource_component_id(), id);
302 entity
303 };
304
305 world.entity_mut(first_entity).remove::<IsResource>();
308 assert!(world.get_resource::<TestResource>().is_none());
309
310 assert!(
311 !world.entity(first_entity).contains::<TestResource>(),
312 "Removing IsResource should also remove the Resource component it corresponds to"
313 );
314
315 world.init_resource::<TestResource>();
316 let second_entity = {
317 let resources = query.iter(&world).collect::<Vec<_>>();
318 assert_eq!(resources.len(), 1);
319 let (entity, _test_resource, is_resource) = resources[0];
320 assert_eq!(is_resource.resource_component_id(), id);
321 entity
322 };
323
324 assert_ne!(
325 first_entity, second_entity,
326 "The first resource entity was invalidated, so the second initialization should be new"
327 );
328
329 let id = world.spawn(TestResource).id();
330 assert!(world.entity(id).get::<TestResource>().is_none());
332 assert!(world.entity(id).get::<IsResource>().is_none());
333 assert!(world.entity(second_entity).get::<TestResource>().is_some());
334 assert!(world.entity(second_entity).get::<IsResource>().is_some());
335 }
336
337 #[test]
338 fn derive_resource_component_features() {
339 static ON_ADD_CALLED: AtomicBool = AtomicBool::new(false);
340
341 #[derive(Resource)]
342 #[component(immutable, on_add)]
343 struct TestResource;
344 impl TestResource {
345 fn on_add(_: DeferredWorld, _: HookContext) {
346 ON_ADD_CALLED.store(true, Relaxed);
347 }
348 }
349
350 let mut world = World::new();
351 world.insert_resource(TestResource);
352
353 assert!(ON_ADD_CALLED.load(Relaxed));
354 assert!(world.get_resource::<TestResource>().is_some());
355 }
356
357 #[test]
358 fn derive_resource_require_features() {
359 #[derive(Component, Default)]
360 struct RequiredComponent;
361
362 #[derive(Resource)]
363 #[require(RequiredComponent)]
364 struct TestResource;
365
366 let mut world = World::new();
367 world.insert_resource(TestResource);
368
369 assert_eq!(
370 world
371 .query::<(&TestResource, &RequiredComponent)>()
372 .iter(&world)
373 .count(),
374 1
375 );
376 }
377
378 #[test]
379 fn get_multiple_dynamic_resources() {
380 use std::any::TypeId;
381
382 #[derive(Resource)]
383 struct ResA(u8);
384
385 #[derive(Resource)]
386 struct ResB(u8);
387
388 let mut world = World::default();
389 world.insert_resource(ResA(12));
390 world.insert_resource(ResB(34));
391
392 let system = (
393 QueryParamBuilder::new(|builder| {
394 builder.data::<EntityRef>();
395 builder.with::<IsResource>();
396 builder.or(|builder| {
397 builder.with::<ResA>();
398 builder.with::<ResB>();
399 });
400 }),
401 ParamBuilder,
402 ParamBuilder,
403 )
404 .build_state(&mut world)
405 .build_system(resource_system);
406
407 fn resource_system(
408 query: Query<FilteredEntityRef>,
409 resource_entities: &ResourceEntities,
410 components: &Components,
411 ) {
412 let component_id_a = components.get_id(TypeId::of::<ResA>()).unwrap();
413 let component_id_b = components.get_id(TypeId::of::<ResB>()).unwrap();
414
415 let entity_a = resource_entities.get(component_id_a).unwrap();
416 let entity_b = resource_entities.get(component_id_b).unwrap();
417
418 let entity_ref_a: FilteredEntityRef = query.get(entity_a).unwrap();
419 assert_eq!(entity_ref_a.get::<ResA>().unwrap().0, 12);
420
421 let entity_ref_b: FilteredEntityRef = query.get(entity_b).unwrap();
422 assert_eq!(entity_ref_b.get::<ResB>().unwrap().0, 34);
423 }
424
425 let _ = world.run_system_once(system);
426 }
427}