Skip to content

Commit bfc76c5

Browse files
Remove insert_or_spawn function family (#18148)
# Objective Based on and closes #18054, this PR builds on #18035 and #18147 to remove: - `Commands::insert_or_spawn_batch` - `Entities::alloc_at_without_replacement` - `Entities::alloc_at` - `entity::AllocAtWithoutReplacement` - `World::insert_or_spawn_batch` - `World::insert_or_spawn_batch_with_caller` ## Testing Just removing unused, deprecated code, so no new tests. Note that as of writing, #18035 is still under testing and review. ## Future Work Per [this](#18054 (comment)) comment on #18054, there may be additional performance improvements possible to the entity allocator now that `alloc_at` no longer is supported. At a glance, I don't see anything obvious to improve, but it may be worth further investigation in the future. --------- Co-authored-by: JaySpruce <[email protected]>
1 parent 3b24f52 commit bfc76c5

File tree

6 files changed

+21
-435
lines changed

6 files changed

+21
-435
lines changed

benches/benches/bevy_ecs/world/commands.rs

Lines changed: 0 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -92,28 +92,6 @@ pub fn insert_commands(criterion: &mut Criterion) {
9292
command_queue.apply(&mut world);
9393
});
9494
});
95-
group.bench_function("insert_or_spawn_batch", |bencher| {
96-
let mut world = World::default();
97-
let mut command_queue = CommandQueue::default();
98-
let mut entities = Vec::new();
99-
for _ in 0..entity_count {
100-
entities.push(world.spawn_empty().id());
101-
}
102-
103-
bencher.iter(|| {
104-
let mut commands = Commands::new(&mut command_queue, &world);
105-
let mut values = Vec::with_capacity(entity_count);
106-
for entity in &entities {
107-
values.push((*entity, (Matrix::default(), Vec3::default())));
108-
}
109-
#[expect(
110-
deprecated,
111-
reason = "This needs to be supported for now, and therefore still needs the benchmark."
112-
)]
113-
commands.insert_or_spawn_batch(values);
114-
command_queue.apply(&mut world);
115-
});
116-
});
11795
group.bench_function("insert_batch", |bencher| {
11896
let mut world = World::default();
11997
let mut command_queue = CommandQueue::default();

crates/bevy_ecs/src/entity/mod.rs

Lines changed: 0 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -243,16 +243,6 @@ impl Hash for Entity {
243243
}
244244
}
245245

246-
#[deprecated(
247-
since = "0.16.0",
248-
note = "This is exclusively used with the now deprecated `Entities::alloc_at_without_replacement`."
249-
)]
250-
pub(crate) enum AllocAtWithoutReplacement {
251-
Exists(EntityLocation),
252-
DidNotExist,
253-
ExistsWithWrongGeneration,
254-
}
255-
256246
impl Entity {
257247
/// Construct an [`Entity`] from a raw `index` value and a non-zero `generation` value.
258248
/// Ensure that the generation value is never greater than `0x7FFF_FFFF`.
@@ -690,87 +680,6 @@ impl Entities {
690680
}
691681
}
692682

693-
/// Allocate a specific entity ID, overwriting its generation.
694-
///
695-
/// Returns the location of the entity currently using the given ID, if any. Location should be
696-
/// written immediately.
697-
#[deprecated(
698-
since = "0.16.0",
699-
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."
700-
)]
701-
pub fn alloc_at(&mut self, entity: Entity) -> Option<EntityLocation> {
702-
self.verify_flushed();
703-
704-
let loc = if entity.index() as usize >= self.meta.len() {
705-
self.pending
706-
.extend((self.meta.len() as u32)..entity.index());
707-
let new_free_cursor = self.pending.len() as IdCursor;
708-
*self.free_cursor.get_mut() = new_free_cursor;
709-
self.meta
710-
.resize(entity.index() as usize + 1, EntityMeta::EMPTY);
711-
None
712-
} else if let Some(index) = self.pending.iter().position(|item| *item == entity.index()) {
713-
self.pending.swap_remove(index);
714-
let new_free_cursor = self.pending.len() as IdCursor;
715-
*self.free_cursor.get_mut() = new_free_cursor;
716-
None
717-
} else {
718-
Some(mem::replace(
719-
&mut self.meta[entity.index() as usize].location,
720-
EntityMeta::EMPTY.location,
721-
))
722-
};
723-
724-
self.meta[entity.index() as usize].generation = entity.generation;
725-
726-
loc
727-
}
728-
729-
/// Allocate a specific entity ID, overwriting its generation.
730-
///
731-
/// Returns the location of the entity currently using the given ID, if any.
732-
#[deprecated(
733-
since = "0.16.0",
734-
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."
735-
)]
736-
#[expect(
737-
deprecated,
738-
reason = "We need to support `AllocAtWithoutReplacement` for now."
739-
)]
740-
pub(crate) fn alloc_at_without_replacement(
741-
&mut self,
742-
entity: Entity,
743-
) -> AllocAtWithoutReplacement {
744-
self.verify_flushed();
745-
746-
let result = if entity.index() as usize >= self.meta.len() {
747-
self.pending
748-
.extend((self.meta.len() as u32)..entity.index());
749-
let new_free_cursor = self.pending.len() as IdCursor;
750-
*self.free_cursor.get_mut() = new_free_cursor;
751-
self.meta
752-
.resize(entity.index() as usize + 1, EntityMeta::EMPTY);
753-
AllocAtWithoutReplacement::DidNotExist
754-
} else if let Some(index) = self.pending.iter().position(|item| *item == entity.index()) {
755-
self.pending.swap_remove(index);
756-
let new_free_cursor = self.pending.len() as IdCursor;
757-
*self.free_cursor.get_mut() = new_free_cursor;
758-
AllocAtWithoutReplacement::DidNotExist
759-
} else {
760-
let current_meta = &self.meta[entity.index() as usize];
761-
if current_meta.location.archetype_id == ArchetypeId::INVALID {
762-
AllocAtWithoutReplacement::DidNotExist
763-
} else if current_meta.generation == entity.generation {
764-
AllocAtWithoutReplacement::Exists(current_meta.location)
765-
} else {
766-
return AllocAtWithoutReplacement::ExistsWithWrongGeneration;
767-
}
768-
};
769-
770-
self.meta[entity.index() as usize].generation = entity.generation;
771-
result
772-
}
773-
774683
/// Destroy an entity, allowing it to be reused.
775684
///
776685
/// Must not be called while reserved entities are awaiting `flush()`.

crates/bevy_ecs/src/lib.rs

