mirror of
https://github.com/bevyengine/bevy
synced 2024-12-25 12:33:07 +00:00
80961d1bd0
Removing the checks on this line https://github.com/bevyengine/bevy/blob/main/crates/bevy_sprite/src/frustum_culling.rs#L64 and running the "many_sprites" example revealed two corner case bugs in bevy_ecs. The first, a simple and honest missed line introduced in #1471. The other, an insidious monster that has been there since the ECS v2 rewrite, just waiting for the time to strike: 1. #1471 accidentally removed the "insert" line for sparse set components with the "mutated" bundle state. Re-adding it fixes the problem. I did a slight refactor here to make the implementation simpler and remove a branch. 2. The other issue is nastier. ECS v2 added an "archetype graph". When determining what components were added/mutated during an archetype change, we read the FromBundle edge (which encodes this state) on the "new" archetype. The problem is that unlike "add edges" which are guaranteed to be unique for a given ("graph node", "bundle id") pair, FromBundle edges are not necessarily unique: ```rust // OLD_ARCHETYPE -> NEW_ARCHETYPE // [] -> [usize] e.insert(2usize); // [usize] -> [usize, i32] e.insert(1i32); // [usize, i32] -> [usize, i32] e.insert(1i32); // [usize, i32] -> [usize] e.remove::<i32>(); // [usize] -> [usize, i32] e.insert(1i32); ``` Note that the second `e.insert(1i32)` command has a different "archetype graph edge" than the first, but they both lead to the same "new archetype". The fix here is simple: just remove FromBundle edges because they are broken and store the information in the "add edges", which are guaranteed to be unique. FromBundle edges were added to cut down on the number of archetype accesses / make the archetype access patterns nicer. But benching this change resulted in no significant perf changes and the addition of get_2_mut() for archetypes resolves the access pattern issue.
126 lines
3.4 KiB
Rust
126 lines
3.4 KiB
Rust
use crate::{
|
|
archetype::{Archetype, ArchetypeId, ComponentStatus},
|
|
bundle::{Bundle, BundleInfo},
|
|
entity::{Entities, Entity},
|
|
storage::{SparseSets, Table},
|
|
world::{add_bundle_to_archetype, World},
|
|
};
|
|
|
|
pub struct SpawnBatchIter<'w, I>
|
|
where
|
|
I: Iterator,
|
|
I::Item: Bundle,
|
|
{
|
|
inner: I,
|
|
entities: &'w mut Entities,
|
|
archetype: &'w mut Archetype,
|
|
table: &'w mut Table,
|
|
sparse_sets: &'w mut SparseSets,
|
|
bundle_info: &'w BundleInfo,
|
|
bundle_status: &'w [ComponentStatus],
|
|
change_tick: u32,
|
|
}
|
|
|
|
impl<'w, I> SpawnBatchIter<'w, I>
|
|
where
|
|
I: Iterator,
|
|
I::Item: Bundle,
|
|
{
|
|
#[inline]
|
|
pub(crate) fn new(world: &'w mut World, iter: I) -> Self {
|
|
// Ensure all entity allocations are accounted for so `self.entities` can realloc if
|
|
// necessary
|
|
world.flush();
|
|
|
|
let (lower, upper) = iter.size_hint();
|
|
|
|
let bundle_info = world.bundles.init_info::<I::Item>(&mut world.components);
|
|
|
|
let length = upper.unwrap_or(lower);
|
|
// SAFE: empty archetype exists and bundle components were initialized above
|
|
let archetype_id = unsafe {
|
|
add_bundle_to_archetype(
|
|
&mut world.archetypes,
|
|
&mut world.storages,
|
|
&mut world.components,
|
|
ArchetypeId::empty(),
|
|
bundle_info,
|
|
)
|
|
};
|
|
let (empty_archetype, archetype) = world
|
|
.archetypes
|
|
.get_2_mut(ArchetypeId::empty(), archetype_id);
|
|
let table = &mut world.storages.tables[archetype.table_id()];
|
|
archetype.reserve(length);
|
|
table.reserve(length);
|
|
world.entities.reserve(length as u32);
|
|
let edge = empty_archetype
|
|
.edges()
|
|
.get_add_bundle(bundle_info.id())
|
|
.unwrap();
|
|
Self {
|
|
inner: iter,
|
|
entities: &mut world.entities,
|
|
archetype,
|
|
table,
|
|
sparse_sets: &mut world.storages.sparse_sets,
|
|
bundle_info,
|
|
change_tick: *world.change_tick.get_mut(),
|
|
bundle_status: &edge.bundle_status,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<I> Drop for SpawnBatchIter<'_, I>
|
|
where
|
|
I: Iterator,
|
|
I::Item: Bundle,
|
|
{
|
|
fn drop(&mut self) {
|
|
for _ in self {}
|
|
}
|
|
}
|
|
|
|
impl<I> Iterator for SpawnBatchIter<'_, I>
|
|
where
|
|
I: Iterator,
|
|
I::Item: Bundle,
|
|
{
|
|
type Item = Entity;
|
|
|
|
fn next(&mut self) -> Option<Entity> {
|
|
let bundle = self.inner.next()?;
|
|
let entity = self.entities.alloc();
|
|
// SAFE: component values are immediately written to relevant storages (which have been
|
|
// allocated)
|
|
unsafe {
|
|
let table_row = self.table.allocate(entity);
|
|
let location = self.archetype.allocate(entity, table_row);
|
|
self.bundle_info.write_components(
|
|
self.sparse_sets,
|
|
entity,
|
|
self.table,
|
|
table_row,
|
|
self.bundle_status,
|
|
bundle,
|
|
self.change_tick,
|
|
);
|
|
self.entities.meta[entity.id as usize].location = location;
|
|
}
|
|
Some(entity)
|
|
}
|
|
|
|
fn size_hint(&self) -> (usize, Option<usize>) {
|
|
self.inner.size_hint()
|
|
}
|
|
}
|
|
|
|
impl<I, T> ExactSizeIterator for SpawnBatchIter<'_, I>
|
|
where
|
|
I: ExactSizeIterator<Item = T>,
|
|
T: Bundle,
|
|
{
|
|
fn len(&self) -> usize {
|
|
self.inner.len()
|
|
}
|
|
}
|