use std::collections::{BTreeMap, HashMap}; use serde::Serialize; const ALIGNMENT: u32 = 64; /// First-fit interval allocator over `u32` offsets; adjacent free ranges coalesce on release. #[derive(Default)] struct FreeList(BTreeMap); impl FreeList { fn allocate(&mut self, size: u32, align: u32) -> Option { let (block, block_size, offset) = self.0.iter().find_map(|(&block, &block_size)| { let offset = block.checked_add(align - 1)? & !(align - 1); (offset.checked_add(size)? <= block + block_size).then_some((block, block_size, offset)) })?; self.0.remove(&block); if offset > block { self.0.insert(block, offset - block); } let end = offset + size; if end < block + block_size { self.0.insert(end, block + block_size - end); } Some(offset) } /// Claims exactly `[offset, offset + size)` when that range is entirely free. fn take(&mut self, offset: u32, size: u32) -> bool { let Some(&available) = self.0.get(&offset) else { return false; }; if available < size { return false; } self.0.remove(&offset); if available > size { self.0.insert(offset + size, available - size); } true } fn release(&mut self, mut offset: u32, mut size: u32) { if let Some((&previous, &previous_size)) = self.0.range(..offset).next_back() { if previous + previous_size == offset { self.0.remove(&previous); offset = previous; size += previous_size; } } if let Some((&next, &next_size)) = self.0.range(offset..).next() { if offset + size == next { self.0.remove(&next); size += next_size; } } self.0.insert(offset, size); } } #[derive(Clone, Serialize)] pub struct Rows { pub name: String, pub rows: u32, pub stride: u32, pub format: String, pub offset: u32, pub bytes: u32, } /// Row spans handed out by `allocate_object`, keyed by their first row. #[derive(Default)] struct Slots { free: FreeList, spans: HashMap, } pub struct RenderData { arena: Box<[u8]>, arena_start: usize, base: u32, free: FreeList, rows: HashMap, slots: HashMap, } impl RenderData { pub fn new(capacity: u32) -> Result { if capacity < ALIGNMENT || capacity > u32::MAX - (ALIGNMENT - 1) { return Err("INIT"); } let mut arena = vec![0; (capacity + ALIGNMENT - 1) as usize].into_boxed_slice(); let pointer = arena.as_mut_ptr() as usize; let aligned = (pointer + ALIGNMENT as usize - 1) & !(ALIGNMENT as usize - 1); let mut data = Self { arena, arena_start: aligned - pointer, base: aligned as u32, free: FreeList(BTreeMap::from([(0, capacity)])), rows: HashMap::new(), slots: HashMap::new(), }; data.create_rows("signals".into(), 1, 32, "f32".into())?; data.write_signal(5, 1.0); Ok(data) } pub fn create_rows( &mut self, name: String, rows: u32, stride: u32, format: String, ) -> Result { if name.is_empty() || rows == 0 || stride < 16 || stride % 16 != 0 || !matches!(format.as_str(), "f32" | "u32" | "i32") { return Err("ROWS"); } if let Some(current) = self.rows.get(&name).cloned() { if current.stride != stride || current.format != format { return Err("ROWS"); } if rows <= current.rows { return Ok(current); } if name == "signals" { return Err("ROWS_BUILTIN"); } let added = rows - current.rows; let descriptor = self.grow_rows(current, rows)?; self.slots .get_mut(&descriptor.name) .unwrap() .free .release(descriptor.rows - added, added); return Ok(descriptor); } let bytes = rows.checked_mul(stride).ok_or("ARENA_OOM")?; let offset = self.reserve(bytes)?; let end = offset + bytes; self.arena[self.arena_start + offset as usize..self.arena_start + end as usize].fill(0); let descriptor = Rows { name: name.clone(), rows, stride, format, offset: self.base + offset, bytes, }; self.rows.insert(name, descriptor.clone()); let mut slots = Slots::default(); slots.free.release(0, rows); self.slots.insert(descriptor.name.clone(), slots); Ok(descriptor) } pub fn delete_rows(&mut self, name: &str) -> Result<(), &'static str> { if name == "signals" { return Err("ROWS_BUILTIN"); } if self .slots .get(name) .is_some_and(|slots| !slots.spans.is_empty()) { return Err("ROWS_ACTIVE"); } let rows = self.rows.remove(name).ok_or("ROWS_UNKNOWN")?; self.slots.remove(name); self.release(rows.offset - self.base, rows.bytes); Ok(()) } pub fn rows(&self, name: &str) -> Option<&Rows> { self.rows.get(name) } pub fn descriptors(&self) -> Vec { let mut rows = self.rows.values().cloned().collect::>(); rows.sort_by_key(|rows| rows.offset); rows } pub fn bytes(&self, name: &str) -> Option<&[u8]> { let rows = self.rows(name)?; let start = self.arena_start + (rows.offset - self.base) as usize; Some(&self.arena[start..start + rows.bytes as usize]) } /// Reserves `count` contiguous rows and returns the first one; grows geometrically when full. pub fn allocate_object(&mut self, name: &str, count: u32) -> Result<(u32, bool), &'static str> { if name == "signals" { return Err("ROWS_BUILTIN"); } if count == 0 { return Err("ROWS"); } let slots = self.slots.get_mut(name).ok_or("ROWS_UNKNOWN")?; let mut grew = false; let id = match slots.free.allocate(count, 1) { Some(id) => id, None => { let descriptor = self.rows[name].clone(); let rows = descriptor .rows .checked_mul(2) .ok_or("OBJECT_LIMIT")? .max(descriptor.rows.checked_add(count).ok_or("OBJECT_LIMIT")?); let old_rows = descriptor.rows; self.grow_rows(descriptor, rows)?; grew = true; let slots = self.slots.get_mut(name).unwrap(); slots.free.release(old_rows, rows - old_rows); slots.free.allocate(count, 1).unwrap() } }; self.slots.get_mut(name).unwrap().spans.insert(id, count); Ok((id, grew)) } pub fn delete_object(&mut self, name: &str, id: u32) -> Result<(), &'static str> { let slots = self.slots.get_mut(name).ok_or("ROWS_UNKNOWN")?; let count = slots.spans.remove(&id).ok_or("OBJECT_UNKNOWN")?; slots.free.release(id, count); let rows = self.rows[name].clone(); let start = self.arena_start + (rows.offset - self.base + id * rows.stride) as usize; self.arena[start..start + (count * rows.stride) as usize].fill(0); Ok(()) } fn grow_rows(&mut self, mut descriptor: Rows, rows: u32) -> Result { let bytes = rows.checked_mul(descriptor.stride).ok_or("ARENA_OOM")?; let old_offset = descriptor.offset - self.base; let old_end = old_offset + descriptor.bytes; let extra = bytes - descriptor.bytes; if self.free.take(old_end, extra) { self.arena[self.arena_start + old_end as usize ..self.arena_start + (old_end + extra) as usize] .fill(0); } else { let offset = self.reserve(bytes)?; let start = self.arena_start + offset as usize; self.arena[start..start + bytes as usize].fill(0); self.arena.copy_within( self.arena_start + old_offset as usize..self.arena_start + old_end as usize, start, ); self.release(old_offset, descriptor.bytes); descriptor.offset = self.base + offset; } descriptor.rows = rows; descriptor.bytes = bytes; self.rows .insert(descriptor.name.clone(), descriptor.clone()); Ok(descriptor) } pub fn update_signals(&mut self, delta: f32, frame: u32, elapsed: f32, fps: u32) { let start = self.arena_start + (self.rows["signals"].offset - self.base) as usize; for (index, value) in [delta, frame as f32, elapsed, fps as f32] .iter() .enumerate() { self.arena[start + index * 4..start + index * 4 + 4] .copy_from_slice(&value.to_le_bytes()); } } pub fn render_pending(&self) -> bool { self.signal(4) == 0.0 && self.signal(5) != 0.0 && self.signal(6) == 0.0 } pub fn begin_render(&mut self) -> bool { if !self.render_pending() { return false; } self.write_signal(5, 0.0); true } pub fn mark_dirty(&mut self) { self.write_signal(5, 1.0); } pub fn loadout_ready(&mut self) { self.write_signal(6, 0.0); self.mark_dirty(); } fn signal(&self, index: usize) -> f32 { let rows = &self.rows["signals"]; let start = self.arena_start + (rows.offset - self.base) as usize + index * 4; f32::from_le_bytes(self.arena[start..start + 4].try_into().unwrap()) } fn write_signal(&mut self, index: usize, value: f32) { let rows = &self.rows["signals"]; let start = self.arena_start + (rows.offset - self.base) as usize + index * 4; self.arena[start..start + 4].copy_from_slice(&value.to_le_bytes()); } fn reserve(&mut self, bytes: u32) -> Result { self.free.allocate(bytes, ALIGNMENT).ok_or("ARENA_OOM") } fn release(&mut self, offset: u32, bytes: u32) { self.free.release(offset, bytes); } } #[cfg(test)] #[path = "render_data_tests.rs"] mod tests;