Skip to content

Commit

Permalink
ecs: component removal tracking
Browse files Browse the repository at this point in the history
  • Loading branch information
cart committed Jul 23, 2020
1 parent 141044a commit f82af10
Show file tree
Hide file tree
Showing 7 changed files with 92 additions and 27 deletions.
2 changes: 1 addition & 1 deletion crates/bevy_ecs/hecs/src/entities.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}

Expand Down
2 changes: 1 addition & 1 deletion crates/bevy_ecs/hecs/src/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}

Expand Down
78 changes: 55 additions & 23 deletions crates/bevy_ecs/hecs/src/world.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
Expand All @@ -56,6 +57,7 @@ impl World {
index,
archetypes,
archetype_generation: 0,
removed_components: HashMap::default(),
}
}

Expand Down Expand Up @@ -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(())
}
Expand Down Expand Up @@ -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();
}
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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| {
Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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();
}
}

Expand Down Expand Up @@ -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)
}));
}
Expand Down
29 changes: 29 additions & 0 deletions crates/bevy_ecs/hecs/tests/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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");
}
4 changes: 4 additions & 0 deletions crates/bevy_ecs/src/system/into_system.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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>(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)))
}
2 changes: 1 addition & 1 deletion crates/bevy_scene/src/scene_spawner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,));
Expand Down

0 comments on commit f82af10

Please sign in to comment.