Open alice-i-cecile opened 2 years ago
One existing tool for this is DynamicScene
s, and the bevy::scene::Entity
type they contain (which is not the Entity
you're thinking about ;-)
I don't see what's impractical about making Bundle
object safe. We'd need to add a NonNull<Self>
version of to_components
, but Bundle
is already unsafe.
Your description of something to add arbitrary Components
is exactly what a Bundle
is
This would be great for what I'm working on now. I have a simple prefab system set up where you can specify components in a scene-like way but with way less boilerplate:
BuiltIn (
Transform {
translation : Vec3 {
x: 15.0, y: 10.5,
},
},
Visible,
Draw,
)
If we could iterate over bundles in a generic way I could just drop them right in.
I poked at it a little but I know next to nothing about the unsafe land of rust so sadly beyond me to implement:
Talking it over, the more aggressive direction would be to attempt to replace the Bundle
trait entirely:
type Bundle = IntoIter<Item = impl Component>
Component
for Box<dyn Component>
This allows Vec<Box<dyn Component>>
and the like to just be a bundle of components directly
Along with that, you'd:
BundleId
and BundleInfo
to something elseEffectively, this would serve as an alternative to #2974. It would also make issues like #792 much easier.
Also related to #2157, which is another way we could make bundles more flexible in more constrained ways. I don't immediately see any direct overlap, but I'll chew on it.
- cut the bundle tuple macro, it adds significant compile time, is unidiomatic
Please don't, a tuple doesn't add any allocations, unlike a potential vec solution. Also, it doesn't really add compile time. Just checked, bevy_ecs with up to 16 part tuples (current config) takes 8.8s, bumping the bundle tuple macro to 30 takes 8.9 seconds, lowering it to 1 takes 9.0 seconds. Plus, is it unidiomatic when it's how the rust std lib is built?
Please don't, a tuple doesn't add any allocations, unlike a potential vec solution.
Deal: you've convinced me on this point here (and other have explained more use cases on Discord).
Have you tried using heterogenous lists (I know them from the shapeless lib in Scala)? There seems to be a frunk
crate in Rust that implement those.
Or it should be possible to add an extend
method to tuples that would extend a tuple with an additional element: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=54ac9ca50c1ac210dde4752868d58514
I don't see what's impractical about making Bundle object safe. We'd need to add a NonNull
version of to_components, but Bundle is already unsafe.
This is my preferred path forward: if we can get this working, we should just use this approach.
More detailed error messages:
|
117 | fn component_ids(components: &mut Components, storages: &mut Storages) -> Vec<ComponentId>;
| ^^^^^^^^^^^^^ the trait cannot be made into an object because associated function `component_ids` has no `self` parameter
...
132 | fn get_components(self, func: impl FnMut(*mut u8));
| ^^^^^^^^^^^^^^ the trait cannot be made into an object because method `get_components` has generic type parameters
Also note you cannot have a method take self
by value from dyn Bundle
as there isn't a way to call such a method. But the compiler won't complain as it is treated kind of like where Self : Sized
, doing it makes it impossible to call but you can define it as long as you don't try to use it.
pub unsafe trait Bundle: Send + Sync + 'static {
/// Gets this [Bundle]'s component ids, in the order of this bundle's Components
fn component_ids(components: &mut Components, storages: &mut Storages) -> Vec<ComponentId>
where
Self: Sized;
/// Trait object form of component_ids
fn component_ids_trait(&self, components: &mut Components, storages: &mut Storages) -> Vec<ComponentId>;
/// Calls `func`, which should return data for each component in the bundle, in the order of
/// this bundle's Components
///
/// # Safety
/// Caller must return data for each component in the bundle, in the order of this bundle's
/// Components
unsafe fn from_components(func: impl FnMut() -> *mut u8) -> Self
where
Self: Sized;
/// Calls `func` on each value, in the order of this bundle's Components. This will
/// "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))
where
Self: Sized;
/// Trait object form of get_components
fn get_components_trait(&mut self, func: &mut dyn FnMut(*mut u8));
}
I expect everything else to be mechanical but I am against the duplication here (even if they are codegened) unfortunately a quick pass didn't lead to an obvious removal of component_ids
given it is used without an instance (by design of course). But maybe this will prove useful to someone else so figured I would pass it along.
As a heads up, I have a WIP prototype for this (coauthored with @alercah) on https://github.com/alice-i-cecile/bevy/tree/dyn-bundle-mad-science. Feel free to poke at it, steal from it, shamelessly clean it up and submit a PR or so on. I'm a little busy, but the initial direction here is promising.
After #12311, Component is no longer a object-safe trait. That PR might need to be reverted if this is something we want to support.
I've been using Bevy and Rust for a few days and have bumped into exactly this issue. I'm wondering if there are any new potential solutions.
I am seeing bad performance upon receiving a lot of components from game server that need to be added to entity for the first time for hundreds of entities.
As there could be multiple possible combinations of components for entity I can not use bundles.
Pseudocode:
for entity_data in entities_data_from_server {
let entity = get_entity_or_spawn(...., entity_data.entity_id);
for component in entity_data.components {
match component {
ComponentA(comp) => { entity.insert(comp); }
ComponentB(comp) => { entity.insert(comp); }
....
}
}
}
As much as I understand:
Each new component insert (directly to world or through command) results in entity being moved from one archetype to another. It would be good if all these operations could be grouped together in dynamic ComponentSet
data structure that could be inserted into entity in one operation. In this case no unneeded intermediate archetypes would be initialized, no redundant data moves of entity between different archetypes would be needed.
It looks like something similar can be done with unsafe: https://docs.rs/bevy/latest/bevy/ecs/prelude/struct.EntityWorldMut.html#method.insert_by_ids It would be nice if there would be a safe variant that is easy to use or bundles with optional components (https://github.com/bevyengine/bevy/issues/2157#issuecomment-2376302215) .
What problem does this solve or what need does it fill?
Working with dynamic collections of components (as might be used in advanced spawning patterns) is very painful at the moment: every straightforward path is blocked due to missing impls. This cannot be fixed in end user code, as they do not own the
Component
trait, or the common wrapper types likeVec
andBox
.What solution would you like?
Component
forBox<dyn Component>
, which uses the box's data as the component.Rc
andArc
(and any other common smart pointers).Bundle
forIntoIter<Item=Component>
, which combined with the above would allow us to use structs likeVec<Box<dyn Component>>
as bundles.Notes:
DynClone
for this pattern to actually work, which would probably force aDynComponent
trait layer.What alternative(s) have you considered?
Other possible approaches:
Bundle
itself object-safe, obviating 3. I don't think this will be feasible.Component
for&Component
, since we can get these out of our boxes. This doesn't work nicely though, due to the need to clone components out of a standard storage.1515 would provide an alternate path to some of the use cases.
Some very limited workarounds may exist using commands (and entity commands) in certain use cases.