Compare commits
3 commits
Author | SHA1 | Date | |
---|---|---|---|
0b98e94421 | |||
1b89120b73 | |||
96d44163fc |
7 changed files with 204 additions and 211 deletions
|
@ -1,6 +1,3 @@
|
|||
/// Collections types for Zenyx
|
||||
///
|
||||
/// - [`SparseSet`]
|
||||
mod sparse_set;
|
||||
|
||||
pub use sparse_set::SparseSet;
|
||||
|
|
|
@ -10,51 +10,16 @@ use bytemuck::Contiguous;
|
|||
const SPARSE_PAGESIZE: usize = (1 << 10) * 4;
|
||||
type SparsePage<A> = Option<(Box<[Option<NonZeroUsize>; SPARSE_PAGESIZE], A>, usize)>;
|
||||
|
||||
/// A sparse set for fast lookup of large indices.
|
||||
///
|
||||
/// The sparse allocator is mainly used for bulk allocations in the system's page size
|
||||
/// for the lookup array. It will also be used for the array of pointers into those
|
||||
/// bulk allocations. Additionally it will be used for the reverse map that generates keys
|
||||
/// from the value in the internal packed array.
|
||||
///
|
||||
/// The packed allocator will exclusively be used to store the values of type `T`.
|
||||
///
|
||||
/// All operations on this datastructure, meaning insertion, lookup, and deletion, are `O(1)`.
|
||||
///
|
||||
/// This data structure does not in any way guarantee ordering of the values on
|
||||
/// its own.
|
||||
#[derive(Hash)]
|
||||
pub struct SparseSet<T, PackedAlloc = Global, SparseAlloc = Global>
|
||||
where
|
||||
PackedAlloc: Allocator,
|
||||
SparseAlloc: Allocator,
|
||||
{
|
||||
/// The paginated array of keys. The value at the key is an index into the dense array minus
|
||||
/// one where the value corresponding to that key is stored.
|
||||
sparse: Vec<SparsePage<SparseAlloc>, SparseAlloc>,
|
||||
/// The dense array where the values corresponding to the keys are stored.
|
||||
dense: Vec<T, PackedAlloc>,
|
||||
/// The reverse map to get the index in the sparse array from the index in the dense array.
|
||||
dense_to_id: Vec<usize, SparseAlloc>,
|
||||
}
|
||||
|
||||
impl<T> SparseSet<T> {
|
||||
/// Creates a new [`SparseSet`] with the global allocator.
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
sparse: Vec::new(),
|
||||
dense: Vec::new(),
|
||||
dense_to_id: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Default for SparseSet<T> {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, PackedAlloc, SparseAlloc> core::fmt::Debug for SparseSet<T, PackedAlloc, SparseAlloc>
|
||||
where
|
||||
T: core::fmt::Debug,
|
||||
|
@ -68,26 +33,21 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
impl<T> SparseSet<T> {
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
sparse: Vec::new(),
|
||||
dense: Vec::new(),
|
||||
dense_to_id: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, PackedAlloc, SparseAlloc> SparseSet<T, PackedAlloc, SparseAlloc>
|
||||
where
|
||||
PackedAlloc: Allocator,
|
||||
SparseAlloc: Allocator + Clone,
|
||||
{
|
||||
/// Inserts an element into the sparse set with the key `id`. This will
|
||||
/// return the previous value if it already exists.
|
||||
///
|
||||
/// ```
|
||||
/// use zenyx::collections::SparseSet;
|
||||
///
|
||||
/// let mut sparse_set: SparseSet<u32> = SparseSet::new();
|
||||
///
|
||||
/// sparse_set.insert(10, 123);
|
||||
/// assert_eq!(sparse_set.get(10), Some(&123));
|
||||
///
|
||||
/// let prev = sparse_set.insert(10, 9);
|
||||
/// assert_eq!(prev, Some(123));
|
||||
/// assert_eq!(sparse_set.get(10), Some(&9));
|
||||
/// ```
|
||||
pub fn insert(&mut self, id: usize, value: T) -> Option<T> {
|
||||
match self.get_dense_idx(id) {
|
||||
Some(idx) => {
|
||||
|
@ -105,39 +65,15 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
/// Gets the value with the key `id`.
|
||||
///
|
||||
/// ```
|
||||
/// use zenyx::collections::SparseSet;
|
||||
///
|
||||
/// let mut sparse_set: SparseSet<u32> = SparseSet::new();
|
||||
///
|
||||
/// sparse_set.insert(10, 123);
|
||||
/// assert_eq!(sparse_set.get(10), Some(&123));
|
||||
/// ```
|
||||
pub fn get(&self, id: usize) -> Option<&T> {
|
||||
self.dense.get(self.get_dense_idx(id)?)
|
||||
}
|
||||
|
||||
/// Gets the value with the key `id` mutably.
|
||||
///
|
||||
/// ```
|
||||
/// use zenyx::collections::SparseSet;
|
||||
///
|
||||
/// let mut sparse_set: SparseSet<u32> = SparseSet::new();
|
||||
///
|
||||
/// sparse_set.insert(10, 123);
|
||||
/// let value = sparse_set.get_mut(10).unwrap();
|
||||
/// *value = 0;
|
||||
/// assert_eq!(sparse_set.get(10), Some(&0));
|
||||
/// ```
|
||||
pub fn get_mut(&mut self, id: usize) -> Option<&mut T> {
|
||||
let idx = self.get_dense_idx(id)?;
|
||||
self.dense.get_mut(idx)
|
||||
}
|
||||
|
||||
/// Sets the dense index of an `key` to `idx`. This will remove said index
|
||||
/// if it is [`None`].
|
||||
fn set_dense_idx(&mut self, id: usize, idx: Option<usize>) {
|
||||
let page = id / SPARSE_PAGESIZE;
|
||||
let sparse_index = id % SPARSE_PAGESIZE;
|
||||
|
@ -161,15 +97,6 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
/// Gets the index in the dense array for a key `id`.
|
||||
///
|
||||
/// ```
|
||||
/// use zenyx::collections::SparseSet;
|
||||
///
|
||||
/// let mut sparse_set: SparseSet<u32> = SparseSet::new();
|
||||
/// sparse_set.insert(10, 123);
|
||||
/// assert_eq!(sparse_set.values()[sparse_set.get_dense_idx(10).unwrap()], 123);
|
||||
/// ```
|
||||
pub fn get_dense_idx(&self, id: usize) -> Option<usize> {
|
||||
let page = id / SPARSE_PAGESIZE;
|
||||
let sparse_index = id % SPARSE_PAGESIZE;
|
||||
|
@ -177,8 +104,6 @@ where
|
|||
page.0[sparse_index].map(|idx| idx.into_integer() - 1)
|
||||
}
|
||||
|
||||
/// This reduces the usage count for a page in the sparse array, deallocating
|
||||
/// it if it is not used anymore.
|
||||
fn reduce_page_usage_count(&mut self, id: usize) {
|
||||
let page = id / SPARSE_PAGESIZE;
|
||||
let Some(usage) = &mut self.sparse[page] else {
|
||||
|
@ -191,7 +116,6 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
/// Increase the page usage count for a page in the sparse array.
|
||||
fn increase_page_usage_count(&mut self, id: usize) {
|
||||
let page = id / SPARSE_PAGESIZE;
|
||||
if page >= self.sparse.len() {
|
||||
|
@ -203,18 +127,6 @@ where
|
|||
usage.1 += 1;
|
||||
}
|
||||
|
||||
/// Removes the value with the key `id` from the sparse set, returning the
|
||||
/// value if it existed.
|
||||
///
|
||||
/// ```
|
||||
/// use zenyx::collections::SparseSet;
|
||||
///
|
||||
/// let mut sparse_set: SparseSet<u32> = SparseSet::new();
|
||||
///
|
||||
/// sparse_set.insert(10, 123);
|
||||
/// assert_eq!(sparse_set.remove(10), Some(123));
|
||||
/// assert_eq!(sparse_set.remove(10), None);
|
||||
/// ```
|
||||
pub fn remove(&mut self, id: usize) -> Option<T> {
|
||||
let index = self.get_dense_idx(id)?;
|
||||
if self.dense.is_empty() {
|
||||
|
@ -230,126 +142,26 @@ where
|
|||
Some(previous)
|
||||
}
|
||||
|
||||
/// Returns if there are values in this sparse set.
|
||||
/// ```
|
||||
/// use zenyx::collections::SparseSet;
|
||||
///
|
||||
/// let mut sparse_set: SparseSet<u32> = SparseSet::new();
|
||||
/// assert!(sparse_set.is_empty());
|
||||
/// sparse_set.insert(10, 123);
|
||||
/// assert!(!sparse_set.is_empty());
|
||||
/// ```
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.len() == 0
|
||||
}
|
||||
|
||||
/// Returns the number of values in this sparse set.
|
||||
///
|
||||
/// ```
|
||||
/// use zenyx::collections::SparseSet;
|
||||
///
|
||||
/// let mut sparse_set: SparseSet<u32> = SparseSet::new();
|
||||
/// sparse_set.insert(10, 123);
|
||||
/// sparse_set.insert(10, 9);
|
||||
/// sparse_set.insert(11, 10);
|
||||
/// assert_eq!(sparse_set.len(), 2);
|
||||
/// ```
|
||||
pub fn len(&self) -> usize {
|
||||
self.dense.len()
|
||||
}
|
||||
|
||||
/// Checks if the sparse set contains a value with key `id`.
|
||||
///
|
||||
/// ```
|
||||
/// use zenyx::collections::SparseSet;
|
||||
///
|
||||
/// let mut sparse_set: SparseSet<u32> = SparseSet::new();
|
||||
/// assert!(!sparse_set.contains(10));
|
||||
/// sparse_set.insert(10, 123);
|
||||
/// assert!(sparse_set.contains(10));
|
||||
/// ```
|
||||
pub fn contains(&self, id: usize) -> bool {
|
||||
self.get_dense_idx(id).is_some()
|
||||
}
|
||||
|
||||
/// Gets the keys of all values in the sparse set. This method does not provide
|
||||
/// any ordering guarantees other than the keys contained corresponding to
|
||||
/// the values with the same index returned by [`Self::values`].
|
||||
///
|
||||
/// ```
|
||||
/// use zenyx::collections::SparseSet;
|
||||
///
|
||||
/// let mut sparse_set: SparseSet<u32> = SparseSet::new();
|
||||
/// assert!(sparse_set.keys().is_empty());
|
||||
/// sparse_set.insert(10, 10);
|
||||
/// sparse_set.insert(9, 10);
|
||||
/// sparse_set.insert(11, 10);
|
||||
///
|
||||
/// assert_eq!(sparse_set.keys(), &[10, 9, 11]);
|
||||
/// sparse_set.remove(10);
|
||||
/// assert_eq!(sparse_set.keys(), &[11, 9]);
|
||||
/// ```
|
||||
pub fn keys(&self) -> &[usize] {
|
||||
&self.dense_to_id
|
||||
}
|
||||
|
||||
/// Gets all values in the sparse set, the corresponding `key` is at the same
|
||||
/// position in the slice returned by [`Self::keys`].
|
||||
///
|
||||
/// Otherwise there are no ordering guarantees.
|
||||
///
|
||||
/// ```
|
||||
/// use zenyx::collections::SparseSet;
|
||||
///
|
||||
/// let mut sparse_set: SparseSet<u32> = SparseSet::new();
|
||||
/// assert!(sparse_set.values().is_empty());
|
||||
/// sparse_set.insert(10, 10);
|
||||
/// sparse_set.insert(9, 9);
|
||||
/// sparse_set.insert(11, 11);
|
||||
///
|
||||
/// assert_eq!(sparse_set.values(), &[10, 9, 11]);
|
||||
/// sparse_set.remove(10);
|
||||
/// assert_eq!(sparse_set.values(), &[11, 9]);
|
||||
/// ```
|
||||
pub fn values(&self) -> &[T] {
|
||||
&self.dense
|
||||
}
|
||||
|
||||
/// Mutable version of [`Self::keys`].
|
||||
///
|
||||
/// ```
|
||||
/// use zenyx::collections::SparseSet;
|
||||
///
|
||||
/// let mut sparse_set: SparseSet<u32> = SparseSet::new();
|
||||
/// assert!(sparse_set.values().is_empty());
|
||||
/// sparse_set.insert(10, 10);
|
||||
/// sparse_set.insert(9, 9);
|
||||
/// sparse_set.insert(11, 11);
|
||||
///
|
||||
/// let dense_of_9 = sparse_set.get_dense_idx(9).unwrap();
|
||||
/// let dense_of_10 = sparse_set.get_dense_idx(10).unwrap();
|
||||
///
|
||||
/// let values = sparse_set.values_mut();
|
||||
/// values[dense_of_10] = 9;
|
||||
/// values[dense_of_9] = 10;
|
||||
///
|
||||
/// assert_eq!(sparse_set.get(10), Some(&9));
|
||||
/// assert_eq!(sparse_set.get(9), Some(&10));
|
||||
/// ```
|
||||
pub fn values_mut(&mut self) -> &mut [T] {
|
||||
&mut self.dense
|
||||
}
|
||||
|
||||
/// Creates a new [`SparseSet`] with the values allocated by the `packed_alloc`
|
||||
/// and everything else, as described in the top level documentation for [`SparseSet`]
|
||||
/// in the `sparse_alloc`.
|
||||
///
|
||||
/// ```
|
||||
/// use zenyx::collections::SparseSet;
|
||||
/// use allocator_api2::alloc::Global;
|
||||
///
|
||||
/// let sparse_set = SparseSet::<u32>::new_in(Global, Global);
|
||||
/// ```
|
||||
pub fn new_in(packed_alloc: PackedAlloc, sparse_alloc: SparseAlloc) -> Self {
|
||||
Self {
|
||||
dense: Vec::new_in(packed_alloc),
|
||||
|
@ -363,16 +175,6 @@ impl<T, PackedAlloc> SparseSet<T, PackedAlloc>
|
|||
where
|
||||
PackedAlloc: Allocator,
|
||||
{
|
||||
/// Creates a new [`SparseSet`] with the values allocated by the `packed_alloc`
|
||||
/// Everything else, as described in the top level documentation for [`SparseSet`]
|
||||
/// is allocated using the global allocator.
|
||||
///
|
||||
/// ```
|
||||
/// use zenyx::collections::SparseSet;
|
||||
/// use allocator_api2::alloc::Global;
|
||||
///
|
||||
/// let sparse_set = SparseSet::<u32>::new_in_packed(Global);
|
||||
/// ```
|
||||
pub const fn new_in_packed(packed_alloc: PackedAlloc) -> Self {
|
||||
Self {
|
||||
sparse: Vec::new(),
|
||||
|
|
73
src/ecs/component.rs
Normal file
73
src/ecs/component.rs
Normal file
|
@ -0,0 +1,73 @@
|
|||
use core::{any::TypeId, marker::PhantomData, num::NonZeroU64, sync::atomic::AtomicU64};
|
||||
use std::{collections::BTreeMap, sync::RwLock};
|
||||
|
||||
use allocator_api2::alloc::{Allocator, Global};
|
||||
use bytemuck::Contiguous;
|
||||
|
||||
static COMPONENT_ID_CREATOR: AtomicU64 = AtomicU64::new(1);
|
||||
static COMPONENT_IDS: RwLock<BTreeMap<TypeId, ComponentId>> = RwLock::new(BTreeMap::new());
|
||||
|
||||
pub fn create_component_id<T>() -> ComponentId
|
||||
where
|
||||
T: 'static + Sized,
|
||||
{
|
||||
let type_id = core::any::TypeId::of::<T>();
|
||||
{
|
||||
// unwrap-justification: this only errors if RwLock is poisoned
|
||||
let component_read = COMPONENT_IDS.read().unwrap();
|
||||
if let Some(id) = component_read.get(&type_id) {
|
||||
return *id;
|
||||
}
|
||||
}
|
||||
|
||||
let new_id = ComponentId(
|
||||
NonZeroU64::new(COMPONENT_ID_CREATOR.fetch_add(1, core::sync::atomic::Ordering::Relaxed))
|
||||
.unwrap(),
|
||||
);
|
||||
{
|
||||
// unwrap-justification: see above
|
||||
let mut component_write = COMPONENT_IDS.write().unwrap();
|
||||
component_write.insert(type_id, new_id);
|
||||
}
|
||||
new_id
|
||||
}
|
||||
|
||||
pub trait ComponentAllocator: Allocator {
|
||||
fn new() -> Self;
|
||||
}
|
||||
|
||||
impl ComponentAllocator for Global {
|
||||
fn new() -> Self {
|
||||
Self
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Component: core::fmt::Debug + Send + Sized + 'static {
|
||||
type Allocator: ComponentAllocator;
|
||||
|
||||
fn id() -> ComponentId {
|
||||
static COMPONENT_ID: AtomicU64 = AtomicU64::new(0);
|
||||
let mut current_id = COMPONENT_ID.load(core::sync::atomic::Ordering::Relaxed);
|
||||
if current_id == 0 {
|
||||
current_id = create_component_id::<Self>().to_int();
|
||||
COMPONENT_ID.store(current_id, core::sync::atomic::Ordering::Relaxed);
|
||||
}
|
||||
ComponentId(NonZeroU64::new(current_id).unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(transparent)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct ComponentId(NonZeroU64);
|
||||
|
||||
impl ComponentId {
|
||||
pub fn to_int(self) -> u64 {
|
||||
self.0.into_integer()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ComponentId> for u64 {
|
||||
fn from(value: ComponentId) -> Self {
|
||||
value.to_int()
|
||||
}
|
||||
}
|
26
src/ecs/entity.rs
Normal file
26
src/ecs/entity.rs
Normal file
|
@ -0,0 +1,26 @@
|
|||
use core::sync::atomic::{AtomicU64, Ordering};
|
||||
|
||||
#[repr(transparent)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct Entity(u64);
|
||||
|
||||
fn create_entity() -> Entity {
|
||||
static ENTITY_ID: AtomicU64 = AtomicU64::new(0);
|
||||
Entity(ENTITY_ID.fetch_add(1, Ordering::Relaxed))
|
||||
}
|
||||
|
||||
impl Entity {
|
||||
pub fn new() -> Self {
|
||||
create_entity()
|
||||
}
|
||||
|
||||
pub fn to_int(self) -> u64 {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Entity> for u64 {
|
||||
fn from(value: Entity) -> Self {
|
||||
value.to_int()
|
||||
}
|
||||
}
|
19
src/ecs/mod.rs
Normal file
19
src/ecs/mod.rs
Normal file
|
@ -0,0 +1,19 @@
|
|||
mod component;
|
||||
pub use component::{Component, ComponentAllocator, ComponentId};
|
||||
mod entity;
|
||||
pub use entity::Entity;
|
||||
mod storage;
|
||||
|
||||
use self::storage::ComponentSet;
|
||||
|
||||
pub type ECS = EntityComponentSystem;
|
||||
|
||||
pub struct EntityComponentSystem {
|
||||
components: ComponentSet,
|
||||
}
|
||||
|
||||
impl EntityComponentSystem {
|
||||
fn spawn(&mut self) -> Entity {
|
||||
Entity::new()
|
||||
}
|
||||
}
|
75
src/ecs/storage.rs
Normal file
75
src/ecs/storage.rs
Normal file
|
@ -0,0 +1,75 @@
|
|||
use allocator_api2::alloc::Allocator;
|
||||
|
||||
use crate::collections::SparseSet;
|
||||
|
||||
use super::{Component, ComponentAllocator, Entity};
|
||||
|
||||
pub trait ComponentStorage: core::fmt::Debug + 'static {}
|
||||
|
||||
impl<T, SparseAlloc> ComponentStorage for SparseSet<T, T::Allocator, SparseAlloc>
|
||||
where
|
||||
T: Component,
|
||||
SparseAlloc: Allocator + Clone + 'static,
|
||||
{
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ComponentSet<A = allocator_api2::alloc::Global> {
|
||||
sets: SparseSet<Box<dyn ComponentStorage>>,
|
||||
cold_alloc: A,
|
||||
}
|
||||
|
||||
impl ComponentSet {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
sets: SparseSet::new(),
|
||||
cold_alloc: allocator_api2::alloc::Global,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<A> ComponentSet<A>
|
||||
where
|
||||
A: Allocator + Clone + 'static,
|
||||
{
|
||||
fn new_in(alloc: A) -> Self {
|
||||
Self {
|
||||
sets: SparseSet::new(),
|
||||
cold_alloc: alloc,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_component_set<T: Component>(&self) -> Option<&SparseSet<T, T::Allocator, A>> {
|
||||
let set = self.sets.get(T::id().to_int() as usize)?;
|
||||
(set as &dyn core::any::Any).downcast_ref()
|
||||
}
|
||||
|
||||
fn get_component_set_mut<T: Component>(
|
||||
&mut self,
|
||||
) -> Option<&mut SparseSet<T, T::Allocator, A>> {
|
||||
let set = self.sets.get_mut(T::id().to_int() as usize)?;
|
||||
(set as &mut dyn core::any::Any).downcast_mut()
|
||||
}
|
||||
|
||||
fn insert_component_set<T: Component>(&mut self) -> &mut SparseSet<T, T::Allocator, A> {
|
||||
if self.sets.contains(T::id().to_int() as usize) {
|
||||
self.get_component_set_mut::<T>().unwrap()
|
||||
} else {
|
||||
let set = SparseSet::<T, _, _>::new_in(T::Allocator::new(), self.cold_alloc.clone());
|
||||
self.sets
|
||||
.insert(T::id().to_int() as usize, Box::new(set) as Box<_>);
|
||||
self.get_component_set_mut::<T>().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
fn add_to_entity<T: Component>(&mut self, entity: Entity, data: T) -> Option<T> {
|
||||
let set = self.insert_component_set::<T>();
|
||||
set.insert(entity.to_int() as usize, data)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ComponentSet {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
|
@ -19,6 +19,7 @@ use zlog::config::LoggerConfig;
|
|||
|
||||
pub mod camera;
|
||||
pub mod collections;
|
||||
pub mod ecs;
|
||||
pub mod model;
|
||||
pub mod texture;
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue