Skip to content

Remove insert_or_spawn function family #18148

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

22 changes: 0 additions & 22 deletions benches/benches/bevy_ecs/world/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,28 +92,6 @@ pub fn insert_commands(criterion: &mut Criterion) {
command_queue.apply(&mut world);
});
});
group.bench_function("insert_or_spawn_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())));
}
#[expect(
deprecated,
reason = "This needs to be supported for now, and therefore still needs the benchmark."
)]
commands.insert_or_spawn_batch(values);
command_queue.apply(&mut world);
});
});
group.bench_function("insert_batch", |bencher| {
let mut world = World::default();
let mut command_queue = CommandQueue::default();
Expand Down
91 changes: 0 additions & 91 deletions crates/bevy_ecs/src/entity/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -243,16 +243,6 @@ impl Hash for Entity {
}
}

#[deprecated(
since = "0.16.0",
note = "This is exclusively used with the now deprecated `Entities::alloc_at_without_replacement`."
)]
pub(crate) enum AllocAtWithoutReplacement {
Exists(EntityLocation),
DidNotExist,
ExistsWithWrongGeneration,
}

impl Entity {
/// Construct an [`Entity`] from a raw `index` value and a non-zero `generation` value.
/// Ensure that the generation value is never greater than `0x7FFF_FFFF`.
Expand Down Expand Up @@ -690,87 +680,6 @@ impl Entities {
}
}

/// Allocate a specific entity ID, overwriting its generation.
///
/// Returns the location of the entity currently using the given ID, if any. Location should be
/// written immediately.
#[deprecated(
since = "0.16.0",
note = "This can cause extreme performance problems when used after freeing a large number of entities and requesting an arbitrary entity. See #18054 on GitHub."
)]
pub fn alloc_at(&mut self, entity: Entity) -> Option<EntityLocation> {
self.verify_flushed();

let loc = if entity.index() as usize >= self.meta.len() {
self.pending
.extend((self.meta.len() as u32)..entity.index());
let new_free_cursor = self.pending.len() as IdCursor;
*self.free_cursor.get_mut() = new_free_cursor;
self.meta
.resize(entity.index() as usize + 1, EntityMeta::EMPTY);
None
} else if let Some(index) = self.pending.iter().position(|item| *item == entity.index()) {
self.pending.swap_remove(index);
let new_free_cursor = self.pending.len() as IdCursor;
*self.free_cursor.get_mut() = new_free_cursor;
None
} else {
Some(mem::replace(
&mut self.meta[entity.index() as usize].location,
EntityMeta::EMPTY.location,
))
};

self.meta[entity.index() as usize].generation = entity.generation;

loc
}

/// Allocate a specific entity ID, overwriting its generation.
///
/// Returns the location of the entity currently using the given ID, if any.
#[deprecated(
since = "0.16.0",
note = "This can cause extreme performance problems when used after freeing a large number of entities and requesting an arbitrary entity. See #18054 on GitHub."
)]
#[expect(
deprecated,
reason = "We need to support `AllocAtWithoutReplacement` for now."
)]
pub(crate) fn alloc_at_without_replacement(
&mut self,
entity: Entity,
) -> AllocAtWithoutReplacement {
self.verify_flushed();

let result = if entity.index() as usize >= self.meta.len() {
self.pending
.extend((self.meta.len() as u32)..entity.index());
let new_free_cursor = self.pending.len() as IdCursor;
*self.free_cursor.get_mut() = new_free_cursor;
self.meta
.resize(entity.index() as usize + 1, EntityMeta::EMPTY);
AllocAtWithoutReplacement::DidNotExist
} else if let Some(index) = self.pending.iter().position(|item| *item == entity.index()) {
self.pending.swap_remove(index);
let new_free_cursor = self.pending.len() as IdCursor;
*self.free_cursor.get_mut() = new_free_cursor;
AllocAtWithoutReplacement::DidNotExist
} else {
let current_meta = &self.meta[entity.index() as usize];
if current_meta.location.archetype_id == ArchetypeId::INVALID {
AllocAtWithoutReplacement::DidNotExist
} else if current_meta.generation == entity.generation {
AllocAtWithoutReplacement::Exists(current_meta.location)
} else {
return AllocAtWithoutReplacement::ExistsWithWrongGeneration;
}
};

self.meta[entity.index() as usize].generation = entity.generation;
result
}

/// Destroy an entity, allowing it to be reused.
///
/// Must not be called while reserved entities are awaiting `flush()`.
Expand Down
92 changes: 0 additions & 92 deletions crates/bevy_ecs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,6 @@ mod tests {
use core::{
any::TypeId,
marker::PhantomData,
num::NonZero,
sync::atomic::{AtomicUsize, Ordering},
};
use std::sync::Mutex;
Expand Down Expand Up @@ -1696,97 +1695,6 @@ mod tests {
assert_eq!(0, query_min_size![(&A, &B), Or<(Changed<A>, Changed<B>)>]);
}

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

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

#[expect(
deprecated,
reason = "This needs to be supported for now, and therefore still needs the test."
)]
world.insert_or_spawn_batch(values).unwrap();

