Skip to content

Add insert_batch and variations #15702

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

Merged
merged 17 commits into from
Oct 13, 2024
20 changes: 19 additions & 1 deletion benches/benches/bevy_ecs/world/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ pub fn insert_commands(criterion: &mut Criterion) {
command_queue.apply(&mut world);
});
});
group.bench_function("insert_batch", |bencher| {
group.bench_function("insert_or_spawn_batch", |bencher| {
let mut world = World::default();
let mut command_queue = CommandQueue::default();
let mut entities = Vec::new();
Expand All @@ -109,6 +109,24 @@ pub fn insert_commands(criterion: &mut Criterion) {
command_queue.apply(&mut world);
});
});
group.bench_function("insert_batch", |bencher| {
let mut world = World::default();
let mut command_queue = CommandQueue::default();
let mut entities = Vec::new();
for _ in 0..entity_count {
entities.push(world.spawn_empty().id());
}

bencher.iter(|| {
let mut commands = Commands::new(&mut command_queue, &world);
let mut values = Vec::with_capacity(entity_count);
for entity in &entities {
values.push((*entity, (Matrix::default(), Vec3::default())));
}
commands.insert_batch(values);
command_queue.apply(&mut world);
});
});

group.finish();
}
Expand Down
128 changes: 128 additions & 0 deletions crates/bevy_ecs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1699,6 +1699,134 @@ mod tests {
);
}

#[test]
fn insert_batch() {
let mut world = World::default();
let e0 = world.spawn(A(0)).id();
let e1 = world.spawn(B(0)).id();

let values = vec![(e0, (A(1), B(0))), (e1, (A(0), B(1)))];

world.insert_batch(values);

assert_eq!(
world.get::<A>(e0),
Some(&A(1)),
"first entity's A component should have been replaced"
);
assert_eq!(
world.get::<B>(e0),
Some(&B(0)),
"first entity should have received B component"
);
assert_eq!(
world.get::<A>(e1),
Some(&A(0)),
"second entity should have received A component"
);
assert_eq!(
world.get::<B>(e1),
Some(&B(1)),
"second entity's B component should have been replaced"
);
}

#[test]
fn insert_batch_same_archetype() {
let mut world = World::default();
let e0 = world.spawn((A(0), B(0))).id();
let e1 = world.spawn((A(0), B(0))).id();
let e2 = world.spawn(B(0)).id();

let values = vec![(e0, (B(1), C)), (e1, (B(2), C)), (e2, (B(3), C))];

world.insert_batch(values);
let mut query = world.query::<(Option<&A>, &B, &C)>();
let component_values = query.get_many(&world, [e0, e1, e2]).unwrap();

assert_eq!(
component_values,
[(Some(&A(0)), &B(1), &C), (Some(&A(0)), &B(2), &C), (None, &B(3), &C)],
"all entities should have had their B component replaced, received C component, and had their A component (or lack thereof) unchanged"
);
}

#[test]
fn insert_batch_if_new() {
let mut world = World::default();
let e0 = world.spawn(A(0)).id();
let e1 = world.spawn(B(0)).id();

let values = vec![(e0, (A(1), B(0))), (e1, (A(0), B(1)))];

world.insert_batch_if_new(values);

assert_eq!(
world.get::<A>(e0),
Some(&A(0)),
"first entity's A component should not have been replaced"
);
assert_eq!(
world.get::<B>(e0),
Some(&B(0)),
"first entity should have received B component"
);
assert_eq!(
world.get::<A>(e1),
Some(&A(0)),
"second entity should have received A component"
);
assert_eq!(
world.get::<B>(e1),
Some(&B(0)),
"second entity's B component should not have been replaced"
);
}

#[test]
fn try_insert_batch() {
let mut world = World::default();
let e0 = world.spawn(A(0)).id();
let e1 = Entity::from_raw(1);

let values = vec![(e0, (A(1), B(0))), (e1, (A(0), B(1)))];

world.try_insert_batch(values);

assert_eq!(
world.get::<A>(e0),
Some(&A(1)),
"first entity's A component should have been replaced"
);
assert_eq!(
world.get::<B>(e0),
Some(&B(0)),
"first entity should have received B component"
);
}

