Add insert_batch
and variations (#15702)
# Objective `insert_or_spawn_batch` exists, but a version for just inserting doesn't - Closes #2693 - Closes #8384 - Adopts/supersedes #8600 ## Solution Add `insert_batch`, along with the most common `insert` variations: - `World::insert_batch` - `World::insert_batch_if_new` - `World::try_insert_batch` - `World::try_insert_batch_if_new` - `Commands::insert_batch` - `Commands::insert_batch_if_new` - `Commands::try_insert_batch` - `Commands::try_insert_batch_if_new` ## Testing Added tests, and added a benchmark for `insert_batch`. Performance is slightly better than `insert_or_spawn_batch` when only inserting:  <details> <summary>old benchmark</summary> This was before reworking it to remove the `UnsafeWorldCell`:  </details> --- ## Showcase Usage is the same as `insert_or_spawn_batch`: ``` use bevy_ecs::{entity::Entity, world::World, component::Component}; #[derive(Component)] struct A(&'static str); #[derive(Component, PartialEq, Debug)] struct B(f32); let mut world = World::new(); let entity_a = world.spawn_empty().id(); let entity_b = world.spawn_empty().id(); world.insert_batch([ (entity_a, (A("a"), B(0.0))), (entity_b, (A("b"), B(1.0))), ]); assert_eq!(world.get::<B>(entity_a), Some(&B(0.0))); ```
This commit is contained in:
parent
bdd0af6bfb
commit
3d6b24880e
@ -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();
|
||||
@ -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();
|
||||
}
|
||||
|
@ -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)]
|
||||
|
@ -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.
|
||||
@ -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.
|
||||
///
|
||||
|
@ -2466,6 +2466,309 @@ impl World {
|
||||
}
|
||||
}
|
||||
|
||||
/// For a given batch of ([`Entity`], [`Bundle`]) pairs,
|
||||
/// adds the `Bundle` of components to each `Entity`.
|
||||
/// This is faster than doing equivalent operations one-by-one.
|
||||
///
|
||||
/// 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]`.
|
||||
///
|
||||
/// This will overwrite any previous values of components shared by the `Bundle`.
|
||||
/// See [`World::insert_batch_if_new`] to keep the old values instead.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This function will panic if any of the associated entities do not exist.
|
||||
///
|
||||
/// For the non-panicking version, see [`World::try_insert_batch`].
|
||||
#[track_caller]
|
||||
pub fn insert_batch<I, B>(&mut self, batch: I)
|
||||
where
|
||||
I: IntoIterator,
|
||||
I::IntoIter: Iterator<Item = (Entity, B)>,
|
||||
B: Bundle,
|
||||
{
|
||||
self.insert_batch_with_caller(
|
||||
batch,
|
||||
InsertMode::Replace,
|
||||
#[cfg(feature = "track_change_detection")]
|
||||
Location::caller(),
|
||||
);
|
||||
}
|
||||
|
||||
/// For a given batch of ([`Entity`], [`Bundle`]) pairs,
|
||||
/// adds the `Bundle` of components to each `Entity` without overwriting.
|
||||
/// This is faster than doing equivalent operations one-by-one.
|
||||
///
|
||||
/// 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]`.
|
||||
///
|
||||
/// This is the same as [`World::insert_batch`], but in case of duplicate
|
||||
/// components it will leave the old values instead of replacing them with new ones.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This function will panic if any of the associated entities do not exist.
|
||||
///
|
||||
/// For the non-panicking version, see [`World::try_insert_batch_if_new`].
|
||||
#[track_caller]
|
||||
pub fn insert_batch_if_new<I, B>(&mut self, batch: I)
|
||||
where
|
||||
I: IntoIterator,
|
||||
I::IntoIter: Iterator<Item = (Entity, B)>,
|
||||
B: Bundle,
|
||||
{
|
||||
self.insert_batch_with_caller(
|
||||
batch,
|
||||
InsertMode::Keep,
|
||||
#[cfg(feature = "track_change_detection")]
|
||||
Location::caller(),
|
||||
);
|
||||
}
|
||||
|
||||
/// Split into a new function so we can differentiate the calling location.
|
||||
///
|
||||
/// This can be called by:
|
||||
/// - [`World::insert_batch`]
|
||||
/// - [`World::insert_batch_if_new`]
|
||||
/// - [`Commands::insert_batch`]
|
||||
/// - [`Commands::insert_batch_if_new`]
|
||||
#[inline]
|
||||
pub(crate) fn insert_batch_with_caller<I, B>(
|
||||
&mut self,
|
||||
iter: I,
|
||||
insert_mode: InsertMode,
|
||||
#[cfg(feature = "track_change_detection")] caller: &'static Location,
|
||||
) where
|
||||
I: IntoIterator,
|
||||
I::IntoIter: Iterator<Item = (Entity, B)>,
|
||||
B: Bundle,
|
||||
{
|
||||
self.flush();
|
||||
|
||||
let change_tick = self.change_tick();
|
||||
|
||||
let bundle_id = self
|
||||
.bundles
|
||||
.register_info::<B>(&mut self.components, &mut self.storages);
|
||||
|
||||
struct InserterArchetypeCache<'w> {
|
||||
inserter: BundleInserter<'w>,
|
||||
archetype_id: ArchetypeId,
|
||||
}
|
||||
|
||||
let mut batch = iter.into_iter();
|
||||
|
||||
if let Some((first_entity, first_bundle)) = batch.next() {
|
||||
if let Some(first_location) = self.entities().get(first_entity) {
|
||||
let mut cache = InserterArchetypeCache {
|
||||
// SAFETY: we initialized this bundle_id in `register_info`
|
||||
inserter: unsafe {
|
||||
BundleInserter::new_with_id(
|
||||
self,
|
||||
first_location.archetype_id,
|
||||
bundle_id,
|
||||
change_tick,
|
||||
)
|
||||
},
|
||||
archetype_id: first_location.archetype_id,
|
||||
};
|
||||
// SAFETY: `entity` is valid, `location` matches entity, bundle matches inserter
|
||||
unsafe {
|
||||
cache.inserter.insert(
|
||||
first_entity,
|
||||
first_location,
|
||||
first_bundle,
|
||||
insert_mode,
|
||||
#[cfg(feature = "track_change_detection")]
|
||||
caller,
|
||||
)
|
||||
};
|
||||
|
||||
for (entity, bundle) in batch {
|
||||
if let Some(location) = cache.inserter.entities().get(entity) {
|
||||
if location.archetype_id != cache.archetype_id {
|
||||
cache = InserterArchetypeCache {
|
||||
// SAFETY: we initialized this bundle_id in `register_info`
|
||||
inserter: unsafe {
|
||||
BundleInserter::new_with_id(
|
||||
self,
|
||||
location.archetype_id,
|
||||
bundle_id,
|
||||
change_tick,
|
||||
)
|
||||
},
|
||||
archetype_id: location.archetype_id,
|
||||
}
|
||||
}
|
||||
// SAFETY: `entity` is valid, `location` matches entity, bundle matches inserter
|
||||
unsafe {
|
||||
cache.inserter.insert(
|
||||
entity,
|
||||
location,
|
||||
bundle,
|
||||
insert_mode,
|
||||
#[cfg(feature = "track_change_detection")]
|
||||
caller,
|
||||
)
|
||||
};
|
||||
} else {
|
||||
panic!("error[B0003]: Could not insert a bundle (of type `{}`) for entity {:?} because it doesn't exist in this World. See: https://bevyengine.org/learn/errors/b0003", core::any::type_name::<B>(), entity);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
panic!("error[B0003]: Could not insert a bundle (of type `{}`) for entity {:?} because it doesn't exist in this World. See: https://bevyengine.org/learn/errors/b0003", core::any::type_name::<B>(), first_entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// For a given batch of ([`Entity`], [`Bundle`]) pairs,
|
||||
/// adds the `Bundle` of components to each `Entity`.
|
||||
/// This is faster than doing equivalent operations one-by-one.
|
||||
///
|
||||
/// 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]`.
|
||||
///
|
||||
/// This will overwrite any previous values of components shared by the `Bundle`.
|
||||
/// See [`World::try_insert_batch_if_new`] to keep the old values instead.
|
||||
///
|
||||
/// This function silently fails by ignoring any entities that do not exist.
|
||||
///
|
||||
/// For the panicking version, see [`World::insert_batch`].
|
||||
#[track_caller]
|
||||
pub fn try_insert_batch<I, B>(&mut self, batch: I)
|
||||
where
|
||||
I: IntoIterator,
|
||||
I::IntoIter: Iterator<Item = (Entity, B)>,
|
||||
B: Bundle,
|
||||
{
|
||||
self.try_insert_batch_with_caller(
|
||||
batch,
|
||||
InsertMode::Replace,
|
||||
#[cfg(feature = "track_change_detection")]
|
||||
Location::caller(),
|
||||
);
|
||||
}
|
||||
/// For a given batch of ([`Entity`], [`Bundle`]) pairs,
|
||||
/// adds the `Bundle` of components to each `Entity` without overwriting.
|
||||
/// This is faster than doing equivalent operations one-by-one.
|
||||
///
|
||||
/// 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]`.
|
||||
///
|
||||
/// This is the same as [`World::try_insert_batch`], but in case of duplicate
|
||||
/// components it will leave the old values instead of replacing them with new ones.
|
||||
///
|
||||
/// This function silently fails by ignoring any entities that do not exist.
|
||||
///
|
||||
/// For the panicking version, see [`World::insert_batch_if_new`].
|
||||
#[track_caller]
|
||||
pub fn try_insert_batch_if_new<I, B>(&mut self, batch: I)
|
||||
where
|
||||
I: IntoIterator,
|
||||
I::IntoIter: Iterator<Item = (Entity, B)>,
|
||||
B: Bundle,
|
||||
{
|
||||
self.try_insert_batch_with_caller(
|
||||
batch,
|
||||
InsertMode::Keep,
|
||||
#[cfg(feature = "track_change_detection")]
|
||||
Location::caller(),
|
||||
);
|
||||
}
|
||||
|
||||
/// Split into a new function so we can differentiate the calling location.
|
||||
///
|
||||
/// This can be called by:
|
||||
/// - [`World::try_insert_batch`]
|
||||
/// - [`World::try_insert_batch_if_new`]
|
||||
/// - [`Commands::try_insert_batch`]
|
||||
/// - [`Commands::try_insert_batch_if_new`]
|
||||
#[inline]
|
||||
pub(crate) fn try_insert_batch_with_caller<I, B>(
|
||||
&mut self,
|
||||
iter: I,
|
||||
insert_mode: InsertMode,
|
||||
#[cfg(feature = "track_change_detection")] caller: &'static Location,
|
||||
) where
|
||||
I: IntoIterator,
|
||||
I::IntoIter: Iterator<Item = (Entity, B)>,
|
||||
B: Bundle,
|
||||
{
|
||||
self.flush();
|
||||
|
||||
let change_tick = self.change_tick();
|
||||
|
||||
let bundle_id = self
|
||||
.bundles
|
||||
.register_info::<B>(&mut self.components, &mut self.storages);
|
||||
|
||||
struct InserterArchetypeCache<'w> {
|
||||
inserter: BundleInserter<'w>,
|
||||
archetype_id: ArchetypeId,
|
||||
}
|
||||
|
||||
let mut batch = iter.into_iter();
|
||||
|
||||
if let Some((first_entity, first_bundle)) = batch.next() {
|
||||
if let Some(first_location) = self.entities().get(first_entity) {
|
||||
let mut cache = InserterArchetypeCache {
|
||||
// SAFETY: we initialized this bundle_id in `register_info`
|
||||
inserter: unsafe {
|
||||
BundleInserter::new_with_id(
|
||||
self,
|
||||
first_location.archetype_id,
|
||||
bundle_id,
|
||||
change_tick,
|
||||
)
|
||||
},
|
||||
archetype_id: first_location.archetype_id,
|
||||
};
|
||||
// SAFETY: `entity` is valid, `location` matches entity, bundle matches inserter
|
||||
unsafe {
|
||||
cache.inserter.insert(
|
||||
first_entity,
|
||||
first_location,
|
||||
first_bundle,
|
||||
insert_mode,
|
||||
#[cfg(feature = "track_change_detection")]
|
||||
caller,
|
||||
)
|
||||
};
|
||||
|
||||
for (entity, bundle) in batch {
|
||||
if let Some(location) = cache.inserter.entities().get(entity) {
|
||||
if location.archetype_id != cache.archetype_id {
|
||||
cache = InserterArchetypeCache {
|
||||
// SAFETY: we initialized this bundle_id in `register_info`
|
||||
inserter: unsafe {
|
||||
BundleInserter::new_with_id(
|
||||
self,
|
||||
location.archetype_id,
|
||||
bundle_id,
|
||||
change_tick,
|
||||
)
|
||||
},
|
||||
archetype_id: location.archetype_id,
|
||||
}
|
||||
}
|
||||
// SAFETY: `entity` is valid, `location` matches entity, bundle matches inserter
|
||||
unsafe {
|
||||
cache.inserter.insert(
|
||||
entity,
|
||||
location,
|
||||
bundle,
|
||||
insert_mode,
|
||||
#[cfg(feature = "track_change_detection")]
|
||||
caller,
|
||||
)
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Temporarily removes the requested resource from this [`World`], runs custom user code,
|
||||
/// then re-adds the resource before returning.
|
||||
///
|
||||
|
Loading…
Reference in New Issue
Block a user