Lines changed: 0 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,6 @@ mod tests {
152152
use core::{
153153
any::TypeId,
154154
marker::PhantomData,
155-
num::NonZero,
156155
sync::atomic::{AtomicUsize, Ordering},
157156
};
158157
use std::sync::Mutex;
@@ -1697,97 +1696,6 @@ mod tests {
16971696
assert_eq!(0, query_min_size![(&A, &B), Or<(Changed<A>, Changed<B>)>]);
16981697
}
16991698

1700-
#[test]
1701-
fn insert_or_spawn_batch() {
1702-
let mut world = World::default();
1703-
let e0 = world.spawn(A(0)).id();
1704-
let e1 = Entity::from_raw(1);
1705-
1706-
let values = vec![(e0, (B(0), C)), (e1, (B(1), C))];
1707-
1708-
#[expect(
1709-
deprecated,
1710-
reason = "This needs to be supported for now, and therefore still needs the test."
1711-
)]
1712-
world.insert_or_spawn_batch(values).unwrap();
1713-
1714-
assert_eq!(
1715-
world.get::<A>(e0),
1716-
Some(&A(0)),
1717-
"existing component was preserved"
1718-
);
1719-
assert_eq!(
1720-
world.get::<B>(e0),
1721-
Some(&B(0)),
1722-
"pre-existing entity received correct B component"
1723-
);
1724-
assert_eq!(
1725-
world.get::<B>(e1),
1726-
Some(&B(1)),
1727-
"new entity was spawned and received correct B component"
1728-
);
1729-
assert_eq!(
1730-
world.get::<C>(e0),
1731-
Some(&C),
1732-
"pre-existing entity received C component"
1733-
);
1734-
assert_eq!(
1735-
world.get::<C>(e1),
1736-
Some(&C),
1737-
"new entity was spawned and received C component"
1738-
);
1739-
}
1740-
1741-
#[test]
1742-
fn insert_or_spawn_batch_invalid() {
1743-
let mut world = World::default();
1744-
let e0 = world.spawn(A(0)).id();
1745-
let e1 = Entity::from_raw(1);
1746-
let e2 = world.spawn_empty().id();
1747-
let invalid_e2 =
1748-
Entity::from_raw_and_generation(e2.index(), NonZero::<u32>::new(2).unwrap());
1749-
1750-
let values = vec![(e0, (B(0), C)), (e1, (B(1), C)), (invalid_e2, (B(2), C))];
1751-
1752-
#[expect(
1753-
deprecated,
1754-
reason = "This needs to be supported for now, and therefore still needs the test."
1755-
)]
1756-
let result = world.insert_or_spawn_batch(values);
1757-
1758-
assert_eq!(
1759-
result,
1760-
Err(vec![invalid_e2]),
1761-
"e2 failed to be spawned or inserted into"
1762-
);
1763-
1764-
assert_eq!(
1765-
world.get::<A>(e0),
1766-
Some(&A(0)),
1767-
"existing component was preserved"
1768-
);
1769-
assert_eq!(
1770-
world.get::<B>(e0),
1771-
Some(&B(0)),
1772-
"pre-existing entity received correct B component"
1773-
);
1774-
assert_eq!(
1775-
world.get::<B>(e1),
1776-
Some(&B(1)),
1777-
"new entity was spawned and received correct B component"
1778-
);
1779-
assert_eq!(
1780-
world.get::<C>(e0),
1781-
Some(&C),
1782-
"pre-existing entity received C component"
1783-
);
1784-
assert_eq!(
1785-
world.get::<C>(e1),
1786-
Some(&C),
1787-
"new entity was spawned and received C component"
1788-
);
1789-
}
1790-
17911699
#[test]
17921700
fn insert_batch() {
17931701
let mut world = World::default();

crates/bevy_ecs/src/system/commands/mod.rs

Lines changed: 1 addition & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,11 @@ pub use parallel_scope::*;
1212

1313
use alloc::boxed::Box;
1414
use core::marker::PhantomData;
15-
use log::error;
1615

1716
use crate::{
1817
self as bevy_ecs,
1918
bundle::{Bundle, InsertMode, NoBundleEffect},
20-
change_detection::{MaybeLocation, Mut},
19+
change_detection::Mut,
2120
component::{Component, ComponentId, Mutable},
2221
entity::{Entities, Entity, EntityClonerBuilder, EntityDoesNotExistError},
2322
error::{ignore, warn, BevyError, CommandWithEntity, ErrorContext, HandleError},
@@ -624,57 +623,6 @@ impl<'w, 's> Commands<'w, 's> {
624623
}
625624
}
626625

627-
/// Pushes a [`Command`] to the queue for creating entities, if needed,
628-
/// and for adding a bundle to each entity.
629-
///
630-
/// `bundles_iter` is a type that can be converted into an ([`Entity`], [`Bundle`]) iterator
631-
/// (it can also be a collection).
632-
///
633-
/// When the command is applied,
634-
/// for each (`Entity`, `Bundle`) pair in the given `bundles_iter`,
635-
/// the `Entity` is spawned, if it does not exist already.
636-
/// Then, the `Bundle` is added to the entity.
637-
///
638-
/// This method is equivalent to iterating `bundles_iter`,
639-
/// calling [`spawn`](Self::spawn) for each bundle,
640-
/// and passing it to [`insert`](EntityCommands::insert),
641-
/// but it is faster due to memory pre-allocation.
642-
///
643-
/// # Note
644-
///
645-
/// Spawning a specific `entity` value is rarely the right choice. Most apps should use [`Commands::spawn_batch`].
646-
/// This method should generally only be used for sharing entities across apps, and only when they have a scheme
647-
/// worked out to share an ID space (which doesn't happen by default).
648-
#[track_caller]
649-
#[deprecated(
650-
since = "0.16.0",
651-
note = "This can cause extreme performance problems when used with lots of arbitrary free entities. See #18054 on GitHub."
652-
)]
653-
pub fn insert_or_spawn_batch<I, B>(&mut self, bundles_iter: I)
654-
where
655-
I: IntoIterator<Item = (Entity, B)> + Send + Sync + 'static,
656-
B: Bundle<Effect: NoBundleEffect>,
657-
{
658-
let caller = MaybeLocation::caller();
659-
self.queue(move |world: &mut World| {
660-
661-
#[expect(
662-
deprecated,
663-
reason = "This needs to be supported for now, and the outer item is deprecated too."
664-
)]
665-
if let Err(invalid_entities) = world.insert_or_spawn_batch_with_caller(
666-
bundles_iter,
667-
caller,
668-
) {
669-
error!(
670-
"{caller}: Failed to 'insert or spawn' bundle of type {} into the following invalid entities: {:?}",
671-
core::any::type_name::<B>(),
672-
invalid_entities
673-
);
674-
}
675-
});
676-
}
677-
678626
/// Adds a series of [`Bundles`](Bundle) to each [`Entity`] they are paired with,
679627
/// based on a batch of `(Entity, Bundle)` pairs.
680628
///

0 commit comments

Comments
 (0)