-
-
Notifications
You must be signed in to change notification settings - Fork 3.9k
Improved Spawn APIs and Bundle Effects #17521
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
Conversation
We probably want to roll this up into / right after the relations release note, but I want to make sure this doesn't get missed when writing them. It's also notable enough that folks skimming the milestone + "Needs-Release-Notes" will be interested. |
IMO this is a step in the right direction, making entity spawning constructs more reusable / composable (having only read the PR description). One thing I feel will still be missing after this PR is argument passing ergonomics, since |
Agreed: the key thing there is "dependency injection-flavored access to asset collections". The |
} | ||
|
||
/// The parts from [`Bundle`] that don't require statically knowing the components of the bundle. | ||
pub trait DynamicBundle { | ||
/// An operation on the entity that happens _after_ inserting this bundle. | ||
type Effect: BundleEffect; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will partially break Box<dyn DynamicBundle>
, since you'll need to specify an Effect associated type.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What are the existing and intended use cases for this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I really like this. The end result is great: like a generic, flexible version of the WithChild
component I hacked together, but without the terrible performance and no bizarre type-system gotchas. I prefer the non-macro API, but I don't mind the macro form, and I'm happy to standardize that in the learning material.
Implementation is solid: lots of moving parts, but I don't see any immediate way to simplify them, and the docs are exceptionally clear. Updating the rest of the example should go in follow-up.
This makes impl Bundle
the blessed API for spawning entity collections, which is super exciting because it unblocks widgets over in bevy_ui
. I'm a little nervous about the lack of boxed trait objects hampering our the flexibility of designs there, but the recently added default query filters + entity cloning should make an entity zoo (prefabs?) approach extremely comfortable.
I think there's a third option here - breaking the concept of nested bundles into a set of new traits. The ambiguity arises because we have the following impl: impl<B: Bundle> Bundle for (B1, B2, ..., Bn) {} I believe we could remove this impl, and introduce a new trait: trait BundleList {}
impl<B: Bundle> BundleList for (B1, B2, ..., Bn) {}
impl<B: Bundle> BundleList for B {} This allows us to use the bundles in
We'll also need to adjust the definition of /// An `Effect` or a `Component`, something that can be "spawned" on a given entity
///
/// Currently this will either be:
/// - a `Component`
/// - The `SpawnRelated<T>` struct, returned by `RelationshipTarget::spawn`.
/// `SpawnRelated<T>` has an Effect that spawns the related entities
trait Spawn {}
/// A bundle is a list of `Spawn`, things that can be spawned on an entity
trait Bundle {}
impl<S: Spawn> Bundle for (S1, S2, ..., Sn) {}
impl<S: Spawn> Bundle for S {} Finally, in cases where we want to support nested bundles, we make use of the /// World::spawn creates a new entity with all of the bundles in the BundleList
impl World {
fn spawn(bundle: impl BundleList);
}
/// `Children::spawn` returns a type that implements `Spawn` that isn't a component, but an Effect
/// The returned `impl Spawn` has an Effect that spawns a new child entity for each Bundle in the BundleList
impl Children {
fn spawn(entities: impl BundleList) -> impl Spawn {}
} This set of traits should allow the following to work: world.spawn((
Foo,
(Bar, Baz),
Children::spawn((
(Bar, Children::spawn(Baz)),
(Bar, Children::spawn(Baz))
)),
Observers::spawn((Observer1, Observer2)),
)); |
Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com> Co-authored-by: Gino Valente <49806985+MrGVSV@users.noreply.github.com> Co-authored-by: Emerson Coskey <emerson@coskey.dev>
# Objective Contributes to #18238 Updates the `state/states` example to use the `children!` macro. Note that this example also requires `--features bevy_dev_tools` ## Solution Updates examples to use the Improved Spawning API merged in #17521 ## Testing - Did you test these changes? If so, how? - Opened the examples before and after and verified the same behavior was observed. I did this on Ubuntu 24.04.2 LTS using `--features wayland`. - Are there any parts that need more testing? - Other OS's and features can't hurt, but this is such a small change it shouldn't be a problem. - How can other people (reviewers) test your changes? Is there anything specific they need to know? - Run the examples yourself with and without these changes. - If relevant, what platforms did you test these changes on, and are there any important ones you can't test? - see above --- ## Showcase n/a ## Migration Guide n/a
…API (#18249) # Objective Contributes to #18238 Updates the `text_input` and `virtual_time` examples to use the `children!` macro. I wanted to keep the PR small but chose to do two examples since they both included only a single `with_children` call. ## Solution Updates examples to use the Improved Spawning API merged in #17521 ## Testing - Did you test these changes? If so, how? - Opened the examples before and after and verified the same behavior was observed. I did this on Ubuntu 24.04.2 LTS using `--features wayland`. - Are there any parts that need more testing? - Other OS's and features can't hurt, but this is such a small change it shouldn't be a problem. - How can other people (reviewers) test your changes? Is there anything specific they need to know? - Run the examples yourself with and without these changes. - If relevant, what platforms did you test these changes on, and are there any important ones you can't test? - see above --- ## Showcase n/a ## Migration Guide n/a
# Objective Contributes to #18238 Updates the `render_primitives` example to use the `children!` macro. ## Solution Updates examples to use the Improved Spawning API merged in #17521 ## Testing - Did you test these changes? If so, how? - Opened the examples before and after and verified the same behavior was observed. I did this on Ubuntu 24.04.2 LTS using `--features wayland`. - Are there any parts that need more testing? - Other OS's and features can't hurt, but this is such a small change it shouldn't be a problem. - How can other people (reviewers) test your changes? Is there anything specific they need to know? - Run the examples yourself with and without these changes. - If relevant, what platforms did you test these changes on, and are there any important ones you can't test? - see above --- ## Showcase n/a ## Migration Guide n/a
# Objective Contributes to #18238 Updates the `SteppingPlugin` of the `breakout` example to use the `children!` macro. Note that in order to test this usage you must use `--features bevy_debug_stepping` and hit the back-tick key to enable stepping mode to see the proper text spans rendered. ## Solution Updates examples to use the Improved Spawning API merged in #17521 ## Testing - Did you test these changes? If so, how? - Opened the examples before and after and verified the same behavior was observed. I did this on Ubuntu 24.04.2 LTS using `--features wayland`. - Are there any parts that need more testing? - Other OS's and features can't hurt, but this is such a small change it shouldn't be a problem. - How can other people (reviewers) test your changes? Is there anything specific they need to know? - Run the examples yourself with and without these changes. - If relevant, what platforms did you test these changes on, and are there any important ones you can't test? - see above --- ## Showcase n/a ## Migration Guide n/a
# Objective Contributes to #18238 Updates the `gamepad_viewer`, example to use the `children!` macro. ## Solution Updates examples to use the Improved Spawning API merged in #17521 ## Testing - Did you test these changes? If so, how? - Opened the examples before and after and verified the same behavior was observed. I did this on Ubuntu 24.04.2 LTS using `--features wayland`. - Are there any parts that need more testing? - Other OS's and features can't hurt, but this is such a small change it shouldn't be a problem. - How can other people (reviewers) test your changes? Is there anything specific they need to know? - Run the examples yourself with and without these changes. - If relevant, what platforms did you test these changes on, and are there any important ones you can't test? - see above --- ## Showcase n/a ## Migration Guide n/a
…ro (#18292) # Objective Contributes to #18238 Updates the `custom_transitions` and `sub_states` examples to use the `children!` macro. ## Solution Updates examples to use the Improved Spawning API merged in #17521 ## Testing - Did you test these changes? If so, how? - Opened the examples before and after and verified the same behavior was observed. I did this on Ubuntu 24.04.2 LTS using `--features wayland`. - Are there any parts that need more testing? - Other OS's and features can't hurt, but this is such a small change it shouldn't be a problem. - How can other people (reviewers) test your changes? Is there anything specific they need to know? - Run the examples yourself with and without these changes. - If relevant, what platforms did you test these changes on, and are there any important ones you can't test? - see above --- ## Showcase n/a ## Migration Guide n/a
# Objective Contributes to #18238 Updates the `computed_states`, example to use the `children!` macro. Note that this example requires `--features bevy_dev_tools` to run ## Solution Updates examples to use the Improved Spawning API merged in #17521 ## Testing - Did you test these changes? If so, how? - Opened the examples before and after and verified the same behavior was observed. I did this on Ubuntu 24.04.2 LTS using `--features wayland`. - Are there any parts that need more testing? - Other OS's and features can't hurt, but this is such a small change it shouldn't be a problem. - How can other people (reviewers) test your changes? Is there anything specific they need to know? - Run the examples yourself with and without these changes. - If relevant, what platforms did you test these changes on, and are there any important ones you can't test? - see above --- ## Showcase n/a ## Migration Guide n/a
Please reconsider having macros in the prelude. Bevy users are encouraged to to use the prelude for imports, which essentially makes As a compromise, the macros could be exported both at the |
…se children macro (#18318) # Objective Contributes to #18238 Updates the `sprite_slice`, `spatial_audio_3d` and `spatial_audio_2d` examples to use the `children!` macro. ## Solution Updates examples to use the Improved Spawning API merged in #17521 ## Testing - Did you test these changes? If so, how? - Opened the examples before and after and verified the same behavior was observed. I did this on Ubuntu 24.04.2 LTS using `--features wayland`. - Are there any parts that need more testing? - Other OS's and features can't hurt, but this is such a small change it shouldn't be a problem. - How can other people (reviewers) test your changes? Is there anything specific they need to know? - Run the examples yourself with and without these changes. - If relevant, what platforms did you test these changes on, and are there any important ones you can't test? - see above --- ## Showcase n/a ## Migration Guide n/a
# Objective Contributes to #18238 Updates the `text2d`, example to use the `children!` macro. ~~The SpawnIter usage in this example is maybe not the best. Very open to opinions. I even left one `with_children` that I thought was just much better than any alternative.~~ ## Solution Updates examples to use the Improved Spawning API merged in #17521 ## Testing - Did you test these changes? If so, how? - Opened the examples before and after and verified the same behavior was observed. I did this on Ubuntu 24.04.2 LTS using `--features wayland`. - Are there any parts that need more testing? - Other OS's and features can't hurt, but this is such a small change it shouldn't be a problem. - How can other people (reviewers) test your changes? Is there anything specific they need to know? - Run the examples yourself with and without these changes. - If relevant, what platforms did you test these changes on, and are there any important ones you can't test? - see above --- ## Showcase n/a ## Migration Guide n/a
# Objective Contributes to #18238 Updates the `text2d`, example to use the `children!` macro. I'm not sure I love the SpawnIter usage here, as I feel the `move` keyword in this case is subtle and error prone for those who lose fights with the borrow checker frequently (like me). Feedback very much welcome. ## Solution Updates examples to use the Improved Spawning API merged in #17521 ## Testing - Did you test these changes? If so, how? - Opened the examples before and after and verified the same behavior was observed. I did this on Ubuntu 24.04.2 LTS using `--features wayland`. - Are there any parts that need more testing? - Other OS's and features can't hurt, but this is such a small change it shouldn't be a problem. - How can other people (reviewers) test your changes? Is there anything specific they need to know? - Run the examples yourself with and without these changes. - If relevant, what platforms did you test these changes on, and are there any important ones you can't test? - see above --- ## Showcase n/a ## Migration Guide n/a
…es to use children! macro (#18270) # Objective Contributes to #18238 Updates the `shader_prepass`, `testbed_2d` and `first_person_view_model` examples to use the `children!` macro. I wanted to keep the PR small but chose to do 3 examples since they were all limited in scope ## Solution Updates examples to use the Improved Spawning API merged in #17521 ## Testing - Did you test these changes? If so, how? - Opened the examples before and after and verified the same behavior was observed. I did this on Ubuntu 24.04.2 LTS using `--features wayland`. - Are there any parts that need more testing? - Other OS's and features can't hurt, but this is such a small change it shouldn't be a problem. - How can other people (reviewers) test your changes? Is there anything specific they need to know? - Run the examples yourself with and without these changes. - If relevant, what platforms did you test these changes on, and are there any important ones you can't test? - see above --- ## Showcase n/a ## Migration Guide n/a
…se children macro (#18318) # Objective Contributes to #18238 Updates the `sprite_slice`, `spatial_audio_3d` and `spatial_audio_2d` examples to use the `children!` macro. ## Solution Updates examples to use the Improved Spawning API merged in #17521 ## Testing - Did you test these changes? If so, how? - Opened the examples before and after and verified the same behavior was observed. I did this on Ubuntu 24.04.2 LTS using `--features wayland`. - Are there any parts that need more testing? - Other OS's and features can't hurt, but this is such a small change it shouldn't be a problem. - How can other people (reviewers) test your changes? Is there anything specific they need to know? - Run the examples yourself with and without these changes. - If relevant, what platforms did you test these changes on, and are there any important ones you can't test? - see above --- ## Showcase n/a ## Migration Guide n/a
Thank you to everyone involved with the authoring or reviewing of this PR! This work is relatively important and needs release notes! Head over to bevyengine/bevy-website#1992 if you'd like to help out. |
Objective
A major critique of Bevy at the moment is how boilerplatey it is to compose (and read) entity hierarchies:
There is also currently no good way to statically define and return an entity hierarchy from a function. Instead, people often do this "internally" with a Commands function that returns nothing, making it impossible to spawn the hierarchy in other cases (direct World spawns, ChildSpawner, etc).
Additionally, because this style of API results in creating the hierarchy bits after the initial spawn of a bundle, it causes ECS archetype changes (and often expensive table moves).
Because children are initialized after the fact, we also can't count them to pre-allocate space. This means each time a child inserts itself, it has a high chance of overflowing the currently allocated capacity in the
RelationshipTarget
collection, causing literal worst-case reallocations.We can do better!
Solution
The Bundle trait has been extended to support an optional
BundleEffect
. This is applied directly to World immediately after the Bundle has fully inserted. Note that this is intentionally not done via a deferred Command, which would require repeatedly copying each remaining subtree of the hierarchy to a new command as we walk down the tree (not good performance).This allows us to implement the new
SpawnRelated
trait for allRelationshipTarget
impls, which looks like this in practice:Children::spawn
returnsSpawnRelatedBundle<Children, L: SpawnableList>
, which is aBundle
that insertsChildren
(preallocated to the size of theSpawnableList::size_hint()
).Spawn<B: Bundle>(pub B)
implementsSpawnableList
with a size of 1.SpawnableList
is also implemented for tuples ofSpawnableList
(same general pattern as the Bundle impl).There are currently three built-in
SpawnableList
implementations:We get the benefits of "structured init", but we have nice flexibility where it is required!
Some readers' first instinct might be to try to remove the need for the
Spawn
wrapper. This is impossible in the Rust type system, as a tuple of "child Bundles to be spawned" and a "tuple of Components to be added via a single Bundle" is ambiguous in the Rust type system. There are two ways to resolve that ambiguity:For the single-entity spawn cases,
Children::spawn_one
does also exist, which removes the need for the wrapper:This works for all Relationships
This API isn't just for
Children
/ChildOf
relationships. It works for any relationship type, and they can be mixed and matched!Macros
While
Spawn
is necessary to satisfy the type system, we can remove the need to express it via macros. The example above can be expressed more succinctly using the newchildren![X]
macro, which internally producesChildren::spawn(Spawn(X))
:There is also a
related!
macro, which is a generic version of thechildren!
macro that supports any relationship type:Returning Hierarchies from Functions
Thanks to these changes, the following pattern is now possible:
Additional Changes and Notes
Bundle::from_components
has been split out intoBundleFromComponents::from_components
, enabling us to implementBundle
for types that cannot be "taken" from the ECS (such as the newSpawnRelatedBundle
).NoBundleEffect
trait (which implementsBundleEffect
) is implemented for empty tuples (and tuples of empty tuples), which allows us to constrain APIs to only accept bundles that do not have effects. This is critical because the current batch spawn APIs cannot efficiently apply BundleEffects in their current form (as doing so in-place could invalidate the cached raw pointers). We could consider allocating a buffer of the effects to be applied later, but that does have performance implications that could offset the balance and value of the batched APIs (and would likely require some refactors to the underlying code). I've decided to be conservative here. We can consider relaxing that requirement on those APIs later, but that should be done in a followup imo.children!
form whenever possible (and for cases that require things like SpawnIter, use the raw APIs).Relationship
to spawn (ex:ChildOf::spawn(Foo)
) instead of theRelationshipTarget
(ex:Children::spawn(Spawn(Foo))
)?". That would allow us to remove theSpawn
wrapper. I've explicitly chosen to disallow this pattern.Bundle::Effect
has the ability to create significant weirdness. Things inBundle
position look like components. For exampleworld.spawn((Foo, ChildOf::spawn(Bar)))
looks and reads like Foo is a child of Bar.ChildOf
is in Foo's "component position" but it is not a component on Foo. This is a huge problem. Now thatBundle::Effect
exists, we should be very principled about keeping the "weird and unintuitive behavior" to a minimum. Things that read like components _should be the components they appear to be".Remaining Work
Next Steps
children!
where possible and rawSpawn
/SpawnIter
/SpawnWith
where the flexibility of the raw API is required.Migration Guide
Existing spawn patterns will continue to work as expected.
Manual Bundle implementations now require a
BundleEffect
associated type. Exisiting bundles would have no bundle effect, so use()
. AdditionallyBundle::from_components
has been moved to the newBundleFromComponents
trait.