#[test]
fn try_insert_batch_if_new() {
let mut world = World::default();
let e0 = world.spawn(A(0)).id();
let e1 = Entity::from_raw(1);

let values = vec![(e0, (A(1), B(0))), (e1, (A(0), B(1)))];

world.try_insert_batch_if_new(values);

assert_eq!(
world.get::<A>(e0),
Some(&A(0)),
"first entity's A component should not have been replaced"
);
assert_eq!(
world.get::<B>(e0),
Some(&B(0)),
"first entity should have received B component"
);
}

#[test]
fn required_components() {
#[derive(Component)]
Expand Down
192 changes: 192 additions & 0 deletions crates/bevy_ecs/src/system/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -614,6 +614,110 @@ impl<'w, 's> Commands<'w, 's> {
self.queue(insert_or_spawn_batch(bundles_iter));
}

/// Pushes a [`Command`] to the queue for adding a [`Bundle`] type to a batch of [`Entities`](Entity).
///
/// A batch can be any type that implements [`IntoIterator`] containing `(Entity, Bundle)` tuples,
/// such as a [`Vec<(Entity, Bundle)>`] or an array `[(Entity, Bundle); N]`.
///
/// When the command is applied, for each `(Entity, Bundle)` pair in the given batch,
/// the `Bundle` is added to the `Entity`, overwriting any existing components shared by the `Bundle`.
///
/// This method is equivalent to iterating the batch,
/// calling [`entity`](Self::entity) for each pair,
/// and passing the bundle to [`insert`](EntityCommands::insert),
/// but it is faster due to memory pre-allocation.
///
/// # Panics
///
/// This command panics if any of the given entities do not exist.
///
/// For the non-panicking version, see [`try_insert_batch`](Self::try_insert_batch).
#[track_caller]
pub fn insert_batch<I, B>(&mut self, batch: I)
where
I: IntoIterator<Item = (Entity, B)> + Send + Sync + 'static,
B: Bundle,
{
self.queue(insert_batch(batch));
}

/// Pushes a [`Command`] to the queue for adding a [`Bundle`] type to a batch of [`Entities`](Entity).
///
/// A batch can be any type that implements [`IntoIterator`] containing `(Entity, Bundle)` tuples,
/// such as a [`Vec<(Entity, Bundle)>`] or an array `[(Entity, Bundle); N]`.
///
/// When the command is applied, for each `(Entity, Bundle)` pair in the given batch,
/// the `Bundle` is added to the `Entity`, except for any components already present on the `Entity`.
///
/// This method is equivalent to iterating the batch,
/// calling [`entity`](Self::entity) for each pair,
/// and passing the bundle to [`insert_if_new`](EntityCommands::insert_if_new),
/// but it is faster due to memory pre-allocation.
///
/// # Panics
///
/// This command panics if any of the given entities do not exist.
///
/// For the non-panicking version, see [`try_insert_batch_if_new`](Self::try_insert_batch_if_new).
#[track_caller]
pub fn insert_batch_if_new<I, B>(&mut self, batch: I)
where
I: IntoIterator<Item = (Entity, B)> + Send + Sync + 'static,
B: Bundle,
{
self.queue(insert_batch_if_new(batch));
}

/// Pushes a [`Command`] to the queue for adding a [`Bundle`] type to a batch of [`Entities`](Entity).
///
/// A batch can be any type that implements [`IntoIterator`] containing `(Entity, Bundle)` tuples,
/// such as a [`Vec<(Entity, Bundle)>`] or an array `[(Entity, Bundle); N]`.
///
/// When the command is applied, for each `(Entity, Bundle)` pair in the given batch,
/// the `Bundle` is added to the `Entity`, overwriting any existing components shared by the `Bundle`.
///
/// This method is equivalent to iterating the batch,
/// calling [`get_entity`](Self::get_entity) for each pair,
/// and passing the bundle to [`insert`](EntityCommands::insert),
/// but it is faster due to memory pre-allocation.
///
/// This command silently fails by ignoring any entities that do not exist.
///
/// For the panicking version, see [`insert_batch`](Self::insert_batch).
#[track_caller]
pub fn try_insert_batch<I, B>(&mut self, batch: I)
where
I: IntoIterator<Item = (Entity, B)> + Send + Sync + 'static,
B: Bundle,
{
self.queue(try_insert_batch(batch));
}

/// Pushes a [`Command`] to the queue for adding a [`Bundle`] type to a batch of [`Entities`](Entity).
///
/// A batch can be any type that implements [`IntoIterator`] containing `(Entity, Bundle)` tuples,
/// such as a [`Vec<(Entity, Bundle)>`] or an array `[(Entity, Bundle); N]`.
///
/// When the command is applied, for each `(Entity, Bundle)` pair in the given batch,
/// the `Bundle` is added to the `Entity`, except for any components already present on the `Entity`.
///
/// This method is equivalent to iterating the batch,
/// calling [`get_entity`](Self::get_entity) for each pair,
/// and passing the bundle to [`insert_if_new`](EntityCommands::insert_if_new),
/// but it is faster due to memory pre-allocation.
///
/// This command silently fails by ignoring any entities that do not exist.
///
/// For the panicking version, see [`insert_batch_if_new`](Self::insert_batch_if_new).
#[track_caller]
pub fn try_insert_batch_if_new<I, B>(&mut self, batch: I)
where
I: IntoIterator<Item = (Entity, B)> + Send + Sync + 'static,
B: Bundle,
{
self.queue(try_insert_batch_if_new(batch));
}

/// Pushes a [`Command`] to the queue for inserting a [`Resource`] in the [`World`] with an inferred value.
///
/// The inferred value is determined by the [`FromWorld`] trait of the resource.
Expand Down Expand Up @@ -1734,6 +1838,94 @@ where
}
}

