Skip to content

Commit 59e9ee3

Browse files
Trashtalk217cartalice-i-ceciledloukadakis
authored
Store Resources as components on singleton entities (#20934)
This is part of #19731. # Resources as Components ## Motivation More things should be entities. This simplifies the API, the lower-level implementation and the tools we have for entities and components can be used for other things in the engine. In particular, for resources, it is really handy to have observers, which we currently don't have. See #20821 under 1A, for a more specific use. ## Current Work This removes the `resources` field from the world storage and instead store the resources on singleton entities. For easy lookup, we add a `HashMap<ComponentId, Entity>` to `World`, in order to quickly find the singleton entity where the resource is stored. Because we store resources on entities, we derive `Component` alongside `Resource`, this means that ```rust #[derive(Resource)] struct Foo; ``` turns into ```rust #[derive(Resource, Component)] struct Foo; ``` This was also done for reflections, meaning that ```rust #[derive(Resource, Reflect)] #[refect(Resource)] struct Bar; ``` becomes ```rust #[derive(Resource, Component, Reflect)] #[refect(Resource, Component)] struct Bar; ``` In order to distinguish resource entities, they are tagged with the `IsResource` component. Additionally, to ensure that they aren't queried by accident, they are also tagged as being internal entities, which means that they don't show up in queries by default. ## Drawbacks - Currently you can't have a struct that is both a `Resource` and a `Component`, because `Resource` expands to also implement `Component`, this means that this throws a compiler error as it's implemented twice. - Because every reflected Resource must also implement `ReflectComponent` you need to import `bevy_ecs::reflect::ReflectComponent` every time you use `#[reflect(Resource)]`. This is kind of unintuitive. ## Future Work - Simplify `Access` in the ECS, to only deal with components (and not components *and* resources). - Newtype `Res<Resource>` to `Single<Ref<Resource>>` (or something similair). - Eliminate `ReflectResource`. - Take stabs at simplifying the public facing API. --------- Co-authored-by: Carter Anderson <mcanders1@gmail.com> Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com> Co-authored-by: Dimitrios Loukadakis <dloukadakis@users.noreply.github.com>
1 parent 9fd2637 commit 59e9ee3

52 files changed

Lines changed: 1675 additions & 1092 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

crates/bevy_animation/src/lib.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ use crate::{
3939

4040
use bevy_app::{AnimationSystems, App, Plugin, PostUpdate};
4141
use bevy_asset::{Asset, AssetApp, AssetEventSystems, Assets};
42-
use bevy_ecs::{prelude::*, world::EntityMutExcept};
42+
use bevy_ecs::{prelude::*, resource::IsResource, world::EntityMutExcept};
4343
use bevy_math::FloatOrd;
4444
use bevy_platform::{collections::HashMap, hash::NoOpHash};
4545
use bevy_reflect::{prelude::ReflectDefault, Reflect, TypePath};
@@ -1032,7 +1032,10 @@ pub fn animate_targets(
10321032
graphs: Res<Assets<AnimationGraph>>,
10331033
threaded_animation_graphs: Res<ThreadedAnimationGraphs>,
10341034
players: Query<(&AnimationPlayer, &AnimationGraphHandle)>,
1035-
mut targets: Query<(Entity, &AnimationTargetId, &AnimatedBy, AnimationEntityMut)>,
1035+
mut targets: Query<
1036+
(Entity, &AnimationTargetId, &AnimatedBy, AnimationEntityMut),
1037+
Without<IsResource>,
1038+
>,
10361039
animation_evaluation_state: Local<ThreadLocal<RefCell<AnimationEvaluationState>>>,
10371040
) {
10381041
// Evaluate all animation targets in parallel.

crates/bevy_app/src/app.rs

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -471,11 +471,18 @@ impl App {
471471
self
472472
}
473473

474-
/// Inserts the [`!Send`](Send) resource into the app, overwriting any existing resource
474+
/// Inserts the [`!Send`](Send) resource into the app, overwriting any existing data
475+
/// of the same type.
476+
#[deprecated(since = "0.19.0", note = "use App::insert_non_send")]
477+
pub fn insert_non_send_resource<R: 'static>(&mut self, resource: R) -> &mut Self {
478+
self.insert_non_send(resource)
479+
}
480+
481+
/// Inserts the [`!Send`](Send) data into the app, overwriting any existing data
475482
/// of the same type.
476483
///
477-
/// There is also an [`init_non_send_resource`](Self::init_non_send_resource) for
478-
/// resources that implement [`Default`]
484+
/// There is also an [`init_non_send`](Self::init_non_send) for [`!Send`](Send) data
485+
/// that implement [`Default`]
479486
///
480487
/// # Examples
481488
///
@@ -488,20 +495,26 @@ impl App {
488495
/// }
489496
///
490497
/// App::new()
491-
/// .insert_non_send_resource(MyCounter { counter: 0 });
498+
/// .insert_non_send(MyCounter { counter: 0 });
492499
/// ```
493-
pub fn insert_non_send_resource<R: 'static>(&mut self, resource: R) -> &mut Self {
494-
self.world_mut().insert_non_send_resource(resource);
500+
pub fn insert_non_send<R: 'static>(&mut self, resource: R) -> &mut Self {
501+
self.world_mut().insert_non_send(resource);
495502
self
496503
}
497504

498505
/// Inserts the [`!Send`](Send) resource into the app if there is no existing instance of `R`.
506+
#[deprecated(since = "0.19.0", note = "use App::init_non_send")]
507+
pub fn init_non_send_resource<R: 'static + FromWorld>(&mut self) -> &mut Self {
508+
self.init_non_send::<R>()
509+
}
510+
511+
/// Inserts the [`!Send`](Send) data into the app if there is no existing instance of `R`.
499512
///
500513
/// `R` must implement [`FromWorld`].
501514
/// If `R` implements [`Default`], [`FromWorld`] will be automatically implemented and
502515
/// initialize the [`Resource`] with [`Default::default`].
503-
pub fn init_non_send_resource<R: 'static + FromWorld>(&mut self) -> &mut Self {
504-
self.world_mut().init_non_send_resource::<R>();
516+
pub fn init_non_send<R: 'static + FromWorld>(&mut self) -> &mut Self {
517+
self.world_mut().init_non_send::<R>();
505518
self
506519
}
507520

