mirror of
https://github.com/bevyengine/bevy
synced 2024-11-22 04:33:37 +00:00
Spawn specific entities: spawn or insert operations, refactor spawn internals, world clearing (#2673)
This upstreams the code changes used by the new renderer to enable cross-app Entity reuse: * Spawning at specific entities * get_or_spawn: spawns an entity if it doesn't already exist and returns an EntityMut * insert_or_spawn_batch: the batched equivalent to `world.get_or_spawn(entity).insert_bundle(bundle)` * Clearing entities and storages * Allocating Entities with "invalid" archetypes. These entities cannot be queried / are treated as "non existent". They serve as "reserved" entities that won't show up when calling `spawn()`. They must be "specifically spawned at" using apis like `get_or_spawn(entity)`. In combination, these changes enable the "render world" to clear entities / storages each frame and reserve all "app world entities". These can then be spawned during the "render extract step". This refactors "spawn" and "insert" code in a way that I think is a massive improvement to legibility and re-usability. It also yields marginal performance wins by reducing some duplicate lookups (less than a percentage point improvement on insertion benchmarks). There is also some potential for future unsafe reduction (by making BatchSpawner and BatchInserter generic). But for now I want to cut down generic usage to a minimum to encourage smaller binaries and faster compiles. This is currently a draft because it needs more tests (although this code has already had some real-world testing on my custom-shaders branch). I also fixed the benchmarks (which currently don't compile!) / added new ones to illustrate batching wins. After these changes, Bevy ECS is basically ready to accommodate the new renderer. I think the biggest missing piece at this point is "sub apps".
This commit is contained in:
parent
c5717b5a91
commit
b47217bfab
12 changed files with 1171 additions and 335 deletions
|
@ -1,4 +1,5 @@
|
|||
use bevy::ecs::{
|
||||
entity::Entity,
|
||||
system::{Command, CommandQueue, Commands},
|
||||
world::World,
|
||||
};
|
||||
|
@ -8,10 +9,12 @@ criterion_group!(
|
|||
benches,
|
||||
empty_commands,
|
||||
spawn_commands,
|
||||
insert_commands,
|
||||
fake_commands,
|
||||
zero_sized_commands,
|
||||
medium_sized_commands,
|
||||
large_sized_commands
|
||||
get_or_spawn,
|
||||
);
|
||||
criterion_main!(benches);
|
||||
|
||||
|
@ -76,6 +79,58 @@ fn spawn_commands(criterion: &mut Criterion) {
|
|||
group.finish();
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct Matrix([[f32; 4]; 4]);
|
||||
|
||||
#[derive(Default)]
|
||||
struct Vec3([f32; 3]);
|
||||
|
||||
fn insert_commands(criterion: &mut Criterion) {
|
||||
let mut group = criterion.benchmark_group("insert_commands");
|
||||
group.warm_up_time(std::time::Duration::from_millis(500));
|
||||
group.measurement_time(std::time::Duration::from_secs(4));
|
||||
|
||||
let entity_count = 10_000;
|
||||
group.bench_function(format!("insert"), |bencher| {
|
||||
let mut world = World::default();
|
||||
let mut command_queue = CommandQueue::default();
|
||||
let mut entities = Vec::new();
|
||||
for i in 0..entity_count {
|
||||
entities.push(world.spawn().id());
|
||||
}
|
||||
|
||||
bencher.iter(|| {
|
||||
let mut commands = Commands::new(&mut command_queue, &world);
|
||||
for entity in entities.iter() {
|
||||
commands.entity(*entity).insert_bundle((Matrix::default(), Vec3::default()));
|
||||
}
|
||||
drop(commands);
|
||||
command_queue.apply(&mut world);
|
||||
});
|
||||
});
|
||||
group.bench_function(format!("insert_batch"), |bencher| {
|
||||
let mut world = World::default();
|
||||
let mut command_queue = CommandQueue::default();
|
||||
let mut entities = Vec::new();
|
||||
for i in 0..entity_count {
|
||||
entities.push(world.spawn().id());
|
||||
}
|
||||
|
||||
bencher.iter(|| {
|
||||
let mut commands = Commands::new(&mut command_queue, &world);
|
||||
let mut values = Vec::with_capacity(entity_count);
|
||||
for entity in entities.iter() {
|
||||
values.push((*entity, (Matrix::default(), Vec3::default())));
|
||||
}
|
||||
commands.insert_or_spawn_batch(values);
|
||||
drop(commands);
|
||||
command_queue.apply(&mut world);
|
||||
});
|
||||
});
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
struct FakeCommandA;
|
||||
struct FakeCommandB(u64);
|
||||
|
||||
|
@ -106,7 +161,7 @@ fn fake_commands(criterion: &mut Criterion) {
|
|||
bencher.iter(|| {
|
||||
let mut commands = Commands::new(&mut command_queue, &world);
|
||||
for i in 0..command_count {
|
||||
if black_box(i % 2 == 0)
|
||||
if black_box(i % 2 == 0) {
|
||||
commands.add(FakeCommandA);
|
||||
} else {
|
||||
commands.add(FakeCommandB(0));
|
||||
|
@ -125,7 +180,7 @@ fn fake_commands(criterion: &mut Criterion) {
|
|||
struct SizedCommand<T: Default + Send + Sync + 'static>(T);
|
||||
|
||||
impl<T: Default + Send + Sync + 'static> Command for SizedCommand<T> {
|
||||
fn write(self: Box<Self>, world: &mut World) {
|
||||
fn write(self, world: &mut World) {
|
||||
black_box(self);
|
||||
black_box(world);
|
||||
}
|
||||
|
@ -175,3 +230,41 @@ fn medium_sized_commands(criterion: &mut Criterion) {
|
|||
fn large_sized_commands(criterion: &mut Criterion) {
|
||||
sized_commands_impl::<SizedCommand<LargeStruct>>(criterion);
|
||||
}
|
||||
|
||||
fn get_or_spawn(criterion: &mut Criterion) {
|
||||
let mut group = criterion.benchmark_group("get_or_spawn");
|
||||
group.warm_up_time(std::time::Duration::from_millis(500));
|
||||
group.measurement_time(std::time::Duration::from_secs(4));
|
||||
|
||||
group.bench_function("individual", |bencher| {
|
||||
let mut world = World::default();
|
||||
let mut command_queue = CommandQueue::default();
|
||||
|
||||
bencher.iter(|| {
|
||||
let mut commands = Commands::new(&mut command_queue, &world);
|
||||
for i in 0..10_000 {
|
||||
commands
|
||||
.get_or_spawn(Entity::new(i))
|
||||
.insert_bundle((Matrix::default(), Vec3::default()));
|
||||
}
|
||||
command_queue.apply(&mut world);
|
||||
});
|
||||
});
|
||||
|
||||
group.bench_function("batched", |bencher| {
|
||||
let mut world = World::default();
|
||||
let mut command_queue = CommandQueue::default();
|
||||
|
||||
bencher.iter(|| {
|
||||
let mut commands = Commands::new(&mut command_queue, &world);
|
||||
let mut values = Vec::with_capacity(10_000);
|
||||
for i in 0..10_000 {
|
||||
values.push((Entity::new(i), (Matrix::default(), Vec3::default())));
|
||||
}
|
||||
commands.insert_or_spawn_batch(values);
|
||||
command_queue.apply(&mut world);
|
||||
});
|
||||
});
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
|
|
@ -15,21 +15,15 @@ use std::{
|
|||
pub struct ArchetypeId(usize);
|
||||
|
||||
impl ArchetypeId {
|
||||
pub const EMPTY: ArchetypeId = ArchetypeId(0);
|
||||
pub const RESOURCE: ArchetypeId = ArchetypeId(1);
|
||||
pub const INVALID: ArchetypeId = ArchetypeId(usize::MAX);
|
||||
|
||||
#[inline]
|
||||
pub const fn new(index: usize) -> Self {
|
||||
ArchetypeId(index)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub const fn empty() -> ArchetypeId {
|
||||
ArchetypeId(0)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub const fn resource() -> ArchetypeId {
|
||||
ArchetypeId(1)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn index(self) -> usize {
|
||||
self.0
|
||||
|
@ -60,7 +54,7 @@ impl Edges {
|
|||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_add_bundle(
|
||||
pub fn insert_add_bundle(
|
||||
&mut self,
|
||||
bundle_id: BundleId,
|
||||
archetype_id: ArchetypeId,
|
||||
|
@ -81,7 +75,7 @@ impl Edges {
|
|||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_remove_bundle(&mut self, bundle_id: BundleId, archetype_id: Option<ArchetypeId>) {
|
||||
pub fn insert_remove_bundle(&mut self, bundle_id: BundleId, archetype_id: Option<ArchetypeId>) {
|
||||
self.remove_bundle.insert(bundle_id, archetype_id);
|
||||
}
|
||||
|
||||
|
@ -94,7 +88,7 @@ impl Edges {
|
|||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_remove_bundle_intersection(
|
||||
pub fn insert_remove_bundle_intersection(
|
||||
&mut self,
|
||||
bundle_id: BundleId,
|
||||
archetype_id: Option<ArchetypeId>,
|
||||
|
@ -309,6 +303,11 @@ impl Archetype {
|
|||
.get(component_id)
|
||||
.map(|info| info.archetype_component_id)
|
||||
}
|
||||
|
||||
pub(crate) fn clear_entities(&mut self) {
|
||||
self.entities.clear();
|
||||
self.table_info.entity_rows.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/// A generational id that changes every time the set of archetypes changes
|
||||
|
@ -377,7 +376,7 @@ impl Default for Archetypes {
|
|||
// adds the resource archetype. it is "special" in that it is inaccessible via a "hash",
|
||||
// which prevents entities from being added to it
|
||||
archetypes.archetypes.push(Archetype::new(
|
||||
ArchetypeId::resource(),
|
||||
ArchetypeId::RESOURCE,
|
||||
TableId::empty(),
|
||||
Cow::Owned(Vec::new()),
|
||||
Cow::Owned(Vec::new()),
|
||||
|
@ -402,7 +401,7 @@ impl Archetypes {
|
|||
#[inline]
|
||||
pub fn empty(&self) -> &Archetype {
|
||||
// SAFE: empty archetype always exists
|
||||
unsafe { self.archetypes.get_unchecked(ArchetypeId::empty().index()) }
|
||||
unsafe { self.archetypes.get_unchecked(ArchetypeId::EMPTY.index()) }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
@ -410,17 +409,14 @@ impl Archetypes {
|
|||
// SAFE: empty archetype always exists
|
||||
unsafe {
|
||||
self.archetypes
|
||||
.get_unchecked_mut(ArchetypeId::empty().index())
|
||||
.get_unchecked_mut(ArchetypeId::EMPTY.index())
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn resource(&self) -> &Archetype {
|
||||
// SAFE: resource archetype always exists
|
||||
unsafe {
|
||||
self.archetypes
|
||||
.get_unchecked(ArchetypeId::resource().index())
|
||||
}
|
||||
unsafe { self.archetypes.get_unchecked(ArchetypeId::RESOURCE.index()) }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
@ -428,7 +424,7 @@ impl Archetypes {
|
|||
// SAFE: resource archetype always exists
|
||||
unsafe {
|
||||
self.archetypes
|
||||
.get_unchecked_mut(ArchetypeId::resource().index())
|
||||
.get_unchecked_mut(ArchetypeId::RESOURCE.index())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -519,6 +515,12 @@ impl Archetypes {
|
|||
pub fn archetype_components_len(&self) -> usize {
|
||||
self.archetype_component_count
|
||||
}
|
||||
|
||||
pub fn clear_entities(&mut self) {
|
||||
for archetype in self.archetypes.iter_mut() {
|
||||
archetype.clear_entities();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Index<ArchetypeId> for Archetypes {
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
pub use bevy_ecs_macros::Bundle;
|
||||
|
||||
use crate::{
|
||||
archetype::ComponentStatus,
|
||||
archetype::{AddBundle, Archetype, ArchetypeId, Archetypes, ComponentStatus},
|
||||
component::{Component, ComponentId, ComponentTicks, Components, StorageType},
|
||||
entity::Entity,
|
||||
storage::{SparseSetIndex, SparseSets, Table},
|
||||
entity::{Entities, Entity, EntityLocation},
|
||||
storage::{SparseSetIndex, SparseSets, Storages, Table},
|
||||
};
|
||||
use bevy_ecs_macros::all_tuples;
|
||||
use std::{any::TypeId, collections::HashMap};
|
||||
|
@ -122,19 +122,119 @@ pub struct BundleInfo {
|
|||
}
|
||||
|
||||
impl BundleInfo {
|
||||
/// # Safety
|
||||
/// table row must exist, entity must be valid
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
#[inline]
|
||||
pub(crate) unsafe fn write_components<T: Bundle>(
|
||||
&self,
|
||||
sparse_sets: &mut SparseSets,
|
||||
entity: Entity,
|
||||
table: &mut Table,
|
||||
table_row: usize,
|
||||
bundle_status: &[ComponentStatus],
|
||||
bundle: T,
|
||||
pub fn id(&self) -> BundleId {
|
||||
self.id
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn components(&self) -> &[ComponentId] {
|
||||
&self.component_ids
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn storage_types(&self) -> &[StorageType] {
|
||||
&self.storage_types
|
||||
}
|
||||
|
||||
pub(crate) fn get_bundle_inserter<'a, 'b>(
|
||||
&'b self,
|
||||
entities: &'a mut Entities,
|
||||
archetypes: &'a mut Archetypes,
|
||||
components: &mut Components,
|
||||
storages: &'a mut Storages,
|
||||
archetype_id: ArchetypeId,
|
||||
change_tick: u32,
|
||||
) -> BundleInserter<'a, 'b> {
|
||||
let new_archetype_id =
|
||||
self.add_bundle_to_archetype(archetypes, storages, components, archetype_id);
|
||||
let archetypes_ptr = archetypes.archetypes.as_mut_ptr();
|
||||
if new_archetype_id == archetype_id {
|
||||
let archetype = &mut archetypes[archetype_id];
|
||||
let table_id = archetype.table_id();
|
||||
BundleInserter {
|
||||
bundle_info: self,
|
||||
archetype,
|
||||
entities,
|
||||
sparse_sets: &mut storages.sparse_sets,
|
||||
table: &mut storages.tables[table_id],
|
||||
archetypes_ptr,
|
||||
change_tick,
|
||||
result: InsertBundleResult::SameArchetype,
|
||||
}
|
||||
} else {
|
||||
let (archetype, new_archetype) = archetypes.get_2_mut(archetype_id, new_archetype_id);
|
||||
let table_id = archetype.table_id();
|
||||
if table_id == new_archetype.table_id() {
|
||||
BundleInserter {
|
||||
bundle_info: self,
|
||||
archetype,
|
||||
archetypes_ptr,
|
||||
entities,
|
||||
sparse_sets: &mut storages.sparse_sets,
|
||||
table: &mut storages.tables[table_id],
|
||||
change_tick,
|
||||
result: InsertBundleResult::NewArchetypeSameTable { new_archetype },
|
||||
}
|
||||
} else {
|
||||
let (table, new_table) = storages
|
||||
.tables
|
||||
.get_2_mut(table_id, new_archetype.table_id());
|
||||
BundleInserter {
|
||||
bundle_info: self,
|
||||
archetype,
|
||||
sparse_sets: &mut storages.sparse_sets,
|
||||
entities,
|
||||
archetypes_ptr,
|
||||
table,
|
||||
change_tick,
|
||||
result: InsertBundleResult::NewArchetypeNewTable {
|
||||
new_archetype,
|
||||
new_table,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn get_bundle_spawner<'a, 'b>(
|
||||
&'b self,
|
||||
entities: &'a mut Entities,
|
||||
archetypes: &'a mut Archetypes,
|
||||
components: &mut Components,
|
||||
storages: &'a mut Storages,
|
||||
change_tick: u32,
|
||||
) -> BundleSpawner<'a, 'b> {
|
||||
let new_archetype_id =
|
||||
self.add_bundle_to_archetype(archetypes, storages, components, ArchetypeId::EMPTY);
|
||||
let (empty_archetype, archetype) =
|
||||
archetypes.get_2_mut(ArchetypeId::EMPTY, new_archetype_id);
|
||||
let table = &mut storages.tables[archetype.table_id()];
|
||||
let add_bundle = empty_archetype.edges().get_add_bundle(self.id()).unwrap();
|
||||
BundleSpawner {
|
||||
archetype,
|
||||
add_bundle,
|
||||
bundle_info: self,
|
||||
table,
|
||||
entities,
|
||||
sparse_sets: &mut storages.sparse_sets,
|
||||
change_tick,
|
||||
}
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
/// `table` must be the "new" table for `entity`. `table_row` must have space allocated for the `entity`, `bundle` must match this BundleInfo's type
|
||||
#[inline]
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
unsafe fn write_components<T: Bundle>(
|
||||
&self,
|
||||
table: &mut Table,
|
||||
sparse_sets: &mut SparseSets,
|
||||
add_bundle: &AddBundle,
|
||||
entity: Entity,
|
||||
table_row: usize,
|
||||
change_tick: u32,
|
||||
bundle: T,
|
||||
) {
|
||||
// NOTE: get_components calls this closure on each component in "bundle order".
|
||||
// bundle_info.component_ids are also in "bundle order"
|
||||
|
@ -144,7 +244,7 @@ impl BundleInfo {
|
|||
match self.storage_types[bundle_component] {
|
||||
StorageType::Table => {
|
||||
let column = table.get_column_mut(component_id).unwrap();
|
||||
match bundle_status.get_unchecked(bundle_component) {
|
||||
match add_bundle.bundle_status.get_unchecked(bundle_component) {
|
||||
ComponentStatus::Added => {
|
||||
column.initialize(
|
||||
table_row,
|
||||
|
@ -166,19 +266,277 @@ impl BundleInfo {
|
|||
});
|
||||
}
|
||||
|
||||
/// Adds a bundle to the given archetype and returns the resulting archetype. This could be the same
|
||||
/// [ArchetypeId], in the event that adding the given bundle does not result in an Archetype change.
|
||||
/// Results are cached in the Archetype Graph to avoid redundant work.
|
||||
pub(crate) fn add_bundle_to_archetype(
|
||||
&self,
|
||||
archetypes: &mut Archetypes,
|
||||
storages: &mut Storages,
|
||||
components: &mut Components,
|
||||
archetype_id: ArchetypeId,
|
||||
) -> ArchetypeId {
|
||||
if let Some(add_bundle) = archetypes[archetype_id].edges().get_add_bundle(self.id) {
|
||||
return add_bundle.archetype_id;
|
||||
}
|
||||
let mut new_table_components = Vec::new();
|
||||
let mut new_sparse_set_components = Vec::new();
|
||||
let mut bundle_status = Vec::with_capacity(self.component_ids.len());
|
||||
|
||||
let current_archetype = &mut archetypes[archetype_id];
|
||||
for component_id in self.component_ids.iter().cloned() {
|
||||
if current_archetype.contains(component_id) {
|
||||
bundle_status.push(ComponentStatus::Mutated);
|
||||
} else {
|
||||
bundle_status.push(ComponentStatus::Added);
|
||||
// SAFE: component_id exists
|
||||
let component_info = unsafe { components.get_info_unchecked(component_id) };
|
||||
match component_info.storage_type() {
|
||||
StorageType::Table => new_table_components.push(component_id),
|
||||
StorageType::SparseSet => {
|
||||
storages.sparse_sets.get_or_insert(component_info);
|
||||
new_sparse_set_components.push(component_id)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if new_table_components.is_empty() && new_sparse_set_components.is_empty() {
|
||||
let edges = current_archetype.edges_mut();
|
||||
// the archetype does not change when we add this bundle
|
||||
edges.insert_add_bundle(self.id, archetype_id, bundle_status);
|
||||
archetype_id
|
||||
} else {
|
||||
let table_id;
|
||||
let table_components;
|
||||
let sparse_set_components;
|
||||
// the archetype changes when we add this bundle. prepare the new archetype and storages
|
||||
{
|
||||
let current_archetype = &archetypes[archetype_id];
|
||||
table_components = if new_table_components.is_empty() {
|
||||
// if there are no new table components, we can keep using this table
|
||||
table_id = current_archetype.table_id();
|
||||
current_archetype.table_components().to_vec()
|
||||
} else {
|
||||
new_table_components.extend(current_archetype.table_components());
|
||||
// sort to ignore order while hashing
|
||||
new_table_components.sort();
|
||||
// SAFE: all component ids in `new_table_components` exist
|
||||
table_id = unsafe {
|
||||
storages
|
||||
.tables
|
||||
.get_id_or_insert(&new_table_components, components)
|
||||
};
|
||||
|
||||
new_table_components
|
||||
};
|
||||
|
||||
sparse_set_components = if new_sparse_set_components.is_empty() {
|
||||
current_archetype.sparse_set_components().to_vec()
|
||||
} else {
|
||||
new_sparse_set_components.extend(current_archetype.sparse_set_components());
|
||||
// sort to ignore order while hashing
|
||||
new_sparse_set_components.sort();
|
||||
new_sparse_set_components
|
||||
};
|
||||
};
|
||||
let new_archetype_id =
|
||||
archetypes.get_id_or_insert(table_id, table_components, sparse_set_components);
|
||||
// add an edge from the old archetype to the new archetype
|
||||
archetypes[archetype_id].edges_mut().insert_add_bundle(
|
||||
self.id,
|
||||
new_archetype_id,
|
||||
bundle_status,
|
||||
);
|
||||
new_archetype_id
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct BundleInserter<'a, 'b> {
|
||||
pub(crate) archetype: &'a mut Archetype,
|
||||
pub(crate) entities: &'a mut Entities,
|
||||
bundle_info: &'b BundleInfo,
|
||||
table: &'a mut Table,
|
||||
sparse_sets: &'a mut SparseSets,
|
||||
result: InsertBundleResult<'a>,
|
||||
archetypes_ptr: *mut Archetype,
|
||||
change_tick: u32,
|
||||
}
|
||||
|
||||
pub(crate) enum InsertBundleResult<'a> {
|
||||
SameArchetype,
|
||||
NewArchetypeSameTable {
|
||||
new_archetype: &'a mut Archetype,
|
||||
},
|
||||
NewArchetypeNewTable {
|
||||
new_archetype: &'a mut Archetype,
|
||||
new_table: &'a mut Table,
|
||||
},
|
||||
}
|
||||
|
||||
impl<'a, 'b> BundleInserter<'a, 'b> {
|
||||
/// # Safety
|
||||
/// `entity` must currently exist in the source archetype for this inserter. `archetype_index` must be `entity`'s location in the archetype.
|
||||
/// `T` must match this BundleInfo's type
|
||||
#[inline]
|
||||
pub fn id(&self) -> BundleId {
|
||||
self.id
|
||||
pub unsafe fn insert<T: Bundle>(
|
||||
&mut self,
|
||||
entity: Entity,
|
||||
archetype_index: usize,
|
||||
bundle: T,
|
||||
) -> EntityLocation {
|
||||
let location = EntityLocation {
|
||||
index: archetype_index,
|
||||
archetype_id: self.archetype.id(),
|
||||
};
|
||||
match &mut self.result {
|
||||
InsertBundleResult::SameArchetype => {
|
||||
// PERF: this could be looked up during Inserter construction and stored (but borrowing makes this nasty)
|
||||
let add_bundle = self
|
||||
.archetype
|
||||
.edges()
|
||||
.get_add_bundle(self.bundle_info.id)
|
||||
.unwrap();
|
||||
self.bundle_info.write_components(
|
||||
self.table,
|
||||
self.sparse_sets,
|
||||
add_bundle,
|
||||
entity,
|
||||
self.archetype.entity_table_row(archetype_index),
|
||||
self.change_tick,
|
||||
bundle,
|
||||
);
|
||||
location
|
||||
}
|
||||
InsertBundleResult::NewArchetypeSameTable { new_archetype } => {
|
||||
let result = self.archetype.swap_remove(location.index);
|
||||
if let Some(swapped_entity) = result.swapped_entity {
|
||||
self.entities.meta[swapped_entity.id as usize].location = location;
|
||||
}
|
||||
let new_location = new_archetype.allocate(entity, result.table_row);
|
||||
self.entities.meta[entity.id as usize].location = new_location;
|
||||
|
||||
// PERF: this could be looked up during Inserter construction and stored (but borrowing makes this nasty)
|
||||
let add_bundle = self
|
||||
.archetype
|
||||
.edges()
|
||||
.get_add_bundle(self.bundle_info.id)
|
||||
.unwrap();
|
||||
self.bundle_info.write_components(
|
||||
self.table,
|
||||
self.sparse_sets,
|
||||
add_bundle,
|
||||
entity,
|
||||
result.table_row,
|
||||
self.change_tick,
|
||||
bundle,
|
||||
);
|
||||
new_location
|
||||
}
|
||||
InsertBundleResult::NewArchetypeNewTable {
|
||||
new_archetype,
|
||||
new_table,
|
||||
} => {
|
||||
let result = self.archetype.swap_remove(location.index);
|
||||
if let Some(swapped_entity) = result.swapped_entity {
|
||||
self.entities.meta[swapped_entity.id as usize].location = location;
|
||||
}
|
||||
// PERF: store "non bundle" components in edge, then just move those to avoid
|
||||
// redundant copies
|
||||
let move_result = self
|
||||
.table
|
||||
.move_to_superset_unchecked(result.table_row, &mut *new_table);
|
||||
let new_location = new_archetype.allocate(entity, move_result.new_row);
|
||||
self.entities.meta[entity.id as usize].location = new_location;
|
||||
|
||||
// if an entity was moved into this entity's table spot, update its table row
|
||||
if let Some(swapped_entity) = move_result.swapped_entity {
|
||||
let swapped_location = self.entities.get(swapped_entity).unwrap();
|
||||
let swapped_archetype = if self.archetype.id() == swapped_location.archetype_id
|
||||
{
|
||||
&mut *self.archetype
|
||||
} else if new_archetype.id() == swapped_location.archetype_id {
|
||||
&mut *new_archetype
|
||||
} else {
|
||||
// SAFE: the only two borrowed archetypes are above and we just did collision checks
|
||||
&mut *self
|
||||
.archetypes_ptr
|
||||
.add(swapped_location.archetype_id.index())
|
||||
};
|
||||
|
||||
swapped_archetype
|
||||
.set_entity_table_row(swapped_location.index, result.table_row);
|
||||
}
|
||||
|
||||
// PERF: this could be looked up during Inserter construction and stored (but borrowing makes this nasty)
|
||||
let add_bundle = self
|
||||
.archetype
|
||||
.edges()
|
||||
.get_add_bundle(self.bundle_info.id)
|
||||
.unwrap();
|
||||
self.bundle_info.write_components(
|
||||
new_table,
|
||||
self.sparse_sets,
|
||||
add_bundle,
|
||||
entity,
|
||||
move_result.new_row,
|
||||
self.change_tick,
|
||||
bundle,
|
||||
);
|
||||
new_location
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct BundleSpawner<'a, 'b> {
|
||||
pub(crate) archetype: &'a mut Archetype,
|
||||
pub(crate) entities: &'a mut Entities,
|
||||
add_bundle: &'a AddBundle,
|
||||
bundle_info: &'b BundleInfo,
|
||||
table: &'a mut Table,
|
||||
sparse_sets: &'a mut SparseSets,
|
||||
change_tick: u32,
|
||||
}
|
||||
|
||||
impl<'a, 'b> BundleSpawner<'a, 'b> {
|
||||
pub fn reserve_storage(&mut self, additional: usize) {
|
||||
self.archetype.reserve(additional);
|
||||
self.table.reserve(additional);
|
||||
}
|
||||
/// # Safety
|
||||
/// `entity` must be allocated (but non existent), `T` must match this BundleInfo's type
|
||||
#[inline]
|
||||
pub unsafe fn spawn_non_existent<T: Bundle>(
|
||||
&mut self,
|
||||
entity: Entity,
|
||||
bundle: T,
|
||||
) -> EntityLocation {
|
||||
let table_row = self.table.allocate(entity);
|
||||
let location = self.archetype.allocate(entity, table_row);
|
||||
self.bundle_info.write_components(
|
||||
self.table,
|
||||
self.sparse_sets,
|
||||
self.add_bundle,
|
||||
entity,
|
||||
table_row,
|
||||
self.change_tick,
|
||||
bundle,
|
||||
);
|
||||
self.entities.meta[entity.id as usize].location = location;
|
||||
|
||||
location
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
/// `T` must match this BundleInfo's type
|
||||
#[inline]
|
||||
pub fn components(&self) -> &[ComponentId] {
|
||||
&self.component_ids
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn storage_types(&self) -> &[StorageType] {
|
||||
&self.storage_types
|
||||
pub unsafe fn spawn<T: Bundle>(&mut self, bundle: T) -> Entity {
|
||||
let entity = self.entities.alloc();
|
||||
// SAFE: entity is allocated (but non-existent), `T` matches this BundleInfo's type
|
||||
self.spawn_non_existent(entity, bundle);
|
||||
entity
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -26,8 +26,19 @@ pub struct Entity {
|
|||
pub(crate) id: u32,
|
||||
}
|
||||
|
||||
pub enum AllocAtWithoutReplacement {
|
||||
Exists(EntityLocation),
|
||||
DidNotExist,
|
||||
ExistsWithWrongGeneration,
|
||||
}
|
||||
|
||||
impl Entity {
|
||||
/// Creates a new entity reference with a generation of 0.
|
||||
///
|
||||
/// # Note
|
||||
/// Spawning a specific `entity` value is rarely the right choice. Most apps should favor [`Commands::spawn`].
|
||||
/// 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).
|
||||
pub fn new(id: u32) -> Entity {
|
||||
Entity { id, generation: 0 }
|
||||
}
|
||||
|
@ -242,11 +253,8 @@ impl Entities {
|
|||
}
|
||||
|
||||
/// Allocate an entity ID directly.
|
||||
///
|
||||
/// Location should be written immediately.
|
||||
pub fn alloc(&mut self) -> Entity {
|
||||
self.verify_flushed();
|
||||
|
||||
self.len += 1;
|
||||
if let Some(id) = self.pending.pop() {
|
||||
let new_free_cursor = self.pending.len() as i64;
|
||||
|
@ -294,6 +302,40 @@ impl Entities {
|
|||
loc
|
||||
}
|
||||
|
||||
/// Allocate a specific entity ID, overwriting its generation.
|
||||
///
|
||||
/// Returns the location of the entity currently using the given ID, if any.
|
||||
pub fn alloc_at_without_replacement(&mut self, entity: Entity) -> AllocAtWithoutReplacement {
|
||||
self.verify_flushed();
|
||||
|
||||
let result = if entity.id as usize >= self.meta.len() {
|
||||
self.pending.extend((self.meta.len() as u32)..entity.id);
|
||||
let new_free_cursor = self.pending.len() as i64;
|
||||
*self.free_cursor.get_mut() = new_free_cursor;
|
||||
self.meta.resize(entity.id as usize + 1, EntityMeta::EMPTY);
|
||||
self.len += 1;
|
||||
AllocAtWithoutReplacement::DidNotExist
|
||||
} else if let Some(index) = self.pending.iter().position(|item| *item == entity.id) {
|
||||
self.pending.swap_remove(index);
|
||||
let new_free_cursor = self.pending.len() as i64;
|
||||
*self.free_cursor.get_mut() = new_free_cursor;
|
||||
self.len += 1;
|
||||
AllocAtWithoutReplacement::DidNotExist
|
||||
} else {
|
||||
let current_meta = &mut self.meta[entity.id 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.id as usize].generation = entity.generation;
|
||||
result
|
||||
}
|
||||
|
||||
/// Destroy an entity, allowing it to be reused.
|
||||
///
|
||||
/// Must not be called while reserved entities are awaiting `flush()`.
|
||||
|
@ -339,27 +381,16 @@ impl Entities {
|
|||
self.meta.clear();
|
||||
self.pending.clear();
|
||||
*self.free_cursor.get_mut() = 0;
|
||||
self.len = 0;
|
||||
}
|
||||
|
||||
/// Access the location storage of an entity.
|
||||
///
|
||||
/// Must not be called on pending entities.
|
||||
#[inline]
|
||||
pub fn get_mut(&mut self, entity: Entity) -> Option<&mut EntityLocation> {
|
||||
let meta = &mut self.meta[entity.id as usize];
|
||||
if meta.generation == entity.generation {
|
||||
Some(&mut meta.location)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `Ok(Location { archetype: 0, index: undefined })` for pending entities.
|
||||
#[inline]
|
||||
/// Returns `Ok(Location { archetype: Archetype::invalid(), index: undefined })` for pending entities.
|
||||
pub fn get(&self, entity: Entity) -> Option<EntityLocation> {
|
||||
if (entity.id as usize) < self.meta.len() {
|
||||
let meta = &self.meta[entity.id as usize];
|
||||
if meta.generation != entity.generation {
|
||||
if meta.generation != entity.generation
|
||||
|| meta.location.archetype_id == ArchetypeId::INVALID
|
||||
{
|
||||
return None;
|
||||
}
|
||||
Some(meta.location)
|
||||
|
@ -402,7 +433,12 @@ impl Entities {
|
|||
|
||||
/// Allocates space for entities previously reserved with `reserve_entity` or
|
||||
/// `reserve_entities`, then initializes each one using the supplied function.
|
||||
pub fn flush(&mut self, mut init: impl FnMut(Entity, &mut EntityLocation)) {
|
||||
///
|
||||
/// # Safety
|
||||
/// Flush _must_ set the entity location to the correct ArchetypeId for the given Entity
|
||||
/// each time init is called. This _can_ be ArchetypeId::INVALID, provided the Entity has
|
||||
/// not been assigned to an Archetype.
|
||||
pub unsafe fn flush(&mut self, mut init: impl FnMut(Entity, &mut EntityLocation)) {
|
||||
let free_cursor = self.free_cursor.get_mut();
|
||||
let current_free_cursor = *free_cursor;
|
||||
|
||||
|
@ -440,6 +476,16 @@ impl Entities {
|
|||
}
|
||||
}
|
||||
|
||||
// Flushes all reserved entities to an "invalid" state. Attempting to retrieve them will return None
|
||||
// unless they are later populated with a valid archetype.
|
||||
pub fn flush_as_invalid(&mut self) {
|
||||
unsafe {
|
||||
self.flush(|_entity, location| {
|
||||
location.archetype_id = ArchetypeId::INVALID;
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn len(&self) -> u32 {
|
||||
self.len
|
||||
|
@ -461,7 +507,7 @@ impl EntityMeta {
|
|||
const EMPTY: EntityMeta = EntityMeta {
|
||||
generation: 0,
|
||||
location: EntityLocation {
|
||||
archetype_id: ArchetypeId::empty(),
|
||||
archetype_id: ArchetypeId::INVALID,
|
||||
index: usize::max_value(), // dummy value, to be filled in
|
||||
},
|
||||
};
|
||||
|
@ -494,7 +540,24 @@ mod tests {
|
|||
fn reserve_entity_len() {
|
||||
let mut e = Entities::default();
|
||||
e.reserve_entity();
|
||||
e.flush(|_, _| {});
|
||||
unsafe { e.flush(|_, _| {}) };
|
||||
assert_eq!(e.len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_reserved_and_invalid() {
|
||||
let mut entities = Entities::default();
|
||||
let e = entities.reserve_entity();
|
||||
assert!(entities.contains(e));
|
||||
assert!(entities.get(e).is_none());
|
||||
|
||||
unsafe {
|
||||
entities.flush(|_entity, _location| {
|
||||
// do nothing ... leaving entity location invalid
|
||||
})
|
||||
};
|
||||
|
||||
assert!(entities.contains(e));
|
||||
assert!(entities.get(e).is_none());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -60,7 +60,9 @@ mod tests {
|
|||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
struct A(usize);
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
struct B(usize);
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
struct C;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
|
@ -1231,4 +1233,256 @@ mod tests {
|
|||
assert_eq!(dropped1.load(Ordering::Relaxed), 1);
|
||||
assert_eq!(dropped2.load(Ordering::Relaxed), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn clear_entities() {
|
||||
let mut world = World::default();
|
||||
world
|
||||
.register_component(ComponentDescriptor::new::<f32>(StorageType::SparseSet))
|
||||
.unwrap();
|
||||
world.insert_resource::<i32>(0);
|
||||
world.spawn().insert(1u32);
|
||||
world.spawn().insert(1.0f32);
|
||||
|
||||
let mut q1 = world.query::<&u32>();
|
||||
let mut q2 = world.query::<&f32>();
|
||||
|
||||
assert_eq!(q1.iter(&world).len(), 1);
|
||||
assert_eq!(q2.iter(&world).len(), 1);
|
||||
assert_eq!(world.entities().len(), 2);
|
||||
|
||||
world.clear_entities();
|
||||
|
||||
assert_eq!(
|
||||
q1.iter(&world).len(),
|
||||
0,
|
||||
"world should not contain table components"
|
||||
);
|
||||
assert_eq!(
|
||||
q2.iter(&world).len(),
|
||||
0,
|
||||
"world should not contain sparse set components"
|
||||
);
|
||||
assert_eq!(
|
||||
world.entities().len(),
|
||||
0,
|
||||
"world should not have any entities"
|
||||
);
|
||||
assert_eq!(
|
||||
*world.get_resource::<i32>().unwrap(),
|
||||
0,
|
||||
"world should still contain resources"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reserve_entities_across_worlds() {
|
||||
let mut world_a = World::default();
|
||||
let mut world_b = World::default();
|
||||
|
||||
let e1 = world_a.spawn().insert(1u32).id();
|
||||
let e2 = world_a.spawn().insert(2u32).id();
|
||||
let e3 = world_a.entities().reserve_entity();
|
||||
world_a.flush();
|
||||
|
||||
let world_a_max_entities = world_a.entities().meta.len();
|
||||
world_b
|
||||
.entities
|
||||
.reserve_entities(world_a_max_entities as u32);
|
||||
world_b.entities.flush_as_invalid();
|
||||
|
||||
let e4 = world_b.spawn().insert(4u32).id();
|
||||
assert_eq!(
|
||||
e4,
|
||||
Entity {
|
||||
generation: 0,
|
||||
id: 3,
|
||||
},
|
||||
"new entity is created immediately after world_a's max entity"
|
||||
);
|
||||
assert!(world_b.get::<u32>(e1).is_none());
|
||||
assert!(world_b.get_entity(e1).is_none());
|
||||
|
||||
assert!(world_b.get::<u32>(e2).is_none());
|
||||
assert!(world_b.get_entity(e2).is_none());
|
||||
|
||||
assert!(world_b.get::<u32>(e3).is_none());
|
||||
assert!(world_b.get_entity(e3).is_none());
|
||||
|
||||
world_b.get_or_spawn(e1).unwrap().insert(1.0f32);
|
||||
assert_eq!(
|
||||
world_b.get::<f32>(e1),
|
||||
Some(&1.0f32),
|
||||
"spawning into 'world_a' entities works"
|
||||
);
|
||||
|
||||
world_b.get_or_spawn(e4).unwrap().insert(4.0f32);
|
||||
assert_eq!(
|
||||
world_b.get::<f32>(e4),
|
||||
Some(&4.0f32),
|
||||
"spawning into existing `world_b` entities works"
|
||||
);
|
||||
assert_eq!(
|
||||
world_b.get::<u32>(e4),
|
||||
Some(&4u32),
|
||||
"spawning into existing `world_b` entities works"
|
||||
);
|
||||
|
||||
let e4_mismatched_generation = Entity {
|
||||
generation: 1,
|
||||
id: 3,
|
||||
};
|
||||
assert!(
|
||||
world_b.get_or_spawn(e4_mismatched_generation).is_none(),
|
||||
"attempting to spawn on top of an entity with a mismatched entity generation fails"
|
||||
);
|
||||
assert_eq!(
|
||||
world_b.get::<f32>(e4),
|
||||
Some(&4.0f32),
|
||||
"failed mismatched spawn doesn't change existing entity"
|
||||
);
|
||||
assert_eq!(
|
||||
world_b.get::<u32>(e4),
|
||||
Some(&4u32),
|
||||
"failed mismatched spawn doesn't change existing entity"
|
||||
);
|
||||
|
||||
let high_non_existent_entity = Entity {
|
||||
generation: 0,
|
||||
id: 6,
|
||||
};
|
||||
world_b
|
||||
.get_or_spawn(high_non_existent_entity)
|
||||
.unwrap()
|
||||
.insert(10.0f32);
|
||||
assert_eq!(
|
||||
world_b.get::<f32>(high_non_existent_entity),
|
||||
Some(&10.0f32),
|
||||
"inserting into newly allocated high / non-continous entity id works"
|
||||
);
|
||||
|
||||
let high_non_existent_but_reserved_entity = Entity {
|
||||
generation: 0,
|
||||
id: 5,
|
||||
};
|
||||
assert!(
|
||||
world_b.get_entity(high_non_existent_but_reserved_entity).is_none(),
|
||||
"entities between high-newly allocated entity and continuous block of existing entities don't exist"
|
||||
);
|
||||
|
||||
let reserved_entities = vec![
|
||||
world_b.entities().reserve_entity(),
|
||||
world_b.entities().reserve_entity(),
|
||||
world_b.entities().reserve_entity(),
|
||||
world_b.entities().reserve_entity(),
|
||||
];
|
||||
|
||||
assert_eq!(
|
||||
reserved_entities,
|
||||
vec![
|
||||
Entity {
|
||||
generation: 0,
|
||||
id: 5
|
||||
},
|
||||
Entity {
|
||||
generation: 0,
|
||||
id: 4
|
||||
},
|
||||
Entity {
|
||||
generation: 0,
|
||||
id: 7,
|
||||
},
|
||||
Entity {
|
||||
generation: 0,
|
||||
id: 8,
|
||||
},
|
||||
],
|
||||
"space between original entities and high entities is used for new entity ids"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn insert_or_spawn_batch() {
|
||||
let mut world = World::default();
|
||||
let e0 = world.spawn().insert(A(0)).id();
|
||||
let e1 = Entity::new(1);
|
||||
|
||||
let values = vec![(e0, (B(0), C)), (e1, (B(1), C))];
|
||||
|
||||
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().insert(A(0)).id();
|
||||
let e1 = Entity::new(1);
|
||||
let e2 = world.spawn().id();
|
||||
let invalid_e2 = Entity {
|
||||
generation: 1,
|
||||
id: e2.id,
|
||||
};
|
||||
|
||||
let values = vec![(e0, (B(0), C)), (e1, (B(1), C)), (invalid_e2, (B(2), C))];
|
||||
|
||||
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"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -82,6 +82,10 @@ impl<I: SparseSetIndex, V> SparseArray<I, V> {
|
|||
*value = Some(func());
|
||||
value.as_mut().unwrap()
|
||||
}
|
||||
|
||||
pub fn clear(&mut self) {
|
||||
self.values.clear();
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -102,6 +106,13 @@ impl ComponentSparseSet {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn clear(&mut self) {
|
||||
self.dense.clear();
|
||||
self.ticks.clear();
|
||||
self.entities.clear();
|
||||
self.sparse.clear();
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn len(&self) -> usize {
|
||||
self.dense.len()
|
||||
|
@ -400,6 +411,12 @@ impl SparseSets {
|
|||
self.sets.get_mut(component_id)
|
||||
}
|
||||
|
||||
pub fn clear(&mut self) {
|
||||
for set in self.sets.values_mut() {
|
||||
set.clear();
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn check_change_ticks(&mut self, change_tick: u32) {
|
||||
for set in self.sets.values_mut() {
|
||||
set.check_change_ticks(change_tick);
|
||||
|
|
|
@ -179,6 +179,11 @@ impl Column {
|
|||
self.ticks.get_unchecked(row).get()
|
||||
}
|
||||
|
||||
pub fn clear(&mut self) {
|
||||
self.data.clear();
|
||||
self.ticks.clear();
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn check_change_ticks(&mut self, change_tick: u32) {
|
||||
for component_ticks in &mut self.ticks {
|
||||
|
@ -396,6 +401,13 @@ impl Table {
|
|||
pub fn iter(&self) -> impl Iterator<Item = &Column> {
|
||||
self.columns.values()
|
||||
}
|
||||
|
||||
pub fn clear(&mut self) {
|
||||
self.entities.clear();
|
||||
for column in self.columns.values_mut() {
|
||||
column.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Tables {
|
||||
|
@ -475,6 +487,16 @@ impl Tables {
|
|||
self.tables.iter()
|
||||
}
|
||||
|
||||
pub fn iter_mut(&mut self) -> std::slice::IterMut<'_, Table> {
|
||||
self.tables.iter_mut()
|
||||
}
|
||||
|
||||
pub fn clear(&mut self) {
|
||||
for table in self.tables.iter_mut() {
|
||||
table.clear();
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn check_change_ticks(&mut self, change_tick: u32) {
|
||||
for table in self.tables.iter_mut() {
|
||||
table.check_change_ticks(change_tick);
|
||||
|
|
|
@ -6,7 +6,7 @@ use crate::{
|
|||
entity::{Entities, Entity},
|
||||
world::World,
|
||||
};
|
||||
use bevy_utils::tracing::debug;
|
||||
use bevy_utils::tracing::{debug, error};
|
||||
pub use command_queue::CommandQueue;
|
||||
use std::marker::PhantomData;
|
||||
|
||||
|
@ -58,6 +58,27 @@ impl<'w, 's> Commands<'w, 's> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Returns an [EntityCommands] for the given `entity` (if it exists) or spawns one if it doesn't exist.
|
||||
/// This will return [None] if the `entity` exists with a different generation.
|
||||
///
|
||||
/// # Note
|
||||
/// Spawning a specific `entity` value is rarely the right choice. Most apps should favor [`Commands::spawn`].
|
||||
/// 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).
|
||||
pub fn get_or_spawn<'a>(&'a mut self, entity: Entity) -> EntityCommands<'w, 's, 'a> {
|
||||
self.add(GetOrSpawn { entity });
|
||||
EntityCommands {
|
||||
entity,
|
||||
commands: self,
|
||||
}
|
||||
}
|
||||
|
||||
/// Spawns a [Bundle] without pre-allocating an [Entity]. The [Entity] will be allocated when
|
||||
/// this [Command] is applied.
|
||||
pub fn spawn_and_forget(&mut self, bundle: impl Bundle) {
|
||||
self.queue.push(Spawn { bundle })
|
||||
}
|
||||
|
||||
/// Creates a new entity with the components contained in `bundle`.
|
||||
///
|
||||
/// This returns an [`EntityCommands`] builder, which enables inserting more components and
|
||||
|
@ -140,6 +161,23 @@ impl<'w, 's> Commands<'w, 's> {
|
|||
self.queue.push(SpawnBatch { bundles_iter });
|
||||
}
|
||||
|
||||
/// For a given batch of ([Entity], [Bundle]) pairs, either spawns each [Entity] with the given
|
||||
/// bundle (if the entity does not exist), or inserts the [Bundle] (if the entity already exists).
|
||||
/// This is faster than doing equivalent operations one-by-one.
|
||||
///
|
||||
/// # 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).
|
||||
pub fn insert_or_spawn_batch<I, B>(&mut self, bundles_iter: I)
|
||||
where
|
||||
I: IntoIterator + Send + Sync + 'static,
|
||||
I::IntoIter: Iterator<Item = (Entity, B)>,
|
||||
B: Bundle,
|
||||
{
|
||||
self.queue.push(InsertOrSpawnBatch { bundles_iter });
|
||||
}
|
||||
|
||||
/// See [`World::insert_resource`].
|
||||
pub fn insert_resource<T: Component>(&mut self, resource: T) {
|
||||
self.queue.push(InsertResource { resource })
|
||||
|
@ -271,6 +309,16 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
pub struct GetOrSpawn {
|
||||
entity: Entity,
|
||||
}
|
||||
|
||||
impl Command for GetOrSpawn {
|
||||
fn write(self, world: &mut World) {
|
||||
world.get_or_spawn(self.entity);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SpawnBatch<I>
|
||||
where
|
||||
I: IntoIterator,
|
||||
|
@ -289,6 +337,32 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
pub struct InsertOrSpawnBatch<I, B>
|
||||
where
|
||||
I: IntoIterator + Send + Sync + 'static,
|
||||
B: Bundle,
|
||||
I::IntoIter: Iterator<Item = (Entity, B)>,
|
||||
{
|
||||
pub bundles_iter: I,
|
||||
}
|
||||
|
||||
impl<I, B> Command for InsertOrSpawnBatch<I, B>
|
||||
where
|
||||
I: IntoIterator + Send + Sync + 'static,
|
||||
B: Bundle,
|
||||
I::IntoIter: Iterator<Item = (Entity, B)>,
|
||||
{
|
||||
fn write(self, world: &mut World) {
|
||||
if let Err(invalid_entities) = world.insert_or_spawn_batch(self.bundles_iter) {
|
||||
error!(
|
||||
"Failed to 'insert or spawn' bundle of type {} into the following invalid entities: {:?}",
|
||||
std::any::type_name::<B>(),
|
||||
invalid_entities
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Despawn {
|
||||
pub entity: Entity,
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use crate::{
|
||||
archetype::{Archetype, ArchetypeId, Archetypes, ComponentStatus},
|
||||
archetype::{Archetype, ArchetypeId, Archetypes},
|
||||
bundle::{Bundle, BundleInfo},
|
||||
change_detection::Ticks,
|
||||
component::{Component, ComponentId, ComponentTicks, Components, StorageType},
|
||||
|
@ -189,112 +189,29 @@ impl<'w> EntityMut<'w> {
|
|||
})
|
||||
}
|
||||
|
||||
/// # Safety:
|
||||
/// Partially moves the entity to a new archetype based on the provided bundle info
|
||||
/// You must handle the other part of moving the entity yourself
|
||||
unsafe fn get_insert_bundle_info<'a>(
|
||||
entities: &mut Entities,
|
||||
archetypes: &'a mut Archetypes,
|
||||
components: &mut Components,
|
||||
storages: &mut Storages,
|
||||
bundle_info: &BundleInfo,
|
||||
current_location: EntityLocation,
|
||||
entity: Entity,
|
||||
) -> (&'a Archetype, &'a Vec<ComponentStatus>, EntityLocation) {
|
||||
// SAFE: component ids in `bundle_info` and self.location are valid
|
||||
let new_archetype_id = add_bundle_to_archetype(
|
||||
archetypes,
|
||||
storages,
|
||||
components,
|
||||
current_location.archetype_id,
|
||||
bundle_info,
|
||||
);
|
||||
if new_archetype_id == current_location.archetype_id {
|
||||
let archetype = &archetypes[current_location.archetype_id];
|
||||
let edge = archetype.edges().get_add_bundle(bundle_info.id).unwrap();
|
||||
(archetype, &edge.bundle_status, current_location)
|
||||
} else {
|
||||
let (old_table_row, old_table_id) = {
|
||||
let old_archetype = &mut archetypes[current_location.archetype_id];
|
||||
let result = old_archetype.swap_remove(current_location.index);
|
||||
if let Some(swapped_entity) = result.swapped_entity {
|
||||
entities.meta[swapped_entity.id as usize].location = current_location;
|
||||
}
|
||||
(result.table_row, old_archetype.table_id())
|
||||
};
|
||||
|
||||
let new_table_id = archetypes[new_archetype_id].table_id();
|
||||
|
||||
let new_location = if old_table_id == new_table_id {
|
||||
archetypes[new_archetype_id].allocate(entity, old_table_row)
|
||||
} else {
|
||||
let (old_table, new_table) = storages.tables.get_2_mut(old_table_id, new_table_id);
|
||||
// PERF: store "non bundle" components in edge, then just move those to avoid
|
||||
// redundant copies
|
||||
let move_result = old_table.move_to_superset_unchecked(old_table_row, new_table);
|
||||
|
||||
let new_location =
|
||||
archetypes[new_archetype_id].allocate(entity, move_result.new_row);
|
||||
// if an entity was moved into this entity's table spot, update its table row
|
||||
if let Some(swapped_entity) = move_result.swapped_entity {
|
||||
let swapped_location = entities.get(swapped_entity).unwrap();
|
||||
archetypes[swapped_location.archetype_id]
|
||||
.set_entity_table_row(swapped_location.index, old_table_row);
|
||||
}
|
||||
new_location
|
||||
};
|
||||
|
||||
entities.meta[entity.id as usize].location = new_location;
|
||||
let (old_archetype, new_archetype) =
|
||||
archetypes.get_2_mut(current_location.archetype_id, new_archetype_id);
|
||||
let edge = old_archetype
|
||||
.edges()
|
||||
.get_add_bundle(bundle_info.id)
|
||||
.unwrap();
|
||||
(&*new_archetype, &edge.bundle_status, new_location)
|
||||
|
||||
// Sparse set components are intentionally ignored here. They don't need to move
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: move relevant methods to World (add/remove bundle)
|
||||
pub fn insert_bundle<T: Bundle>(&mut self, bundle: T) -> &mut Self {
|
||||
let change_tick = self.world.change_tick();
|
||||
let bundle_info = self
|
||||
.world
|
||||
.bundles
|
||||
.init_info::<T>(&mut self.world.components);
|
||||
|
||||
let (archetype, bundle_status, new_location) = unsafe {
|
||||
Self::get_insert_bundle_info(
|
||||
&mut self.world.entities,
|
||||
&mut self.world.archetypes,
|
||||
&mut self.world.components,
|
||||
&mut self.world.storages,
|
||||
bundle_info,
|
||||
self.location,
|
||||
self.entity,
|
||||
)
|
||||
};
|
||||
self.location = new_location;
|
||||
|
||||
let table = &mut self.world.storages.tables[archetype.table_id()];
|
||||
let table_row = archetype.entity_table_row(new_location.index);
|
||||
// SAFE: table row is valid
|
||||
let mut bundle_inserter = bundle_info.get_bundle_inserter(
|
||||
&mut self.world.entities,
|
||||
&mut self.world.archetypes,
|
||||
&mut self.world.components,
|
||||
&mut self.world.storages,
|
||||
self.location.archetype_id,
|
||||
change_tick,
|
||||
);
|
||||
// SAFE: location matches current entity. `T` matches `bundle_info`
|
||||
unsafe {
|
||||
bundle_info.write_components(
|
||||
&mut self.world.storages.sparse_sets,
|
||||
self.entity,
|
||||
table,
|
||||
table_row,
|
||||
bundle_status,
|
||||
bundle,
|
||||
change_tick,
|
||||
)
|
||||
};
|
||||
self.location = bundle_inserter.insert(self.entity, self.location.index, bundle);
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
// TODO: move to BundleInfo
|
||||
pub fn remove_bundle<T: Bundle>(&mut self) -> Option<T> {
|
||||
let archetypes = &mut self.world.archetypes;
|
||||
let storages = &mut self.world.storages;
|
||||
|
@ -415,6 +332,7 @@ impl<'w> EntityMut<'w> {
|
|||
entities.meta[entity.id as usize].location = new_location;
|
||||
}
|
||||
|
||||
// TODO: move to BundleInfo
|
||||
/// Remove any components in the bundle that the entity has.
|
||||
pub fn remove_bundle_intersection<T: Bundle>(&mut self) {
|
||||
let archetypes = &mut self.world.archetypes;
|
||||
|
@ -545,6 +463,7 @@ impl<'w> EntityMut<'w> {
|
|||
}
|
||||
}
|
||||
|
||||
// TODO: move to Storages?
|
||||
/// # Safety
|
||||
/// `entity_location` must be within bounds of the given archetype and `entity` must exist inside
|
||||
/// the archetype
|
||||
|
@ -574,6 +493,7 @@ unsafe fn get_component(
|
|||
}
|
||||
}
|
||||
|
||||
// TODO: move to Storages?
|
||||
/// # Safety
|
||||
/// Caller must ensure that `component_id` is valid
|
||||
#[inline]
|
||||
|
@ -604,6 +524,7 @@ unsafe fn get_component_and_ticks(
|
|||
}
|
||||
}
|
||||
|
||||
// TODO: move to Storages?
|
||||
/// Moves component data out of storage.
|
||||
///
|
||||
/// This function leaves the underlying memory unchanged, but the component behind
|
||||
|
@ -685,95 +606,6 @@ fn contains_component_with_id(
|
|||
world.archetypes[location.archetype_id].contains(component_id)
|
||||
}
|
||||
|
||||
/// Adds a bundle to the given archetype and returns the resulting archetype. This could be the same
|
||||
/// [ArchetypeId], in the event that adding the given bundle does not result in an Archetype change.
|
||||
/// Results are cached in the Archetype Graph to avoid redundant work.
|
||||
///
|
||||
/// # Safety
|
||||
/// components in `bundle_info` must exist
|
||||
pub(crate) unsafe fn add_bundle_to_archetype(
|
||||
archetypes: &mut Archetypes,
|
||||
storages: &mut Storages,
|
||||
components: &mut Components,
|
||||
archetype_id: ArchetypeId,
|
||||
bundle_info: &BundleInfo,
|
||||
) -> ArchetypeId {
|
||||
if let Some(add_bundle) = archetypes[archetype_id]
|
||||
.edges()
|
||||
.get_add_bundle(bundle_info.id)
|
||||
{
|
||||
return add_bundle.archetype_id;
|
||||
}
|
||||
let mut new_table_components = Vec::new();
|
||||
let mut new_sparse_set_components = Vec::new();
|
||||
let mut bundle_status = Vec::with_capacity(bundle_info.component_ids.len());
|
||||
|
||||
let current_archetype = &mut archetypes[archetype_id];
|
||||
for component_id in bundle_info.component_ids.iter().cloned() {
|
||||
if current_archetype.contains(component_id) {
|
||||
bundle_status.push(ComponentStatus::Mutated);
|
||||
} else {
|
||||
bundle_status.push(ComponentStatus::Added);
|
||||
let component_info = components.get_info_unchecked(component_id);
|
||||
match component_info.storage_type() {
|
||||
StorageType::Table => new_table_components.push(component_id),
|
||||
StorageType::SparseSet => {
|
||||
storages.sparse_sets.get_or_insert(component_info);
|
||||
new_sparse_set_components.push(component_id)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if new_table_components.is_empty() && new_sparse_set_components.is_empty() {
|
||||
let edges = current_archetype.edges_mut();
|
||||
// the archetype does not change when we add this bundle
|
||||
edges.set_add_bundle(bundle_info.id, archetype_id, bundle_status);
|
||||
archetype_id
|
||||
} else {
|
||||
let table_id;
|
||||
let table_components;
|
||||
let sparse_set_components;
|
||||
// the archetype changes when we add this bundle. prepare the new archetype and storages
|
||||
{
|
||||
let current_archetype = &archetypes[archetype_id];
|
||||
table_components = if new_table_components.is_empty() {
|
||||
// if there are no new table components, we can keep using this table
|
||||
table_id = current_archetype.table_id();
|
||||
current_archetype.table_components().to_vec()
|
||||
} else {
|
||||
new_table_components.extend(current_archetype.table_components());
|
||||
// sort to ignore order while hashing
|
||||
new_table_components.sort();
|
||||
// SAFE: all component ids in `new_table_components` exist
|
||||
table_id = storages
|
||||
.tables
|
||||
.get_id_or_insert(&new_table_components, components);
|
||||
|
||||
new_table_components
|
||||
};
|
||||
|
||||
sparse_set_components = if new_sparse_set_components.is_empty() {
|
||||
current_archetype.sparse_set_components().to_vec()
|
||||
} else {
|
||||
new_sparse_set_components.extend(current_archetype.sparse_set_components());
|
||||
// sort to ignore order while hashing
|
||||
new_sparse_set_components.sort();
|
||||
new_sparse_set_components
|
||||
};
|
||||
};
|
||||
let new_archetype_id =
|
||||
archetypes.get_id_or_insert(table_id, table_components, sparse_set_components);
|
||||
// add an edge from the old archetype to the new archetype
|
||||
archetypes[archetype_id].edges_mut().set_add_bundle(
|
||||
bundle_info.id,
|
||||
new_archetype_id,
|
||||
bundle_status,
|
||||
);
|
||||
new_archetype_id
|
||||
}
|
||||
}
|
||||
|
||||
/// Removes a bundle from the given archetype and returns the resulting archetype (or None if the
|
||||
/// removal was invalid). in the event that adding the given bundle does not result in an Archetype
|
||||
/// change. Results are cached in the Archetype Graph to avoid redundant work.
|
||||
|
@ -828,7 +660,7 @@ unsafe fn remove_bundle_from_archetype(
|
|||
// graph
|
||||
current_archetype
|
||||
.edges_mut()
|
||||
.set_remove_bundle(bundle_info.id, None);
|
||||
.insert_remove_bundle(bundle_info.id, None);
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
@ -867,11 +699,11 @@ unsafe fn remove_bundle_from_archetype(
|
|||
if intersection {
|
||||
current_archetype
|
||||
.edges_mut()
|
||||
.set_remove_bundle_intersection(bundle_info.id, result);
|
||||
.insert_remove_bundle_intersection(bundle_info.id, result);
|
||||
} else {
|
||||
current_archetype
|
||||
.edges_mut()
|
||||
.set_remove_bundle(bundle_info.id, result);
|
||||
.insert_remove_bundle(bundle_info.id, result);
|
||||
}
|
||||
result
|
||||
}
|
||||
|
|
|
@ -9,13 +9,13 @@ pub use world_cell::*;
|
|||
|
||||
use crate::{
|
||||
archetype::{ArchetypeComponentId, ArchetypeComponentInfo, ArchetypeId, Archetypes},
|
||||
bundle::{Bundle, Bundles},
|
||||
bundle::{Bundle, BundleInserter, BundleSpawner, Bundles},
|
||||
change_detection::Ticks,
|
||||
component::{
|
||||
Component, ComponentDescriptor, ComponentId, ComponentTicks, Components, ComponentsError,
|
||||
StorageType,
|
||||
},
|
||||
entity::{Entities, Entity},
|
||||
entity::{AllocAtWithoutReplacement, Entities, Entity},
|
||||
query::{FilterFetch, QueryState, WorldQuery},
|
||||
storage::{Column, SparseSet, Storages},
|
||||
};
|
||||
|
@ -93,6 +93,12 @@ impl World {
|
|||
&self.entities
|
||||
}
|
||||
|
||||
/// Retrieves this world's [Entities] collection mutably
|
||||
#[inline]
|
||||
pub fn entities_mut(&mut self) -> &mut Entities {
|
||||
&mut self.entities
|
||||
}
|
||||
|
||||
/// Retrieves this world's [Archetypes] collection
|
||||
#[inline]
|
||||
pub fn archetypes(&self) -> &Archetypes {
|
||||
|
@ -214,6 +220,29 @@ impl World {
|
|||
self.get_entity_mut(entity).expect("Entity does not exist")
|
||||
}
|
||||
|
||||
/// Returns an [EntityMut] for the given `entity` (if it exists) or spawns one if it doesn't exist.
|
||||
/// This will return [None] if the `entity` exists with a different generation.
|
||||
///
|
||||
/// # Note
|
||||
/// Spawning a specific `entity` value is rarely the right choice. Most apps should favor [`World::spawn`].
|
||||
/// 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).
|
||||
#[inline]
|
||||
pub fn get_or_spawn(&mut self, entity: Entity) -> Option<EntityMut> {
|
||||
self.flush();
|
||||
match self.entities.alloc_at_without_replacement(entity) {
|
||||
AllocAtWithoutReplacement::Exists(location) => {
|
||||
// SAFE: `entity` exists and `location` is that entity's location
|
||||
Some(unsafe { EntityMut::new(self, entity, location) })
|
||||
}
|
||||
AllocAtWithoutReplacement::DidNotExist => {
|
||||
// SAFE: entity was just allocated
|
||||
Some(unsafe { self.spawn_at_internal(entity) })
|
||||
}
|
||||
AllocAtWithoutReplacement::ExistsWithWrongGeneration => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Retrieves an [EntityRef] that exposes read-only operations for the given `entity`.
|
||||
/// Returns [None] if the `entity` does not exist. Use [World::entity] if you don't want
|
||||
/// to unwrap the [EntityRef] yourself.
|
||||
|
@ -292,20 +321,25 @@ impl World {
|
|||
pub fn spawn(&mut self) -> EntityMut {
|
||||
self.flush();
|
||||
let entity = self.entities.alloc();
|
||||
// SAFE: entity was just allocated
|
||||
unsafe { self.spawn_at_internal(entity) }
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
/// must be called on an entity that was just allocated
|
||||
unsafe fn spawn_at_internal(&mut self, entity: Entity) -> EntityMut {
|
||||
let archetype = self.archetypes.empty_mut();
|
||||
unsafe {
|
||||
// PERF: consider avoiding allocating entities in the empty archetype unless needed
|
||||
let table_row = self.storages.tables[archetype.table_id()].allocate(entity);
|
||||
// SAFE: no components are allocated by archetype.allocate() because the archetype is
|
||||
// empty
|
||||
let location = archetype.allocate(entity, table_row);
|
||||
// SAFE: entity index was just allocated
|
||||
self.entities
|
||||
.meta
|
||||
.get_unchecked_mut(entity.id() as usize)
|
||||
.location = location;
|
||||
EntityMut::new(self, entity, location)
|
||||
}
|
||||
// PERF: consider avoiding allocating entities in the empty archetype unless needed
|
||||
let table_row = self.storages.tables[archetype.table_id()].allocate(entity);
|
||||
// SAFE: no components are allocated by archetype.allocate() because the archetype is
|
||||
// empty
|
||||
let location = archetype.allocate(entity, table_row);
|
||||
// SAFE: entity index was just allocated
|
||||
self.entities
|
||||
.meta
|
||||
.get_unchecked_mut(entity.id() as usize)
|
||||
.location = location;
|
||||
EntityMut::new(self, entity, location)
|
||||
}
|
||||
|
||||
/// Spawns a batch of entities with the same component [Bundle] type. Takes a given [Bundle]
|
||||
|
@ -669,6 +703,127 @@ impl World {
|
|||
self.get_non_send_unchecked_mut_with_id(component_id)
|
||||
}
|
||||
|
||||
/// For a given batch of ([Entity], [Bundle]) pairs, either spawns each [Entity] with the given
|
||||
/// bundle (if the entity does not exist), or inserts the [Bundle] (if the entity already exists).
|
||||
/// This is faster than doing equivalent operations one-by-one.
|
||||
/// Returns [Ok] if all entities were successfully inserted into or spawned. Otherwise it returns an [Err]
|
||||
/// with a list of entities that could not be spawned or inserted into. A "spawn or insert" operation can
|
||||
/// only fail if an [Entity] is passed in with an "invalid generation" that conflicts with an existing [Entity].
|
||||
///
|
||||
/// # Note
|
||||
/// Spawning a specific `entity` value is rarely the right choice. Most apps should use [`World::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).
|
||||
///
|
||||
/// ```
|
||||
/// use bevy_ecs::{entity::Entity, world::World};
|
||||
///
|
||||
/// let mut world = World::new();
|
||||
/// let e0 = world.spawn().id();
|
||||
/// let e1 = world.spawn().id();
|
||||
/// world.insert_or_spawn_batch(vec![
|
||||
/// (e0, ("a", 0.0)), // the first entity
|
||||
/// (e1, ("b", 1.0)), // the second entity
|
||||
/// ]);
|
||||
///
|
||||
/// assert_eq!(world.get::<f64>(e0), Some(&0.0));
|
||||
/// ```
|
||||
pub fn insert_or_spawn_batch<I, B>(&mut self, iter: I) -> Result<(), Vec<Entity>>
|
||||
where
|
||||
I: IntoIterator,
|
||||
I::IntoIter: Iterator<Item = (Entity, B)>,
|
||||
B: Bundle,
|
||||
{
|
||||
self.flush();
|
||||
|
||||
let iter = iter.into_iter();
|
||||
let change_tick = *self.change_tick.get_mut();
|
||||
|
||||
let bundle_info = self.bundles.init_info::<B>(&mut self.components);
|
||||
enum SpawnOrInsert<'a, 'b> {
|
||||
Spawn(BundleSpawner<'a, 'b>),
|
||||
Insert(BundleInserter<'a, 'b>, ArchetypeId),
|
||||
}
|
||||
|
||||
impl<'a, 'b> SpawnOrInsert<'a, 'b> {
|
||||
fn entities(&mut self) -> &mut Entities {
|
||||
match self {
|
||||
SpawnOrInsert::Spawn(spawner) => spawner.entities,
|
||||
SpawnOrInsert::Insert(inserter, _) => inserter.entities,
|
||||
}
|
||||
}
|
||||
}
|
||||
let mut spawn_or_insert = SpawnOrInsert::Spawn(bundle_info.get_bundle_spawner(
|
||||
&mut self.entities,
|
||||
&mut self.archetypes,
|
||||
&mut self.components,
|
||||
&mut self.storages,
|
||||
change_tick,
|
||||
));
|
||||
|
||||
let mut invalid_entities = Vec::new();
|
||||
for (entity, bundle) in iter {
|
||||
match spawn_or_insert
|
||||
.entities()
|
||||
.alloc_at_without_replacement(entity)
|
||||
{
|
||||
AllocAtWithoutReplacement::Exists(location) => {
|
||||
match spawn_or_insert {
|
||||
SpawnOrInsert::Insert(ref mut inserter, archetype)
|
||||
if location.archetype_id == archetype =>
|
||||
{
|
||||
// SAFE: `entity` is valid, `location` matches entity, bundle matches inserter
|
||||
unsafe { inserter.insert(entity, location.index, bundle) };
|
||||
}
|
||||
_ => {
|
||||
let mut inserter = bundle_info.get_bundle_inserter(
|
||||
&mut self.entities,
|
||||
&mut self.archetypes,
|
||||
&mut self.components,
|
||||
&mut self.storages,
|
||||
location.archetype_id,
|
||||
change_tick,
|
||||
);
|
||||
// SAFE: `entity` is valid, `location` matches entity, bundle matches inserter
|
||||
unsafe { inserter.insert(entity, location.index, bundle) };
|
||||
spawn_or_insert =
|
||||
SpawnOrInsert::Insert(inserter, location.archetype_id);
|
||||
}
|
||||
};
|
||||
}
|
||||
AllocAtWithoutReplacement::DidNotExist => {
|
||||
match spawn_or_insert {
|
||||
SpawnOrInsert::Spawn(ref mut spawner) => {
|
||||
// SAFE: `entity` is allocated (but non existent), bundle matches inserter
|
||||
unsafe { spawner.spawn_non_existent(entity, bundle) };
|
||||
}
|
||||
_ => {
|
||||
let mut spawner = bundle_info.get_bundle_spawner(
|
||||
&mut self.entities,
|
||||
&mut self.archetypes,
|
||||
&mut self.components,
|
||||
&mut self.storages,
|
||||
change_tick,
|
||||
);
|
||||
// SAFE: `entity` is valid, `location` matches entity, bundle matches inserter
|
||||
unsafe { spawner.spawn_non_existent(entity, bundle) };
|
||||
spawn_or_insert = SpawnOrInsert::Spawn(spawner);
|
||||
}
|
||||
};
|
||||
}
|
||||
AllocAtWithoutReplacement::ExistsWithWrongGeneration => {
|
||||
invalid_entities.push(entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if invalid_entities.is_empty() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(invalid_entities)
|
||||
}
|
||||
}
|
||||
|
||||
/// Temporarily removes the requested resource from this [World], then re-adds it before
|
||||
/// returning. This enables safe mutable access to a resource while still providing mutable
|
||||
/// world access
|
||||
|
@ -808,7 +963,7 @@ impl World {
|
|||
let resource_archetype = self
|
||||
.archetypes
|
||||
.archetypes
|
||||
.get_unchecked_mut(ArchetypeId::resource().index());
|
||||
.get_unchecked_mut(ArchetypeId::RESOURCE.index());
|
||||
let resource_archetype_components = &mut resource_archetype.components;
|
||||
let archetype_component_count = &mut self.archetypes.archetype_component_count;
|
||||
let components = &self.components;
|
||||
|
@ -877,6 +1032,7 @@ impl World {
|
|||
unsafe {
|
||||
let table = &mut self.storages.tables[empty_archetype.table_id()];
|
||||
// PERF: consider pre-allocating space for flushed entities
|
||||
// SAFE: entity is set to a valid location
|
||||
self.entities.flush(|entity, location| {
|
||||
// SAFE: no components are allocated by archetype.allocate() because the archetype
|
||||
// is empty
|
||||
|
@ -916,6 +1072,13 @@ impl World {
|
|||
column.check_change_ticks(change_tick);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clear_entities(&mut self) {
|
||||
self.storages.tables.clear();
|
||||
self.storages.sparse_sets.clear();
|
||||
self.archetypes.clear_entities();
|
||||
self.entities.clear();
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for World {
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
use crate::{
|
||||
archetype::{Archetype, ArchetypeId, ComponentStatus},
|
||||
bundle::{Bundle, BundleInfo},
|
||||
entity::{Entities, Entity},
|
||||
storage::{SparseSets, Table},
|
||||
world::{add_bundle_to_archetype, World},
|
||||
bundle::{Bundle, BundleSpawner},
|
||||
entity::Entity,
|
||||
world::World,
|
||||
};
|
||||
|
||||
pub struct SpawnBatchIter<'w, I>
|
||||
|
@ -12,13 +10,7 @@ where
|
|||
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,
|
||||
spawner: BundleSpawner<'w, 'w>,
|
||||
}
|
||||
|
||||
impl<'w, I> SpawnBatchIter<'w, I>
|
||||
|
@ -33,40 +25,22 @@ where
|
|||
world.flush();
|
||||
|
||||
let (lower, upper) = iter.size_hint();
|
||||
let length = upper.unwrap_or(lower);
|
||||
|
||||
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();
|
||||
let mut spawner = bundle_info.get_bundle_spawner(
|
||||
&mut world.entities,
|
||||
&mut world.archetypes,
|
||||
&mut world.components,
|
||||
&mut world.storages,
|
||||
*world.change_tick.get_mut(),
|
||||
);
|
||||
spawner.reserve_storage(length);
|
||||
|
||||
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,
|
||||
spawner,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -90,24 +64,8 @@ where
|
|||
|
||||
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)
|
||||
// SAFE: bundle matches spawner type
|
||||
unsafe { Some(self.spawner.spawn(bundle)) }
|
||||
}
|
||||
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
|
|
|
@ -296,7 +296,7 @@ mod tests {
|
|||
.components
|
||||
.get_resource_id(TypeId::of::<u32>())
|
||||
.unwrap();
|
||||
let resource_archetype = world.archetypes.get(ArchetypeId::resource()).unwrap();
|
||||
let resource_archetype = world.archetypes.get(ArchetypeId::RESOURCE).unwrap();
|
||||
let u32_archetype_component_id = resource_archetype
|
||||
.get_archetype_component_id(u32_component_id)
|
||||
.unwrap();
|
||||
|
|
Loading…
Reference in a new issue