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