How do I spawn an entity with a dynamic set of components? #11409
-
When working with Bevy, I often find myself wanting to spawn entities within a single function that may not have the same set of components. Suppose we have: use bevy::prelude::*;
#[derive(Component)]
struct A;
#[derive(Component)]
struct B;
#[allow(dead_code)]
enum ComponentStrategy {
A,
B,
AAndB,
} Then we want to do: /// This is the obvious way to do it,
/// but simply doesn't work due to mismatched arm types
#[test]
fn impl_bundle_return_type() {
fn spawn_bundle_naive(strategy: &ComponentStrategy) -> impl Bundle {
match strategy {
ComponentStrategy::A => (A,),
ComponentStrategy::B => (B,),
ComponentStrategy::AAndB => (A, B),
}
}
} How do I fix this?! This question comes up regularly, and I have yet to see a good answer for it. So I made a repo exploring this problem. My answers are taken directly from there. Feel free to steal code from it directly: it's CC0. This has been discussed in issue format in #2157 and #3227: while I still think we should add direct support for this to Bevy, here are some solutions that work today (in Bevy 0.12). |
Beta Was this translation helpful? Give feedback.
Replies: 6 comments
-
If we follow the compiler error, we can try to use #[test]
fn impl_boxed_bundle_return_type() {
fn spawn_bundle_naive(strategy: &ComponentStrategy) -> Box<dyn Bundle> {
match strategy {
ComponentStrategy::A => Box::new((A,)),
ComponentStrategy::B => Box::new((B,)),
ComponentStrategy::AAndB => Box::new((A, B)),
}
}
} |
Beta Was this translation helpful? Give feedback.
-
We can brute force this, by operating on the world directly. This works, but requires blocking access. #[test]
fn exclusive_world_access() {
fn spawn_bundle_exclusive_world_access(
world: &mut World,
strategy: ComponentStrategy,
) -> Entity {
let mut entity_world_mut = world.spawn_empty();
match strategy {
ComponentStrategy::A => entity_world_mut.insert(A).id(),
ComponentStrategy::B => entity_world_mut.insert(B).id(),
ComponentStrategy::AAndB => entity_world_mut.insert(A).insert(B).id(),
}
}
let mut world = World::new();
spawn_bundle_exclusive_world_access(&mut world, ComponentStrategy::A);
spawn_bundle_exclusive_world_access(&mut world, ComponentStrategy::B);
spawn_bundle_exclusive_world_access(&mut world, ComponentStrategy::AAndB);
} |
Beta Was this translation helpful? Give feedback.
-
We could also pass in a mutable reference to This can be used in an ordinary system, but requires plumbing through the #[test]
fn ref_mut_commands() {
use bevy::ecs::system::RunSystemOnce;
fn spawn_bundle_ref_mut_commands(
commands: &mut Commands,
strategy: ComponentStrategy,
) -> Entity {
let mut entity_commands = commands.spawn_empty();
match strategy {
ComponentStrategy::A => entity_commands.insert(A).id(),
ComponentStrategy::B => entity_commands.insert(B).id(),
ComponentStrategy::AAndB => entity_commands.insert(A).insert(B).id(),
}
}
let mut world = World::new();
fn my_system(mut commands: Commands) {
spawn_bundle_ref_mut_commands(&mut commands, ComponentStrategy::A);
spawn_bundle_ref_mut_commands(&mut commands, ComponentStrategy::B);
spawn_bundle_ref_mut_commands(&mut commands, ComponentStrategy::AAndB);
}
world.run_system_once(my_system);
} |
Beta Was this translation helpful? Give feedback.
-
We can restrict the power of the This requires a bit more boilerplate, as you must spawn your entity first, but is more restrictive. #[test]
fn ref_mut_entity_commands() {
use bevy::ecs::system::{EntityCommands, RunSystemOnce};
fn spawn_bundle_ref_mut_entity_commands<'arg, 'w, 's, 'a>(
commands: &'arg mut EntityCommands<'w, 's, 'a>,
strategy: ComponentStrategy,
) -> &'arg mut EntityCommands<'w, 's, 'a> {
match strategy {
ComponentStrategy::A => commands.insert(A),
ComponentStrategy::B => commands.insert(B),
ComponentStrategy::AAndB => commands.insert(A).insert(B),
}
}
let mut world = World::new();
fn my_system(mut commands: Commands) {
let mut entity_a = commands.spawn_empty();
spawn_bundle_ref_mut_entity_commands(&mut entity_a, ComponentStrategy::A);
let mut entity_b = commands.spawn_empty();
spawn_bundle_ref_mut_entity_commands(&mut entity_b, ComponentStrategy::B);
let mut entity_a_and_b = commands.spawn_empty();
spawn_bundle_ref_mut_entity_commands(&mut entity_a_and_b, ComponentStrategy::AAndB);
}
world.run_system_once(my_system);
} |
Beta Was this translation helpful? Give feedback.
-
We can clean up the #[test]
fn entity_commands_simple_extension() {
use bevy::ecs::system::{EntityCommands, RunSystemOnce};
trait EntityCommandsExt {
fn spawn_bundle_by_strategy(&mut self, strategy: ComponentStrategy) -> &mut Self;
}
impl EntityCommandsExt for EntityCommands<'_, '_, '_> {
fn spawn_bundle_by_strategy(&mut self, strategy: ComponentStrategy) -> &mut Self {
match strategy {
ComponentStrategy::A => self.insert(A),
ComponentStrategy::B => self.insert(B),
ComponentStrategy::AAndB => self.insert(A).insert(B),
}
}
}
let mut world = World::new();
fn my_system(mut commands: Commands) {
let mut entity_a = commands.spawn_empty();
entity_a.spawn_bundle_by_strategy(ComponentStrategy::A);
let mut entity_b = commands.spawn_empty();
entity_b.spawn_bundle_by_strategy(ComponentStrategy::B);
let mut entity_a_and_b = commands.spawn_empty();
entity_a_and_b.spawn_bundle_by_strategy(ComponentStrategy::AAndB);
}
world.run_system_once(my_system);
} |
Beta Was this translation helpful? Give feedback.
-
However, the direct Instead, let's pass in a closure into our extension method, which controls which builder we're using. This approach has an elaborate setup, but very flexible and quite comfortable to use. Critically, by setting your #[test]
fn entity_commands_closure_extension() {
use bevy::ecs::system::{EntityCommands, RunSystemOnce};
trait EntityCommandsExt<Config> {
fn spawn_dynamic_bundle(
&mut self,
config: Config,
f: impl FnOnce(Config, &mut Self),
) -> &mut Self;
}
impl<Config> EntityCommandsExt<Config> for EntityCommands<'_, '_, '_> {
fn spawn_dynamic_bundle(
&mut self,
config: Config,
f: impl FnOnce(Config, &mut Self),
) -> &mut Self {
f(config, self);
self
}
}
fn my_dynamic_builder(strategy: ComponentStrategy, commands: &mut EntityCommands<'_, '_, '_>) {
match strategy {
ComponentStrategy::A => commands.insert(A),
ComponentStrategy::B => commands.insert(B),
ComponentStrategy::AAndB => commands.insert(A).insert(B),
};
}
let mut world = World::new();
fn my_system(mut commands: Commands) {
let mut entity_a = commands.spawn_empty();
entity_a.spawn_dynamic_bundle(ComponentStrategy::A, my_dynamic_builder);
let mut entity_b = commands.spawn_empty();
entity_b.spawn_dynamic_bundle(ComponentStrategy::B, my_dynamic_builder);
let mut entity_a_and_b = commands.spawn_empty();
entity_a_and_b.spawn_dynamic_bundle(ComponentStrategy::AAndB, my_dynamic_builder);
}
world.run_system_once(my_system);
} |
Beta Was this translation helpful? Give feedback.
However, the direct
EntityCommands
extension trait isn't very flexible: now we need a different extension method for every place we want to use this pattern!Instead, let's pass in a closure into our extension method, which controls which builder we're using.
This approach has an elaborate setup, but very flexible and quite comfortable to use. Critically, by setting your
Config
type to something more complex, we can pass in things likeResMut<Assets<Mesh>>
orHandle<ColorMaterial>
.