/// A [`Command`] that consumes an iterator to add a series of [`Bundles`](Bundle) to a set of entities.
/// If any entities do not exist in the world, this command will panic.
///
/// This is more efficient than inserting the bundles individually.
#[track_caller]
fn insert_batch<I, B>(batch: I) -> impl Command
where
I: IntoIterator<Item = (Entity, B)> + Send + Sync + 'static,
B: Bundle,
{
#[cfg(feature = "track_change_detection")]
let caller = Location::caller();
move |world: &mut World| {
world.insert_batch_with_caller(
batch,
InsertMode::Replace,
#[cfg(feature = "track_change_detection")]
caller,
);
}
}

/// A [`Command`] that consumes an iterator to add a series of [`Bundles`](Bundle) to a set of entities.
/// If any entities do not exist in the world, this command will panic.
///
/// This is more efficient than inserting the bundles individually.
#[track_caller]
fn insert_batch_if_new<I, B>(batch: I) -> impl Command
where
I: IntoIterator<Item = (Entity, B)> + Send + Sync + 'static,
B: Bundle,
{
#[cfg(feature = "track_change_detection")]
let caller = Location::caller();
move |world: &mut World| {
world.insert_batch_with_caller(
batch,
InsertMode::Keep,
#[cfg(feature = "track_change_detection")]
caller,
);
}
}

/// A [`Command`] that consumes an iterator to add a series of [`Bundles`](Bundle) to a set of entities.
/// If any entities do not exist in the world, this command will ignore them.
///
/// This is more efficient than inserting the bundles individually.
#[track_caller]
fn try_insert_batch<I, B>(batch: I) -> impl Command
where
I: IntoIterator<Item = (Entity, B)> + Send + Sync + 'static,
B: Bundle,
{
#[cfg(feature = "track_change_detection")]
let caller = Location::caller();
move |world: &mut World| {
world.try_insert_batch_with_caller(
batch,
InsertMode::Replace,
#[cfg(feature = "track_change_detection")]
caller,
);
}
}

/// A [`Command`] that consumes an iterator to add a series of [`Bundles`](Bundle) to a set of entities.
/// If any entities do not exist in the world, this command will ignore them.
///
/// This is more efficient than inserting the bundles individually.
#[track_caller]
fn try_insert_batch_if_new<I, B>(batch: I) -> impl Command
where
I: IntoIterator<Item = (Entity, B)> + Send + Sync + 'static,
B: Bundle,
{
#[cfg(feature = "track_change_detection")]
let caller = Location::caller();
move |world: &mut World| {
world.try_insert_batch_with_caller(
batch,
InsertMode::Keep,
#[cfg(feature = "track_change_detection")]
caller,
);
}
}

/// A [`Command`] that despawns a specific entity.
/// This will emit a warning if the entity does not exist.
///
Expand Down
Loading