diff --git a/crates/voltex_ecs/src/sparse_set.rs b/crates/voltex_ecs/src/sparse_set.rs index 3699519..bd60703 100644 --- a/crates/voltex_ecs/src/sparse_set.rs +++ b/crates/voltex_ecs/src/sparse_set.rs @@ -5,6 +5,8 @@ pub struct SparseSet { sparse: Vec>, dense_entities: Vec, dense_data: Vec, + ticks: Vec, + current_tick: u64, } impl SparseSet { @@ -13,6 +15,8 @@ impl SparseSet { sparse: Vec::new(), dense_entities: Vec::new(), dense_data: Vec::new(), + ticks: Vec::new(), + current_tick: 1, } } @@ -27,11 +31,13 @@ impl SparseSet { // Overwrite existing self.dense_data[dense_idx] = value; self.dense_entities[dense_idx] = entity; + self.ticks[dense_idx] = self.current_tick; } else { let dense_idx = self.dense_data.len(); self.sparse[id] = Some(dense_idx); self.dense_entities.push(entity); self.dense_data.push(value); + self.ticks.push(self.current_tick); } } @@ -49,12 +55,14 @@ impl SparseSet { if dense_idx == last_idx { self.dense_entities.pop(); + self.ticks.pop(); Some(self.dense_data.pop().unwrap()) } else { // Swap with last let swapped_entity = self.dense_entities[last_idx]; self.sparse[swapped_entity.id as usize] = Some(dense_idx); self.dense_entities.swap_remove(dense_idx); + self.ticks.swap_remove(dense_idx); Some(self.dense_data.swap_remove(dense_idx)) } } @@ -74,6 +82,7 @@ impl SparseSet { if self.dense_entities[dense_idx] != entity { return None; } + self.ticks[dense_idx] = self.current_tick; Some(&mut self.dense_data[dense_idx]) } @@ -114,6 +123,29 @@ impl SparseSet { pub fn data_mut(&mut self) -> &mut [T] { &mut self.dense_data } + + /// Check if an entity's component was changed this tick. + pub fn is_changed(&self, entity: Entity) -> bool { + if let Some(&index) = self.sparse.get(entity.id as usize).and_then(|o| o.as_ref()) { + self.ticks[index] == self.current_tick + } else { + false + } + } + + /// Advance the tick counter (call at end of frame). + pub fn increment_tick(&mut self) { + self.current_tick += 1; + } + + /// Return entities changed this tick with their data. + pub fn iter_changed(&self) -> impl Iterator + '_ { + self.dense_entities.iter() + .zip(self.dense_data.iter()) + .zip(self.ticks.iter()) + .filter(move |((_, _), &tick)| tick == self.current_tick) + .map(|((entity, data), _)| (*entity, data)) + } } impl Default for SparseSet { @@ -127,6 +159,7 @@ pub trait ComponentStorage: Any { fn as_any_mut(&mut self) -> &mut dyn Any; fn remove_entity(&mut self, entity: Entity); fn storage_len(&self) -> usize; + fn increment_tick(&mut self); } impl ComponentStorage for SparseSet { @@ -142,6 +175,9 @@ impl ComponentStorage for SparseSet { fn storage_len(&self) -> usize { self.dense_data.len() } + fn increment_tick(&mut self) { + self.current_tick += 1; + } } #[cfg(test)] @@ -235,6 +271,74 @@ mod tests { assert!(!set.contains(e)); } + #[test] + fn test_insert_is_changed() { + let mut set = SparseSet::::new(); + let e = make_entity(0, 0); + set.insert(e, 42); + assert!(set.is_changed(e)); + } + + #[test] + fn test_get_mut_marks_changed() { + let mut set = SparseSet::::new(); + let e = make_entity(0, 0); + set.insert(e, 42); + set.increment_tick(); + assert!(!set.is_changed(e)); + let _ = set.get_mut(e); + assert!(set.is_changed(e)); + } + + #[test] + fn test_get_not_changed() { + let mut set = SparseSet::::new(); + let e = make_entity(0, 0); + set.insert(e, 42); + set.increment_tick(); + let _ = set.get(e); + assert!(!set.is_changed(e)); + } + + #[test] + fn test_clear_resets_changed() { + let mut set = SparseSet::::new(); + let e = make_entity(0, 0); + set.insert(e, 42); + assert!(set.is_changed(e)); + set.increment_tick(); + assert!(!set.is_changed(e)); + } + + #[test] + fn test_iter_changed() { + let mut set = SparseSet::::new(); + let e1 = make_entity(0, 0); + let e2 = make_entity(1, 0); + let e3 = make_entity(2, 0); + set.insert(e1, 10); + set.insert(e2, 20); + set.insert(e3, 30); + set.increment_tick(); + let _ = set.get_mut(e2); + let changed: Vec<_> = set.iter_changed().collect(); + assert_eq!(changed.len(), 1); + assert_eq!(changed[0].0.id, 1); + } + + #[test] + fn test_remove_preserves_ticks() { + let mut set = SparseSet::::new(); + let e1 = make_entity(0, 0); + let e2 = make_entity(1, 0); + set.insert(e1, 10); + set.insert(e2, 20); + set.increment_tick(); + let _ = set.get_mut(e2); + set.remove(e1); + assert!(set.is_changed(e2)); + } + #[test] fn test_swap_remove_correctness() { let mut set: SparseSet = SparseSet::new(); diff --git a/crates/voltex_ecs/src/world.rs b/crates/voltex_ecs/src/world.rs index 0667181..f1ee25b 100644 --- a/crates/voltex_ecs/src/world.rs +++ b/crates/voltex_ecs/src/world.rs @@ -228,6 +228,23 @@ impl World { result } + /// Query entities whose component T was changed this tick. + pub fn query_changed(&self) -> Vec<(Entity, &T)> { + if let Some(storage) = self.storages.get(&TypeId::of::()) { + let set = storage.as_any().downcast_ref::>().unwrap(); + set.iter_changed().collect() + } else { + Vec::new() + } + } + + /// Advance tick on all component storages (call at end of frame). + pub fn clear_changed(&mut self) { + for storage in self.storages.values_mut() { + storage.increment_tick(); + } + } + pub fn has_component(&self, entity: Entity) -> bool { self.storage::().map_or(false, |s| s.contains(entity)) } @@ -526,6 +543,37 @@ mod tests { assert_eq!(results[0].0, e1); } + #[test] + fn test_query_changed() { + let mut world = World::new(); + let e1 = world.spawn(); + let e2 = world.spawn(); + world.add(e1, 10u32); + world.add(e2, 20u32); + world.clear_changed(); + if let Some(v) = world.get_mut::(e1) { + *v = 100; + } + let changed = world.query_changed::(); + assert_eq!(changed.len(), 1); + assert_eq!(*changed[0].1, 100); + } + + #[test] + fn test_clear_changed_all_storages() { + let mut world = World::new(); + let e = world.spawn(); + world.add(e, 42u32); + world.add(e, 3.14f32); + let changed_u32 = world.query_changed::(); + assert_eq!(changed_u32.len(), 1); + world.clear_changed(); + let changed_u32 = world.query_changed::(); + assert_eq!(changed_u32.len(), 0); + let changed_f32 = world.query_changed::(); + assert_eq!(changed_f32.len(), 0); + } + #[test] fn test_entity_count() { let mut world = World::new();