refactor: redistribute ECS into multiple files
All checks were successful
Build Zenyx ⚡ / 🧪 Run Cargo Tests (push) Successful in 4m35s
Build Zenyx ⚡ / 🧪 Run Cargo Tests (pull_request) Successful in 4m31s
Build Zenyx ⚡ / 🏗️ Build aarch64-pc-windows-msvc (push) Successful in 9m5s
Build Zenyx ⚡ / 🏗️ Build x86_64-pc-windows-msvc (push) Successful in 9m22s
Build Zenyx ⚡ / 🏗️ Build x86_64-unknown-linux-gnu (push) Successful in 9m34s
Build Zenyx ⚡ / 🏗️ Build aarch64-unknown-linux-gnu (push) Successful in 9m42s
Build Zenyx ⚡ / 🏗️ Build aarch64-pc-windows-msvc (pull_request) Successful in 9m1s
Build Zenyx ⚡ / 🏗️ Build x86_64-pc-windows-msvc (pull_request) Successful in 8m56s
Build Zenyx ⚡ / 🏗️ Build aarch64-unknown-linux-gnu (pull_request) Successful in 9m28s
Build Zenyx ⚡ / 🏗️ Build x86_64-unknown-linux-gnu (pull_request) Successful in 9m15s
All checks were successful
Build Zenyx ⚡ / 🧪 Run Cargo Tests (push) Successful in 4m35s
Build Zenyx ⚡ / 🧪 Run Cargo Tests (pull_request) Successful in 4m31s
Build Zenyx ⚡ / 🏗️ Build aarch64-pc-windows-msvc (push) Successful in 9m5s
Build Zenyx ⚡ / 🏗️ Build x86_64-pc-windows-msvc (push) Successful in 9m22s
Build Zenyx ⚡ / 🏗️ Build x86_64-unknown-linux-gnu (push) Successful in 9m34s
Build Zenyx ⚡ / 🏗️ Build aarch64-unknown-linux-gnu (push) Successful in 9m42s
Build Zenyx ⚡ / 🏗️ Build aarch64-pc-windows-msvc (pull_request) Successful in 9m1s
Build Zenyx ⚡ / 🏗️ Build x86_64-pc-windows-msvc (pull_request) Successful in 8m56s
Build Zenyx ⚡ / 🏗️ Build aarch64-unknown-linux-gnu (pull_request) Successful in 9m28s
Build Zenyx ⚡ / 🏗️ Build x86_64-unknown-linux-gnu (pull_request) Successful in 9m15s
This commit is contained in:
parent
1b89120b73
commit
0b98e94421
4 changed files with 181 additions and 135 deletions
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()
|
||||||
|
}
|
||||||
|
}
|
142
src/ecs/mod.rs
142
src/ecs/mod.rs
|
@ -1,147 +1,19 @@
|
||||||
use core::{any::TypeId, num::NonZeroU64, sync::atomic::AtomicU64};
|
mod component;
|
||||||
use std::{collections::BTreeMap, sync::RwLock};
|
pub use component::{Component, ComponentAllocator, ComponentId};
|
||||||
|
mod entity;
|
||||||
|
pub use entity::Entity;
|
||||||
|
mod storage;
|
||||||
|
|
||||||
use allocator_api2::alloc::Allocator;
|
use self::storage::ComponentSet;
|
||||||
use bytemuck::Contiguous;
|
|
||||||
|
|
||||||
use crate::collections::SparseSet;
|
|
||||||
|
|
||||||
pub type ECS = EntityComponentSystem;
|
pub type ECS = EntityComponentSystem;
|
||||||
|
|
||||||
#[repr(transparent)]
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
|
||||||
pub struct Entity(u64);
|
|
||||||
|
|
||||||
static COMPONENT_ID_CREATOR: AtomicU64 = AtomicU64::new(1);
|
|
||||||
static COMPONENT_IDS: RwLock<BTreeMap<TypeId, ComponentId>> = RwLock::new(BTreeMap::new());
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
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>().0.into_integer();
|
|
||||||
COMPONENT_ID.store(current_id, core::sync::atomic::Ordering::Relaxed);
|
|
||||||
}
|
|
||||||
ComponentId(NonZeroU64::new(current_id).unwrap())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait ComponentStorage: core::fmt::Debug + 'static {}
|
|
||||||
|
|
||||||
impl<T, SparseAlloc> ComponentStorage for SparseSet<T, T::Allocator, SparseAlloc>
|
|
||||||
where
|
|
||||||
T: Component,
|
|
||||||
SparseAlloc: Allocator + Clone + 'static,
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
#[repr(transparent)]
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
|
||||||
pub struct ComponentId(NonZeroU64);
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct ComponentSet<A = allocator_api2::alloc::Global> {
|
|
||||||
sets: SparseSet<Box<dyn ComponentStorage>>,
|
|
||||||
cold_alloc: A,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct EntityComponentSystem {
|
pub struct EntityComponentSystem {
|
||||||
components: ComponentSet,
|
components: ComponentSet,
|
||||||
next_id: AtomicU64,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EntityComponentSystem {
|
impl EntityComponentSystem {
|
||||||
fn spawn(&mut self) -> Entity {
|
fn spawn(&mut self) -> Entity {
|
||||||
let entity_id = self
|
Entity::new()
|
||||||
.next_id
|
|
||||||
.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
|
|
||||||
Entity(entity_id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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().0.into_integer() 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().0.into_integer() 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().0.into_integer() 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().0.into_integer() 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.0 as usize, data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for ComponentSet {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::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()
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue