diff --git a/crates/bevy_ecs/src/bundle.rs b/crates/bevy_ecs/src/bundle.rs index 3bf6881eb31d2..69026048edb38 100644 --- a/crates/bevy_ecs/src/bundle.rs +++ b/crates/bevy_ecs/src/bundle.rs @@ -22,6 +22,32 @@ use std::{any::TypeId, collections::HashMap}; /// particularly useful for spawning multiple empty entities by using /// [`Commands::spawn_batch`](crate::system::Commands::spawn_batch). /// +/// This trait is not object-safe as the components added by a bundle are cached by the type +/// of the [`Bundle`]. To create a version of a bundle which can be used dynamically, use +/// [`Box`](`ApplicableBundle`): +/// +/// ```rust +/// # use bevy_ecs::bundle::{ApplicableBundle, Bundle}; +/// # #[derive(Bundle)] +/// # struct EnemyShip {} +/// # #[derive(Bundle)] +/// # struct TradePost {} +/// /// # use bevy_ecs::bundle::Bundle; +/// # let hostility = 10; +/// let result: Box = if hostility > 5 { +/// Box::new(TradePost { /* ... */}) +/// } else { +/// Box::new(EnemyShip { /* ... */}) +/// }; +/// # if false { +/// # let commands: bevy_ecs::system::Commands = panic!(); // For type checking +/// commands.spawn_bundle(result); +/// # } +/// ``` +/// +/// Note that this dynamic bundle cannot be nested within other bundles, again because of +/// the caching features. +/// /// # Examples /// /// Typically, you will simply use `#[derive(Bundle)]` when creating your own `Bundle`. Each @@ -75,19 +101,17 @@ use std::{any::TypeId, collections::HashMap}; /// bundle, in the _exact_ order that [`Bundle::get_components`] is called. /// - [`Bundle::from_components`] must call `func` exactly once for each [`ComponentId`] returned by /// [`Bundle::component_ids`]. -pub unsafe trait Bundle: Send + Sync + 'static { - /// Gets this [`Bundle`]'s component ids, in the order of this bundle's [`Component`]s - fn component_ids(components: &mut Components, storages: &mut Storages) -> Vec; - +pub unsafe trait Bundle: ApplicableBundle + Sized { /// Calls `func`, which should return data for each component in the bundle, in the order of /// this bundle's [`Component`]s /// /// # Safety /// Caller must return data for each component in the bundle, in the order of this bundle's /// [`Component`]s - unsafe fn from_components(func: impl FnMut() -> *mut u8) -> Self - where - Self: Sized; + unsafe fn from_components(func: impl FnMut() -> *mut u8) -> Self; + + /// Gets this [`Bundle`]'s component ids, in the order of this bundle's [`Component`]s + fn component_ids(components: &mut Components, storages: &mut Storages) -> Vec; /// Calls `func` on each value, in the order of this bundle's [`Component`]s. This will /// [`std::mem::forget`] the bundle fields, so callers are responsible for dropping the fields @@ -95,6 +119,90 @@ pub unsafe trait Bundle: Send + Sync + 'static { fn get_components(self, func: impl FnMut(*mut u8)); } +mod sealed { + use super::{ApplicableBundle, Bundle}; + + pub trait SealedApplicableBundle {} + impl SealedApplicableBundle for T where T: Bundle {} + impl SealedApplicableBundle for Box {} +} + +/// A bundle that can be added to entities +/// +/// # Safety +/// The bundle returned from `init_bundle_info` must be the same as used for `get_component_box`. +/// +/// This trait is sealed and cannot be implemented outside of `bevy_ecs`, since it +/// is not useful to implement for custom types. +/// +/// However, it is implemented for every type which implements [`Bundle`] +pub unsafe trait ApplicableBundle: + Send + Sync + 'static + sealed::SealedApplicableBundle +{ + fn init_bundle_info<'a>( + &self, + bundles: &'a mut Bundles, + components: &mut Components, + storages: &mut Storages, + ) -> &'a BundleInfo; + // Same requirements as [`Bundle::get_components`] + fn get_components_box(self: Box, func: &mut dyn FnMut(*mut u8)); + // Same requirements as [`Bundle::get_components`] + fn get_components_self(self, func: impl FnMut(*mut u8)) + where + Self: Sized; +} + +unsafe impl ApplicableBundle for T +where + T: Bundle, +{ + fn init_bundle_info<'a>( + &self, + bundles: &'a mut Bundles, + components: &mut Components, + storages: &mut Storages, + ) -> &'a BundleInfo { + bundles.init_info::(components, storages) + } + + fn get_components_box(self: Box, func: &mut dyn FnMut(*mut u8)) { + Self::get_components(*self, func) + } + + fn get_components_self(self, func: impl FnMut(*mut u8)) + where + Self: Sized, + { + Self::get_components(self, func) + } +} + +unsafe impl ApplicableBundle for Box { + fn init_bundle_info<'a>( + &self, + bundles: &'a mut Bundles, + components: &mut Components, + storages: &mut Storages, + ) -> &'a BundleInfo { + let this = &**self; + ::init_bundle_info( + this, bundles, components, storages, + ) + } + + fn get_components_box(self: Box, func: &mut dyn FnMut(*mut u8)) { + ::get_components_box(*self, func) + } + + fn get_components_self(self, mut func: impl FnMut(*mut u8)) + where + Self: Sized, + { + ::get_components_box(self, &mut func) + } +} + macro_rules! tuple_impl { ($($name: ident),*) => { unsafe impl<$($name: Component),*> Bundle for ($($name,)*) { @@ -261,7 +369,7 @@ impl BundleInfo { /// `entity`, `bundle` must match this [`BundleInfo`]'s type #[inline] #[allow(clippy::too_many_arguments)] - unsafe fn write_components( + unsafe fn write_components( &self, table: &mut Table, sparse_sets: &mut SparseSets, @@ -274,7 +382,7 @@ impl BundleInfo { // NOTE: get_components calls this closure on each component in "bundle order". // bundle_info.component_ids are also in "bundle order" let mut bundle_component = 0; - bundle.get_components(|component_ptr| { + bundle.get_components_self(|component_ptr| { let component_id = *self.component_ids.get_unchecked(bundle_component); match self.storage_types[bundle_component] { StorageType::Table => { @@ -412,7 +520,7 @@ impl<'a, 'b> BundleInserter<'a, 'b> { /// `entity` must currently exist in the source archetype for this inserter. `archetype_index` /// must be `entity`'s location in the archetype. `T` must match this [`BundleInfo`]'s type #[inline] - pub unsafe fn insert( + pub unsafe fn insert( &mut self, entity: Entity, archetype_index: usize, @@ -540,7 +648,7 @@ impl<'a, 'b> BundleSpawner<'a, 'b> { /// # Safety /// `entity` must be allocated (but non-existent), `T` must match this [`BundleInfo`]'s type #[inline] - pub unsafe fn spawn_non_existent( + pub unsafe fn spawn_non_existent( &mut self, entity: Entity, bundle: T, @@ -564,7 +672,7 @@ impl<'a, 'b> BundleSpawner<'a, 'b> { /// # Safety /// `T` must match this [`BundleInfo`]'s type #[inline] - pub unsafe fn spawn(&mut self, bundle: T) -> Entity { + pub unsafe fn spawn(&mut self, bundle: T) -> Entity { let entity = self.entities.alloc(); // SAFE: entity is allocated (but non-existent), `T` matches this BundleInfo's type self.spawn_non_existent(entity, bundle); diff --git a/crates/bevy_ecs/src/lib.rs b/crates/bevy_ecs/src/lib.rs index 30d70ca17efab..198b8c994291c 100644 --- a/crates/bevy_ecs/src/lib.rs +++ b/crates/bevy_ecs/src/lib.rs @@ -21,7 +21,7 @@ pub mod prelude { pub use crate::reflect::ReflectComponent; #[doc(hidden)] pub use crate::{ - bundle::Bundle, + bundle::{ApplicableBundle, Bundle}, change_detection::DetectChanges, component::Component, entity::Entity, diff --git a/crates/bevy_ecs/src/system/commands/mod.rs b/crates/bevy_ecs/src/system/commands/mod.rs index 6de329856cf2a..404a408014958 100644 --- a/crates/bevy_ecs/src/system/commands/mod.rs +++ b/crates/bevy_ecs/src/system/commands/mod.rs @@ -1,7 +1,7 @@ mod command_queue; use crate::{ - bundle::Bundle, + bundle::{ApplicableBundle, Bundle}, component::Component, entity::{Entities, Entity}, world::World, @@ -50,7 +50,7 @@ impl<'w, 's> Commands<'w, 's> { /// Creates a new empty [`Entity`] and returns an [`EntityCommands`] builder for it. /// - /// To directly spawn an entity with a [`Bundle`] included, you can use + /// To directly spawn an entity with an [`ApplicableBundle`] included, you can use /// [`spawn_bundle`](Self::spawn_bundle) instead of `.spawn().insert_bundle()`. /// /// See [`World::spawn`] for more details. @@ -104,9 +104,9 @@ impl<'w, 's> Commands<'w, 's> { } } - /// Spawns a [`Bundle`] without pre-allocating an [`Entity`]. The [`Entity`] will be allocated + /// Spawns an [`ApplicableBundle`] without pre-allocating an [`Entity`]. The [`Entity`] will be allocated /// when this [`Command`] is applied. - pub fn spawn_and_forget(&mut self, bundle: impl Bundle) { + pub fn spawn_and_forget(&mut self, bundle: impl ApplicableBundle) { self.queue.push(Spawn { bundle }) } @@ -115,7 +115,7 @@ impl<'w, 's> Commands<'w, 's> { /// This returns an [`EntityCommands`] builder, which enables inserting more components and /// bundles using a "builder pattern". /// - /// Note that `bundle` is a [`Bundle`], which is a collection of components. [`Bundle`] is + /// Note that `bundle` is an [`ApplicableBundle`], which is a collection of components. [`ApplicableBundle`] is /// automatically implemented for tuples of components. You can also create your own bundle /// types by deriving [`derive@Bundle`]. /// @@ -158,7 +158,10 @@ impl<'w, 's> Commands<'w, 's> { /// } /// # example_system.system(); /// ``` - pub fn spawn_bundle<'a, T: Bundle>(&'a mut self, bundle: T) -> EntityCommands<'w, 's, 'a> { + pub fn spawn_bundle<'a, T: ApplicableBundle>( + &'a mut self, + bundle: T, + ) -> EntityCommands<'w, 's, 'a> { let mut e = self.spawn(); e.insert_bundle(bundle); e @@ -376,7 +379,7 @@ impl<'w, 's, 'a> EntityCommands<'w, 's, 'a> { self.entity } - /// Adds a [`Bundle`] of components to the entity. + /// Adds an [`ApplicableBundle`] of components to the entity. /// /// # Example /// @@ -407,7 +410,7 @@ impl<'w, 's, 'a> EntityCommands<'w, 's, 'a> { /// } /// # add_combat_stats_system.system(); /// ``` - pub fn insert_bundle(&mut self, bundle: impl Bundle) -> &mut Self { + pub fn insert_bundle(&mut self, bundle: impl ApplicableBundle) -> &mut Self { self.commands.add(InsertBundle { entity: self.entity, bundle, @@ -550,7 +553,7 @@ pub struct Spawn { impl Command for Spawn where - T: Bundle, + T: ApplicableBundle, { fn write(self, world: &mut World) { world.spawn().insert_bundle(self.bundle); @@ -570,7 +573,7 @@ impl Command for GetOrSpawn { pub struct SpawnBatch where I: IntoIterator, - I::Item: Bundle, + I::Item: ApplicableBundle, { pub bundles_iter: I, } @@ -633,7 +636,7 @@ pub struct InsertBundle { impl Command for InsertBundle where - T: Bundle + 'static, + T: ApplicableBundle + 'static, { fn write(self, world: &mut World) { if let Some(mut entity) = world.get_entity_mut(self.entity) { diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index b85fa2b74f0f2..dd6ed3b9ac7d0 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -1,6 +1,6 @@ use crate::{ archetype::{Archetype, ArchetypeId, Archetypes}, - bundle::{Bundle, BundleInfo}, + bundle::{ApplicableBundle, Bundle, BundleInfo}, change_detection::Ticks, component::{Component, ComponentId, ComponentTicks, Components, StorageType}, entity::{Entities, Entity, EntityLocation}, @@ -189,12 +189,14 @@ impl<'w> EntityMut<'w> { }) } - pub fn insert_bundle(&mut self, bundle: T) -> &mut Self { + pub fn insert_bundle(&mut self, bundle: T) -> &mut Self { let change_tick = self.world.change_tick(); - let bundle_info = self - .world - .bundles - .init_info::(&mut self.world.components, &mut self.world.storages); + let bundle_info = bundle.init_bundle_info( + &mut self.world.bundles, + &mut self.world.components, + &mut self.world.storages, + ); + let mut bundle_inserter = bundle_info.get_bundle_inserter( &mut self.world.entities, &mut self.world.archetypes,