From 684e7f787d55b7f165620e5cd8a3f393e2f36546 Mon Sep 17 00:00:00 2001 From: squi.dev Date: Sun, 09 Aug 2026 20:59:16 +0000 Subject: [PATCH] entities yippe --- src/blob.rs | 2 -- src/entity.rs | 224 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 7 ++----- 3 file(s) changed, 226 insertion(s)(+), 7 deletion(s)(-) diff --git a/src/blob.rs b/src/blob.rs --- a/src/blob.rs +++ b/src/blob.rs @@ -1,5 +1,3 @@ -extern crate alloc; - use core::{alloc::Layout, marker::PhantomData}; pub struct BlobPtr<'p> { diff --git a/src/entity.rs b/src/entity.rs new file mode 100644 --- /dev/null +++ b/src/entity.rs @@ -0,0 +1,224 @@ +use core::num::NonZero; + +use alloc::collections::vec_deque::VecDeque; +use alloc::vec; +use alloc::vec::Vec; + +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +pub struct Entity(pub(crate) u32, pub(crate) NonZero); + +pub(crate) struct Census { + storage: Vec>, + masks: VecDeque, + mask_offset: u32, + known: Option, +} + +impl Census { + pub fn new() -> Self { + Census { + storage: vec![], + masks: VecDeque::new(), + mask_offset: 0, + known: None, + } + } + + fn inc(&mut self, idx: u32) -> NonZero { + let new = self.storage[idx as usize].saturating_add(1); + self.storage[idx as usize] = new; + new + } + + fn mark_used(&mut self, idx: u32) { + let real = idx - self.mask_offset; + let mask_idx = (real / u64::BITS) as usize; + let mask_pos = (real % u64::BITS) as u64; + + self.masks[mask_idx] ^= 1u64 << mask_pos; + if self.masks[0] == 0 { + self.masks.pop_front(); + self.mask_offset += u64::BITS; + } + } + + fn mark_empty(&mut self, idx: u32) { + let real = idx - self.mask_offset; + let mask_idx = (real / u64::BITS) as usize; + let mask_pos = (real % u64::BITS) as u64; + + self.masks[mask_idx] |= 1u64 << mask_pos; + } + + pub fn reserve(&mut self) -> Entity { + if let Some(idx) = self.known { + self.mark_used(idx); + return Entity(idx, self.inc(idx)); + } + + for mask in self.masks.iter() { + if let Some(mask_idx) = mask.lowest_one() { + let idx = mask_idx + self.mask_offset; + if idx >= self.storage.len() as u32 { + break; + } + self.mark_used(idx); + return Entity(idx, self.inc(idx)); + } + } + + self.storage.push(NonZero::new(1).unwrap()); + let idx = (self.storage.len() - 1) as u32; + if (self.masks.len() * u64::BITS as usize) + self.mask_offset as usize <= self.storage.len() + { + self.masks.push_back(u64::MAX); + } + + self.mark_used(idx); + + Entity(idx, NonZero::new(1).unwrap()) + } + + pub fn remove(&mut self, Entity(idx, _): Entity) { + if idx >= self.mask_offset { + self.mark_empty(idx); + } else { + while self.mask_offset > idx { + self.masks.push_front(0); + self.mask_offset -= u64::BITS; + } + self.mark_empty(idx); + } + } + + pub fn validate(&self, Entity(idx, generation): Entity) -> bool { + (self.storage[idx as usize] == generation) && self.is_taken(idx) + } + + fn is_taken(&self, idx: u32) -> bool { + if idx < self.mask_offset { + return true; + } + let real = idx - self.mask_offset; + let mask_idx = (real / u64::BITS) as usize; + let mask_pos = (real % u64::BITS) as u64; + + (self.masks[mask_idx] & 1u64 << mask_pos) == 0 + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn reserve_validate() { + let mut cen = Census::new(); + let e = cen.reserve(); + assert!(cen.validate(e)); + assert_eq!(e.0, 0); + } + + #[test] + fn reserve_validate_large() { + let mut cen = Census::new(); + let e1 = cen.reserve(); + for _ in 0..200 { + cen.reserve(); + } + let e2 = cen.reserve(); + + assert!(cen.validate(e1)); + assert!(cen.validate(e2)); + } + + #[test] + fn mask_backwards() { + let mut cen = Census::new(); + let e1 = cen.reserve(); + for _ in 0..200 { + cen.reserve(); + } + cen.remove(e1); + let e2 = cen.reserve(); + + assert_eq!(e1.0, e2.0); + assert_ne!(e1.1, e2.1); + } + + #[test] + fn reserve_despawn() { + let mut cen = Census::new(); + let e = cen.reserve(); + assert!(cen.validate(e)); + cen.remove(e); + assert!(!cen.validate(e)); + } + + #[test] + fn reserve_two() { + let mut cen = Census::new(); + let e1 = cen.reserve(); + let e2 = cen.reserve(); + + assert_ne!(e1, e2); + } + + #[test] + fn many() { + let mut cen = Census::new(); + let first = cen.reserve(); + for _ in 0..129 { + cen.reserve(); + } + let last = cen.reserve(); + + assert_ne!(last, first); + assert_eq!(last.0, 130); + } + + #[test] + fn recylce_simple() { + let mut cen = Census::new(); + let e1 = cen.reserve(); + cen.remove(e1); + let e2 = cen.reserve(); + assert_eq!(e1.0, e2.0); + assert_ne!(e1.1, e2.1); + } + + #[test] + fn recycle_medium() { + let mut cen = Census::new(); + let e1 = cen.reserve(); + let e2 = cen.reserve(); + let e3 = cen.reserve(); + + cen.remove(e2); + let e4 = cen.reserve(); + + assert_eq!(e4.0, e2.0); + assert_ne!(e4.1, e2.1); + assert!(cen.validate(e1) && cen.validate(e3)); + } + + #[test] + fn recylce_large() { + let mut cen = Census::new(); + for _ in 0..200 { + cen.reserve(); + } + + let e1 = cen.reserve(); + + for _ in 0..200 { + cen.reserve(); + } + + cen.remove(e1); + let e2 = cen.reserve(); + + assert_eq!(e1.0, e2.0); + assert_ne!(e1.1, e2.1); + } +} diff --git a/src/lib.rs b/src/lib.rs --- a/src/lib.rs +++ b/src/lib.rs @@ -1,10 +1,7 @@ #![feature(integer_cast_extras)] #![no_std] -use core::num::NonZero; +extern crate alloc; pub mod blob; - -pub struct Entity(pub u32, pub NonZero); - -pub struct ComponentId(pub Entity, pub Option); +pub mod entity; -- tangled.sh