Skip to content

Commit f3e8ae0

Browse files
authored
Runtime required components (#15458)
# Objective Fixes #15367. Currently, required components can only be defined through the `require` macro attribute. While this should be used in most cases, there are also several instances where you may want to define requirements at runtime, commonly in plugins. Example use cases: - Require components only if the relevant optional plugins are enabled. For example, a `SleepTimer` component (for physics) is only relevant if the `SleepPlugin` is enabled. - Third party crates can define their own requirements for first party types. For example, "each `Handle<Mesh>` should require my custom rendering data components". This also gets around the orphan rule. - Generic plugins that add marker components based on the existence of other components, like a generic `ColliderPlugin<C: AnyCollider>` that wants to add a `ColliderMarker` component for all types of colliders. - This is currently relevant for the retained render world in #15320. The `ExtractComponentPlugin<C>` should add `SyncToRenderWorld` to all components that should be extracted. This is currently done with observers, which is more expensive than required components, and causes archetype moves. - Replace some built-in components with custom versions. For example, if `GlobalTransform` required `Transform` through `TransformPlugin`, but we wanted to use a `CustomTransform` type, we could replace `TransformPlugin` with our own plugin. (This specific example isn't good, but there are likely better use cases where this may be useful) See #15367 for more in-depth reasoning. ## Solution Add `register_required_components::<T, R>` and `register_required_components_with::<T, R>` methods for `Default` and custom constructors respectively. These methods exist on `App` and `World`. ```rust struct BirdPlugin; impl Plugin for BirdPlugin { fn plugin(app: &mut App) { // Make `Bird` require `Wings` with a `Default` constructor. app.register_required_components::<Bird, Wings>(); // Make `Wings` require `FlapSpeed` with a custom constructor. // Fun fact: Some hummingbirds can flutter their wings 80 times per second! app.register_required_components_with::<Wings, FlapSpeed>(|| FlapSpeed::from_duration(1.0 / 80.0)); } } ``` The custom constructor is a function pointer to match the `require` API, though it could take a raw value too. Requirement inheritance works similarly as with the `require` attribute. If `Bird` required `FlapSpeed` directly, it would take precedence over indirectly requiring it through `Wings`. The same logic applies to all levels of the inheritance tree. Note that registering the same component requirement more than once will panic, similarly to trying to add multiple component hooks of the same type to the same component. This avoids constructor conflicts and confusing ordering issues. ### Implementation Runtime requirements have two additional challenges in comparison to the `require` attribute. 1. The `require` attribute uses recursion and macros with clever ordering to populate hash maps of required components for each component type. The expected semantics are that "more specific" requirements override ones deeper in the inheritance tree. However, at runtime, there is no representation of how "specific" each requirement is. 2. If you first register the requirement `X -> Y`, and later register `Y -> Z`, then `X` should also indirectly require `Z`. However, `Y` itself doesn't know that it is required by `X`, so it's not aware that it should update the list of required components for `X`. My solutions to these problems are: 1. Store the depth in the inheritance tree for each entry of a given component's `RequiredComponents`. This is used to determine how "specific" each requirement is. For `require`-based registration, these depths are computed as part of the recursion. 2. Store and maintain a `required_by` list in each component's `ComponentInfo`, next to `required_components`. For `require`-based registration, these are also added after each registration, as part of the recursion. When calling `register_required_components`, it works as follows: 1. Get the required components of `Foo`, and check that `Bar` isn't already a *direct* requirement. 3. Register `Bar` as a required component for `Foo`, and add `Foo` to the `required_by` list for `Bar`. 4. Find and register all indirect requirements inherited from `Bar`, adding `Foo` to the `required_by` list for each component. 5. Iterate through components that require `Foo`, registering the new inherited requires for them as indirect requirements. The runtime registration is likely slightly more expensive than the `require` version, but it is a one-time cost, and quite negligible in practice, unless projects have hundreds or thousands of runtime requirements. I have not benchmarked this however. This does also add a small amount of extra cost to the `require` attribute for updating `required_by` lists, but I expect it to be very minor. ## Testing I added some tests that are copies of the `require` versions, as well as some tests that are more specific to the runtime implementation. I might add a few more tests though. ## Discussion - Is `register_required_components` a good name? Originally I went for `register_component_requirement` to be consistent with `register_component_hooks`, but the general feature is often referred to as "required components", which is why I changed it to `register_required_components`. - Should we *not* panic for duplicate requirements? If so, should they just be ignored, or should the latest registration overwrite earlier ones? - If we do want to panic for duplicate, conflicting registrations, should we at least not panic if the registrations are *exactly* the same, i.e. same component and same constructor? The current implementation panics for all duplicate direct registrations regardless of the constructor. ## Next Steps - Allow `register_required_components` to take a `Bundle` instead of a single required component. - I could also try to do it in this PR if that would be preferable. - Not directly related, but archetype invariants?
1 parent 66717b0 commit f3e8ae0

File tree

7 files changed

+1151
-21
lines changed

7 files changed

+1151
-21
lines changed

crates/bevy_app/src/app.rs

+255
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use crate::{
44
};
55
pub use bevy_derive::AppLabel;
66
use bevy_ecs::{
7+
component::RequiredComponentsError,
78
event::{event_update_system, EventCursor},
89
intern::Interned,
910
prelude::*,
@@ -753,6 +754,260 @@ impl App {
753754
self
754755
}
755756

757+
/// Registers the given component `R` as a [required component] for `T`.
758+
///
759+
/// When `T` is added to an entity, `R` and its own required components will also be added
760+
/// if `R` was not already provided. The [`Default`] `constructor` will be used for the creation of `R`.
761+
/// If a custom constructor is desired, use [`App::register_required_components_with`] instead.
762+
///
763+
/// For the non-panicking version, see [`App::try_register_required_components`].
764+
///
765+
/// Note that requirements must currently be registered before `T` is inserted into the world
766+
/// for the first time. Commonly, this is done in plugins. This limitation may be fixed in the future.
767+
///
768+
/// [required component]: Component#required-components
769+
///
770+
/// # Panics
771+
///
772+
/// Panics if `R` is already a directly required component for `T`, or if `T` has ever been added
773+
/// on an entity before the registration.
774+
///
775+
/// Indirect requirements through other components are allowed. In those cases, any existing requirements
776+
/// will only be overwritten if the new requirement is more specific.
777+
///
778+
/// # Example
779+
///
780+
/// ```
781+
/// # use bevy_app::{App, NoopPluginGroup as MinimalPlugins, Startup};
782+
/// # use bevy_ecs::prelude::*;
783+
/// #[derive(Component)]
784+
/// struct A;
785+
///
786+
/// #[derive(Component, Default, PartialEq, Eq, Debug)]
787+
/// struct B(usize);
788+
///
789+
/// #[derive(Component, Default, PartialEq, Eq, Debug)]
790+
/// struct C(u32);
791+
///
792+
/// # let mut app = App::new();
793+
/// # app.add_plugins(MinimalPlugins).add_systems(Startup, setup);
794+
/// // Register B as required by A and C as required by B.
795+
/// app.register_required_components::<A, B>();
796+
/// app.register_required_components::<B, C>();
797+
///
798+
/// fn setup(mut commands: Commands) {
799+
/// // This will implicitly also insert B and C with their Default constructors.
800+
/// commands.spawn(A);
801+
/// }
802+
///
803+
/// fn validate(query: Query<(&A, &B, &C)>) {
804+
/// let (a, b, c) = query.single();
805+
/// assert_eq!(b, &B(0));
806+
/// assert_eq!(c, &C(0));
807+
/// }
808+
/// # app.update();
809+
/// ```
810+
pub fn register_required_components<T: Component, R: Component + Default>(
811+
&mut self,
812+
) -> &mut Self {
813+
self.world_mut().register_required_components::<T, R>();
814+
self
815+
}
816+
817+
/// Registers the given component `R` as a [required component] for `T`.
818+
///
819+
/// When `T` is added to an entity, `R` and its own required components will also be added
820+
/// if `R` was not already provided. The given `constructor` will be used for the creation of `R`.
821+
/// If a [`Default`] constructor is desired, use [`App::register_required_components`] instead.
822+
///
823+
/// For the non-panicking version, see [`App::try_register_required_components_with`].
824+
///
825+
/// Note that requirements must currently be registered before `T` is inserted into the world
826+
/// for the first time. Commonly, this is done in plugins. This limitation may be fixed in the future.
827+
///
828+
/// [required component]: Component#required-components
829+
///
830+
/// # Panics
831+
///
832+
/// Panics if `R` is already a directly required component for `T`, or if `T` has ever been added
833+
/// on an entity before the registration.
834+
///
835+
/// Indirect requirements through other components are allowed. In those cases, any existing requirements
836+
/// will only be overwritten if the new requirement is more specific.
837+
///
838+
/// # Example
839+
///
840+
/// ```
841+
/// # use bevy_app::{App, NoopPluginGroup as MinimalPlugins, Startup};
842+
/// # use bevy_ecs::prelude::*;
843+
/// #[derive(Component)]
844+
/// struct A;
845+
///
846+
/// #[derive(Component, Default, PartialEq, Eq, Debug)]
847+
/// struct B(usize);
848+
///
849+
/// #[derive(Component, Default, PartialEq, Eq, Debug)]
850+
/// struct C(u32);
851+
///
852+
/// # let mut app = App::new();
853+
/// # app.add_plugins(MinimalPlugins).add_systems(Startup, setup);
854+
/// // Register B and C as required by A and C as required by B.
855+
/// // A requiring C directly will overwrite the indirect requirement through B.
856+
/// app.register_required_components::<A, B>();
857+
/// app.register_required_components_with::<B, C>(|| C(1));
858+
/// app.register_required_components_with::<A, C>(|| C(2));
859+
///
860+
/// fn setup(mut commands: Commands) {
861+
/// // This will implicitly also insert B with its Default constructor and C
862+
/// // with the custom constructor defined by A.
863+
/// commands.spawn(A);
864+
/// }
865+
///
866+
/// fn validate(query: Query<(&A, &B, &C)>) {
867+
/// let (a, b, c) = query.single();
868+
/// assert_eq!(b, &B(0));
869+
/// assert_eq!(c, &C(2));
870+
/// }
871+
/// # app.update();
872+
/// ```
873+
pub fn register_required_components_with<T: Component, R: Component>(
874+
&mut self,
875+
constructor: fn() -> R,
876+
) -> &mut Self {
877+
self.world_mut()
878+
.register_required_components_with::<T, R>(constructor);
879+
self
880+
}
881+
882+
/// Tries to register the given component `R` as a [required component] for `T`.
883+
///
884+
/// When `T` is added to an entity, `R` and its own required components will also be added
885+
/// if `R` was not already provided. The [`Default`] `constructor` will be used for the creation of `R`.
886+
/// If a custom constructor is desired, use [`App::register_required_components_with`] instead.
887+
///
888+
/// For the panicking version, see [`App::register_required_components`].
889+
///
890+
/// Note that requirements must currently be registered before `T` is inserted into the world
891+
/// for the first time. Commonly, this is done in plugins. This limitation may be fixed in the future.
892+
///
893+
/// [required component]: Component#required-components
894+
///
895+
/// # Errors
896+
///
897+
/// Returns a [`RequiredComponentsError`] if `R` is already a directly required component for `T`, or if `T` has ever been added
898+
/// on an entity before the registration.
899+
///
900+
/// Indirect requirements through other components are allowed. In those cases, any existing requirements
901+
/// will only be overwritten if the new requirement is more specific.
902+
///
903+
/// # Example
904+
///
905+
/// ```
906+
/// # use bevy_app::{App, NoopPluginGroup as MinimalPlugins, Startup};
907+
/// # use bevy_ecs::prelude::*;
908+
/// #[derive(Component)]
909+
/// struct A;
910+
///
911+
/// #[derive(Component, Default, PartialEq, Eq, Debug)]
912+
/// struct B(usize);
913+
///
914+
/// #[derive(Component, Default, PartialEq, Eq, Debug)]
915+
/// struct C(u32);
916+
///
917+
/// # let mut app = App::new();
918+
/// # app.add_plugins(MinimalPlugins).add_systems(Startup, setup);
919+
/// // Register B as required by A and C as required by B.
920+
/// app.register_required_components::<A, B>();
921+
/// app.register_required_components::<B, C>();
922+
///
923+
/// // Duplicate registration! This will fail.
924+
/// assert!(app.try_register_required_components::<A, B>().is_err());
925+
///
926+
/// fn setup(mut commands: Commands) {
927+
/// // This will implicitly also insert B and C with their Default constructors.
928+
/// commands.spawn(A);
929+
/// }
930+
///
931+
/// fn validate(query: Query<(&A, &B, &C)>) {
932+
/// let (a, b, c) = query.single();
933+
/// assert_eq!(b, &B(0));
934+
/// assert_eq!(c, &C(0));
935+
/// }
936+
/// # app.update();
937+
/// ```
938+
pub fn try_register_required_components<T: Component, R: Component + Default>(
939+
&mut self,
940+
) -> Result<(), RequiredComponentsError> {
941+
self.world_mut().try_register_required_components::<T, R>()
942+
}
943+
944+
/// Tries to register the given component `R` as a [required component] for `T`.
945+
///
946+
/// When `T` is added to an entity, `R` and its own required components will also be added
947+
/// if `R` was not already provided. The given `constructor` will be used for the creation of `R`.
948+
/// If a [`Default`] constructor is desired, use [`App::register_required_components`] instead.
949+
///
950+
/// For the panicking version, see [`App::register_required_components_with`].
951+
///
952+
/// Note that requirements must currently be registered before `T` is inserted into the world
953+
/// for the first time. Commonly, this is done in plugins. This limitation may be fixed in the future.
954+
///
955+
/// [required component]: Component#required-components
956+
///
957+
/// # Errors
958+
///
959+
/// Returns a [`RequiredComponentsError`] if `R` is already a directly required component for `T`, or if `T` has ever been added
960+
/// on an entity before the registration.
961+
///
962+
/// Indirect requirements through other components are allowed. In those cases, any existing requirements
963+
/// will only be overwritten if the new requirement is more specific.
964+
///
965+
/// # Example
966+
///
967+
/// ```
968+
/// # use bevy_app::{App, NoopPluginGroup as MinimalPlugins, Startup};
969+
/// # use bevy_ecs::prelude::*;
970+
/// #[derive(Component)]
971+
/// struct A;
972+
///
973+
/// #[derive(Component, Default, PartialEq, Eq, Debug)]
974+
/// struct B(usize);
975+
///
976+
/// #[derive(Component, Default, PartialEq, Eq, Debug)]
977+
/// struct C(u32);
978+
///
979+
/// # let mut app = App::new();
980+
/// # app.add_plugins(MinimalPlugins).add_systems(Startup, setup);
981+
/// // Register B and C as required by A and C as required by B.
982+
/// // A requiring C directly will overwrite the indirect requirement through B.
983+
/// app.register_required_components::<A, B>();
984+
/// app.register_required_components_with::<B, C>(|| C(1));
985+
/// app.register_required_components_with::<A, C>(|| C(2));
986+
///
987+
/// // Duplicate registration! Even if the constructors were different, this would fail.
988+
/// assert!(app.try_register_required_components_with::<B, C>(|| C(1)).is_err());
989+
///
990+
/// fn setup(mut commands: Commands) {
991+
/// // This will implicitly also insert B with its Default constructor and C
992+
/// // with the custom constructor defined by A.
993+
/// commands.spawn(A);
994+
/// }
995+
///
996+
/// fn validate(query: Query<(&A, &B, &C)>) {
997+
/// let (a, b, c) = query.single();
998+
/// assert_eq!(b, &B(0));
999+
/// assert_eq!(c, &C(2));
1000+
/// }
1001+
/// # app.update();
1002+
/// ```
1003+
pub fn try_register_required_components_with<T: Component, R: Component>(
1004+
&mut self,
1005+
constructor: fn() -> R,
1006+
) -> Result<(), RequiredComponentsError> {
1007+
self.world_mut()
1008+
.try_register_required_components_with::<T, R>(constructor)
1009+
}
1010+
7561011
/// Returns a reference to the [`World`].
7571012
pub fn world(&self) -> &World {
7581013
self.main().world()

crates/bevy_diagnostic/src/system_information_diagnostics_plugin.rs

+2-4
Original file line numberDiff line numberDiff line change
@@ -59,11 +59,9 @@ pub struct SystemInfo {
5959
not(feature = "dynamic_linking")
6060
))]
6161
pub mod internal {
62+
use alloc::sync::Arc;
6263
use bevy_ecs::{prelude::ResMut, system::Local};
63-
use std::{
64-
sync::{Arc, Mutex},
65-
time::Instant,
66-
};
64+
use std::{sync::Mutex, time::Instant};
6765

6866
use bevy_app::{App, First, Startup, Update};
6967
use bevy_ecs::system::Resource;

crates/bevy_ecs/macros/src/component.rs

+22-4
Original file line numberDiff line numberDiff line change
@@ -82,15 +82,31 @@ pub fn derive_component(input: TokenStream) -> TokenStream {
8282
for require in requires {
8383
let ident = &require.path;
8484
register_recursive_requires.push(quote! {
85-
<#ident as Component>::register_required_components(components, storages, required_components);
85+
<#ident as Component>::register_required_components(
86+
requiree,
87+
components,
88+
storages,
89+
required_components,
90+
inheritance_depth + 1
91+
);
8692
});
8793
if let Some(func) = &require.func {
8894
register_required.push(quote! {
89-
required_components.register(components, storages, || { let x: #ident = #func().into(); x });
95+
components.register_required_components_manual::<Self, #ident>(
96+
storages,
97+
required_components,
98+
|| { let x: #ident = #func().into(); x },
99+
inheritance_depth
100+
);
90101
});
91102
} else {
92103
register_required.push(quote! {
93-
required_components.register(components, storages, <#ident as Default>::default);
104+
components.register_required_components_manual::<Self, #ident>(
105+
storages,
106+
required_components,
107+
<#ident as Default>::default,
108+
inheritance_depth
109+
);
94110
});
95111
}
96112
}
@@ -117,9 +133,11 @@ pub fn derive_component(input: TokenStream) -> TokenStream {
117133
impl #impl_generics #bevy_ecs_path::component::Component for #struct_name #type_generics #where_clause {
118134
const STORAGE_TYPE: #bevy_ecs_path::component::StorageType = #storage;
119135
fn register_required_components(
136+
requiree: #bevy_ecs_path::component::ComponentId,
120137
components: &mut #bevy_ecs_path::component::Components,
121138
storages: &mut #bevy_ecs_path::storage::Storages,
122-
required_components: &mut #bevy_ecs_path::component::RequiredComponents
139+
required_components: &mut #bevy_ecs_path::component::RequiredComponents,
140+
inheritance_depth: u16,
123141
) {
124142
#(#register_required)*
125143
#(#register_recursive_requires)*

crates/bevy_ecs/src/bundle.rs

+9-2
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,14 @@ unsafe impl<C: Component> Bundle for C {
224224
storages: &mut Storages,
225225
required_components: &mut RequiredComponents,
226226
) {
227-
<C as Component>::register_required_components(components, storages, required_components);
227+
let component_id = components.register_component::<C>(storages);
228+
<C as Component>::register_required_components(
229+
component_id,
230+
components,
231+
storages,
232+
required_components,
233+
0,
234+
);
228235
}
229236

230237
fn get_component_ids(components: &Components, ids: &mut impl FnMut(Option<ComponentId>)) {
@@ -412,7 +419,7 @@ impl BundleInfo {
412419
// This adds required components to the component_ids list _after_ using that list to remove explicitly provided
413420
// components. This ordering is important!
414421
component_ids.push(component_id);
415-
v
422+
v.constructor
416423
})
417424
.collect();
418425

0 commit comments

Comments
 (0)