@@ -1961,7 +1974,7 @@ mod tests {
19611974
}
19621975

19631976
App::new()
1964-
.init_non_send_resource::<NonSendTestResource>()
1977+
.init_non_send::<NonSendTestResource>()
19651978
.init_resource::<TestResource>();
19661979
}
19671980

crates/bevy_dev_tools/src/render_debug.rs

Lines changed: 38 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -57,10 +57,11 @@ impl Plugin for RenderDebugOverlayPlugin {
5757
embedded_asset!(app, "debug_overlay.wgsl");
5858

5959
app.register_type::<RenderDebugOverlay>()
60-
.init_resource::<RenderDebugOverlay>()
60+
.register_type::<GlobalRenderDebugOverlay>()
61+
.init_resource::<GlobalRenderDebugOverlay>()
6162
.add_message::<RenderDebugOverlayEvent>()
6263
.add_plugins((
63-
ExtractResourcePlugin::<RenderDebugOverlay>::default(),
64+
ExtractResourcePlugin::<GlobalRenderDebugOverlay>::default(),
6465
ExtractComponentPlugin::<RenderDebugOverlay>::default(),
6566
))
6667
.add_systems(bevy_app::Update, (handle_input, update_overlay).chain());
@@ -109,7 +110,7 @@ pub fn handle_input(
109110
pub fn update_overlay(
110111
mut commands: Commands,
111112
mut events: MessageReader<RenderDebugOverlayEvent>,
112-
mut config_res: ResMut<RenderDebugOverlay>,
113+
mut config_res: ResMut<GlobalRenderDebugOverlay>,
113114
cameras: Query<
114115
(
115116
Entity,
@@ -238,9 +239,15 @@ pub fn update_overlay(
238239
}
239240
}
240241

242+
let config_comp = RenderDebugOverlay {
243+
enabled: config_res.enabled,
244+
mode: config_res.mode,
245+
opacity: config_res.opacity,
246+
};
247+
241248
for (entity, existing_config, ..) in &cameras {
242-
if existing_config.is_none() || (changed && Some(config_res.as_ref()) != existing_config) {
243-
commands.entity(entity).insert(config_res.clone());
249+
if existing_config.is_none() || (changed && Some(&config_comp) != existing_config) {
250+
commands.entity(entity).insert(config_comp.clone());
244251
}
245252
}
246253
}
@@ -256,8 +263,9 @@ pub enum RenderDebugOverlayEvent {
256263
}
257264

258265
/// Configure the render debug overlay.
259-
#[derive(Resource, Component, Clone, ExtractResource, ExtractComponent, Reflect, PartialEq)]
260-
#[reflect(Resource, Component, Default)]
266+
/// Overwrites the default [`GlobalRenderDebugOverlay`] resource.
267+
#[derive(Component, Clone, ExtractComponent, Reflect, PartialEq)]
268+
#[reflect(Component, Default)]
261269
pub struct RenderDebugOverlay {
262270
/// Enables or disables drawing the overlay.
263271
pub enabled: bool,
@@ -277,6 +285,29 @@ impl Default for RenderDebugOverlay {
277285
}
278286
}
279287

288+
/// Configure the render debug overlay globally.
289+
/// Can be overwritten by using a [`RenderDebugOverlay`] component.
290+
#[derive(Resource, Clone, ExtractResource, ExtractComponent, Reflect, PartialEq)]
291+
#[reflect(Resource, Default)]
292+
pub struct GlobalRenderDebugOverlay {
293+
/// Enables or disables drawing the overlay.
294+
pub enabled: bool,
295+
/// The kind of data to write to the overlay.
296+
pub mode: RenderDebugMode,
297+
/// The opacity of the overlay, to allow seeing the rendered image underneath.
298+
pub opacity: f32,
299+
}
300+
301+
impl Default for GlobalRenderDebugOverlay {
302+
fn default() -> Self {
303+
Self {
304+
enabled: false,
305+
mode: RenderDebugMode::Depth,
306+
opacity: 1.0,
307+
}
308+
}
309+
}
310+
280311
/// The kind of renderer data to visualize.
281312
#[expect(missing_docs, reason = "Enum variants are self-explanatory")]
282313
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash, Reflect)]

crates/bevy_ecs/macros/src/component.rs

Lines changed: 81 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,37 @@ pub fn derive_resource(input: TokenStream) -> TokenStream {
3232
return err.into_compile_error().into();
3333
}
3434

35+
// Implement the Component trait.
36+
let map_entities = map_entities(
37+
&ast.data,
38+
&bevy_ecs_path,
39+
Ident::new("this", Span::call_site()),
40+
false,
41+
false,
42+
None
43+
).map(|map_entities_impl| quote! {
44+
fn map_entities<M: #bevy_ecs_path::entity::EntityMapper>(this: &mut Self, mapper: &mut M) {
45+
use #bevy_ecs_path::entity::MapEntities;
46+
#map_entities_impl
47+
}
48+
});
49+
50+
let storage = storage_path(&bevy_ecs_path, StorageTy::Table);
51+
52+
let on_add_path = None;
53+
let on_remove_path = None;
54+
let on_insert_path = None;
55+
let on_replace_path = None;
56+
let on_despawn_path = None;
57+
58+
let on_add = hook_register_function_call(&bevy_ecs_path, quote! {on_add}, on_add_path);
59+
let on_remove = hook_register_function_call(&bevy_ecs_path, quote! {on_remove}, on_remove_path);
60+
let on_insert = hook_register_function_call(&bevy_ecs_path, quote! {on_insert}, on_insert_path);
61+
let on_replace =
62+
hook_register_function_call(&bevy_ecs_path, quote! {on_replace}, on_replace_path);
63+
let on_despawn =
64+
hook_register_function_call(&bevy_ecs_path, quote! {on_despawn}, on_despawn_path);
65+
3566
ast.generics
3667
.make_where_clause()
3768
.predicates
@@ -40,10 +71,58 @@ pub fn derive_resource(input: TokenStream) -> TokenStream {
4071
let struct_name = &ast.ident;
4172
let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl();
4273

43-
TokenStream::from(quote! {
74+
let mut register_required = Vec::with_capacity(1);
75+
// We add the component_id existence check here to avoid recursive init during required components initialization.
76+
register_required.push(quote! {
77+
let resource_component_id = if let Some(id) = required_components.components_registrator().component_id::<#struct_name #type_generics>() {
78+
id
79+
} else {
80+
required_components.components_registrator().register_component::<#struct_name #type_generics>()
81+
};
82+
required_components.register_required::<#bevy_ecs_path::resource::IsResource>(move || #bevy_ecs_path::resource::IsResource::new(resource_component_id));
83+
});
84+
85+
// This puts `register_required` before `register_recursive_requires` to ensure that the constructors of _all_ top
86+
// level components are initialized first, giving them precedence over recursively defined constructors for the same component type
87+
let component_derive_token_stream = TokenStream::from(quote! {
88+
impl #impl_generics #bevy_ecs_path::component::Component for #struct_name #type_generics #where_clause {
89+
const STORAGE_TYPE: #bevy_ecs_path::component::StorageType = #storage;
90+
type Mutability = #bevy_ecs_path::component::Mutable;
91+
fn register_required_components(
92+
_requiree: #bevy_ecs_path::component::ComponentId,
93+
required_components: &mut #bevy_ecs_path::component::RequiredComponentsRegistrator,
94+
) {
95+
#(#register_required)*
96+
}
97+
98+
#on_add
99+
#on_insert
100+
#on_replace
101+
#on_remove
102+
#on_despawn
103+
104+
fn clone_behavior() -> #bevy_ecs_path::component::ComponentCloneBehavior {
105+
#bevy_ecs_path::component::ComponentCloneBehavior::Default
106+
}
107+
108+
#map_entities
109+
110+
fn relationship_accessor() -> Option<#bevy_ecs_path::relationship::ComponentRelationshipAccessor<Self>> {
111+
None
112+
}
113+
}
114+
});
115+
116+
// Implement the Resource trait.
117+
let resource_impl_token_stream = TokenStream::from(quote! {
44118
impl #impl_generics #bevy_ecs_path::resource::Resource for #struct_name #type_generics #where_clause {
45119
}
46-
})
120+
});
121+
122+
resource_impl_token_stream
123+
.into_iter()
124+
.chain(component_derive_token_stream)
125+
.collect()
47126
}
48127

