concinnity_core/ecs/
entity.rs1use alloc::vec::Vec;
12
13use core::num::NonZeroU32;
14use core::sync::atomic::{AtomicU32, Ordering};
15
16const FIRST_GEN: NonZeroU32 = NonZeroU32::MIN;
19
20#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Debug)]
21pub struct Entity {
24 index: u32,
25 generation: NonZeroU32,
26}
27
28impl Entity {
29 pub(crate) fn new(index: u32, generation: NonZeroU32) -> Entity {
30 Entity { index, generation }
31 }
32
33 pub fn index(self) -> u32 {
35 self.index
36 }
37
38 pub fn generation(self) -> u32 {
40 self.generation.get()
41 }
42
43 pub fn to_bits(self) -> u64 {
46 ((self.index as u64) << 32) | self.generation.get() as u64
47 }
48
49 pub fn from_bits(bits: u64) -> Option<Entity> {
53 let generation = NonZeroU32::new(bits as u32)?;
54 Some(Entity {
55 index: (bits >> 32) as u32,
56 generation,
57 })
58 }
59
60 pub fn dangling() -> Entity {
65 Entity {
66 index: u32::MAX,
67 generation: FIRST_GEN,
68 }
69 }
70}
71
72#[derive(Debug)]
73struct EntityMeta {
74 generation: NonZeroU32,
75 alive: bool,
76}
77
78#[derive(Debug, Default)]
79pub struct Entities {
82 meta: Vec<EntityMeta>,
83 free: Vec<u32>,
85 next_fresh: AtomicU32,
89}
90
91impl Entities {
92 pub fn new() -> Entities {
94 Entities::default()
95 }
96
97 pub fn alloc(&mut self) -> Entity {
101 if let Some(index) = self.free.pop() {
102 let meta = &mut self.meta[index as usize];
103 meta.alive = true;
104 return Entity::new(index, meta.generation);
105 }
106 let index = {
107 let next = self.next_fresh.get_mut();
108 let i = *next;
109 *next += 1;
110 i
111 };
112 self.grow_to(index);
113 let meta = &mut self.meta[index as usize];
114 meta.alive = true;
115 Entity::new(index, meta.generation)
116 }
117
118 pub fn reserve(&self) -> Entity {
123 let index = self.next_fresh.fetch_add(1, Ordering::Relaxed);
124 Entity::new(index, FIRST_GEN)
125 }
126
127 pub fn flush(&mut self) {
130 let high = *self.next_fresh.get_mut();
131 if high == 0 {
132 return;
133 }
134 self.grow_to(high - 1);
135 }
136
137 pub fn despawn(&mut self, entity: Entity) -> bool {
141 self.flush();
142 let Some(meta) = self.meta.get_mut(entity.index as usize) else {
143 return false;
144 };
145 if !meta.alive || meta.generation != entity.generation {
146 return false;
147 }
148 meta.alive = false;
149 meta.generation = next_generation(meta.generation);
150 self.free.push(entity.index);
151 true
152 }
153
154 pub fn is_alive(&self, entity: Entity) -> bool {
157 self.meta
158 .get(entity.index as usize)
159 .is_some_and(|meta| meta.alive && meta.generation == entity.generation)
160 }
161
162 #[cfg(test)]
165 pub(crate) fn total_slots(&self) -> usize {
166 self.meta.len()
167 }
168
169 fn grow_to(&mut self, index: u32) {
174 while self.meta.len() as u32 <= index {
175 self.meta.push(EntityMeta {
176 generation: FIRST_GEN,
177 alive: true,
178 });
179 }
180 }
181}
182
183fn next_generation(generation: NonZeroU32) -> NonZeroU32 {
187 NonZeroU32::new(generation.get().wrapping_add(1)).unwrap_or(FIRST_GEN)
188}
189
190#[cfg(test)]
191mod tests {
192 use super::*;
193
194 #[test]
195 fn alloc_hands_out_distinct_fresh_ids() {
196 let mut entities = Entities::new();
197 let a = entities.alloc();
198 let b = entities.alloc();
199 assert_ne!(a.index(), b.index());
200 assert_eq!(a.generation(), 1);
201 assert_eq!(b.generation(), 1);
202 assert!(entities.is_alive(a));
203 assert!(entities.is_alive(b));
204 }
205
206 #[test]
207 fn despawn_recycles_index_and_bumps_generation() {
208 let mut entities = Entities::new();
209 let a = entities.alloc();
210 assert!(entities.despawn(a));
211 let b = entities.alloc();
213 assert_eq!(a.index(), b.index());
214 assert_eq!(b.generation(), 2);
215 assert!(!entities.is_alive(a));
217 assert!(entities.is_alive(b));
218 }
219
220 #[test]
221 fn double_despawn_and_stale_despawn_are_noops() {
222 let mut entities = Entities::new();
223 let a = entities.alloc();
224 assert!(entities.despawn(a));
225 assert!(!entities.despawn(a));
226 let b = entities.alloc();
227 assert!(!entities.despawn(a));
229 assert!(entities.is_alive(b));
230 }
231
232 #[test]
233 fn reserve_then_flush_materializes_live_entities() {
234 let mut entities = Entities::new();
235 let a = entities.reserve();
236 let b = entities.reserve();
237 assert_ne!(a.index(), b.index());
238 entities.flush();
239 assert!(entities.is_alive(a));
240 assert!(entities.is_alive(b));
241 assert_eq!(entities.total_slots(), 2);
242 }
243
244 #[test]
245 fn reserve_is_distinct_across_threads() {
246 use std::sync::Arc;
247 let entities = Arc::new(Entities::new());
248 let mut handles = Vec::new();
249 for _ in 0..8 {
250 let shared = Arc::clone(&entities);
251 handles.push(std::thread::spawn(move || {
252 (0..1000)
253 .map(|_| shared.reserve().index())
254 .collect::<Vec<_>>()
255 }));
256 }
257 let mut all: Vec<u32> = handles
258 .into_iter()
259 .flat_map(|h| h.join().unwrap())
260 .collect();
261 all.sort_unstable();
262 let count = all.len();
263 all.dedup();
264 assert_eq!(all.len(), count, "reserved indices must be unique");
265 }
266
267 #[test]
268 fn alloc_and_reserve_never_collide() {
269 let mut entities = Entities::new();
270 let reserved = entities.reserve();
271 let allocated = entities.alloc();
272 assert_ne!(reserved.index(), allocated.index());
273 }
274
275 #[test]
276 fn to_bits_round_trips_and_rejects_zero_generation() {
277 let mut entities = Entities::new();
278 let a = entities.alloc();
279 let bits = a.to_bits();
280 assert_eq!(Entity::from_bits(bits), Some(a));
281 assert_eq!(Entity::from_bits(0), None);
283 assert_eq!(Entity::from_bits(7u64 << 32), None);
284 }
285
286 #[test]
287 fn generation_wraps_past_max_to_one() {
288 let max = NonZeroU32::new(u32::MAX).unwrap();
289 assert_eq!(next_generation(max), FIRST_GEN);
290 assert_eq!(next_generation(FIRST_GEN).get(), 2);
291 }
292}