1use crate::{
12 scene_storage::{reserved, Archetype, Location, Slot, SnapshotPermit},
13 EntityId, MovingColumns, RenderBinding, RenderError, SceneInstance, SceneSnapshot, Transform,
14 Velocity,
15};
16use std::sync::{
17 atomic::{AtomicU32, AtomicU64, Ordering},
18 Arc,
19};
20static NEXT_WORLD: AtomicU64 = AtomicU64::new(1);
21#[derive(Debug, Clone, Copy)]
24pub struct SceneLimits {
25 pub max_entities: u32,
27 pub max_snapshot_instances: u32,
29 pub max_snapshots: u32,
31 pub managed_cpu_bytes: u64,
33}
34impl Default for SceneLimits {
35 fn default() -> Self {
36 Self {
37 max_entities: 8192,
38 max_snapshot_instances: 8192,
39 max_snapshots: 2,
40 managed_cpu_bytes: 32 * 1024 * 1024,
41 }
42 }
43}
44impl SceneLimits {
45 pub fn reservation_bytes(self) -> Result<u64, RenderError> {
47 if self.max_entities == 0
48 || self.max_entities > 65536
49 || self.max_snapshot_instances == 0
50 || self.max_snapshot_instances > self.max_entities
51 || !(1..=8).contains(&self.max_snapshots)
52 || self.managed_cpu_bytes > 512 * 1024 * 1024
53 {
54 return Err(RenderError::InvalidLimits);
55 }
56 let row = std::mem::size_of::<EntityId>()
57 + std::mem::size_of::<Transform>()
58 + std::mem::size_of::<RenderBinding>();
59 let per_entity = 2 * row
60 + std::mem::size_of::<Velocity>()
61 + std::mem::size_of::<Slot>()
62 + std::mem::size_of::<u32>()
63 + std::mem::size_of::<Transform>();
64 let bytes = u64::from(self.max_entities) * per_entity as u64
65 + u64::from(self.max_snapshots)
66 * u64::from(self.max_snapshot_instances)
67 * std::mem::size_of::<SceneInstance>() as u64;
68 if bytes > self.managed_cpu_bytes {
69 return Err(RenderError::BudgetExceeded);
70 }
71 Ok(bytes)
72 }
73}
74pub struct SceneWorld {
77 owner: u64,
78 limits: SceneLimits,
79 slots: Vec<Slot>,
80 free_slots: Vec<u32>,
81 archetypes: [Archetype; 2],
82 live: usize,
83 snapshots: Arc<AtomicU32>,
84}
85impl SceneWorld {
86 pub fn new(limits: SceneLimits) -> Result<Self, RenderError> {
88 limits.reservation_bytes()?;
89 let owner = NEXT_WORLD
90 .fetch_update(Ordering::Relaxed, Ordering::Relaxed, |n| n.checked_add(1))
91 .map_err(|_| RenderError::IdentityExhausted)?;
92 let capacity = limits.max_entities as usize;
93 Ok(Self {
94 owner,
95 limits,
96 slots: reserved(capacity)?,
97 free_slots: reserved(capacity)?,
98 archetypes: [
99 Archetype::new(capacity, false)?,
100 Archetype::new(capacity, true)?,
101 ],
102 live: 0,
103 snapshots: Arc::new(AtomicU32::new(0)),
104 })
105 }
106 pub fn len(&self) -> usize {
108 self.live
109 }
110 pub fn is_empty(&self) -> bool {
112 self.live == 0
113 }
114 pub fn spawn(
117 &mut self,
118 transform: Transform,
119 binding: RenderBinding,
120 velocity: Option<Velocity>,
121 ) -> Result<EntityId, RenderError> {
122 let binding = binding.validate()?;
123 let index = if let Some(index) = self.free_slots.pop() {
124 index as usize
125 } else {
126 if self.slots.len() >= self.limits.max_entities as usize {
127 return Err(RenderError::BudgetExceeded);
128 }
129 self.slots.push(Slot {
130 generation: 0,
131 location: None,
132 });
133 self.slots.len() - 1
134 };
135 let slot = &mut self.slots[index];
136 slot.generation += 1;
137 let id = EntityId {
138 owner: self.owner,
139 index: index as u32,
140 generation: slot.generation,
141 };
142 let archetype = usize::from(velocity.is_some());
143 let row = self.archetypes[archetype].push(id, transform, binding, velocity);
144 self.slots[index].location = Some(Location { archetype, row });
145 self.live += 1;
146 Ok(id)
147 }
148 fn locate(&self, id: EntityId) -> Result<Location, RenderError> {
149 if id.owner != self.owner {
150 return Err(RenderError::InvalidResource);
151 }
152 self.slots
153 .get(id.index as usize)
154 .filter(|s| s.generation == id.generation)
155 .and_then(|s| s.location)
156 .ok_or(RenderError::InvalidResource)
157 }
158 fn remove_row(&mut self, location: Location) -> (Transform, RenderBinding, Option<Velocity>) {
159 let table = &mut self.archetypes[location.archetype];
160 let values = table.remove(location.row);
161 if let Some(moved) = table.entities.get(location.row) {
162 self.slots[moved.index as usize].location = Some(location);
163 }
164 values
165 }
166 pub fn despawn(&mut self, id: EntityId) -> Result<(), RenderError> {
168 let location = self.locate(id)?;
169 self.remove_row(location);
170 let slot = &mut self.slots[id.index as usize];
171 slot.location = None;
172 if slot.generation < u64::MAX {
173 self.free_slots.push(id.index);
174 }
175 self.live -= 1;
176 Ok(())
177 }
178 pub fn transform(&self, id: EntityId) -> Result<Transform, RenderError> {
180 let l = self.locate(id)?;
181 Ok(self.archetypes[l.archetype].transforms[l.row])
182 }
183 pub fn set_transform(&mut self, id: EntityId, value: Transform) -> Result<(), RenderError> {
185 let l = self.locate(id)?;
186 self.archetypes[l.archetype].transforms[l.row] = value;
187 Ok(())
188 }
189 pub fn set_velocity(
191 &mut self,
192 id: EntityId,
193 velocity: Option<Velocity>,
194 ) -> Result<(), RenderError> {
195 let l = self.locate(id)?;
196 let target = usize::from(velocity.is_some());
197 if l.archetype == target {
198 if let (Some(column), Some(value)) = (&mut self.archetypes[target].velocities, velocity)
199 {
200 column[l.row] = value;
201 }
202 return Ok(());
203 }
204 let (transform, binding, _) = self.remove_row(l);
205 let row = self.archetypes[target].push(id, transform, binding, velocity);
206 self.slots[id.index as usize].location = Some(Location {
207 archetype: target,
208 row,
209 });
210 Ok(())
211 }
212 pub fn moving_columns(&mut self) -> MovingColumns<'_> {
214 let table = &mut self.archetypes[1];
215 MovingColumns {
216 entities: &table.entities,
217 transforms: &mut table.transforms,
218 velocities: table
219 .velocities
220 .as_mut()
221 .map_or(&mut [], |v| v.as_mut_slice()),
222 }
223 }
224 pub fn step_velocity(&mut self, dt: f32) -> Result<(), RenderError> {
226 if !dt.is_finite() || !(0.0..=0.05).contains(&dt) {
227 return Err(RenderError::InvalidLimits);
228 }
229 let table = &mut self.archetypes[1];
230 let velocities = table
231 .velocities
232 .as_ref()
233 .ok_or(RenderError::InvalidLimits)?;
234 let mut next = reserved(table.transforms.len())?;
235 for (transform, velocity) in table.transforms.iter().zip(velocities) {
236 next.push(transform.with_position(std::array::from_fn(|i| {
237 transform.position()[i] + velocity.value()[i] * dt
238 }))?);
239 }
240 table.transforms.copy_from_slice(&next);
241 Ok(())
242 }
243 pub fn snapshot(&self) -> Result<SceneSnapshot, RenderError> {
246 if self.live > self.limits.max_snapshot_instances as usize {
247 return Err(RenderError::BudgetExceeded);
248 }
249 self.snapshots
250 .fetch_update(Ordering::AcqRel, Ordering::Acquire, |n| {
251 (n < self.limits.max_snapshots).then_some(n + 1)
252 })
253 .map_err(|_| RenderError::SnapshotsPending)?;
254 let permit = SnapshotPermit(self.snapshots.clone());
255 let mut instances = reserved(self.live)?;
256 for table in &self.archetypes {
257 for i in 0..table.entities.len() {
258 instances.push(SceneInstance {
259 entity: table.entities[i],
260 transform: table.transforms[i],
261 binding: table.bindings[i],
262 });
263 }
264 }
265 Ok(SceneSnapshot {
266 _permit: permit,
267 instances,
268 })
269 }
270}
271
272#[cfg(test)]
273mod tests {
274 use super::*;
275 #[test]
276 fn exhausted_generation_is_retired_without_reentering_the_free_list() -> Result<(), RenderError>
277 {
278 let limits = SceneLimits {
279 max_entities: 1,
280 max_snapshot_instances: 1,
281 ..Default::default()
282 };
283 let mut world = SceneWorld::new(limits)?;
284 let mut resources = crate::ResourceArena::new(crate::RenderLimits::default())?;
285 let binding = RenderBinding {
286 mesh: resources.insert((), 1)?,
287 material: resources.insert((), 1)?,
288 tint: [1.; 4],
289 animation: 0,
290 morph: 0,
291 };
292 let initial = world.spawn(Transform::default(), binding, None)?;
293 let exhausted = EntityId {
294 generation: u64::MAX,
295 ..initial
296 };
297 world.slots[0].generation = u64::MAX;
298 world.archetypes[0].entities[0] = exhausted;
299 world.despawn(exhausted)?;
300 assert!(world.free_slots.is_empty());
301 assert_eq!(
302 world.spawn(Transform::default(), binding, None),
303 Err(RenderError::BudgetExceeded)
304 );
305 assert_eq!(world.transform(initial), Err(RenderError::InvalidResource));
306 assert_eq!(
307 world.transform(exhausted),
308 Err(RenderError::InvalidResource)
309 );
310 Ok(())
311 }
312}