Skip to content

Commit caa6622

Browse files
authored
bevy_ecs: add untyped methods for inserting components and bundles (#7204)
This MR is a rebased and alternative proposal to #5602 # Objective - #4447 implemented untyped (using component ids instead of generics and TypeId) APIs for inserting/accessing resources and accessing components, but left inserting components for another PR (this one) ## Solution - add `EntityMut::insert_by_id` - split `Bundle` into `DynamicBundle` with `get_components` and `Bundle: DynamicBundle`. This allows the `BundleInserter` machinery to be reused for bundles that can only be written, not read, and have no statically available `ComponentIds` - Compared to the original MR this approach exposes unsafe endpoints and requires the user to manage instantiated `BundleIds`. This is quite easy for the end user to do and does not incur the performance penalty of checking whether component input is correctly provided for the `BundleId`. - This MR does ensure that constructing `BundleId` itself is safe --- ## Changelog - add methods for inserting bundles and components to: `world.entity_mut(entity).insert_by_id`
1 parent 3b51e1c commit caa6622

File tree

3 files changed

+303
-10
lines changed

3 files changed

+303
-10
lines changed

crates/bevy_ecs/macros/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,9 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream {
126126
#(#field_from_components)*
127127
}
128128
}
129+
}
129130

131+
impl #impl_generics #ecs_path::bundle::DynamicBundle for #struct_name #ty_generics #where_clause {
130132
#[allow(unused_variables)]
131133
#[inline]
132134
fn get_components(

crates/bevy_ecs/src/bundle.rs

Lines changed: 103 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
//! This module contains the [`Bundle`] trait and some other helper types.
44
55
pub use bevy_ecs_macros::Bundle;
6-
use bevy_utils::HashSet;
6+
use bevy_utils::{HashMap, HashSet};
77

88
use crate::{
99
archetype::{
@@ -135,10 +135,10 @@ use std::any::TypeId;
135135
/// [`Query`]: crate::system::Query
136136
// Some safety points:
137137
// - [`Bundle::component_ids`] must return the [`ComponentId`] for each component type in the
138-
// bundle, in the _exact_ order that [`Bundle::get_components`] is called.
138+
// bundle, in the _exact_ order that [`DynamicBundle::get_components`] is called.
139139
// - [`Bundle::from_components`] must call `func` exactly once for each [`ComponentId`] returned by
140140
// [`Bundle::component_ids`].
141-
pub unsafe trait Bundle: Send + Sync + 'static {
141+
pub unsafe trait Bundle: DynamicBundle + Send + Sync + 'static {
142142
/// Gets this [`Bundle`]'s component ids, in the order of this bundle's [`Component`]s
143143
#[doc(hidden)]
144144
fn component_ids(
@@ -159,7 +159,10 @@ pub unsafe trait Bundle: Send + Sync + 'static {
159159
// Ensure that the `OwningPtr` is used correctly
160160
F: for<'a> FnMut(&'a mut T) -> OwningPtr<'a>,
161161
Self: Sized;
162+
}
162163

164+
/// The parts from [`Bundle`] that don't require statically knowing the components of the bundle.
165+
pub trait DynamicBundle {
163166
// SAFETY:
164167
// The `StorageType` argument passed into [`Bundle::get_components`] must be correct for the
165168
// component being fetched.
@@ -192,7 +195,9 @@ unsafe impl<C: Component> Bundle for C {
192195
// Safety: The id given in `component_ids` is for `Self`
193196
func(ctx).read()
194197
}
198+
}
195199

200+
impl<C: Component> DynamicBundle for C {
196201
#[inline]
197202
fn get_components(self, func: &mut impl FnMut(StorageType, OwningPtr<'_>)) {
198203
OwningPtr::make(self, |ptr| func(C::Storage::STORAGE_TYPE, ptr));
@@ -203,7 +208,7 @@ macro_rules! tuple_impl {
203208
($($name: ident),*) => {
204209
// SAFETY:
205210
// - `Bundle::component_ids` calls `ids` for each component type in the
206-
// bundle, in the exact order that `Bundle::get_components` is called.
211+
// bundle, in the exact order that `DynamicBundle::get_components` is called.
207212
// - `Bundle::from_components` calls `func` exactly once for each `ComponentId` returned by `Bundle::component_ids`.
208213
// - `Bundle::get_components` is called exactly once for each member. Relies on the above implementation to pass the correct
209214
// `StorageType` into the callback.
@@ -223,7 +228,9 @@ macro_rules! tuple_impl {
223228
// https://doc.rust-lang.org/reference/expressions.html#evaluation-order-of-operands
224229
($(<$name as Bundle>::from_components(ctx, func),)*)
225230
}
231+
}
226232

233+
impl<$($name: Bundle),*> DynamicBundle for ($($name,)*) {
227234
#[allow(unused_variables, unused_mut)]
228235
#[inline(always)]
229236
fn get_components(self, func: &mut impl FnMut(StorageType, OwningPtr<'_>)) {
@@ -376,7 +383,7 @@ impl BundleInfo {
376383
/// `entity`, `bundle` must match this [`BundleInfo`]'s type
377384
#[inline]
378385
#[allow(clippy::too_many_arguments)]
379-
unsafe fn write_components<T: Bundle, S: BundleComponentStatus>(
386+
unsafe fn write_components<T: DynamicBundle, S: BundleComponentStatus>(
380387
&self,
381388
table: &mut Table,
382389
sparse_sets: &mut SparseSets,
@@ -527,7 +534,7 @@ impl<'a, 'b> BundleInserter<'a, 'b> {
527534
/// `entity` must currently exist in the source archetype for this inserter. `archetype_row`
528535
/// must be `entity`'s location in the archetype. `T` must match this [`BundleInfo`]'s type
529536
#[inline]
530-
pub unsafe fn insert<T: Bundle>(
537+
pub unsafe fn insert<T: DynamicBundle>(
531538
&mut self,
532539
entity: Entity,
533540
location: EntityLocation,
@@ -677,7 +684,7 @@ impl<'a, 'b> BundleSpawner<'a, 'b> {
677684
/// # Safety
678685
/// `entity` must be allocated (but non-existent), `T` must match this [`BundleInfo`]'s type
679686
#[inline]
680-
pub unsafe fn spawn_non_existent<T: Bundle>(
687+
pub unsafe fn spawn_non_existent<T: DynamicBundle>(
681688
&mut self,
682689
entity: Entity,
683690
bundle: T,
@@ -712,7 +719,12 @@ impl<'a, 'b> BundleSpawner<'a, 'b> {
712719
#[derive(Default)]
713720
pub struct Bundles {
714721
bundle_infos: Vec<BundleInfo>,
722+
/// Cache static [`BundleId`]
715723
bundle_ids: TypeIdMap<BundleId>,
724+
/// Cache dynamic [`BundleId`] with multiple components
725+
dynamic_bundle_ids: HashMap<Vec<ComponentId>, (BundleId, Vec<StorageType>)>,
726+
/// Cache optimized dynamic [`BundleId`] with single component
727+
dynamic_component_bundle_ids: HashMap<ComponentId, (BundleId, StorageType)>,
716728
}
717729

718730
impl Bundles {
@@ -726,6 +738,7 @@ impl Bundles {
726738
self.bundle_ids.get(&type_id).cloned()
727739
}
728740

741+
/// Initializes a new [`BundleInfo`] for a statically known type.
729742
pub(crate) fn init_info<'a, T: Bundle>(
730743
&'a mut self,
731744
components: &mut Components,
@@ -745,6 +758,64 @@ impl Bundles {
745758
// SAFETY: index either exists, or was initialized
746759
unsafe { self.bundle_infos.get_unchecked(id.0) }
747760
}
761+
762+
/// Initializes a new [`BundleInfo`] for a dynamic [`Bundle`].
763+
///
764+
/// # Panics
765+
///
766+
/// Panics if any of the provided [`ComponentId`]s do not exist in the
767+
/// provided [`Components`].
768+
pub(crate) fn init_dynamic_info(
769+
&mut self,
770+
components: &mut Components,
771+
component_ids: &[ComponentId],
772+
) -> (&BundleInfo, &Vec<StorageType>) {
773+
let bundle_infos = &mut self.bundle_infos;
774+
775+
// Use `raw_entry_mut` to avoid cloning `component_ids` to access `Entry`
776+
let (_, (bundle_id, storage_types)) = self
777+
.dynamic_bundle_ids
778+
.raw_entry_mut()
779+
.from_key(component_ids)
780+
.or_insert_with(|| {
781+
(
782+
Vec::from(component_ids),
783+
initialize_dynamic_bundle(bundle_infos, components, Vec::from(component_ids)),
784+
)
785+
});
786+
787+
// SAFETY: index either exists, or was initialized
788+
let bundle_info = unsafe { bundle_infos.get_unchecked(bundle_id.0) };
789+
790+
(bundle_info, storage_types)
791+
}
792+
793+
/// Initializes a new [`BundleInfo`] for a dynamic [`Bundle`] with single component.
794+
///
795+
/// # Panics
796+
///
797+
/// Panics if the provided [`ComponentId`] does not exist in the provided [`Components`].
798+
pub(crate) fn init_component_info(
799+
&mut self,
800+
components: &mut Components,
801+
component_id: ComponentId,
802+
) -> (&BundleInfo, StorageType) {
803+
let bundle_infos = &mut self.bundle_infos;
804+
let (bundle_id, storage_types) = self
805+
.dynamic_component_bundle_ids
806+
.entry(component_id)
807+
.or_insert_with(|| {
808+
let (id, storage_type) =
809+
initialize_dynamic_bundle(bundle_infos, components, vec![component_id]);
810+
// SAFETY: `storage_type` guaranteed to have length 1
811+
(id, storage_type[0])
812+
});
813+
814+
// SAFETY: index either exists, or was initialized
815+
let bundle_info = unsafe { bundle_infos.get_unchecked(bundle_id.0) };
816+
817+
(bundle_info, *storage_types)
818+
}
748819
}
749820

750821
/// # Safety
@@ -784,3 +855,28 @@ unsafe fn initialize_bundle(
784855

785856
BundleInfo { id, component_ids }
786857
}
858+
859+
/// Asserts that all components are part of [`Components`]
860+
/// and initializes a [`BundleInfo`].
861+
fn initialize_dynamic_bundle(
862+
bundle_infos: &mut Vec<BundleInfo>,
863+
components: &Components,
864+
component_ids: Vec<ComponentId>,
865+
) -> (BundleId, Vec<StorageType>) {
866+
// Assert component existence
867+
let storage_types = component_ids.iter().map(|&id| {
868+
components.get_info(id).unwrap_or_else(|| {
869+
panic!(
870+
"init_dynamic_info called with component id {id:?} which doesn't exist in this world"
871+
)
872+
}).storage_type()
873+
}).collect();
874+
875+
let id = BundleId(bundle_infos.len());
876+
let bundle_info =
877+
// SAFETY: `component_ids` are valid as they were just checked
878+
unsafe { initialize_bundle("<dynamic bundle>", components, component_ids, id) };
879+
bundle_infos.push(bundle_info);
880+
881+
(id, storage_types)
882+
}

0 commit comments

Comments
 (0)