Skip to content

Rework required components #20110

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

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
49 changes: 9 additions & 40 deletions crates/bevy_ecs/macros/src/component.rs
Original file line number Diff line number Diff line change
Expand Up @@ -237,41 +237,17 @@ pub fn derive_component(input: TokenStream) -> TokenStream {

let requires = &attrs.requires;
let mut register_required = Vec::with_capacity(attrs.requires.iter().len());
let mut register_recursive_requires = Vec::with_capacity(attrs.requires.iter().len());
if let Some(requires) = requires {
for require in requires {
let ident = &require.path;
register_recursive_requires.push(quote! {
<#ident as #bevy_ecs_path::component::Component>::register_required_components(
requiree,
components,
required_components,
inheritance_depth + 1,
recursion_check_stack
);
let constructor = match &require.func {
Some(func) => quote! { || { let x: #ident = (#func)().into(); x } },
None => quote! { <#ident as Default>::default },
};
register_required.push(quote! {
// SAFETY: we registered all components with the same instance of components.
unsafe { required_components.register::<#ident>(components, #constructor) };
});
match &require.func {
Some(func) => {
register_required.push(quote! {
components.register_required_components_manual::<Self, #ident>(
required_components,
|| { let x: #ident = (#func)().into(); x },
inheritance_depth,
recursion_check_stack
);
});
}
None => {
register_required.push(quote! {
components.register_required_components_manual::<Self, #ident>(
required_components,
<#ident as Default>::default,
inheritance_depth,
recursion_check_stack
);
});
}
}
}
}
let struct_name = &ast.ident;
Expand Down Expand Up @@ -311,19 +287,12 @@ 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;
type Mutability = #mutable_type;
fn register_required_components(
requiree: #bevy_ecs_path::component::ComponentId,
unsafe fn register_required_components(
_requiree: #bevy_ecs_path::component::ComponentId,
components: &mut #bevy_ecs_path::component::ComponentsRegistrator,
required_components: &mut #bevy_ecs_path::component::RequiredComponents,
inheritance_depth: u16,
recursion_check_stack: &mut #bevy_ecs_path::__macro_exports::Vec<#bevy_ecs_path::component::ComponentId>
) {
#bevy_ecs_path::component::enforce_no_required_components_recursion(components, recursion_check_stack);
let self_id = components.register_component::<Self>();
recursion_check_stack.push(self_id);
#(#register_required)*
#(#register_recursive_requires)*
recursion_check_stack.pop();
}

#on_add
Expand Down
87 changes: 49 additions & 38 deletions crates/bevy_ecs/src/bundle/info.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
use alloc::{boxed::Box, vec, vec::Vec};
use bevy_platform::collections::{HashMap, HashSet};
use bevy_platform::{
collections::{HashMap, HashSet},
hash::FixedHasher,
};
use bevy_ptr::OwningPtr;
use bevy_utils::TypeIdMap;
use core::{any::TypeId, ptr::NonNull};
use indexmap::{IndexMap, IndexSet};

use crate::{
archetype::{Archetype, BundleComponentStatus, ComponentStatus},
bundle::{Bundle, DynamicBundle},
change_detection::MaybeLocation,
component::{
ComponentId, Components, ComponentsRegistrator, RequiredComponentConstructor,
RequiredComponents, StorageType, Tick,
ComponentId, Components, ComponentsRegistrator, RequiredComponentConstructor, StorageType,
Tick,
},
entity::Entity,
query::DebugCheckedUnwrap as _,
Expand Down Expand Up @@ -59,6 +63,7 @@ pub enum InsertMode {
/// [`World`]: crate::world::World
pub struct BundleInfo {
pub(super) id: BundleId,

/// The list of all components contributed by the bundle (including Required Components). This is in
/// the order `[EXPLICIT_COMPONENTS][REQUIRED_COMPONENTS]`
///
Expand All @@ -67,9 +72,10 @@ pub struct BundleInfo {
/// must have its storage initialized (i.e. columns created in tables, sparse set created),
/// and the range (0..`explicit_components_len`) must be in the same order as the source bundle
/// type writes its components in.
pub(super) component_ids: Vec<ComponentId>,
pub(super) required_components: Vec<RequiredComponentConstructor>,
pub(super) explicit_components_len: usize,
pub(super) contributed_components: Vec<ComponentId>,

/// The list of constructors for all required components indirectly contributed by this bundle.
pub(super) required_component_constructors: Vec<RequiredComponentConstructor>,
}

impl BundleInfo {
Expand All @@ -86,11 +92,13 @@ impl BundleInfo {
mut component_ids: Vec<ComponentId>,
id: BundleId,
) -> BundleInfo {
let explicit_component_ids = component_ids
.iter()
.copied()
.collect::<IndexSet<_, FixedHasher>>();

// check for duplicates
let mut deduped = component_ids.clone();
deduped.sort_unstable();
deduped.dedup();
if deduped.len() != component_ids.len() {
if explicit_component_ids.len() != component_ids.len() {
// TODO: Replace with `Vec::partition_dedup` once https://github.com/rust-lang/rust/issues/54279 is stabilized
let mut seen = <HashSet<_>>::default();
let mut dups = Vec::new();
Expand All @@ -111,41 +119,39 @@ impl BundleInfo {
panic!("Bundle {bundle_type_name} has duplicate components: {names:?}");
}

// handle explicit components
let explicit_components_len = component_ids.len();
let mut required_components = RequiredComponents::default();
for component_id in component_ids.iter().copied() {
let mut depth_first_components = IndexMap::<_, _, FixedHasher>::default();
for &component_id in &component_ids {
// SAFETY: caller has verified that all ids are valid
let info = unsafe { components.get_info_unchecked(component_id) };
required_components.merge(info.required_components());

for (&required_id, required_component) in &info.required_components().all {
depth_first_components
.entry(required_id)
.or_insert_with(|| required_component.clone());
}

storages.prepare_component(info);
}
required_components.remove_explicit_components(&component_ids);

// handle required components
let required_components = required_components
.0
.into_iter()
.map(|(component_id, v)| {
// Safety: These ids came out of the passed `components`, so they must be valid.
let info = unsafe { components.get_info_unchecked(component_id) };
storages.prepare_component(info);
// 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.constructor

let required_components = depth_first_components
.iter()
.filter(|&(required_id, _)| !explicit_component_ids.contains(required_id))
.inspect(|&(&required_id, _)| {
// SAFETY: These ids came out of the passed `components`, so they must be valid.
storages.prepare_component(unsafe { components.get_info_unchecked(required_id) });
component_ids.push(required_id);
})
.collect();
.map(|(_, required_component)| required_component.constructor.clone())
.collect::<Vec<_>>();

// SAFETY: The caller ensures that component_ids:
// - is valid for the associated world
// - has had its storage initialized
// - is in the same order as the source bundle type
BundleInfo {
id,
component_ids,
required_components,
explicit_components_len,
contributed_components: component_ids,
required_component_constructors: required_components,
}
}

Expand All @@ -155,27 +161,32 @@ impl BundleInfo {
self.id
}

/// Returns the length of the explicit components part of the [`contributed_components`](Self::contributed_components) list.
pub(super) fn explicit_components_len(&self) -> usize {
self.contributed_components.len() - self.required_component_constructors.len()
}

/// Returns the [ID](ComponentId) of each component explicitly defined in this bundle (ex: Required Components are excluded).
///
/// For all components contributed by this bundle (including Required Components), see [`BundleInfo::contributed_components`]
#[inline]
pub fn explicit_components(&self) -> &[ComponentId] {
&self.component_ids[0..self.explicit_components_len]
&self.contributed_components[0..self.explicit_components_len()]
}

/// Returns the [ID](ComponentId) of each Required Component needed by this bundle. This _does not include_ Required Components that are
/// explicitly provided by the bundle.
#[inline]
pub fn required_components(&self) -> &[ComponentId] {
&self.component_ids[self.explicit_components_len..]
&self.contributed_components[self.explicit_components_len()..]
}

/// Returns the [ID](ComponentId) of each component contributed by this bundle. This includes Required Components.
///
/// For only components explicitly defined in this bundle, see [`BundleInfo::explicit_components`]
#[inline]
pub fn contributed_components(&self) -> &[ComponentId] {
&self.component_ids
&self.contributed_components
}

/// Returns an iterator over the [ID](ComponentId) of each component explicitly defined in this bundle (ex: this excludes Required Components).
Expand All @@ -190,7 +201,7 @@ impl BundleInfo {
/// To iterate only components explicitly defined in this bundle, see [`BundleInfo::iter_explicit_components`]
#[inline]
pub fn iter_contributed_components(&self) -> impl Iterator<Item = ComponentId> + Clone + '_ {
self.component_ids.iter().copied()
self.contributed_components().iter().copied()
}

/// Returns an iterator over the [ID](ComponentId) of each Required Component needed by this bundle. This _does not include_ Required Components that are
Expand Down Expand Up @@ -236,7 +247,7 @@ impl BundleInfo {
// bundle_info.component_ids are also in "bundle order"
let mut bundle_component = 0;
let after_effect = bundle.get_components(&mut |storage_type, component_ptr| {
let component_id = *self.component_ids.get_unchecked(bundle_component);
let component_id = *self.contributed_components.get_unchecked(bundle_component);
// SAFETY: bundle_component is a valid index for this bundle
let status = unsafe { bundle_component_status.get_status(bundle_component) };
match storage_type {
Expand Down
4 changes: 2 additions & 2 deletions crates/bevy_ecs/src/bundle/insert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -433,7 +433,7 @@ impl BundleInfo {
}
let mut new_table_components = Vec::new();
let mut new_sparse_set_components = Vec::new();
let mut bundle_status = Vec::with_capacity(self.explicit_components_len);
let mut bundle_status = Vec::with_capacity(self.explicit_components_len());
let mut added_required_components = Vec::new();
let mut added = Vec::new();
let mut existing = Vec::new();
Expand All @@ -457,7 +457,7 @@ impl BundleInfo {

for (index, component_id) in self.iter_required_components().enumerate() {
if !current_archetype.contains(component_id) {
added_required_components.push(self.required_components[index].clone());
added_required_components.push(self.required_component_constructors[index].clone());
added.push(component_id);
// SAFETY: component_id exists
let component_info = unsafe { components.get_info_unchecked(component_id) };
Expand Down
2 changes: 1 addition & 1 deletion crates/bevy_ecs/src/bundle/spawner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ impl<'w> BundleSpawner<'w> {
table,
sparse_sets,
&SpawnBundleStatus,
bundle_info.required_components.iter(),
bundle_info.required_component_constructors.iter(),
entity,
table_row,
self.change_tick,
Expand Down
22 changes: 18 additions & 4 deletions crates/bevy_ecs/src/component/info.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use alloc::{borrow::Cow, vec::Vec};
use bevy_platform::{collections::HashSet, sync::PoisonError};
use bevy_platform::{hash::FixedHasher, sync::PoisonError};
use bevy_ptr::OwningPtr;
#[cfg(feature = "bevy_reflect")]
use bevy_reflect::Reflect;
Expand All @@ -10,6 +10,7 @@ use core::{
fmt::Debug,
mem::needs_drop,
};
use indexmap::IndexSet;

use crate::{
archetype::ArchetypeFlags,
Expand All @@ -30,7 +31,10 @@ pub struct ComponentInfo {
pub(super) descriptor: ComponentDescriptor,
pub(super) hooks: ComponentHooks,
pub(super) required_components: RequiredComponents,
pub(super) required_by: HashSet<ComponentId>,
/// The set of components that require this components.
/// Invariant: this is stored in a depth-first order, that is components are stored after the components
/// that they depend on.
pub(super) required_by: IndexSet<ComponentId, FixedHasher>,
}

impl ComponentInfo {
Expand Down Expand Up @@ -505,6 +509,13 @@ impl Components {
.and_then(|info| info.as_mut().map(|info| &mut info.hooks))
}

#[inline]
pub(crate) fn get_required_components(&self, id: ComponentId) -> Option<&RequiredComponents> {
self.components
.get(id.0)
.and_then(|info| info.as_ref().map(|info| &info.required_components))
}

#[inline]
pub(crate) fn get_required_components_mut(
&mut self,
Expand All @@ -516,7 +527,10 @@ impl Components {
}

#[inline]
pub(crate) fn get_required_by(&self, id: ComponentId) -> Option<&HashSet<ComponentId>> {
pub(crate) fn get_required_by(
&self,
id: ComponentId,
) -> Option<&IndexSet<ComponentId, FixedHasher>> {
self.components
.get(id.0)
.and_then(|info| info.as_ref().map(|info| &info.required_by))
Expand All @@ -526,7 +540,7 @@ impl Components {
pub(crate) fn get_required_by_mut(
&mut self,
id: ComponentId,
) -> Option<&mut HashSet<ComponentId>> {
) -> Option<&mut IndexSet<ComponentId, FixedHasher>> {
self.components
.get_mut(id.0)
.and_then(|info| info.as_mut().map(|info| &mut info.required_by))
Expand Down
9 changes: 5 additions & 4 deletions crates/bevy_ecs/src/component/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ use crate::{
system::{Local, SystemParam},
world::{FromWorld, World},
};
use alloc::vec::Vec;
pub use bevy_ecs_macros::Component;
use core::{fmt::Debug, marker::PhantomData, ops::Deref};

Expand Down Expand Up @@ -523,12 +522,14 @@ pub trait Component: Send + Sync + 'static {
}

/// Registers required components.
fn register_required_components(
///
/// # Safety
///
/// - `_required_components` must only contain components valid in `_components`.
unsafe fn register_required_components(
_component_id: ComponentId,
_components: &mut ComponentsRegistrator,
_required_components: &mut RequiredComponents,
_inheritance_depth: u16,
_recursion_check_stack: &mut Vec<ComponentId>,
) {
}

Expand Down
Loading
Loading