diff --git a/crates/bevy_ecs/hecs/src/archetype.rs b/crates/bevy_ecs/hecs/src/archetype.rs index 85d1fa331b..ce294b1253 100644 --- a/crates/bevy_ecs/hecs/src/archetype.rs +++ b/crates/bevy_ecs/hecs/src/archetype.rs @@ -134,6 +134,13 @@ impl Archetype { Some(unsafe { NonNull::new_unchecked(state.modified_entities.as_ptr() as *mut bool) }) } + #[allow(missing_docs)] + #[inline] + pub fn get_added(&self) -> Option> { + let state = self.state.get(&TypeId::of::())?; + Some(unsafe { NonNull::new_unchecked(state.added_entities.as_ptr() as *mut bool) }) + } + #[allow(missing_docs)] pub fn get_type_state_mut(&mut self, ty: TypeId) -> Option<&mut TypeState> { self.state.get_mut(&ty) @@ -258,6 +265,7 @@ impl Archetype { for type_state in self.state.values_mut() { type_state.modified_entities.resize_with(count, || false); + type_state.added_entities.resize_with(count, || false); } let old_data_size = mem::replace(&mut self.data_size, 0); @@ -318,6 +326,7 @@ impl Archetype { let type_state = self.state.get_mut(&ty.id).unwrap(); type_state.modified_entities[index as usize] = type_state.modified_entities[last as usize]; + type_state.added_entities[index as usize] = type_state.added_entities[last as usize]; } } self.len = last; @@ -333,7 +342,7 @@ impl Archetype { pub(crate) unsafe fn move_to( &mut self, index: u32, - mut f: impl FnMut(*mut u8, TypeId, usize, bool), + mut f: impl FnMut(*mut u8, TypeId, usize, bool, bool), ) -> Option { let last = self.len - 1; for ty in &self.types { @@ -342,8 +351,9 @@ impl Archetype { .unwrap() .as_ptr(); let type_state = self.state.get(&ty.id).unwrap(); + let is_added= type_state.added_entities[index as usize]; let is_modified = type_state.modified_entities[index as usize]; - f(moved, ty.id(), ty.layout().size(), is_modified); + f(moved, ty.id(), ty.layout().size(), is_added, is_modified); if index != last { ptr::copy_nonoverlapping( self.get_dynamic(ty.id, ty.layout.size(), last) @@ -353,6 +363,7 @@ impl Archetype { ty.layout.size(), ); let type_state = self.state.get_mut(&ty.id).unwrap(); + type_state.added_entities[index as usize] = type_state.added_entities[last as usize]; type_state.modified_entities[index as usize] = type_state.modified_entities[last as usize]; } } @@ -366,12 +377,15 @@ impl Archetype { } #[allow(missing_docs)] - pub unsafe fn put_dynamic(&mut self, component: *mut u8, ty: TypeId, size: usize, index: u32) { - let ptr = self - .get_dynamic(ty, size, index) - .unwrap() - .as_ptr() - .cast::(); + pub unsafe fn put_dynamic(&mut self, component: *mut u8, ty: TypeId, size: usize, index: u32, added: bool) { + let state = self.state.get_mut(&ty).unwrap(); + if added { + state.added_entities[index as usize] = true; + } + let ptr = (*self.data.get()) + .as_ptr() + .add(state.offset + size * index as usize) + .cast::(); ptr::copy_nonoverlapping(component, ptr, size); } @@ -402,6 +416,7 @@ pub struct TypeState { offset: usize, borrow: AtomicBorrow, pub modified_entities: Vec, + pub added_entities: Vec, } impl TypeState { @@ -410,6 +425,7 @@ impl TypeState { offset: 0, borrow: AtomicBorrow::new(), modified_entities: Vec::new(), + added_entities: Vec::new(), } } @@ -417,6 +433,10 @@ impl TypeState { for modified in self.modified_entities.iter_mut() { *modified = false; } + + for added in self.added_entities.iter_mut() { + *added = false; + } } } diff --git a/crates/bevy_ecs/hecs/src/query.rs b/crates/bevy_ecs/hecs/src/query.rs index 21e1029bc1..f86690acd0 100644 --- a/crates/bevy_ecs/hecs/src/query.rs +++ b/crates/bevy_ecs/hecs/src/query.rs @@ -150,7 +150,6 @@ impl Query for Option { type Fetch = TryFetch; } - /// Unique borrow of an entity's component pub struct Mut<'a, T: Component> { value: &'a mut T, @@ -279,6 +278,61 @@ impl<'a, T: Component> Fetch<'a> for FetchChanged { } } +#[allow(missing_docs)] +pub struct Added<'a, T> { + value: &'a T, +} + +impl<'a, T: Component> Deref for Added<'a, T> { + type Target = T; + fn deref(&self) -> &T { + self.value + } +} + +impl<'a, T: Component> Query for Added<'a, T> { + type Fetch = FetchAdded; +} + +#[doc(hidden)] +pub struct FetchAdded(NonNull, NonNull); + +impl<'a, T: Component> Fetch<'a> for FetchAdded { + type Item = Added<'a, T>; + + fn access(archetype: &Archetype) -> Option { + if archetype.has::() { + Some(Access::Read) + } else { + None + } + } + + fn borrow(archetype: &Archetype) { + archetype.borrow::(); + } + unsafe fn get(archetype: &'a Archetype, offset: usize) -> Option { + let components = NonNull::new_unchecked(archetype.get::()?.as_ptr().add(offset)); + let modified = NonNull::new_unchecked(archetype.get_added::()?.as_ptr().add(offset)); + Some(Self(components, modified)) + } + fn release(archetype: &Archetype) { + archetype.release::(); + } + + unsafe fn should_skip(&self) -> bool { + // skip if the current item wasn't changed + !*self.1.as_ref() + } + + #[inline] + unsafe fn next(&mut self) -> Self::Item { + self.1 = NonNull::new_unchecked(self.1.as_ptr().add(1)); + let value = self.0.as_ptr(); + self.0 = NonNull::new_unchecked(value.add(1)); + Added { value: &*value } + } +} #[doc(hidden)] pub struct TryFetch(Option); @@ -782,6 +836,38 @@ mod tests { struct B(usize); struct C; + #[test] + fn added_trackers() { + let mut world = World::default(); + let e1 = world.spawn((A(0),)); + + fn get_added(world: &World) -> Vec { + world + .query::<(Added, Entity)>() + .iter() + .map(|(_added, e)| e) + .collect::>() + }; + + assert_eq!(get_added::(&world), vec![e1]); + world.insert(e1, (B(0),)).unwrap(); + assert_eq!(get_added::(&world), vec![e1]); + assert_eq!(get_added::(&world), vec![e1]); + + world.clear_trackers(); + assert!(get_added::(&world).is_empty()); + let e2 = world.spawn((A(1), B(1))); + assert_eq!(get_added::(&world), vec![e2]); + assert_eq!(get_added::(&world), vec![e2]); + + let added = world + .query::<(Entity, Added, Added)>() + .iter() + .map(|a| a.0) + .collect::>(); + assert_eq!(added, vec![e2]); + } + #[test] fn modified_trackers() { let mut world = World::default(); diff --git a/crates/bevy_ecs/hecs/src/world.rs b/crates/bevy_ecs/hecs/src/world.rs index 4969db4f65..1f0e686196 100644 --- a/crates/bevy_ecs/hecs/src/world.rs +++ b/crates/bevy_ecs/hecs/src/world.rs @@ -106,7 +106,7 @@ impl World { unsafe { let index = archetype.allocate(entity.id()); components.put(|ptr, ty, size| { - archetype.put_dynamic(ptr, ty, size, index); + archetype.put_dynamic(ptr, ty, size, index, true); true }); self.entities.insert( @@ -371,7 +371,7 @@ impl World { // Update components in the current archetype let arch = &mut self.archetypes[loc.archetype as usize]; components.put(|ptr, ty, size| { - arch.put_dynamic(ptr, ty, size, loc.index); + arch.put_dynamic(ptr, ty, size, loc.index, false); true }); return Ok(()); @@ -386,14 +386,17 @@ 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_modified| { - target_arch.put_dynamic(ptr, ty, size, target_index); - target_arch.get_type_state_mut(ty).unwrap().modified_entities[target_index as usize] = is_modified; + if let Some(moved) = source_arch.move_to(old_index, |ptr, ty, size, is_added, is_modified| { + 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.modified_entities[target_index as usize] = is_modified; }) { self.entities.get_mut(Entity::with_id(moved)).unwrap().index = old_index; } + components.put(|ptr, ty, size| { - target_arch.put_dynamic(ptr, ty, size, target_index); + target_arch.put_dynamic(ptr, ty, size, target_index, true); true }); } @@ -464,11 +467,13 @@ 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_modified| { + if let Some(moved) = source_arch.move_to(old_index, |src, ty, size, is_added, is_modified| { // 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); - target_arch.get_type_state_mut(ty).unwrap().modified_entities[target_index as usize] = is_modified; + let state = target_arch.get_type_state_mut(ty).unwrap(); + state.added_entities[target_index as usize] = is_added; + state.modified_entities[target_index as usize] = is_modified; } }) { self.entities.get_mut(Entity::with_id(moved)).unwrap().index = old_index; @@ -746,7 +751,7 @@ where unsafe { let index = self.archetype.allocate(entity.id()); components.put(|ptr, ty, size| { - self.archetype.put_dynamic(ptr, ty, size, index); + self.archetype.put_dynamic(ptr, ty, size, index, true); true }); self.entities.insert( diff --git a/crates/bevy_ecs/src/resource/resources.rs b/crates/bevy_ecs/src/resource/resources.rs index 5630a21d77..59385ae05f 100644 --- a/crates/bevy_ecs/src/resource/resources.rs +++ b/crates/bevy_ecs/src/resource/resources.rs @@ -65,13 +65,13 @@ impl Resources { }); let archetype = &mut data.archetype; - + let mut added = false; let index = match resource_index { - ResourceIndex::Global => *data.default_index.get_or_insert_with(|| archetype.len()), + ResourceIndex::Global => *data.default_index.get_or_insert_with(|| { added = true; archetype.len() }), ResourceIndex::System(id) => *data .system_id_to_archetype_index .entry(id.0) - .or_insert_with(|| archetype.len()), + .or_insert_with(|| { added = true; archetype.len() }), }; if index == archetype.len() { @@ -82,7 +82,7 @@ impl Resources { unsafe { let resource_ptr = (&mut resource as *mut T).cast::(); - archetype.put_dynamic(resource_ptr, type_id, core::mem::size_of::(), index); + archetype.put_dynamic(resource_ptr, type_id, core::mem::size_of::(), index, added); std::mem::forget(resource); } }