assert_eq!(
world.get::<A>(e0),
Some(&A(0)),
"existing component was preserved"
);
assert_eq!(
world.get::<B>(e0),
Some(&B(0)),
"pre-existing entity received correct B component"
);
assert_eq!(
world.get::<B>(e1),
Some(&B(1)),
"new entity was spawned and received correct B component"
);
assert_eq!(
world.get::<C>(e0),
Some(&C),
"pre-existing entity received C component"
);
assert_eq!(
world.get::<C>(e1),
Some(&C),
"new entity was spawned and received C component"
);
}

#[test]
fn insert_or_spawn_batch_invalid() {
let mut world = World::default();
let e0 = world.spawn(A(0)).id();
let e1 = Entity::from_raw(1);
let e2 = world.spawn_empty().id();
let invalid_e2 =
Entity::from_raw_and_generation(e2.index(), NonZero::<u32>::new(2).unwrap());

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

#[expect(
deprecated,
reason = "This needs to be supported for now, and therefore still needs the test."
)]
let result = world.insert_or_spawn_batch(values);

assert_eq!(
result,
Err(vec![invalid_e2]),
"e2 failed to be spawned or inserted into"
);

assert_eq!(
world.get::<A>(e0),
Some(&A(0)),
"existing component was preserved"
);
assert_eq!(
world.get::<B>(e0),
Some(&B(0)),
"pre-existing entity received correct B component"
);
assert_eq!(
world.get::<B>(e1),
Some(&B(1)),
"new entity was spawned and received correct B component"
);
assert_eq!(
world.get::<C>(e0),
Some(&C),
"pre-existing entity received C component"
);
assert_eq!(
world.get::<C>(e1),
Some(&C),
"new entity was spawned and received C component"
);
}

#[test]
fn insert_batch() {
let mut world = World::default();
Expand Down
54 changes: 1 addition & 53 deletions crates/bevy_ecs/src/system/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,11 @@ pub use parallel_scope::*;

use alloc::boxed::Box;
use core::marker::PhantomData;
use log::error;

use crate::{
self as bevy_ecs,
bundle::{Bundle, InsertMode, NoBundleEffect},
change_detection::{MaybeLocation, Mut},
change_detection::Mut,
component::{Component, ComponentId, Mutable},
entity::{Entities, Entity, EntityClonerBuilder, EntityDoesNotExistError},
error::{ignore, warn, BevyError, CommandWithEntity, ErrorContext, HandleError},
Expand Down Expand Up @@ -624,57 +623,6 @@ impl<'w, 's> Commands<'w, 's> {
}
}

/// Pushes a [`Command`] to the queue for creating entities, if needed,
/// and for adding a bundle to each entity.
///
/// `bundles_iter` is a type that can be converted into an ([`Entity`], [`Bundle`]) iterator
/// (it can also be a collection).
///
/// When the command is applied,
/// for each (`Entity`, `Bundle`) pair in the given `bundles_iter`,
/// the `Entity` is spawned, if it does not exist already.
/// Then, the `Bundle` is added to the entity.
///
/// This method is equivalent to iterating `bundles_iter`,
/// calling [`spawn`](Self::spawn) for each bundle,
/// and passing it to [`insert`](EntityCommands::insert),
/// but it is faster due to memory pre-allocation.
///
/// # Note
///
/// Spawning a specific `entity` value is rarely the right choice. Most apps should use [`Commands::spawn_batch`].
/// This method should generally only be used for sharing entities across apps, and only when they have a scheme
/// worked out to share an ID space (which doesn't happen by default).
#[track_caller]
#[deprecated(
since = "0.16.0",
note = "This can cause extreme performance problems when used with lots of arbitrary free entities. See #18054 on GitHub."
)]
pub fn insert_or_spawn_batch<I, B>(&mut self, bundles_iter: I)
where
I: IntoIterator<Item = (Entity, B)> + Send + Sync + 'static,
B: Bundle<Effect: NoBundleEffect>,
{
let caller = MaybeLocation::caller();
self.queue(move |world: &mut World| {

#[expect(
deprecated,
reason = "This needs to be supported for now, and the outer item is deprecated too."
)]
if let Err(invalid_entities) = world.insert_or_spawn_batch_with_caller(
bundles_iter,
caller,
) {
error!(
"{caller}: Failed to 'insert or spawn' bundle of type {} into the following invalid entities: {:?}",
core::any::type_name::<B>(),
invalid_entities
);
}
});
}

/// Adds a series of [`Bundles`](Bundle) to each [`Entity`] they are paired with,
/// based on a batch of `(Entity, Bundle)` pairs.
///
Expand Down
Loading