Repository navigation
Make a dynamically applicable version of Bundle
#3694
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
Changes from all commits
c0e7fc9
4a13a80
0f2cbdd
57caadc
8cfd009
8322460
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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<dyn ApplicableBundle>`](`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<dyn ApplicableBundle> = 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,26 +101,108 @@ 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<ComponentId>; | ||
|
|
||
| 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<ComponentId>; | ||
|
|
||
| /// 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 | ||
| /// if that is desirable. | ||
| fn get_components(self, func: impl FnMut(*mut u8)); | ||
|
DJMcNab marked this conversation as resolved.
Resolved
|
||
| } | ||
|
|
||
| mod sealed { | ||
| use super::{ApplicableBundle, Bundle}; | ||
|
|
||
| pub trait SealedApplicableBundle {} | ||
| impl<T> SealedApplicableBundle for T where T: Bundle {} | ||
| impl SealedApplicableBundle for Box<dyn ApplicableBundle> {} | ||
| } | ||
|
|
||
| /// 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: | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe call this
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Possibly? It is not limited to dynamic use cases; just cases where Normal
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My preference here is either:
The former is nice, because in 99% of cases, users only need the methods from this trait to work with what they need. Removing bundles is typically not very useful. However it's a bit confusing, as deriving Bundle would derive a trait and its super trait. The latter is just playing with synonyms to better reflect intent in terms of things users are familiar with.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I did consider option 1. My concern was that it requires any manual impls of
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. IMO I think that manual impls of
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. One other thing is that the renaming would require a careful audit of documentation etc. as |
||
| 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<Self>, 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<T> 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::<Self>(components, storages) | ||
| } | ||
|
|
||
| fn get_components_box(self: Box<Self>, 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<dyn ApplicableBundle> { | ||
|
DJMcNab marked this conversation as resolved.
Resolved
|
||
| fn init_bundle_info<'a>( | ||
| &self, | ||
| bundles: &'a mut Bundles, | ||
| components: &mut Components, | ||
| storages: &mut Storages, | ||
| ) -> &'a BundleInfo { | ||
| let this = &**self; | ||
| <dyn ApplicableBundle as ApplicableBundle>::init_bundle_info( | ||
| this, bundles, components, storages, | ||
| ) | ||
| } | ||
|
|
||
| fn get_components_box(self: Box<Self>, func: &mut dyn FnMut(*mut u8)) { | ||
| <dyn ApplicableBundle as ApplicableBundle>::get_components_box(*self, func) | ||
| } | ||
|
|
||
| fn get_components_self(self, mut func: impl FnMut(*mut u8)) | ||
| where | ||
| Self: Sized, | ||
| { | ||
| <dyn ApplicableBundle as ApplicableBundle>::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<T: Bundle>( | ||
| unsafe fn write_components<T: ApplicableBundle>( | ||
| &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<T: Bundle>( | ||
| pub unsafe fn insert<T: ApplicableBundle>( | ||
| &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<T: Bundle>( | ||
| pub unsafe fn spawn_non_existent<T: ApplicableBundle>( | ||
| &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<T: Bundle>(&mut self, bundle: T) -> Entity { | ||
| pub unsafe fn spawn<T: ApplicableBundle>(&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); | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.