Skip to content

Commit 9cdb915

Browse files
cartalice-i-cecileBD103killercup
authored
Required Components (#14791)
## Introduction This is the first step in my [Next Generation Scene / UI Proposal](#14437). Fixes #7272 #14800. Bevy's current Bundles as the "unit of construction" hamstring the UI user experience and have been a pain point in the Bevy ecosystem generally when composing scenes: * They are an additional _object defining_ concept, which must be learned separately from components. Notably, Bundles _are not present at runtime_, which is confusing and limiting. * They can completely erase the _defining component_ during Bundle init. For example, `ButtonBundle { style: Style::default(), ..default() }` _makes no mention_ of the `Button` component symbol, which is what makes the Entity a "button"! * They are not capable of representing "dependency inheritance" without completely non-viable / ergonomically crushing nested bundles. This limitation is especially painful in UI scenarios, but it applies to everything across the board. * They introduce a bunch of additional nesting when defining scenes, making them ugly to look at * They introduce component name "stutter": `SomeBundle { component_name: ComponentName::new() }` * They require copious sprinklings of `..default()` when spawning them in Rust code, due to the additional layer of nesting **Required Components** solve this by allowing you to define which components a given component needs, and how to construct those components when they aren't explicitly provided. This is what a `ButtonBundle` looks like with Bundles (the current approach): ```rust #[derive(Component, Default)] struct Button; #[derive(Bundle, Default)] struct ButtonBundle { pub button: Button, pub node: Node, pub style: Style, pub interaction: Interaction, pub focus_policy: FocusPolicy, pub border_color: BorderColor, pub border_radius: BorderRadius, pub image: UiImage, pub transform: Transform, pub global_transform: GlobalTransform, pub visibility: Visibility, pub inherited_visibility: InheritedVisibility, pub view_visibility: ViewVisibility, pub z_index: ZIndex, } commands.spawn(ButtonBundle { style: Style { width: Val::Px(100.0), height: Val::Px(50.0), ..default() }, focus_policy: FocusPolicy::Block, ..default() }) ``` And this is what it looks like with Required Components: ```rust #[derive(Component)] #[require(Node, UiImage)] struct Button; commands.spawn(( Button, Style { width: Val::Px(100.0), height: Val::Px(50.0), ..default() }, FocusPolicy::Block, )); ``` With Required Components, we mention only the most relevant components. Every component required by `Node` (ex: `Style`, `FocusPolicy`, etc) is automatically brought in! ### Efficiency 1. At insertion/spawn time, Required Components (including recursive required components) are initialized and inserted _as if they were manually inserted alongside the given components_. This means that this is maximally efficient: there are no archetype or table moves. 2. Required components are only initialized and inserted if they were not manually provided by the developer. For the code example in the previous section, because `Style` and `FocusPolicy` are inserted manually, they _will not_ be initialized and inserted as part of the required components system. Efficient! 3. The "missing required components _and_ constructors needed for an insertion" are cached in the "archetype graph edge", meaning they aren't computed per-insertion. When a component is inserted, the "missing required components" list is iterated (and that graph edge (AddBundle) is actually already looked up for us during insertion, because we need that for "normal" insert logic too). ### IDE Integration The `#[require(SomeComponent)]` macro has been written in such a way that Rust Analyzer can provide type-inspection-on-hover and `F12` / go-to-definition for required components. ### Custom Constructors The `require` syntax expects a `Default` constructor by default, but it can be overridden with a custom constructor: ```rust #[derive(Component)] #[require( Node, Style(button_style), UiImage )] struct Button; fn button_style() -> Style { Style { width: Val::Px(100.0), ..default() } } ``` ### Multiple Inheritance You may have noticed by now that this behaves a bit like "multiple inheritance". One of the problems that this presents is that it is possible to have duplicate requires for a given type at different levels of the inheritance tree: ```rust #[derive(Component) struct X(usize); #[derive(Component)] #[require(X(x1)) struct Y; fn x1() -> X { X(1) } #[derive(Component)] #[require( Y, X(x2), )] struct Z; fn x2() -> X { X(2) } // What version of X is inserted for Z? commands.spawn(Z); ``` This is allowed (and encouraged), although this doesn't appear to occur much in practice. First: only one version of `X` is initialized and inserted for `Z`. In the case above, I think we can all probably agree that it makes the most sense to use the `x2` constructor for `X`, because `Y`'s `x1` constructor exists "beneath" `Z` in the inheritance hierarchy; `Z`'s constructor is "more specific". The algorithm is simple and predictable: 1. Use all of the constructors (including default constructors) directly defined in the spawned component's require list 2. In the order the requires are defined in `#[require()]`, recursively visit the require list of each of the components in the list (this is a depth Depth First Search). When a constructor is found, it will only be used if one has not already been found. From a user perspective, just think about this as the following: 1. Specifying a required component constructor for `Foo` directly on a spawned component `Bar` will result in that constructor being used (and overriding existing constructors lower in the inheritance tree). This is the classic "inheritance override" behavior people expect. 2. For cases where "multiple inheritance" results in constructor clashes, Components should be listed in "importance order". List a component earlier in the requirement list to initialize its inheritance tree earlier. Required Components _does_ generally result in a model where component values are decoupled from each other at construction time. Notably, some existing Bundle patterns use bundle constructors to initialize multiple components with shared state. I think (in general) moving away from this is necessary: 1. It allows Required Components (and the Scene system more generally) to operate according to simple rules 2. The "do arbitrary init value sharing in Bundle constructors" approach _already_ causes data consistency problems, and those problems would be exacerbated in the context of a Scene/UI system. For cases where shared state is truly necessary, I think we are better served by observers / hooks. 3. If a situation _truly_ needs shared state constructors (which should be rare / generally discouraged), Bundles are still there if they are needed. ## Next Steps * **Require Construct-ed Components**: I have already implemented this (as defined in the [Next Generation Scene / UI Proposal](#14437). However I've removed `Construct` support from this PR, as that has not landed yet. Adding this back in requires relatively minimal changes to the current impl, and can be done as part of a future Construct pr. * **Port Built-in Bundles to Required Components**: This isn't something we should do right away. It will require rethinking our public interfaces, which IMO should be done holistically after the rest of Next Generation Scene / UI lands. I think we should merge this PR first and let people experiment _inside their own code with their own Components_ while we wait for the rest of the new scene system to land. * **_Consider_ Automatic Required Component Removal**: We should evaluate _if_ automatic Required Component removal should be done. Ex: if all components that explicitly require a component are removed, automatically remove that component. This issue has been explicitly deferred in this PR, as I consider the insertion behavior to be desirable on its own (and viable on its own). I am also doubtful that we can find a design that has behavior we actually want. Aka: can we _really_ distinguish between a component that is "only there because it was automatically inserted" and "a component that was necessary / should be kept". See my [discussion response here](#14437 (reply in thread)) for more details. --------- Co-authored-by: Alice Cecile <[email protected]> Co-authored-by: BD103 <[email protected]> Co-authored-by: Pascal Hertleif <[email protected]>
1 parent 5f061ea commit 9cdb915

File tree

10 files changed

+987
-113
lines changed

10 files changed

+987
-113
lines changed

crates/bevy_ecs/macros/src/component.rs

+124-29
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,16 @@
11
use proc_macro::TokenStream;
22
use proc_macro2::{Span, TokenStream as TokenStream2};
3-
use quote::quote;
4-
use syn::{parse_macro_input, parse_quote, DeriveInput, ExprPath, Ident, LitStr, Path, Result};
3+
use quote::{quote, ToTokens};
4+
use std::collections::HashSet;
5+
use syn::{
6+
parenthesized,
7+
parse::Parse,
8+
parse_macro_input, parse_quote,
9+
punctuated::Punctuated,
10+
spanned::Spanned,
11+
token::{Comma, Paren},
12+
DeriveInput, ExprPath, Ident, LitStr, Path, Result,
13+
};
514

615
pub fn derive_event(input: TokenStream) -> TokenStream {
716
let mut ast = parse_macro_input!(input as DeriveInput);
@@ -66,12 +75,55 @@ pub fn derive_component(input: TokenStream) -> TokenStream {
6675
.predicates
6776
.push(parse_quote! { Self: Send + Sync + 'static });
6877

78+
let requires = &attrs.requires;
79+
let mut register_required = Vec::with_capacity(attrs.requires.iter().len());
80+
let mut register_recursive_requires = Vec::with_capacity(attrs.requires.iter().len());
81+
if let Some(requires) = requires {
82+
for require in requires {
83+
let ident = &require.path;
84+
register_recursive_requires.push(quote! {
85+
<#ident as Component>::register_required_components(components, storages, required_components);
86+
});
87+
if let Some(func) = &require.func {
88+
register_required.push(quote! {
89+
required_components.register(components, storages, || { let x: #ident = #func().into(); x });
90+
});
91+
} else {
92+
register_required.push(quote! {
93+
required_components.register(components, storages, <#ident as Default>::default);
94+
});
95+
}
96+
}
97+
}
6998
let struct_name = &ast.ident;
7099
let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl();
71100

101+
let required_component_docs = attrs.requires.map(|r| {
102+
let paths = r
103+
.iter()
104+
.map(|r| format!("[`{}`]", r.path.to_token_stream()))
105+
.collect::<Vec<_>>()
106+
.join(", ");
107+
let doc = format!("Required Components: {paths}. \n\n A component's Required Components are inserted whenever it is inserted. Note that this will also insert the required components _of_ the required components, recursively, in depth-first order.");
108+
quote! {
109+
#[doc = #doc]
110+
}
111+
});
112+
113+
// This puts `register_required` before `register_recursive_requires` to ensure that the constructors of _all_ top
114+
// level components are initialized first, giving them precedence over recursively defined constructors for the same component type
72115
TokenStream::from(quote! {
116+
#required_component_docs
73117
impl #impl_generics #bevy_ecs_path::component::Component for #struct_name #type_generics #where_clause {
74118
const STORAGE_TYPE: #bevy_ecs_path::component::StorageType = #storage;
119+
fn register_required_components(
120+
components: &mut #bevy_ecs_path::component::Components,
121+
storages: &mut #bevy_ecs_path::storage::Storages,
122+
required_components: &mut #bevy_ecs_path::component::RequiredComponents
123+
) {
124+
#(#register_required)*
125+
#(#register_recursive_requires)*
126+
}
75127

76128
#[allow(unused_variables)]
77129
fn register_component_hooks(hooks: &mut #bevy_ecs_path::component::ComponentHooks) {
@@ -86,13 +138,16 @@ pub fn derive_component(input: TokenStream) -> TokenStream {
86138

87139
pub const COMPONENT: &str = "component";
88140
pub const STORAGE: &str = "storage";
141+
pub const REQUIRE: &str = "require";
142+
89143
pub const ON_ADD: &str = "on_add";
90144
pub const ON_INSERT: &str = "on_insert";
91145
pub const ON_REPLACE: &str = "on_replace";
92146
pub const ON_REMOVE: &str = "on_remove";
93147

94148
struct Attrs {
95149
storage: StorageTy,
150+
requires: Option<Punctuated<Require, Comma>>,
96151
on_add: Option<ExprPath>,
97152
on_insert: Option<ExprPath>,
98153
on_replace: Option<ExprPath>,
@@ -105,6 +160,11 @@ enum StorageTy {
105160
SparseSet,
106161
}
107162

163+
struct Require {
164+
path: Path,
165+
func: Option<Path>,
166+
}
167+
108168
// values for `storage` attribute
109169
const TABLE: &str = "Table";
110170
const SPARSE_SET: &str = "SparseSet";
@@ -116,42 +176,77 @@ fn parse_component_attr(ast: &DeriveInput) -> Result<Attrs> {
116176
on_insert: None,
117177
on_replace: None,
118178
on_remove: None,
179+
requires: None,
119180
};
120181

121-
for meta in ast.attrs.iter().filter(|a| a.path().is_ident(COMPONENT)) {
122-
meta.parse_nested_meta(|nested| {
123-
if nested.path.is_ident(STORAGE) {
124-
attrs.storage = match nested.value()?.parse::<LitStr>()?.value() {
125-
s if s == TABLE => StorageTy::Table,
126-
s if s == SPARSE_SET => StorageTy::SparseSet,
127-
s => {
128-
return Err(nested.error(format!(
129-
"Invalid storage type `{s}`, expected '{TABLE}' or '{SPARSE_SET}'.",
130-
)));
131-
}
132-
};
133-
Ok(())
134-
} else if nested.path.is_ident(ON_ADD) {
135-
attrs.on_add = Some(nested.value()?.parse::<ExprPath>()?);
136-
Ok(())
137-
} else if nested.path.is_ident(ON_INSERT) {
138-
attrs.on_insert = Some(nested.value()?.parse::<ExprPath>()?);
139-
Ok(())
140-
} else if nested.path.is_ident(ON_REPLACE) {
141-
attrs.on_replace = Some(nested.value()?.parse::<ExprPath>()?);
142-
Ok(())
143-
} else if nested.path.is_ident(ON_REMOVE) {
144-
attrs.on_remove = Some(nested.value()?.parse::<ExprPath>()?);
145-
Ok(())
182+
let mut require_paths = HashSet::new();
183+
for attr in ast.attrs.iter() {
184+
if attr.path().is_ident(COMPONENT) {
185+
attr.parse_nested_meta(|nested| {
186+
if nested.path.is_ident(STORAGE) {
187+
attrs.storage = match nested.value()?.parse::<LitStr>()?.value() {
188+
s if s == TABLE => StorageTy::Table,
189+
s if s == SPARSE_SET => StorageTy::SparseSet,
190+
s => {
191+
return Err(nested.error(format!(
192+
"Invalid storage type `{s}`, expected '{TABLE}' or '{SPARSE_SET}'.",
193+
)));
194+
}
195+
};
196+
Ok(())
197+
} else if nested.path.is_ident(ON_ADD) {
198+
attrs.on_add = Some(nested.value()?.parse::<ExprPath>()?);
199+
Ok(())
200+
} else if nested.path.is_ident(ON_INSERT) {
201+
attrs.on_insert = Some(nested.value()?.parse::<ExprPath>()?);
202+
Ok(())
203+
} else if nested.path.is_ident(ON_REPLACE) {
204+
attrs.on_replace = Some(nested.value()?.parse::<ExprPath>()?);
205+
Ok(())
206+
} else if nested.path.is_ident(ON_REMOVE) {
207+
attrs.on_remove = Some(nested.value()?.parse::<ExprPath>()?);
208+
Ok(())
209+
} else {
210+
Err(nested.error("Unsupported attribute"))
211+
}
212+
})?;
213+
} else if attr.path().is_ident(REQUIRE) {
214+
let punctuated =
215+
attr.parse_args_with(Punctuated::<Require, Comma>::parse_terminated)?;
216+
for require in punctuated.iter() {
217+
if !require_paths.insert(require.path.to_token_stream().to_string()) {
218+
return Err(syn::Error::new(
219+
require.path.span(),
220+
"Duplicate required components are not allowed.",
221+
));
222+
}
223+
}
224+
if let Some(current) = &mut attrs.requires {
225+
current.extend(punctuated);
146226
} else {
147-
Err(nested.error("Unsupported attribute"))
227+
attrs.requires = Some(punctuated);
148228
}
149-
})?;
229+
}
150230
}
151231

152232
Ok(attrs)
153233
}
154234

235+
impl Parse for Require {
236+
fn parse(input: syn::parse::ParseStream) -> Result<Self> {
237+
let path = input.parse::<Path>()?;
238+
let func = if input.peek(Paren) {
239+
let content;
240+
parenthesized!(content in input);
241+
let func = content.parse::<Path>()?;
242+
Some(func)
243+
} else {
244+
None
245+
};
246+
Ok(Require { path, func })
247+
}
248+
}
249+
155250
fn storage_path(bevy_ecs_path: &Path, ty: StorageTy) -> TokenStream2 {
156251
let storage_type = match ty {
157252
StorageTy::Table => Ident::new("Table", Span::call_site()),

crates/bevy_ecs/macros/src/lib.rs

+13-1
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream {
7777
let mut field_get_component_ids = Vec::new();
7878
let mut field_get_components = Vec::new();
7979
let mut field_from_components = Vec::new();
80+
let mut field_required_components = Vec::new();
8081
for (((i, field_type), field_kind), field) in field_type
8182
.iter()
8283
.enumerate()
@@ -88,6 +89,9 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream {
8889
field_component_ids.push(quote! {
8990
<#field_type as #ecs_path::bundle::Bundle>::component_ids(components, storages, &mut *ids);
9091
});
92+
field_required_components.push(quote! {
93+
<#field_type as #ecs_path::bundle::Bundle>::register_required_components(components, storages, required_components);
94+
});
9195
field_get_component_ids.push(quote! {
9296
<#field_type as #ecs_path::bundle::Bundle>::get_component_ids(components, &mut *ids);
9397
});
@@ -153,6 +157,14 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream {
153157
#(#field_from_components)*
154158
}
155159
}
160+
161+
fn register_required_components(
162+
components: &mut #ecs_path::component::Components,
163+
storages: &mut #ecs_path::storage::Storages,
164+
required_components: &mut #ecs_path::component::RequiredComponents
165+
){
166+
#(#field_required_components)*
167+
}
156168
}
157169

158170
impl #impl_generics #ecs_path::bundle::DynamicBundle for #struct_name #ty_generics #where_clause {
@@ -527,7 +539,7 @@ pub fn derive_resource(input: TokenStream) -> TokenStream {
527539
component::derive_resource(input)
528540
}
529541

530-
#[proc_macro_derive(Component, attributes(component))]
542+
#[proc_macro_derive(Component, attributes(component, require))]
531543
pub fn derive_component(input: TokenStream) -> TokenStream {
532544
component::derive_component(input)
533545
}

crates/bevy_ecs/src/archetype.rs

+24-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
2222
use crate::{
2323
bundle::BundleId,
24-
component::{ComponentId, Components, StorageType},
24+
component::{ComponentId, Components, RequiredComponentConstructor, StorageType},
2525
entity::{Entity, EntityLocation},
2626
observer::Observers,
2727
storage::{ImmutableSparseSet, SparseArray, SparseSet, SparseSetIndex, TableId, TableRow},
@@ -123,10 +123,31 @@ pub(crate) struct AddBundle {
123123
/// For each component iterated in the same order as the source [`Bundle`](crate::bundle::Bundle),
124124
/// indicate if the component is newly added to the target archetype or if it already existed
125125
pub bundle_status: Vec<ComponentStatus>,
126+
/// The set of additional required components that must be initialized immediately when adding this Bundle.
127+
///
128+
/// The initial values are determined based on the provided constructor, falling back to the `Default` trait if none is given.
129+
pub required_components: Vec<RequiredComponentConstructor>,
130+
/// The components added by this bundle. This includes any Required Components that are inserted when adding this bundle.
126131
pub added: Vec<ComponentId>,
132+
/// The components that were explicitly contributed by this bundle, but already existed in the archetype. This _does not_ include any
133+
/// Required Components.
127134
pub existing: Vec<ComponentId>,
128135
}
129136

137+
impl AddBundle {
138+
pub(crate) fn iter_inserted(&self) -> impl Iterator<Item = ComponentId> + '_ {
139+
self.added.iter().chain(self.existing.iter()).copied()
140+
}
141+
142+
pub(crate) fn iter_added(&self) -> impl Iterator<Item = ComponentId> + '_ {
143+
self.added.iter().copied()
144+
}
145+
146+
pub(crate) fn iter_existing(&self) -> impl Iterator<Item = ComponentId> + '_ {
147+
self.existing.iter().copied()
148+
}
149+
}
150+
130151
/// This trait is used to report the status of [`Bundle`](crate::bundle::Bundle) components
131152
/// being added to a given entity, relative to that entity's original archetype.
132153
/// See [`crate::bundle::BundleInfo::write_components`] for more info.
@@ -208,6 +229,7 @@ impl Edges {
208229
bundle_id: BundleId,
209230
archetype_id: ArchetypeId,
210231
bundle_status: Vec<ComponentStatus>,
232+
required_components: Vec<RequiredComponentConstructor>,
211233
added: Vec<ComponentId>,
212234
existing: Vec<ComponentId>,
213235
) {
@@ -216,6 +238,7 @@ impl Edges {
216238
AddBundle {
217239
archetype_id,
218240
bundle_status,
241+
required_components,
219242
added,
220243
existing,
221244
},

0 commit comments

Comments
 (0)