49128
/// Component derive syntax is documented on both the macro and the trait.
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
//! Constant components included in every world.
2+
3+
/// `usize` for the [`Add`](crate::lifecycle::Add) component used in lifecycle observers.
4+
pub const ADD: usize = 0;
5+
/// `usize` for the [`Insert`](crate::lifecycle::Insert) component used in lifecycle observers.
6+
pub const INSERT: usize = 1;
7+
/// `usize` for the [`Replace`](crate::lifecycle::Replace) component used in lifecycle observers.
8+
pub const REPLACE: usize = 2;
9+
/// `usize` for the [`Remove`](crate::lifecycle::Remove) component used in lifecycle observers.
10+
pub const REMOVE: usize = 3;
11+
/// `usize` for [`Despawn`](crate::lifecycle::Despawn) component used in lifecycle observers.
12+
pub const DESPAWN: usize = 4;
13+
/// `usize` of the [`IsResource`](crate::resource::IsResource) component used to mark entities with resources.
14+
pub const IS_RESOURCE: usize = 5;

crates/bevy_ecs/src/component/info.rs

Lines changed: 8 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -302,19 +302,7 @@ impl ComponentDescriptor {
302302
///
303303
/// The [`StorageType`] for resources is always [`StorageType::Table`].
304304
pub fn new_resource<T: Resource>() -> Self {
305-
Self {
306-
name: DebugName::type_name::<T>(),
307-
// PERF: `SparseStorage` may actually be a more
308-
// reasonable choice as `storage_type` for resources.
309-
storage_type: StorageType::Table,
310-
is_send_and_sync: true,
311-
type_id: Some(TypeId::of::<T>()),
312-
layout: Layout::new::<T>(),
313-
drop: needs_drop::<T>().then_some(Self::drop_ptr::<T> as _),
314-
mutable: true,
315-
clone_behavior: ComponentCloneBehavior::Default,
316-
relationship_accessor: None,
317-
}
305+
Self::new::<T>()
318306
}
319307

320308
pub(super) fn new_non_send<T: Any>(storage_type: StorageType) -> Self {
@@ -362,7 +350,6 @@ impl ComponentDescriptor {
362350
pub struct Components {
363351
pub(super) components: Vec<Option<ComponentInfo>>,
364352
pub(super) indices: TypeIdMap<ComponentId>,
365-
pub(super) resource_indices: TypeIdMap<ComponentId>,
366353
// This is kept internal and local to verify that no deadlocks can occur.
367354
pub(super) queued: bevy_platform::sync::RwLock<QueuedComponents>,
368355
}
@@ -407,7 +394,7 @@ impl Components {
407394
#[inline]
408395
pub fn num_queued(&self) -> usize {
409396
let queued = self.queued.read().unwrap_or_else(PoisonError::into_inner);
410-
queued.components.len() + queued.dynamic_registrations.len() + queued.resources.len()
397+
queued.components.len() + queued.dynamic_registrations.len()
411398
}
412399

413400
/// Returns `true` if there are any components registered with this instance. Otherwise, this returns `false`.
@@ -423,7 +410,7 @@ impl Components {
423410
.queued
424411
.get_mut()
425412
.unwrap_or_else(PoisonError::into_inner);
426-
queued.components.len() + queued.dynamic_registrations.len() + queued.resources.len()
413+
queued.components.len() + queued.dynamic_registrations.len()
427414
}
428415

429416
/// A faster version of [`Self::any_queued`].
@@ -470,7 +457,6 @@ impl Components {
470457
queued
471458
.components
472459
.values()
473-
.chain(queued.resources.values())
474460
.chain(queued.dynamic_registrations.iter())
475461
.find(|queued| queued.id == id)
476462
.map(|queued| Cow::Owned(queued.descriptor.clone()))
@@ -492,7 +478,6 @@ impl Components {
492478
queued
493479
.components
494480
.values()
495-
.chain(queued.resources.values())
496481
.chain(queued.dynamic_registrations.iter())
497482
.find(|queued| queued.id == id)
498483
.map(|queued| queued.descriptor.name.clone())
@@ -602,7 +587,7 @@ impl Components {
602587
/// Type-erased equivalent of [`Components::valid_resource_id()`].
603588
#[inline]
604589
pub fn get_valid_resource_id(&self, type_id: TypeId) -> Option<ComponentId> {
605-
self.resource_indices.get(&type_id).copied()
590+
self.indices.get(&type_id).copied()
606591
}
607592

608593
/// Returns the [`ComponentId`] of the given [`Resource`] type `T` if it is fully registered.
@@ -680,11 +665,11 @@ impl Components {
680665
/// Type-erased equivalent of [`Components::resource_id()`].
681666
#[inline]
682667
pub fn get_resource_id(&self, type_id: TypeId) -> Option<ComponentId> {
683-
self.resource_indices.get(&type_id).copied().or_else(|| {
668+
self.indices.get(&type_id).copied().or_else(|| {
684669
self.queued
685670
.read()
686671
.unwrap_or_else(PoisonError::into_inner)
687-
.resources
672+
.components
688673
.get(&type_id)
689674
.map(|queued| queued.id)
690675
})
@@ -728,7 +713,7 @@ impl Components {
728713
/// The [`ComponentId`] must be unique.
729714
/// The [`TypeId`] and [`ComponentId`] must not be registered or queued.
730715
#[inline]
731-
pub(super) unsafe fn register_resource_unchecked(
716+
pub(super) unsafe fn register_non_send_unchecked(
732717
&mut self,
733718
type_id: TypeId,
734719
component_id: ComponentId,
@@ -738,7 +723,7 @@ impl Components {
738723
unsafe {
739724
self.register_component_inner(component_id, descriptor);
740725
}
741-
let prev = self.resource_indices.insert(type_id, component_id);
726+
let prev = self.indices.insert(type_id, component_id);
742727
debug_assert!(prev.is_none());
743728
}
744729

crates/bevy_ecs/src/component/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
//! Types for declaring and storing [`Component`]s.
22
33
mod clone;
4+
mod constants;
45
mod info;
56
mod register;
67
mod required;
78

89
pub use clone::*;
10+
pub use constants::*;
911
pub use info::*;
1012
pub use register::*;
1113
pub use required::*;

0 commit comments

Comments
 (0)