diff --git a/crates/bevy_ecs/hecs/src/entities.rs b/crates/bevy_ecs/hecs/src/entities.rs index 31e3469851..89b60a2b35 100644 --- a/crates/bevy_ecs/hecs/src/entities.rs +++ b/crates/bevy_ecs/hecs/src/entities.rs @@ -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) } diff --git a/crates/bevy_ecs/hecs/src/query.rs b/crates/bevy_ecs/hecs/src/query.rs index 63d9b30edd..63e5dea3de 100644 --- a/crates/bevy_ecs/hecs/src/query.rs +++ b/crates/bevy_ecs/hecs/src/query.rs @@ -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) } } diff --git a/crates/bevy_ecs/hecs/src/world.rs b/crates/bevy_ecs/hecs/src/world.rs index 8abdbe794f..a8af885200 100644 --- a/crates/bevy_ecs/hecs/src/world.rs +++ b/crates/bevy_ecs/hecs/src/world.rs @@ -39,6 +39,7 @@ use crate::{ pub struct World { entities: Entities, index: HashMap, u32>, + removed_components: HashMap>, archetypes: Vec, 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(&self) -> &[Entity] { + self.removed_components.get(&TypeId::of::()).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) })); } diff --git a/crates/bevy_ecs/hecs/tests/tests.rs b/crates/bevy_ecs/hecs/tests/tests.rs index eff39ebc47..9383e614ed 100644 --- a/crates/bevy_ecs/hecs/tests/tests.rs +++ b/crates/bevy_ecs/hecs/tests/tests.rs @@ -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::(), &[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::(), &[a], "archetype moves does not result in 'removed component' state"); + + world.remove_one::(b).unwrap(); + assert_eq!(world.removed::(), &[a, b], "removing a component results in a 'removed component' state"); + + world.clear_trackers(); + assert_eq!(world.removed::(), &[], "clearning trackers clears removals"); + assert_eq!(world.removed::<&'static str>(), &[], "clearning trackers clears removals"); + assert_eq!(world.removed::(), &[], "clearning trackers clears removals"); + + let c = world.spawn(("abc", 123)); + let d = world.spawn(("abc", 123)); + world.clear(); + assert_eq!(world.removed::(), &[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::(), &[b], "world clears result in 'removed component' states"); +} \ No newline at end of file diff --git a/crates/bevy_ecs/src/system/into_system.rs b/crates/bevy_ecs/src/system/into_system.rs index 0992d41e2b..2e9de795c7 100644 --- a/crates/bevy_ecs/src/system/into_system.rs +++ b/crates/bevy_ecs/src/system/into_system.rs @@ -200,6 +200,10 @@ impl<'a, Q: HecsQuery> Query<'a, Q> { } } + pub fn removed(&self) -> &[Entity] { + self.world.removed::() + } + /// 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( diff --git a/crates/bevy_property/src/impl_property/impl_property_bevy_ecs.rs b/crates/bevy_property/src/impl_property/impl_property_bevy_ecs.rs index 8a7931ba8a..a4e640817f 100644 --- a/crates/bevy_property/src/impl_property/impl_property_bevy_ecs.rs +++ b/crates/bevy_property/src/impl_property/impl_property_bevy_ecs.rs @@ -20,5 +20,5 @@ fn deserialize_entity( _registry: &PropertyTypeRegistry, ) -> Result, 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))) } diff --git a/crates/bevy_scene/src/scene_spawner.rs b/crates/bevy_scene/src/scene_spawner.rs index 37bbd72ea1..e5dcb9e995 100644 --- a/crates/bevy_scene/src/scene_spawner.rs +++ b/crates/bevy_scene/src/scene_spawner.rs @@ -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,));