use crate::{Index, time_slice::Skill}; /// Dense Vec-backed store for per-agent skill state within a TimeSlice. /// /// Indexed directly by Index.0, eliminating HashMap hashing in the inner /// convergence loop. Uses a parallel `present` mask so iteration skips /// absent slots without incurring per-slot Option overhead in the hot path. #[derive(Debug, Default)] pub struct SkillStore { skills: Vec, present: Vec, n_present: usize, } impl SkillStore { pub fn new() -> Self { Self::default() } fn ensure_capacity(&mut self, idx: usize) { if idx >= self.skills.len() { self.skills.resize_with(idx + 1, Skill::default); self.present.resize(idx + 1, false); } } pub fn insert(&mut self, idx: Index, skill: Skill) { self.ensure_capacity(idx.0); if !self.present[idx.0] { self.n_present += 1; } self.skills[idx.0] = skill; self.present[idx.0] = true; } pub fn get(&self, idx: Index) -> Option<&Skill> { if idx.0 < self.present.len() && self.present[idx.0] { Some(&self.skills[idx.0]) } else { None } } pub fn get_mut(&mut self, idx: Index) -> Option<&mut Skill> { if idx.0 < self.present.len() && self.present[idx.0] { Some(&mut self.skills[idx.0]) } else { None } } #[allow(dead_code)] pub fn contains(&self, idx: Index) -> bool { idx.0 < self.present.len() && self.present[idx.0] } #[allow(dead_code)] pub fn len(&self) -> usize { self.n_present } #[allow(dead_code)] pub fn is_empty(&self) -> bool { self.n_present == 0 } pub fn iter(&self) -> impl Iterator { self.present.iter().enumerate().filter_map(|(i, &p)| { if p { Some((Index(i), &self.skills[i])) } else { None } }) } pub fn iter_mut(&mut self) -> impl Iterator { self.skills .iter_mut() .zip(self.present.iter()) .enumerate() .filter_map(|(i, (s, &p))| if p { Some((Index(i), s)) } else { None }) } pub fn keys(&self) -> impl Iterator + '_ { self.present .iter() .enumerate() .filter_map(|(i, &p)| if p { Some(Index(i)) } else { None }) } } #[cfg(test)] mod tests { use super::*; #[test] fn insert_then_get() { let mut store = SkillStore::new(); let idx = Index(3); store.insert(idx, Skill::default()); assert!(store.contains(idx)); assert_eq!(store.len(), 1); assert!(store.get(idx).is_some()); } #[test] fn missing_returns_none() { let store = SkillStore::new(); assert!(store.get(Index(0)).is_none()); assert!(!store.contains(Index(42))); } #[test] fn iter_skips_absent_slots() { let mut store = SkillStore::new(); store.insert(Index(0), Skill::default()); store.insert(Index(5), Skill::default()); let keys: Vec = store.keys().collect(); assert_eq!(keys, vec![Index(0), Index(5)]); } #[test] fn double_insert_does_not_double_count() { let mut store = SkillStore::new(); store.insert(Index(2), Skill::default()); store.insert(Index(2), Skill::default()); assert_eq!(store.len(), 1); } }