1use std::fmt;
2use std::sync::atomic::{AtomicU32, Ordering};
3
4use crate::command::Rejection;
5use crate::domain::DomainError;
6
7#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
8pub struct RuntimeId(u32);
9
10static NEXT_RUNTIME: AtomicU32 = AtomicU32::new(1);
11
12impl RuntimeId {
13 pub(crate) fn allocate() -> Self {
14 match NEXT_RUNTIME
15 .fetch_update(Ordering::Relaxed, Ordering::Relaxed, |id| id.checked_add(1))
16 {
17 Ok(id) => Self(id),
18 Err(_) => panic!("runtime id space exhausted"),
19 }
20 }
21}
22
23#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash, PartialOrd, Ord)]
25pub struct Epoch(u32);
26
27impl Epoch {
28 pub fn advance(self) -> Self {
29 match self.0.checked_add(1) {
30 Some(epoch) => Self(epoch),
31 None => panic!("epoch space exhausted"),
32 }
33 }
34}
35
36#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
37pub struct SceneId {
38 pub(crate) runtime: RuntimeId,
39 pub(crate) epoch: Epoch,
40}
41
42impl SceneId {
43 pub(crate) const UNBOUND: Self = Self {
44 runtime: RuntimeId(0),
45 epoch: Epoch(0),
46 };
47
48 pub fn runtime(self) -> RuntimeId {
49 self.runtime
50 }
51
52 pub fn epoch(self) -> Epoch {
53 self.epoch
54 }
55}
56
57#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
59pub struct EntityKey {
60 slot: u32,
61 generation: u32,
62}
63
64impl EntityKey {
65 pub fn slot(self) -> u32 {
66 self.slot
67 }
68
69 pub fn generation(self) -> u32 {
70 self.generation
71 }
72}
73
74impl fmt::Display for EntityKey {
75 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
76 write!(f, "entity {} gen {}", self.slot, self.generation)
77 }
78}
79
80#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
82pub struct Entity {
83 scene: SceneId,
84 key: EntityKey,
85}
86
87impl Entity {
88 pub(crate) fn new(scene: SceneId, key: EntityKey) -> Self {
89 Self { scene, key }
90 }
91
92 pub fn scene(self) -> SceneId {
93 self.scene
94 }
95
96 pub fn key(self) -> EntityKey {
97 self.key
98 }
99}
100
101impl fmt::Display for Entity {
102 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
103 fmt::Display::fmt(&self.key, f)
104 }
105}
106
107#[derive(Clone)]
108struct Slot {
109 generation: u32,
110 live: bool,
111 reserved: bool,
112}
113
114pub struct Entities {
115 scene: SceneId,
116 slots: Vec<Slot>,
117 free: Vec<u32>,
118 live: usize,
119}
120
121impl Entities {
122 pub(crate) fn new(scene: SceneId) -> Self {
123 Self {
124 scene,
125 slots: Vec::new(),
126 free: Vec::new(),
127 live: 0,
128 }
129 }
130
131 pub fn scene(&self) -> SceneId {
132 self.scene
133 }
134
135 pub fn len(&self) -> usize {
136 self.live
137 }
138
139 pub fn is_empty(&self) -> bool {
140 self.live == 0
141 }
142
143 pub fn spawn(&mut self) -> Entity {
144 let entity = self.reserve();
145 self.commit(entity);
146 entity
147 }
148
149 pub(crate) fn reserve(&mut self) -> Entity {
150 let slot = match self.free.pop() {
151 Some(slot) => slot,
152 None => {
153 self.slots.push(Slot {
154 generation: 0,
155 live: false,
156 reserved: false,
157 });
158 (self.slots.len() - 1) as u32
159 }
160 };
161 let entry = &mut self.slots[slot as usize];
162 entry.reserved = true;
163 Entity::new(
164 self.scene,
165 EntityKey {
166 slot,
167 generation: entry.generation,
168 },
169 )
170 }
171
172 pub(crate) fn commit(&mut self, entity: Entity) {
173 if let Some(slot) = self.reserved_index(entity) {
174 self.slots[slot].reserved = false;
175 self.slots[slot].live = true;
176 self.live += 1;
177 }
178 }
179
180 pub(crate) fn release(&mut self, entity: Entity) {
181 if let Some(slot) = self.reserved_index(entity) {
182 self.slots[slot].reserved = false;
183 self.retire(slot);
184 }
185 }
186
187 pub fn is_reserved(&self, entity: Entity) -> bool {
188 self.reserved_index(entity).is_some()
189 }
190
191 pub(crate) fn has_reservations(&self) -> bool {
192 self.slots.iter().any(|slot| slot.reserved)
193 }
194
195 fn reserved_index(&self, entity: Entity) -> Option<usize> {
196 if entity.scene != self.scene {
197 return None;
198 }
199 let key = entity.key;
200 let slot = self.slots.get(key.slot as usize)?;
201 (slot.reserved && slot.generation == key.generation).then_some(key.slot as usize)
202 }
203
204 pub fn despawn(&mut self, entity: Entity) -> Result<EntityKey, Rejection> {
206 let key = self
207 .resolve(entity)
208 .ok_or(Rejection::Domain(DomainError::Stale(entity)))?;
209 self.slots[key.slot as usize].live = false;
210 self.live -= 1;
211 self.retire(key.slot as usize);
212 Ok(key)
213 }
214
215 fn retire(&mut self, slot: usize) {
216 let entry = &mut self.slots[slot];
217 if let Some(next) = entry.generation.checked_add(1) {
218 entry.generation = next;
219 self.free.push(slot as u32);
220 }
221 }
222
223 pub fn resolve(&self, entity: Entity) -> Option<EntityKey> {
224 if entity.scene != self.scene {
225 return None;
226 }
227 let key = entity.key;
228 let slot = self.slots.get(key.slot as usize)?;
229 (slot.live && slot.generation == key.generation).then_some(key)
230 }
231
232 pub fn snapshot(&self) -> EntitiesSnapshot {
233 EntitiesSnapshot {
234 slots: self.slots.clone(),
235 free: self.free.clone(),
236 live: self.live,
237 }
238 }
239
240 pub fn restore(&mut self, from: &EntitiesSnapshot) {
242 self.scene.epoch = self.scene.epoch.advance();
243 self.slots.clone_from(&from.slots);
244 self.free.clone_from(&from.free);
245 self.live = from.live;
246 }
247}
248
249pub struct EntitiesSnapshot {
250 slots: Vec<Slot>,
251 free: Vec<u32>,
252 live: usize,
253}
254
255#[cfg(test)]
256mod tests {
257 use super::*;
258
259 fn scene(runtime: u32) -> SceneId {
260 SceneId {
261 runtime: RuntimeId(runtime),
262 epoch: Epoch::default(),
263 }
264 }
265
266 #[test]
267 fn stale_reset_or_foreign_handle_never_reaches_a_recycled_object() {
268 let mut entities = Entities::new(scene(1));
269 let doomed = entities.spawn();
270 assert!(entities.despawn(doomed).is_ok());
271 let recycled = entities.spawn();
272 assert_eq!(recycled.key().slot(), doomed.key().slot());
273 assert_eq!(entities.resolve(doomed), None);
274 assert_eq!(entities.resolve(recycled), Some(recycled.key()));
275 assert!(matches!(
276 entities.despawn(doomed),
277 Err(Rejection::Domain(DomainError::Stale(stale))) if stale == doomed
278 ));
279 assert_eq!(entities.len(), 1);
280
281 let foreign = Entity::new(scene(2), recycled.key());
282 assert_eq!(entities.resolve(foreign), None);
283
284 let snapshot = entities.snapshot();
285 entities.restore(&snapshot);
286 assert_eq!(entities.resolve(recycled), None);
287 let rebased = Entity::new(entities.scene(), recycled.key());
288 assert_eq!(entities.resolve(rebased), Some(recycled.key()));
289 assert_eq!(entities.scene().epoch(), Epoch::default().advance());
290 }
291}