early version of ecs working
This commit is contained in:
parent
ad35015478
commit
cde7d140ea
|
@ -0,0 +1 @@
|
||||||
|
/target
|
|
@ -0,0 +1,45 @@
|
||||||
|
{
|
||||||
|
// Use IntelliSense to learn about possible attributes.
|
||||||
|
// Hover to view descriptions of existing attributes.
|
||||||
|
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"type": "lldb",
|
||||||
|
"request": "launch",
|
||||||
|
"name": "Debug executable 'lyra-ecs'",
|
||||||
|
"cargo": {
|
||||||
|
"args": [
|
||||||
|
"build",
|
||||||
|
"--bin=lyra-ecs",
|
||||||
|
"--package=lyra-ecs"
|
||||||
|
],
|
||||||
|
"filter": {
|
||||||
|
"name": "lyra-ecs",
|
||||||
|
"kind": "bin"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"args": [],
|
||||||
|
"cwd": "${workspaceFolder}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "lldb",
|
||||||
|
"request": "launch",
|
||||||
|
"name": "Debug unit tests in executable 'lyra-ecs'",
|
||||||
|
"cargo": {
|
||||||
|
"args": [
|
||||||
|
"test",
|
||||||
|
"--no-run",
|
||||||
|
"--bin=lyra-ecs",
|
||||||
|
"--package=lyra-ecs"
|
||||||
|
],
|
||||||
|
"filter": {
|
||||||
|
"name": "lyra-ecs",
|
||||||
|
"kind": "bin"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"args": [],
|
||||||
|
"cwd": "${workspaceFolder}"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"nixEnvSelector.nixFile": "${workspaceRoot}/shell.nix"
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
version = 3
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lyra-ecs"
|
||||||
|
version = "0.1.0"
|
|
@ -0,0 +1,8 @@
|
||||||
|
[package]
|
||||||
|
name = "lyra-ecs"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2023 SeanOMik
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
|
@ -0,0 +1,21 @@
|
||||||
|
{ pkgs ? import <nixpkgs> { } }:
|
||||||
|
|
||||||
|
with pkgs;
|
||||||
|
|
||||||
|
mkShell rec {
|
||||||
|
nativeBuildInputs = [
|
||||||
|
pkg-config
|
||||||
|
openssl
|
||||||
|
wasm-pack
|
||||||
|
trunk
|
||||||
|
valgrind
|
||||||
|
heaptrack
|
||||||
|
mold
|
||||||
|
];
|
||||||
|
buildInputs = [
|
||||||
|
udev alsa-lib vulkan-loader
|
||||||
|
xorg.libX11 xorg.libXcursor xorg.libXi xorg.libXrandr # To use the x11 feature
|
||||||
|
libxkbcommon wayland # To use the wayland feature
|
||||||
|
];
|
||||||
|
LD_LIBRARY_PATH = lib.makeLibraryPath buildInputs;
|
||||||
|
}
|
|
@ -0,0 +1,182 @@
|
||||||
|
use std::any::{Any, TypeId};
|
||||||
|
|
||||||
|
use crate::{world::{Entity, ArchetypeEntityId}, bundle::Bundle};
|
||||||
|
|
||||||
|
pub trait ComponentColumn: Any {
|
||||||
|
fn as_any(&self) -> &dyn Any;
|
||||||
|
fn as_any_mut(&mut self) -> &mut dyn Any;
|
||||||
|
fn new_empty_column(&self) -> Box<dyn ComponentColumn>;
|
||||||
|
fn is_same_type(&self, column: &dyn ComponentColumn) -> bool;
|
||||||
|
fn len(&self) -> usize;
|
||||||
|
fn append(&mut self, column: &mut dyn ComponentColumn);
|
||||||
|
|
||||||
|
fn component_type_id(&self) -> TypeId;
|
||||||
|
// used for debugging
|
||||||
|
fn component_type_name(&self) -> String;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Send + Sync + 'static> ComponentColumn for Vec<T> {
|
||||||
|
fn as_any(&self) -> &dyn Any {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_any_mut(&mut self) -> &mut dyn Any {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new_empty_column(&self) -> Box<dyn ComponentColumn> {
|
||||||
|
Box::new(Vec::<T>::new())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_same_type(&self, column: &dyn ComponentColumn) -> bool {
|
||||||
|
column.as_any().downcast_ref::<Self>().is_some()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn len(&self) -> usize {
|
||||||
|
Vec::len(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn append(&mut self, column: &mut dyn ComponentColumn) {
|
||||||
|
let column: &mut Self = column.as_any_mut().downcast_mut()
|
||||||
|
.expect("Attempt at appending an different column type!");
|
||||||
|
|
||||||
|
self.append(column);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn component_type_id(&self) -> TypeId {
|
||||||
|
self.first().unwrap().type_id()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn component_type_name(&self) -> String {
|
||||||
|
//self.first().unwrap().type_id()
|
||||||
|
std::any::type_name::<T>().to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||||
|
pub struct ArchetypeId(pub u64);
|
||||||
|
|
||||||
|
impl ArchetypeId {
|
||||||
|
/// Increments the id and returns a new id with the value it was before incrementing.
|
||||||
|
pub(crate) fn increment(&mut self) -> Self {
|
||||||
|
let v = self.0;
|
||||||
|
self.0 += 1;
|
||||||
|
|
||||||
|
ArchetypeId(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Archetype {
|
||||||
|
pub(crate) id: ArchetypeId,
|
||||||
|
entities: Vec<Entity>,
|
||||||
|
pub(crate) columns: Vec<Box<dyn ComponentColumn>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Archetype {
|
||||||
|
/// Create a new archetype from another archetype and add a column
|
||||||
|
pub fn new_archetype_add<T: Send + Sync + 'static>(new_id: ArchetypeId, archetype: &Archetype) -> Archetype {
|
||||||
|
let mut columns: Vec<_> = archetype
|
||||||
|
.columns
|
||||||
|
.iter()
|
||||||
|
.map(|c| c.new_empty_column())
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
assert!(columns
|
||||||
|
.iter()
|
||||||
|
.find(|column| column.as_any().is::<Vec<T>>())
|
||||||
|
.is_none());
|
||||||
|
columns.push(Box::new(Vec::<T>::new()));
|
||||||
|
|
||||||
|
Archetype {
|
||||||
|
id: new_id,
|
||||||
|
entities: Vec::new(),
|
||||||
|
columns,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new archetype from another archetype and remove a column
|
||||||
|
pub fn new_archetype_remove<T: Send + Sync + 'static>(new_id: ArchetypeId, archetype: &Archetype) -> Archetype {
|
||||||
|
let mut columns: Vec<_> = archetype
|
||||||
|
.columns
|
||||||
|
.iter()
|
||||||
|
.map(|c| c.new_empty_column())
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let idx = columns
|
||||||
|
.iter()
|
||||||
|
.position(|column| column.as_any().is::<Vec<T>>())
|
||||||
|
.unwrap();
|
||||||
|
columns.remove(idx);
|
||||||
|
|
||||||
|
Archetype {
|
||||||
|
id: new_id,
|
||||||
|
entities: Vec::new(),
|
||||||
|
columns,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_columns(new_id: ArchetypeId, columns: Vec<Box<dyn ComponentColumn>>) -> Archetype {
|
||||||
|
Archetype {
|
||||||
|
id: new_id,
|
||||||
|
entities: Vec::new(),
|
||||||
|
columns,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_component_mut<T: Send + Sync + 'static>(&mut self, entity: ArchetypeEntityId) -> Option<&mut T> {
|
||||||
|
for col in self.columns.iter_mut() {
|
||||||
|
if col.as_any().is::<Vec<T>>() {
|
||||||
|
let components: &mut Vec<T> = col.as_any_mut().downcast_mut().unwrap();
|
||||||
|
|
||||||
|
return components.get_mut(entity.0 as usize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_component<T: Send + Sync + 'static>(&self, entity: ArchetypeEntityId) -> Option<&T> {
|
||||||
|
for col in self.columns.iter() {
|
||||||
|
if col.as_ref().as_any().is::<Vec<T>>() {
|
||||||
|
let components: &Vec<T> = col.as_any().downcast_ref().unwrap();
|
||||||
|
|
||||||
|
return components.get(entity.0 as usize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn add_entity(&mut self, components: Vec<Box<dyn ComponentColumn>>) -> ArchetypeEntityId {
|
||||||
|
let mut created_entity: Option<ArchetypeEntityId> = None;
|
||||||
|
|
||||||
|
for mut component in components.into_iter() {
|
||||||
|
for col in self.columns.iter_mut() {
|
||||||
|
if col.is_same_type(component.as_ref()) {
|
||||||
|
match created_entity {
|
||||||
|
Some(e) => {
|
||||||
|
assert!(e.0 == col.len() as u64);
|
||||||
|
},
|
||||||
|
None => {
|
||||||
|
created_entity = Some(ArchetypeEntityId(col.len() as u64));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
col.append(component.as_mut());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
created_entity.expect("Failure to create entity!")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// returns a boolean indicating whether this archetype can store the TypeIds given
|
||||||
|
pub(crate) fn is_archetype_for(&self, types: Vec<TypeId>) -> bool {
|
||||||
|
let types_iter = types.into_iter();
|
||||||
|
|
||||||
|
self.columns
|
||||||
|
.iter()
|
||||||
|
.map(|c| c.component_type_id())
|
||||||
|
.eq(types_iter)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
use std::any::{TypeId, Any};
|
||||||
|
|
||||||
|
use crate::archetype::ComponentColumn;
|
||||||
|
|
||||||
|
pub trait Bundle {
|
||||||
|
// Get a list of type ids that this bundle is storing
|
||||||
|
fn types(&self) -> Vec<TypeId>;
|
||||||
|
/// Take components into a list.
|
||||||
|
/// The return value could be seen as a list of a list of components, but that inner list
|
||||||
|
/// only contains a single value to make it easier to add it to an archetype.
|
||||||
|
fn take_components(self) -> Vec<Box<dyn ComponentColumn>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! impl_bundle_tuple {
|
||||||
|
( $(($name: ident, $index: tt))+ ) => (
|
||||||
|
impl<$($name: Send + Sync + 'static),+> Bundle for ($($name,)+) {
|
||||||
|
fn types(&self) -> Vec<TypeId> {
|
||||||
|
vec![$(self.$index.type_id()),+]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn take_components(self) -> Vec<Box<dyn ComponentColumn>> {
|
||||||
|
vec![$(Box::new(vec![self.$index])),+]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// hopefully 16 components in a bundle is enough
|
||||||
|
impl_bundle_tuple! { (C1, 0) }
|
||||||
|
impl_bundle_tuple! { (C1, 0) (C2, 1) }
|
||||||
|
impl_bundle_tuple! { (C1, 0) (C2, 1) (C3, 2) }
|
||||||
|
impl_bundle_tuple! { (C1, 0) (C2, 1) (C3, 2) (C4, 3) }
|
||||||
|
impl_bundle_tuple! { (C1, 0) (C2, 1) (C3, 2) (C4, 3) (C5, 4) }
|
||||||
|
impl_bundle_tuple! { (C1, 0) (C2, 1) (C3, 2) (C4, 3) (C5, 4) (C6, 5) }
|
||||||
|
impl_bundle_tuple! { (C1, 0) (C2, 1) (C3, 2) (C4, 3) (C5, 4) (C6, 5) (C7, 6) }
|
||||||
|
impl_bundle_tuple! { (C1, 0) (C2, 1) (C3, 2) (C4, 3) (C5, 4) (C6, 5) (C7, 6) (C8, 7) }
|
||||||
|
impl_bundle_tuple! { (C1, 0) (C2, 1) (C3, 2) (C4, 3) (C5, 4) (C6, 5) (C7, 6) (C8, 7) (C9, 8) }
|
||||||
|
impl_bundle_tuple! { (C1, 0) (C2, 1) (C3, 2) (C4, 3) (C5, 4) (C6, 5) (C7, 6) (C8, 7) (C9, 8) (C10, 9) }
|
||||||
|
impl_bundle_tuple! { (C1, 0) (C2, 1) (C3, 2) (C4, 3) (C5, 4) (C6, 5) (C7, 6) (C8, 7) (C9, 8) (C10, 9) (C11, 10) }
|
||||||
|
impl_bundle_tuple! { (C1, 0) (C2, 1) (C3, 2) (C4, 3) (C5, 4) (C6, 5) (C7, 6) (C8, 7) (C9, 8) (C10, 9) (C11, 10) (C12, 11) }
|
||||||
|
impl_bundle_tuple! { (C1, 0) (C2, 1) (C3, 2) (C4, 3) (C5, 4) (C6, 5) (C7, 6) (C8, 7) (C9, 8) (C10, 9) (C11, 10) (C12, 11) (C13, 12) }
|
||||||
|
impl_bundle_tuple! { (C1, 0) (C2, 1) (C3, 2) (C4, 3) (C5, 4) (C6, 5) (C7, 6) (C8, 7) (C9, 8) (C10, 9) (C11, 10) (C12, 11) (C13, 12) (C14, 13) }
|
||||||
|
impl_bundle_tuple! { (C1, 0) (C2, 1) (C3, 2) (C4, 3) (C5, 4) (C6, 5) (C7, 6) (C8, 7) (C9, 8) (C10, 9) (C11, 10) (C12, 11) (C13, 12) (C14, 13) (C15, 14) }
|
|
@ -0,0 +1,5 @@
|
||||||
|
use std::any::Any;
|
||||||
|
|
||||||
|
pub trait Component : Any {
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
use std::any::Any;
|
||||||
|
|
||||||
|
use crate::world::World;
|
||||||
|
|
||||||
|
mod archetype;
|
||||||
|
mod world;
|
||||||
|
mod bundle;
|
||||||
|
mod component;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Position2d(i32, i32);
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let mut world = World::new();
|
||||||
|
|
||||||
|
let pos = Position2d(50, 50);
|
||||||
|
let e = world.spawn((pos,));
|
||||||
|
|
||||||
|
if let Some(pos) = world.get_component::<Position2d>(e) {
|
||||||
|
println!("Got Position2d: {:?}", pos);
|
||||||
|
} else {
|
||||||
|
println!("no component found :(");
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,112 @@
|
||||||
|
use std::{collections::{HashMap, VecDeque}, any::{Any, TypeId}};
|
||||||
|
|
||||||
|
use crate::{archetype::{ArchetypeId, Archetype}, bundle::Bundle, component::Component};
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||||
|
pub struct EntityId(pub u64);
|
||||||
|
|
||||||
|
/// The id of the entity for the Archetype.
|
||||||
|
/// The Archetype struct uses this as the index in the component columns
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||||
|
pub struct ArchetypeEntityId(pub u64);
|
||||||
|
|
||||||
|
pub struct Entity {
|
||||||
|
id: EntityId,
|
||||||
|
generation: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Record {
|
||||||
|
id: ArchetypeId,
|
||||||
|
index: ArchetypeEntityId,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct World {
|
||||||
|
archetypes: HashMap<ArchetypeId, Archetype>,
|
||||||
|
next_archetype_id: ArchetypeId,
|
||||||
|
entity_index: HashMap<EntityId, Record>,
|
||||||
|
dead_entities: VecDeque<Entity>,
|
||||||
|
next_entity_id: EntityId,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl World {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
archetypes: HashMap::new(),
|
||||||
|
next_archetype_id: ArchetypeId(0),
|
||||||
|
entity_index: HashMap::new(),
|
||||||
|
dead_entities: VecDeque::new(),
|
||||||
|
next_entity_id: EntityId(0),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_new_entity(&mut self) -> Entity {
|
||||||
|
match self.dead_entities.pop_front() {
|
||||||
|
Some(e) => e,
|
||||||
|
None => {
|
||||||
|
let new_id = self.next_entity_id;
|
||||||
|
self.next_entity_id.0 += 1;
|
||||||
|
|
||||||
|
Entity {
|
||||||
|
id: new_id,
|
||||||
|
generation: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn spawn<B>(&mut self, bundle: B) -> Entity
|
||||||
|
where
|
||||||
|
B: Bundle
|
||||||
|
{
|
||||||
|
let bundle_types = bundle.types();
|
||||||
|
let new_entity = self.get_new_entity();
|
||||||
|
|
||||||
|
// try to find an archetype
|
||||||
|
let archetype = self.archetypes
|
||||||
|
.values_mut()
|
||||||
|
.find(|a| a.is_archetype_for(bundle_types.clone()));
|
||||||
|
|
||||||
|
if let Some(archetype) = archetype {
|
||||||
|
// take components from the bundle and add it to the archetype
|
||||||
|
let columns = bundle.take_components();
|
||||||
|
let arche_idx = archetype.add_entity(columns);
|
||||||
|
|
||||||
|
// Create entity record and store it
|
||||||
|
let record = Record {
|
||||||
|
id: archetype.id,
|
||||||
|
index: arche_idx,
|
||||||
|
};
|
||||||
|
|
||||||
|
self.entity_index.insert(new_entity.id, record);
|
||||||
|
}
|
||||||
|
// create a new archetype if one isn't found
|
||||||
|
else {
|
||||||
|
let columns = bundle.take_components();
|
||||||
|
|
||||||
|
// create archetype
|
||||||
|
let new_arch_id = self.next_archetype_id.increment();
|
||||||
|
let archetype = Archetype::from_columns(new_arch_id, columns);
|
||||||
|
|
||||||
|
// store archetype
|
||||||
|
self.archetypes.insert(new_arch_id, archetype);
|
||||||
|
|
||||||
|
// Create entity record and store it
|
||||||
|
let record = Record {
|
||||||
|
id: new_arch_id,
|
||||||
|
// this is the first entity in the archetype
|
||||||
|
index: ArchetypeEntityId(0),
|
||||||
|
};
|
||||||
|
|
||||||
|
self.entity_index.insert(new_entity.id, record);
|
||||||
|
}
|
||||||
|
|
||||||
|
new_entity
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_component<T: Send + Sync + 'static>(&self, entity: Entity) -> Option<&T> {
|
||||||
|
let record = self.entity_index.get(&entity.id)?;
|
||||||
|
let archetype = self.archetypes.get(&record.id)?;
|
||||||
|
|
||||||
|
archetype.get_component(record.index)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue