Skip to content
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

Use single hash tables for compoment insertion/removal edges to improve spatial locality. #225

Merged
merged 3 commits into from
Dec 26, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions benches/bench.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,21 @@ fn insert(b: &mut Bencher) {
});
}

fn insert_remove(b: &mut Bencher) {
let mut world = World::new();
let entities = world
.spawn_batch((0..1_000).map(|_| (Position(0.0), Velocity(0.0))))
.collect::<Vec<_>>();
let mut entities = entities.iter().cycle();
b.iter(|| {
let e = *entities.next().unwrap();
world.remove_one::<Velocity>(e).unwrap();
world.insert_one(e, true).unwrap();
world.remove_one::<bool>(e).unwrap();
world.insert_one(e, Velocity(0.0)).unwrap();
});
}

fn iterate_100k(b: &mut Bencher) {
let mut world = World::new();
for i in 0..100_000 {
Expand Down Expand Up @@ -250,6 +265,7 @@ benchmark_group!(
spawn_batch,
remove,
insert,
insert_remove,
iterate_100k,
iterate_mut_100k,
iterate_uncached_100_by_50,
Expand Down
4 changes: 0 additions & 4 deletions src/archetype.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,6 @@ pub struct Archetype {
entities: Box<[u32]>,
/// One allocation per type, in the same order as `types`
data: Box<[Data]>,
/// Maps static bundle types to the archetype that an entity from this archetype is moved to
/// after removing the components from that bundle.
pub(crate) remove_edges: TypeIdMap<u32>,
}

impl Archetype {
Expand Down Expand Up @@ -70,7 +67,6 @@ impl Archetype {
storage: NonNull::new(max_align as *mut u8).unwrap(),
})
.collect(),
remove_edges: HashMap::default(),
}
}

Expand Down
52 changes: 38 additions & 14 deletions src/world.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use crate::alloc::{vec, vec::Vec};
use core::any::TypeId;
use core::borrow::Borrow;
use core::convert::TryFrom;
use core::hash::{BuildHasherDefault, Hasher};
use core::marker::PhantomData;
use spin::Mutex;

Expand Down Expand Up @@ -50,6 +51,12 @@ pub struct World {
archetypes: ArchetypeSet,
/// Maps statically-typed bundle types to archetypes
bundle_to_archetype: TypeIdMap<u32>,
/// Maps source archetype and static bundle types to the archetype that an entity is moved to
/// after inserting the components from that bundle.
insert_edges: IndexTypeIdMap<InsertTarget>,
/// Maps source archetype and static bundle types to the archetype that an entity is moved to
/// after removing the components from that bundle.
remove_edges: IndexTypeIdMap<u32>,
id: u64,
}

Expand All @@ -69,6 +76,8 @@ impl World {
entities: Entities::default(),
archetypes: ArchetypeSet::new(),
bundle_to_archetype: HashMap::default(),
insert_edges: HashMap::default(),
remove_edges: HashMap::default(),
id,
}
}
Expand Down Expand Up @@ -554,15 +563,13 @@ impl World {
.get_insert_target(loc.archetype, &components);
&target_storage
}
Some(key) => match self.archetypes.insert_edges[loc.archetype as usize].get(&key) {
Some(key) => match self.insert_edges.get(&(loc.archetype, key)) {
Some(x) => x,
None => {
let t = self
.archetypes
.get_insert_target(loc.archetype, &components);
self.archetypes.insert_edges[loc.archetype as usize]
.entry(key)
.or_insert(t)
self.insert_edges.entry((loc.archetype, key)).or_insert(t)
}
},
};
Expand Down Expand Up @@ -653,6 +660,7 @@ impl World {

// Gather current metadata
let loc = self.entities.get_mut(entity)?;
let old_archetype = loc.archetype;
let old_index = loc.index;
let source_arch = &self.archetypes.archetypes[loc.archetype as usize];

Expand All @@ -662,7 +670,7 @@ impl World {
};

// Find the target archetype ID
let target = match source_arch.remove_edges.get(&TypeId::of::<T>()) {
let target = match self.remove_edges.get(&(old_archetype, TypeId::of::<T>())) {
Some(&x) => x,
None => {
let removed = T::with_static_ids(|ids| ids.iter().copied().collect::<HashSet<_>>());
Expand All @@ -674,9 +682,8 @@ impl World {
.collect::<Vec<_>>();
let elements = info.iter().map(|x| x.id()).collect::<Box<_>>();
let index = self.archetypes.get(&*elements, move || info);
self.archetypes.archetypes[loc.archetype as usize]
.remove_edges
.insert(TypeId::of::<T>(), index);
self.remove_edges
.insert((old_archetype, TypeId::of::<T>()), index);
index
}
};
Expand Down Expand Up @@ -1123,10 +1130,6 @@ struct ArchetypeSet {
index: HashMap<Box<[TypeId]>, u32>,
archetypes: Vec<Archetype>,
generation: u64,
/// Maps static bundle types to the archetype that an entity from this archetype is moved to
/// after inserting the components from that bundle. Stored separately from archetypes to avoid
/// borrowck difficulties in `World::insert`.
insert_edges: Vec<TypeIdMap<InsertTarget>>,
}

impl ArchetypeSet {
Expand All @@ -1136,7 +1139,6 @@ impl ArchetypeSet {
index: Some((Box::default(), 0)).into_iter().collect(),
archetypes: vec![Archetype::new(Vec::new())],
generation: 0,
insert_edges: vec![HashMap::default()],
}
}

Expand Down Expand Up @@ -1193,7 +1195,6 @@ impl ArchetypeSet {
}

fn post_insert(&mut self) {
self.insert_edges.push(HashMap::default());
self.generation += 1;
}

Expand Down Expand Up @@ -1245,6 +1246,29 @@ struct InsertTarget {
index: u32,
}

type IndexTypeIdMap<V> = HashMap<(u32, TypeId), V, BuildHasherDefault<IndexTypeIdHasher>>;

#[derive(Default)]
struct IndexTypeIdHasher(u64);

impl Hasher for IndexTypeIdHasher {
fn write_u32(&mut self, index: u32) {
self.0 ^= u64::from(index);
}

fn write_u64(&mut self, type_id: u64) {
self.0 ^= type_id;
}

fn write(&mut self, _bytes: &[u8]) {
unreachable!()
}

fn finish(&self) -> u64 {
self.0
}
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down