use super::World;
use crate::archetype::{ComponentInfo, EntityLocation};
use crate::component::Component;
use crate::entity::Entity;
use std::any::TypeId;
impl World {
pub fn add_bundle<B: crate::component::Bundle>(&mut self, entity: Entity, bundle: B) {
if !self.is_alive(entity) { return; }
let eid = entity.id();
let infos = B::get_infos();
if infos
.iter()
.any(|i| i.storage_type == crate::component::StorageType::SparseSet)
{
bundle.apply(self, entity);
return;
}
for info in &infos {
self.component_infos.entry(info.type_id).or_insert_with(|| *info);
}
let old_arch_id = match self.archetype_index.entity_archetype.get(&eid) {
Some(&id) => id,
None => {
let _arch = &mut self.archetype_index.archetypes[0];
0
}
};
let mut new_types = self.archetype_index.archetypes[old_arch_id].sorted_component_types();
for info in &infos {
if let Err(pos) = new_types.binary_search(&info.type_id) {
new_types.insert(pos, info.type_id);
}
}
let target_arch_id = if let Some(&id) = self.archetype_index.set_to_id.get(&new_types) {
id
} else {
let id = self.archetype_index.archetypes.len();
let mut new_infos = Vec::new();
for &t in &new_types {
new_infos.push(self.component_infos.get(&t).cloned().unwrap());
}
self.archetype_index.archetypes.push(crate::archetype::Archetype::new(id as u32, &new_infos));
self.archetype_index.set_to_id.insert(new_types, id);
id
};
if old_arch_id == target_arch_id {
let loc = self.entity_locations[eid as usize];
let arch = &mut self.archetype_index.archetypes[target_arch_id];
unsafe { bundle.write_to_archetype(arch, loc.row as usize, self.tick); }
return;
}
let old_loc = self.entity_locations[eid as usize];
let (new_row, moved_eid) = {
let [old_arch, target_arch] = self
.archetype_index
.archetypes
.get_disjoint_mut([old_arch_id, target_arch_id])
.expect("old and target archetype indices are distinct and in bounds");
unsafe { old_arch.move_entity_to(old_loc.row as usize, target_arch) }
};
if let Some(moved) = moved_eid {
self.entity_locations[moved as usize].row = old_loc.row;
}
let arch = &mut self.archetype_index.archetypes[target_arch_id];
unsafe { bundle.write_to_archetype(arch, new_row as usize, self.tick); }
self.entity_locations[eid as usize] = EntityLocation {
archetype_id: target_arch_id as u32,
row: new_row,
};
self.archetype_index.entity_archetype.insert(eid, target_arch_id);
}
pub fn remove_bundle<B: crate::component::Bundle>(&mut self, entity: Entity) {
if !self.is_alive(entity) { return; }
let eid = entity.id();
let infos = B::get_infos();
for info in &infos {
if info.storage_type == crate::component::StorageType::SparseSet {
let removed = self
.sparse_sets
.get_mut(&info.type_id)
.is_some_and(|set| set.remove(eid));
if removed {
let tid = info.type_id;
self.run_hooks(tid, |h, w| {
for hook in &mut h.on_remove {
hook(w, entity);
}
});
}
}
}
let old_arch_id = match self.archetype_index.entity_archetype.get(&eid) {
Some(&id) => id,
None => return,
};
let mut new_types = self.archetype_index.archetypes[old_arch_id].sorted_component_types();
for info in &infos {
if let Ok(pos) = new_types.binary_search(&info.type_id) {
new_types.remove(pos);
}
}
let target_arch_id = if let Some(&id) = self.archetype_index.set_to_id.get(&new_types) {
id
} else {
let id = self.archetype_index.archetypes.len();
let mut new_infos = Vec::new();
for &t in &new_types {
new_infos.push(self.component_infos.get(&t).cloned().unwrap());
}
self.archetype_index.archetypes.push(crate::archetype::Archetype::new(id as u32, &new_infos));
self.archetype_index.set_to_id.insert(new_types, id);
id
};
if old_arch_id == target_arch_id { return; }
let old_loc = self.entity_locations[eid as usize];
let (new_row, moved_eid) = {
let [old_arch, target_arch] = self
.archetype_index
.archetypes
.get_disjoint_mut([old_arch_id, target_arch_id])
.expect("old and target archetype indices are distinct and in bounds");
unsafe { old_arch.move_entity_to(old_loc.row as usize, target_arch) }
};
if let Some(moved) = moved_eid {
self.entity_locations[moved as usize].row = old_loc.row;
}
self.entity_locations[eid as usize] = EntityLocation {
archetype_id: target_arch_id as u32,
row: new_row,
};
self.archetype_index.entity_archetype.insert(eid, target_arch_id);
}
pub fn add_component<T: Component>(&mut self, entity: Entity, component: T) {
if !self.is_alive(entity) { return; }
let eid = entity.id();
self.register_component_type::<T>();
let type_id = TypeId::of::<T>();
if T::storage_type() == crate::component::StorageType::SparseSet {
let info = self.component_infos.get(&type_id).copied().unwrap_or_else(|| ComponentInfo::of::<T>());
let set = self.sparse_sets.entry(type_id).or_insert_with(|| {
crate::archetype::sparse_set::ComponentSparseSet::new(info)
});
let existed = set.contains(eid);
let ptr = &component as *const T as *const u8;
unsafe { set.insert(eid, ptr, self.tick); }
std::mem::forget(component);
self.run_hooks(type_id, |h, w| {
if !existed {
for hook in &mut h.on_add { hook(w, entity); }
}
for hook in &mut h.on_set { hook(w, entity); }
});
return;
}
let target_arch_id =
match self
.archetype_index
.get_add_component_target(eid, type_id, &self.component_infos)
{
Some(id) => id,
None => return,
};
let old_loc = self.entity_locations[eid as usize];
if old_loc.archetype_id == target_arch_id as u32 {
{
let arch = &self.archetype_index.archetypes[target_arch_id];
let col = unsafe { arch.get_column_mut(type_id) }
.expect("component column missing in current archetype");
unsafe {
let ptr = col.get_ptr(old_loc.row as usize) as *mut T;
*ptr = component;
col.ticks_ptr_mut()
.add(old_loc.row as usize)
.write(crate::archetype::ComponentTicks::new(self.tick));
}
}
let mut hooks = self.component_hooks.remove(&type_id);
if let Some(ref mut h) = hooks {
for hook in &mut h.on_set {
hook(self, entity);
}
}
if let Some(h) = hooks {
if let Some(existing) = self.component_hooks.get_mut(&type_id) {
existing.on_add.extend(h.on_add);
existing.on_set.extend(h.on_set);
existing.on_remove.extend(h.on_remove);
} else {
self.component_hooks.insert(type_id, h);
}
}
return;
}
let (eid, old_arch_id, old_row) = (
entity.id(),
old_loc.archetype_id as usize,
old_loc.row as usize,
);
let (new_row, moved_eid) = {
let [old_arch, target_arch] = self
.archetype_index
.archetypes
.get_disjoint_mut([old_arch_id, target_arch_id])
.expect("old and target archetype indices must be distinct and in bounds");
unsafe { old_arch.move_entity_to(old_row, target_arch) }
};
if let Some(moved) = moved_eid {
self.entity_locations[moved as usize].row = old_row as u32;
}
{
let arch = &self.archetype_index.archetypes[target_arch_id];
let col = unsafe { arch.get_column_mut(type_id) }
.expect("Mandatory component column missing");
unsafe {
let ptr = col.get_ptr(new_row as usize) as *mut T;
std::ptr::write(ptr, component);
col.ticks_ptr_mut()
.add(new_row as usize)
.write(crate::archetype::ComponentTicks::new(self.tick));
}
}
self.entity_locations[eid as usize] = EntityLocation {
archetype_id: target_arch_id as u32,
row: new_row,
};
self.archetype_index
.entity_archetype
.insert(eid, target_arch_id);
let mut hooks = self.component_hooks.remove(&type_id);
if let Some(ref mut h) = hooks {
for hook in &mut h.on_add {
hook(self, entity);
}
for hook in &mut h.on_set {
hook(self, entity);
}
}
if let Some(h) = hooks {
if let Some(existing) = self.component_hooks.get_mut(&type_id) {
existing.on_add.extend(h.on_add);
existing.on_set.extend(h.on_set);
existing.on_remove.extend(h.on_remove);
} else {
self.component_hooks.insert(type_id, h);
}
}
}
pub fn get_component_ptr(&self, entity: Entity, type_id: TypeId) -> Option<*const u8> {
if let Some(set) = self.sparse_sets.get(&type_id) {
if let Some(p) = set.get_ptr(entity.id()) {
return Some(p);
}
}
let loc = self.entity_locations.get(entity.id() as usize).copied()?;
if !loc.is_valid() {
return None;
}
let arch = &self.archetype_index.archetypes[loc.archetype_id as usize];
let col = arch.get_column(type_id)?;
Some(unsafe { col.get_ptr(loc.row as usize) })
}
pub fn get_component_mut_ptr(&mut self, entity: Entity, type_id: TypeId) -> Option<*mut u8> {
if let Some(set) = self.sparse_sets.get_mut(&type_id) {
if let Some(p) = set.get_ptr_mut(entity.id()) {
return Some(p);
}
}
let loc = self.entity_locations.get(entity.id() as usize).copied()?;
if !loc.is_valid() {
return None;
}
let arch = &mut self.archetype_index.archetypes[loc.archetype_id as usize];
let col = unsafe { arch.get_column_mut(type_id) }?;
Some(unsafe { col.get_mut_ptr(loc.row as usize) })
}
pub fn remove_component<T: Component>(&mut self, entity: Entity) {
if !self.is_alive(entity) { return; }
let eid = entity.id();
let type_id = TypeId::of::<T>();
if T::storage_type() == crate::component::StorageType::SparseSet {
if let Some(set) = self.sparse_sets.get_mut(&type_id) {
if set.remove(eid) {
self.run_hooks(type_id, |h, w| {
for hook in &mut h.on_remove { hook(w, entity); }
});
}
}
return;
}
let old_loc = self.entity_locations[eid as usize];
let target_arch_id_opt =
self.archetype_index
.get_remove_component_target(eid, type_id, &self.component_infos);
let target_arch_id = match target_arch_id_opt {
Some(id) => id,
None => return, };
if old_loc.archetype_id == target_arch_id as u32 {
return; }
let (new_row, moved_eid) = {
let [old_arch, target_arch] = self
.archetype_index
.archetypes
.get_disjoint_mut([old_loc.archetype_id as usize, target_arch_id])
.expect("old and target archetype indices are distinct and in bounds");
unsafe { old_arch.move_entity_to(old_loc.row as usize, target_arch) }
};
if let Some(moved) = moved_eid {
self.entity_locations[moved as usize].row = old_loc.row;
}
self.entity_locations[eid as usize] = EntityLocation {
archetype_id: target_arch_id as u32,
row: new_row,
};
self.archetype_index
.entity_archetype
.insert(eid, target_arch_id);
self.run_hooks(type_id, |h, w| {
for hook in &mut h.on_remove {
hook(w, entity);
}
});
}
pub fn insert_batch<T: Component + Clone>(&mut self, entities: &[Entity], component: T) {
if T::storage_type() == crate::component::StorageType::SparseSet {
for &e in entities {
self.add_component(e, component.clone());
}
return;
}
self.register_component_type::<T>();
let type_id = TypeId::of::<T>();
let mut groups: std::collections::HashMap<u32, Vec<Entity>> = std::collections::HashMap::new();
for &e in entities {
if !self.is_alive(e) { continue; }
let loc = self.entity_locations[e.id() as usize];
if !loc.is_valid() { continue; }
groups.entry(loc.archetype_id).or_default().push(e);
}
for (source_arch_id, group_entities) in groups {
let target_arch_id = match self.archetype_index.get_add_component_target(
group_entities[0].id(), type_id, &self.component_infos
) {
Some(id) => id,
None => continue,
};
if source_arch_id == target_arch_id as u32 {
let arch = &self.archetype_index.archetypes[target_arch_id];
let col = unsafe { arch.get_column_mut(type_id) }.unwrap();
for e in &group_entities {
let row = self.entity_locations[e.id() as usize].row as usize;
unsafe {
*(col.get_ptr(row) as *mut T) = component.clone();
col.ticks_ptr_mut().add(row).write(crate::archetype::ComponentTicks::new(self.tick));
}
}
self.run_hooks(type_id, |h, w| {
for e in &group_entities {
for hook in &mut h.on_set {
hook(w, *e);
}
}
});
continue;
}
for e in &group_entities {
let eid = e.id();
let old_loc = self.entity_locations[eid as usize];
let old_row = old_loc.row as usize;
let (new_row, moved_eid) = {
let [old_arch, target_arch] = self
.archetype_index
.archetypes
.get_disjoint_mut([source_arch_id as usize, target_arch_id])
.expect("source and target archetype indices are distinct and in bounds");
unsafe { old_arch.move_entity_to(old_row, target_arch) }
};
if let Some(moved) = moved_eid {
self.entity_locations[moved as usize].row = old_row as u32;
}
{
let arch = &self.archetype_index.archetypes[target_arch_id];
let col = unsafe { arch.get_column_mut(type_id) }.unwrap();
unsafe {
std::ptr::write(col.get_ptr(new_row as usize) as *mut T, component.clone());
col.ticks_ptr_mut().add(new_row as usize).write(crate::archetype::ComponentTicks::new(self.tick));
}
}
self.entity_locations[eid as usize] = EntityLocation {
archetype_id: target_arch_id as u32,
row: new_row,
};
self.archetype_index.entity_archetype.insert(eid, target_arch_id);
}
self.run_hooks(type_id, |h, w| {
for e in &group_entities {
for hook in &mut h.on_add { hook(w, *e); }
for hook in &mut h.on_set { hook(w, *e); }
}
});
}
}
pub fn remove_batch<T: Component>(&mut self, entities: &[Entity]) {
if T::storage_type() == crate::component::StorageType::SparseSet {
for &e in entities {
self.remove_component::<T>(e);
}
return;
}
let type_id = TypeId::of::<T>();
let mut groups: std::collections::HashMap<u32, Vec<Entity>> = std::collections::HashMap::new();
for &e in entities {
if !self.is_alive(e) { continue; }
let loc = self.entity_locations[e.id() as usize];
if !loc.is_valid() { continue; }
groups.entry(loc.archetype_id).or_default().push(e);
}
for (source_arch_id, group_entities) in groups {
let target_arch_id = match self.archetype_index.get_remove_component_target(
group_entities[0].id(), type_id, &self.component_infos
) {
Some(id) => id,
None => continue,
};
if source_arch_id == target_arch_id as u32 {
continue;
}
for e in &group_entities {
let eid = e.id();
let old_loc = self.entity_locations[eid as usize];
let (new_row, moved_eid) = {
let [old_arch, target_arch] = self
.archetype_index
.archetypes
.get_disjoint_mut([source_arch_id as usize, target_arch_id])
.expect("source and target archetype indices are distinct and in bounds");
unsafe { old_arch.move_entity_to(old_loc.row as usize, target_arch) }
};
if let Some(moved) = moved_eid {
self.entity_locations[moved as usize].row = old_loc.row;
}
self.entity_locations[eid as usize] = EntityLocation {
archetype_id: target_arch_id as u32,
row: new_row,
};
self.archetype_index.entity_archetype.insert(eid, target_arch_id);
}
self.run_hooks(type_id, |h, w| {
for e in &group_entities {
for hook in &mut h.on_remove { hook(w, *e); }
}
});
}
}
}
#[cfg(test)]
mod tests {
use crate::component::Component;
use crate::world::World;
#[derive(Clone, PartialEq, Debug)]
struct Pos(i32);
impl Component for Pos {}
#[derive(Clone, PartialEq, Debug)]
struct Vel(i32);
impl Component for Vel {}
#[test]
fn archetype_migration_preserves_all_components() {
let mut world = World::new();
let e0 = world.spawn();
world.add_component(e0, Pos(0));
let e1 = world.spawn();
world.add_component(e1, Pos(1));
let e2 = world.spawn();
world.add_component(e2, Pos(2));
world.add_component(e1, Vel(10));
assert_eq!(world.borrow::<Pos>().get(e0.id()).unwrap().0, 0);
assert_eq!(world.borrow::<Pos>().get(e1.id()).unwrap().0, 1);
assert_eq!(world.borrow::<Pos>().get(e2.id()).unwrap().0, 2);
assert_eq!(world.borrow::<Vel>().get(e1.id()).unwrap().0, 10);
assert!(world.borrow::<Vel>().get(e0.id()).is_none());
assert!(world.borrow::<Vel>().get(e2.id()).is_none());
world.remove_component::<Vel>(e1);
assert_eq!(world.borrow::<Pos>().get(e0.id()).unwrap().0, 0);
assert_eq!(world.borrow::<Pos>().get(e1.id()).unwrap().0, 1);
assert_eq!(world.borrow::<Pos>().get(e2.id()).unwrap().0, 2);
assert!(world.borrow::<Vel>().get(e1.id()).is_none());
}
}