ecs: component removal tracking

This commit is contained in:
Carter Anderson 2020-07-22 17:56:04 -07:00
parent 141044aae7
commit f82af10a69
7 changed files with 92 additions and 27 deletions

View File

@ -19,7 +19,7 @@ impl Entity {
#[allow(missing_docs)]
#[inline]
pub fn with_id(id: u32) -> Self {
pub fn from_id(id: u32) -> Self {
Self(id)
}

View File

@ -100,7 +100,7 @@ impl<'a> Fetch<'a> for EntityFetch {
unsafe fn next(&mut self) -> Self::Item {
let id = self.0.as_ptr();
self.0 = NonNull::new_unchecked(id.add(1));
Entity::with_id(*id)
Entity::from_id(*id)
}
}

View File

@ -39,6 +39,7 @@ use crate::{
pub struct World {
entities: Entities,
index: HashMap<Vec<TypeId>, u32>,
removed_components: HashMap<TypeId, Vec<Entity>>,
archetypes: Vec<Archetype>,
archetype_generation: u64,
}
@ -56,6 +57,7 @@ impl World {
index,
archetypes,
archetype_generation: 0,
removed_components: HashMap::default(),
}
}
@ -154,8 +156,16 @@ impl World {
/// Destroy an entity and all its components
pub fn despawn(&mut self, entity: Entity) -> Result<(), NoSuchEntity> {
let loc = self.entities.free(entity)?;
if let Some(moved) = unsafe { self.archetypes[loc.archetype as usize].remove(loc.index) } {
self.entities.get_mut(Entity::with_id(moved)).unwrap().index = loc.index;
let archetype = &mut self.archetypes[loc.archetype as usize];
if let Some(moved) = unsafe { archetype.remove(loc.index) } {
self.entities.get_mut(Entity::from_id(moved)).unwrap().index = loc.index;
}
for ty in archetype.types() {
let removed_entities = self
.removed_components
.entry(ty.id())
.or_insert_with(|| Vec::new());
removed_entities.push(entity);
}
Ok(())
}
@ -186,8 +196,15 @@ impl World {
///
/// Preserves allocated storage for reuse.
pub fn clear(&mut self) {
for x in &mut self.archetypes {
x.clear();
for archetype in &mut self.archetypes {
for ty in archetype.types() {
let removed_entities = self
.removed_components
.entry(ty.id())
.or_insert_with(|| Vec::new());
removed_entities.extend(archetype.iter_entities().map(|id| Entity::from_id(*id)));
}
archetype.clear();
}
self.entities.clear();
}
@ -317,6 +334,11 @@ impl World {
Iter::new(&self.archetypes, &self.entities)
}
#[allow(missing_docs)]
pub fn removed<C: Component>(&self) -> &[Entity] {
self.removed_components.get(&TypeId::of::<C>()).map_or(&[], |entities| entities.as_slice())
}
/// Add `components` to `entity`
///
/// Computational cost is proportional to the number of components `entity` has. If an entity
@ -386,13 +408,15 @@ impl World {
let target_index = target_arch.allocate(entity.id());
loc.archetype = target;
let old_index = mem::replace(&mut loc.index, target_index);
if let Some(moved) = source_arch.move_to(old_index, |ptr, ty, size, is_added, is_mutated| {
target_arch.put_dynamic(ptr, ty, size, target_index, false);
let type_state = target_arch.get_type_state_mut(ty).unwrap();
type_state.added_entities[target_index as usize] = is_added;
type_state.mutated_entities[target_index as usize] = is_mutated;
}) {
self.entities.get_mut(Entity::with_id(moved)).unwrap().index = old_index;
if let Some(moved) =
source_arch.move_to(old_index, |ptr, ty, size, is_added, is_mutated| {
target_arch.put_dynamic(ptr, ty, size, target_index, false);
let type_state = target_arch.get_type_state_mut(ty).unwrap();
type_state.added_entities[target_index as usize] = is_added;
type_state.mutated_entities[target_index as usize] = is_mutated;
})
{
self.entities.get_mut(Entity::from_id(moved)).unwrap().index = old_index;
}
components.put(|ptr, ty, size| {
@ -467,16 +491,22 @@ impl World {
let target_index = target_arch.allocate(entity.id());
loc.archetype = target;
loc.index = target_index;
if let Some(moved) = source_arch.move_to(old_index, |src, ty, size, is_added, is_mutated| {
// Only move the components present in the target archetype, i.e. the non-removed ones.
if let Some(dst) = target_arch.get_dynamic(ty, size, target_index) {
ptr::copy_nonoverlapping(src, dst.as_ptr(), size);
let state = target_arch.get_type_state_mut(ty).unwrap();
state.added_entities[target_index as usize] = is_added;
state.mutated_entities[target_index as usize] = is_mutated;
}
}) {
self.entities.get_mut(Entity::with_id(moved)).unwrap().index = old_index;
let removed_components = &mut self.removed_components;
if let Some(moved) =
source_arch.move_to(old_index, |src, ty, size, is_added, is_mutated| {
// Only move the components present in the target archetype, i.e. the non-removed ones.
if let Some(dst) = target_arch.get_dynamic(ty, size, target_index) {
ptr::copy_nonoverlapping(src, dst.as_ptr(), size);
let state = target_arch.get_type_state_mut(ty).unwrap();
state.added_entities[target_index as usize] = is_added;
state.mutated_entities[target_index as usize] = is_mutated;
} else {
let removed_entities = removed_components.entry(ty).or_insert_with(|| Vec::new());
removed_entities.push(entity);
}
})
{
self.entities.get_mut(Entity::from_id(moved)).unwrap().index = old_index;
}
Ok(bundle)
}
@ -567,11 +597,13 @@ impl World {
self.entities.get(entity).ok()
}
/// Clears each entity's tracker state. For example, each entity's component "mutated" state will be reset to `false`.
/// Clears each entity's tracker state. For example, each entity's component "mutated" state will be reset to `false`.
pub fn clear_trackers(&mut self) {
for archetype in self.archetypes.iter_mut() {
archetype.clear_trackers();
}
self.removed_components.clear();
}
}
@ -680,7 +712,7 @@ impl<'a> Iterator for Iter<'a> {
let index = self.index;
self.index += 1;
let id = current.entity_id(index);
return Some((Entity::with_id(id), unsafe {
return Some((Entity::from_id(id), unsafe {
EntityRef::new(current, index)
}));
}

View File

@ -335,3 +335,32 @@ fn query_one() {
world.despawn(a).unwrap();
assert!(world.query_one::<&i32>(a).is_err());
}
#[test]
fn remove_tracking() {
let mut world = World::new();
let a = world.spawn(("abc", 123));
let b = world.spawn(("abc", 123));
world.despawn(a).unwrap();
assert_eq!(world.removed::<i32>(), &[a], "despawning results in 'removed component' state");
assert_eq!(world.removed::<&'static str>(), &[a], "despawning results in 'removed component' state");
world.insert_one(b, 10.0).unwrap();
assert_eq!(world.removed::<i32>(), &[a], "archetype moves does not result in 'removed component' state");
world.remove_one::<i32>(b).unwrap();
assert_eq!(world.removed::<i32>(), &[a, b], "removing a component results in a 'removed component' state");
world.clear_trackers();
assert_eq!(world.removed::<i32>(), &[], "clearning trackers clears removals");
assert_eq!(world.removed::<&'static str>(), &[], "clearning trackers clears removals");
assert_eq!(world.removed::<f64>(), &[], "clearning trackers clears removals");
let c = world.spawn(("abc", 123));
let d = world.spawn(("abc", 123));
world.clear();
assert_eq!(world.removed::<i32>(), &[c, d], "world clears result in 'removed component' states");
assert_eq!(world.removed::<&'static str>(), &[c, d, b], "world clears result in 'removed component' states");
assert_eq!(world.removed::<f64>(), &[b], "world clears result in 'removed component' states");
}

View File

@ -200,6 +200,10 @@ impl<'a, Q: HecsQuery> Query<'a, Q> {
}
}
pub fn removed<C: Component>(&self) -> &[Entity] {
self.world.removed::<C>()
}
/// Sets the entity's component to the given value. This will fail if the entity does not already have
/// the given component type or if the given component type does not match this query.
pub fn set<T: Component>(

View File

@ -20,5 +20,5 @@ fn deserialize_entity(
_registry: &PropertyTypeRegistry,
) -> Result<Box<dyn Property>, erased_serde::Error> {
let entity = private::Entity::deserialize(deserializer)?;
Ok(Box::new(Entity::with_id(entity.0)))
Ok(Box::new(Entity::from_id(entity.0)))
}

View File

@ -100,7 +100,7 @@ impl SceneSpawner {
.entry(scene_entity.entity)
.or_insert_with(|| bevy_ecs::Entity::new())
} else {
bevy_ecs::Entity::with_id(scene_entity.entity)
bevy_ecs::Entity::from_id(scene_entity.entity)
};
if !world.contains(entity) {
world.spawn_as_entity(entity, (1,));