Skip to content

Commit

Permalink
Runtime required components (bevyengine#15458)
Browse files Browse the repository at this point in the history
# Objective

Fixes bevyengine#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 bevyengine#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 bevyengine#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?
  • Loading branch information
Jondolf authored and robtfm committed Oct 4, 2024
1 parent 116c1bc commit 16d3f30
Show file tree
Hide file tree
Showing 7 changed files with 1,151 additions and 21 deletions.
255 changes: 255 additions & 0 deletions crates/bevy_app/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use crate::{
};
pub use bevy_derive::AppLabel;
use bevy_ecs::{
component::RequiredComponentsError,
event::{event_update_system, EventCursor},
intern::Interned,
prelude::*,
Expand Down Expand Up @@ -753,6 +754,260 @@ impl App {
self
}

/// Registers the given component `R` as a [required component] for `T`.
///
/// When `T` is added to an entity, `R` and its own required components will also be added
/// if `R` was not already provided. The [`Default`] `constructor` will be used for the creation of `R`.
/// If a custom constructor is desired, use [`App::register_required_components_with`] instead.
///
/// For the non-panicking version, see [`App::try_register_required_components`].
///
/// Note that requirements must currently be registered before `T` is inserted into the world
/// for the first time. Commonly, this is done in plugins. This limitation may be fixed in the future.
///
/// [required component]: Component#required-components
///
/// # Panics
///
/// Panics if `R` is already a directly required component for `T`, or if `T` has ever been added
/// on an entity before the registration.
///
/// Indirect requirements through other components are allowed. In those cases, any existing requirements
/// will only be overwritten if the new requirement is more specific.
///
/// # Example
///
/// ```
/// # use bevy_app::{App, NoopPluginGroup as MinimalPlugins, Startup};
/// # use bevy_ecs::prelude::*;
/// #[derive(Component)]
/// struct A;
///
/// #[derive(Component, Default, PartialEq, Eq, Debug)]
/// struct B(usize);
///
/// #[derive(Component, Default, PartialEq, Eq, Debug)]
/// struct C(u32);
///
/// # let mut app = App::new();
/// # app.add_plugins(MinimalPlugins).add_systems(Startup, setup);
/// // Register B as required by A and C as required by B.
/// app.register_required_components::<A, B>();
/// app.register_required_components::<B, C>();
///
/// fn setup(mut commands: Commands) {
/// // This will implicitly also insert B and C with their Default constructors.
/// commands.spawn(A);
/// }
///
/// fn validate(query: Query<(&A, &B, &C)>) {
/// let (a, b, c) = query.single();
/// assert_eq!(b, &B(0));
/// assert_eq!(c, &C(0));
/// }
/// # app.update();
/// ```
pub fn register_required_components<T: Component, R: Component + Default>(
&mut self,
) -> &mut Self {
self.world_mut().register_required_components::<T, R>();
self
}

/// Registers the given component `R` as a [required component] for `T`.
///
/// When `T` is added to an entity, `R` and its own required components will also be added
/// if `R` was not already provided. The given `constructor` will be used for the creation of `R`.
/// If a [`Default`] constructor is desired, use [`App::register_required_components`] instead.
///
/// For the non-panicking version, see [`App::try_register_required_components_with`].
///
/// Note that requirements must currently be registered before `T` is inserted into the world
/// for the first time. Commonly, this is done in plugins. This limitation may be fixed in the future.
///
/// [required component]: Component#required-components
///
/// # Panics
///
/// Panics if `R` is already a directly required component for `T`, or if `T` has ever been added
/// on an entity before the registration.
///
/// Indirect requirements through other components are allowed. In those cases, any existing requirements
/// will only be overwritten if the new requirement is more specific.
///
/// # Example
///
/// ```
/// # use bevy_app::{App, NoopPluginGroup as MinimalPlugins, Startup};
/// # use bevy_ecs::prelude::*;
/// #[derive(Component)]
/// struct A;
///
/// #[derive(Component, Default, PartialEq, Eq, Debug)]
/// struct B(usize);
///
/// #[derive(Component, Default, PartialEq, Eq, Debug)]
/// struct C(u32);
///
/// # let mut app = App::new();
/// # app.add_plugins(MinimalPlugins).add_systems(Startup, setup);
/// // Register B and C as required by A and C as required by B.
/// // A requiring C directly will overwrite the indirect requirement through B.
/// app.register_required_components::<A, B>();
/// app.register_required_components_with::<B, C>(|| C(1));
/// app.register_required_components_with::<A, C>(|| C(2));
///
/// fn setup(mut commands: Commands) {
/// // This will implicitly also insert B with its Default constructor and C
/// // with the custom constructor defined by A.
/// commands.spawn(A);
/// }
///
/// fn validate(query: Query<(&A, &B, &C)>) {
/// let (a, b, c) = query.single();
/// assert_eq!(b, &B(0));
/// assert_eq!(c, &C(2));
/// }
/// # app.update();
/// ```
pub fn register_required_components_with<T: Component, R: Component>(
&mut self,
constructor: fn() -> R,
) -> &mut Self {
self.world_mut()
.register_required_components_with::<T, R>(constructor);
self
}

/// Tries to register the given component `R` as a [required component] for `T`.
///
/// When `T` is added to an entity, `R` and its own required components will also be added
/// if `R` was not already provided. The [`Default`] `constructor` will be used for the creation of `R`.
/// If a custom constructor is desired, use [`App::register_required_components_with`] instead.
///
/// For the panicking version, see [`App::register_required_components`].
///
/// Note that requirements must currently be registered before `T` is inserted into the world
/// for the first time. Commonly, this is done in plugins. This limitation may be fixed in the future.
///
/// [required component]: Component#required-components
///
/// # Errors
///
/// Returns a [`RequiredComponentsError`] if `R` is already a directly required component for `T`, or if `T` has ever been added
/// on an entity before the registration.
///
/// Indirect requirements through other components are allowed. In those cases, any existing requirements
/// will only be overwritten if the new requirement is more specific.
///
/// # Example
///
/// ```
/// # use bevy_app::{App, NoopPluginGroup as MinimalPlugins, Startup};
/// # use bevy_ecs::prelude::*;
/// #[derive(Component)]
/// struct A;
///
/// #[derive(Component, Default, PartialEq, Eq, Debug)]
/// struct B(usize);
///
/// #[derive(Component, Default, PartialEq, Eq, Debug)]
/// struct C(u32);
///
/// # let mut app = App::new();
/// # app.add_plugins(MinimalPlugins).add_systems(Startup, setup);
/// // Register B as required by A and C as required by B.
/// app.register_required_components::<A, B>();
/// app.register_required_components::<B, C>();
///
/// // Duplicate registration! This will fail.
/// assert!(app.try_register_required_components::<A, B>().is_err());
///
/// fn setup(mut commands: Commands) {
/// // This will implicitly also insert B and C with their Default constructors.
/// commands.spawn(A);
/// }
///
/// fn validate(query: Query<(&A, &B, &C)>) {
/// let (a, b, c) = query.single();
/// assert_eq!(b, &B(0));
/// assert_eq!(c, &C(0));
/// }
/// # app.update();
/// ```
pub fn try_register_required_components<T: Component, R: Component + Default>(
&mut self,
) -> Result<(), RequiredComponentsError> {
self.world_mut().try_register_required_components::<T, R>()
}

/// Tries to register the given component `R` as a [required component] for `T`.
///
/// When `T` is added to an entity, `R` and its own required components will also be added
/// if `R` was not already provided. The given `constructor` will be used for the creation of `R`.
/// If a [`Default`] constructor is desired, use [`App::register_required_components`] instead.
///
/// For the panicking version, see [`App::register_required_components_with`].
///
/// Note that requirements must currently be registered before `T` is inserted into the world
/// for the first time. Commonly, this is done in plugins. This limitation may be fixed in the future.
///
/// [required component]: Component#required-components
///
/// # Errors
///
/// Returns a [`RequiredComponentsError`] if `R` is already a directly required component for `T`, or if `T` has ever been added
/// on an entity before the registration.
///
/// Indirect requirements through other components are allowed. In those cases, any existing requirements
/// will only be overwritten if the new requirement is more specific.
///
/// # Example
///
/// ```
/// # use bevy_app::{App, NoopPluginGroup as MinimalPlugins, Startup};
/// # use bevy_ecs::prelude::*;
/// #[derive(Component)]
/// struct A;
///
/// #[derive(Component, Default, PartialEq, Eq, Debug)]
/// struct B(usize);
///
/// #[derive(Component, Default, PartialEq, Eq, Debug)]
/// struct C(u32);
///
/// # let mut app = App::new();
/// # app.add_plugins(MinimalPlugins).add_systems(Startup, setup);
/// // Register B and C as required by A and C as required by B.
/// // A requiring C directly will overwrite the indirect requirement through B.
/// app.register_required_components::<A, B>();
/// app.register_required_components_with::<B, C>(|| C(1));
/// app.register_required_components_with::<A, C>(|| C(2));
///
/// // Duplicate registration! Even if the constructors were different, this would fail.
/// assert!(app.try_register_required_components_with::<B, C>(|| C(1)).is_err());
///
/// fn setup(mut commands: Commands) {
/// // This will implicitly also insert B with its Default constructor and C
/// // with the custom constructor defined by A.
/// commands.spawn(A);
/// }
///
/// fn validate(query: Query<(&A, &B, &C)>) {
/// let (a, b, c) = query.single();
/// assert_eq!(b, &B(0));
/// assert_eq!(c, &C(2));
/// }
/// # app.update();
/// ```
pub fn try_register_required_components_with<T: Component, R: Component>(
&mut self,
constructor: fn() -> R,
) -> Result<(), RequiredComponentsError> {
self.world_mut()
.try_register_required_components_with::<T, R>(constructor)
}

/// Returns a reference to the [`World`].
pub fn world(&self) -> &World {
self.main().world()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,11 +59,9 @@ pub struct SystemInfo {
not(feature = "dynamic_linking")
))]
pub mod internal {
use alloc::sync::Arc;
use bevy_ecs::{prelude::ResMut, system::Local};
use std::{
sync::{Arc, Mutex},
time::Instant,
};
use std::{sync::Mutex, time::Instant};

use bevy_app::{App, First, Startup, Update};
use bevy_ecs::system::Resource;
Expand Down
26 changes: 22 additions & 4 deletions crates/bevy_ecs/macros/src/component.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,15 +82,31 @@ pub fn derive_component(input: TokenStream) -> TokenStream {
for require in requires {
let ident = &require.path;
register_recursive_requires.push(quote! {
<#ident as Component>::register_required_components(components, storages, required_components);
<#ident as Component>::register_required_components(
requiree,
components,
storages,
required_components,
inheritance_depth + 1
);
});
if let Some(func) = &require.func {
register_required.push(quote! {
required_components.register(components, storages, || { let x: #ident = #func().into(); x });
components.register_required_components_manual::<Self, #ident>(
storages,
required_components,
|| { let x: #ident = #func().into(); x },
inheritance_depth
);
});
} else {
register_required.push(quote! {
required_components.register(components, storages, <#ident as Default>::default);
components.register_required_components_manual::<Self, #ident>(
storages,
required_components,
<#ident as Default>::default,
inheritance_depth
);
});
}
}
Expand All @@ -117,9 +133,11 @@ pub fn derive_component(input: TokenStream) -> TokenStream {
impl #impl_generics #bevy_ecs_path::component::Component for #struct_name #type_generics #where_clause {
const STORAGE_TYPE: #bevy_ecs_path::component::StorageType = #storage;
fn register_required_components(
requiree: #bevy_ecs_path::component::ComponentId,
components: &mut #bevy_ecs_path::component::Components,
storages: &mut #bevy_ecs_path::storage::Storages,
required_components: &mut #bevy_ecs_path::component::RequiredComponents
required_components: &mut #bevy_ecs_path::component::RequiredComponents,
inheritance_depth: u16,
) {
#(#register_required)*
#(#register_recursive_requires)*
Expand Down
11 changes: 9 additions & 2 deletions crates/bevy_ecs/src/bundle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,14 @@ unsafe impl<C: Component> Bundle for C {
storages: &mut Storages,
required_components: &mut RequiredComponents,
) {
<C as Component>::register_required_components(components, storages, required_components);
let component_id = components.register_component::<C>(storages);
<C as Component>::register_required_components(
component_id,
components,
storages,
required_components,
0,
);
}

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

Expand Down
Loading

0 comments on commit 16d3f30

Please sign in to comment.