Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Runtime required components #15458

Merged
merged 23 commits into from
Sep 30, 2024
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
115 changes: 115 additions & 0 deletions crates/bevy_app/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -754,6 +754,121 @@ 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.
///
/// [required component]: Component#required-components
///
/// # Panics
///
/// Panics if `R` is already a directly required component for `T`.
///
/// 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>(
Jondolf marked this conversation as resolved.
Show resolved Hide resolved
Jondolf marked this conversation as resolved.
Show resolved Hide resolved
&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.
///
/// [required component]: Component#required-components
///
/// # Panics
///
/// Panics if `R` is already a directly required component for `T`.
///
/// 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
}

/// Returns a reference to the [`World`].
pub fn world(&self) -> &World {
self.main().world()
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.init_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