lib: basic ECS
parent
179cc63dd1
commit
7ef85bd570
13
engine/BUILD
13
engine/BUILD
|
@ -1,4 +1,4 @@
|
|||
load("@io_bazel_rules_rust//rust:rust.bzl", "rust_binary")
|
||||
load("@io_bazel_rules_rust//rust:rust.bzl", "rust_binary", "rust_library")
|
||||
load("@rules_pkg//:pkg.bzl", "pkg_tar")
|
||||
|
||||
rust_binary(
|
||||
|
@ -11,16 +11,19 @@ rust_binary(
|
|||
],
|
||||
srcs = [
|
||||
"src/main.rs",
|
||||
|
||||
"src/physics/mod.rs",
|
||||
"src/physics/color.rs",
|
||||
|
||||
"src/render/mod.rs",
|
||||
"src/render/light.rs",
|
||||
"src/render/material.rs",
|
||||
"src/render/mesh.rs",
|
||||
"src/render/mod.rs",
|
||||
"src/render/renderable.rs",
|
||||
|
||||
"src/render/vulkan/mod.rs",
|
||||
"src/render/vulkan/data.rs",
|
||||
"src/render/vulkan/material.rs",
|
||||
"src/render/vulkan/mod.rs",
|
||||
"src/render/vulkan/pipeline.rs",
|
||||
"src/render/vulkan/pipeline_forward.rs",
|
||||
"src/render/vulkan/qfi.rs",
|
||||
|
@ -28,13 +31,15 @@ rust_binary(
|
|||
"src/render/vulkan/surface_binding.rs",
|
||||
"src/render/vulkan/swapchain_binding.rs",
|
||||
"src/render/vulkan/worker.rs",
|
||||
|
||||
"src/util/mod.rs",
|
||||
"src/util/counter.rs",
|
||||
"src/util/file.rs",
|
||||
"src/util/mod.rs",
|
||||
"src/util/profiler.rs",
|
||||
"src/util/resourcemap.rs",
|
||||
],
|
||||
deps = [
|
||||
"//lib/ecs",
|
||||
"//third_party/cargo:cgmath",
|
||||
"//third_party/cargo:image",
|
||||
"//third_party/cargo:winit",
|
||||
|
|
|
@ -25,6 +25,7 @@ mod render;
|
|||
mod util;
|
||||
mod physics;
|
||||
|
||||
use ecs::{component, world};
|
||||
use render::vulkan::data;
|
||||
use render::light::Omni;
|
||||
use render::material::{Texture, PBRMaterialBuilder};
|
||||
|
@ -32,10 +33,40 @@ use render::mesh::Mesh;
|
|||
use render::renderable::{Light, Object, Renderable, ResourceManager};
|
||||
use physics::color;
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Dupa {
|
||||
zupa: u32,
|
||||
kupa: Vec<u32>,
|
||||
}
|
||||
|
||||
impl component::Component for Dupa {}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Position {
|
||||
x: u32,
|
||||
y: u32,
|
||||
z: u32,
|
||||
}
|
||||
|
||||
impl component::Component for Position {}
|
||||
|
||||
fn main() {
|
||||
env_logger::init();
|
||||
log::info!("Starting...");
|
||||
|
||||
let mut world = world::World::new();
|
||||
world.new_entity().with(Dupa { zupa: 2137, kupa: vec!(1,2,3,4)} ).with(Position { x: 2, y: 13, z: 7 }).build();
|
||||
world.new_entity().with(Dupa { zupa: 2137, kupa: vec!(1,2,3,4)} ).build();
|
||||
world.new_entity().with(Dupa { zupa: 2137, kupa: vec!(1,2,3,4)} ).build();
|
||||
world.new_entity().with(Dupa { zupa: 2137, kupa: vec!(1,2,3,4)} ).build();
|
||||
|
||||
for dupa in world.components::<Dupa>() {
|
||||
log::info!("dupa {:?}", dupa);
|
||||
}
|
||||
for dupa in world.components::<Position>() {
|
||||
log::info!("position {:?}", dupa);
|
||||
}
|
||||
|
||||
let mut rm = ResourceManager::new();
|
||||
|
||||
let mesh = {
|
||||
|
|
|
@ -78,7 +78,7 @@ pub fn srgb_to_cie_xyz(r: f32, g: f32, b: f32) -> (f32, f32, f32) {
|
|||
if v < 0.004045 {
|
||||
v / 12.92
|
||||
} else {
|
||||
let v: f32 = ((v + 0.055) / (1.055));
|
||||
let v: f32 = (v + 0.055) / (1.055);
|
||||
v.powf(2.4) as f32
|
||||
}
|
||||
};
|
||||
|
|
|
@ -14,7 +14,6 @@
|
|||
// You should have received a copy of the GNU General Public License along with
|
||||
// Abrasion. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
use log;
|
||||
use std::ffi::CStr;
|
||||
use std::fs::File;
|
||||
use std::io::prelude::*;
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
load("@io_bazel_rules_rust//rust:rust.bzl", "rust_test", "rust_library")
|
||||
|
||||
rust_library(
|
||||
name = "ecs",
|
||||
srcs = [
|
||||
"src/lib.rs",
|
||||
"src/component.rs",
|
||||
"src/entity.rs",
|
||||
"src/system.rs",
|
||||
"src/world.rs",
|
||||
],
|
||||
visibility = ["//engine:__subpackages__"],
|
||||
)
|
||||
|
||||
rust_test(
|
||||
name = "ecs_test",
|
||||
crate = ":ecs",
|
||||
)
|
|
@ -0,0 +1,9 @@
|
|||
pub type ID = std::any::TypeId;
|
||||
|
||||
|
||||
pub trait Component: 'static {
|
||||
}
|
||||
|
||||
pub fn id<T: Component>() -> ID {
|
||||
std::any::TypeId::of::<T>()
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
use crate::component;
|
||||
use crate::world;
|
||||
|
||||
pub type ID = u64;
|
||||
|
||||
#[derive(Clone, Copy, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)]
|
||||
pub struct Entity(pub ID);
|
||||
|
||||
impl Entity {
|
||||
pub fn id(&self) -> ID {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
pub struct EntityBuilder<'a> {
|
||||
world: &'a mut world::World,
|
||||
ent: Entity,
|
||||
}
|
||||
|
||||
impl<'a> EntityBuilder<'a> {
|
||||
pub fn new(world: &'a mut world::World, id: ID) -> Self {
|
||||
Self {
|
||||
world,
|
||||
ent: Entity(id),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with<T: component::Component>(self, c: T) -> Self {
|
||||
self.world.register_component_entity(component::id::<T>(), Box::new(c), self.ent);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn build(self) -> ID {
|
||||
let id = self.ent.id();
|
||||
self.world.commit(self.ent);
|
||||
|
||||
id
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
pub mod component;
|
||||
pub mod entity;
|
||||
pub mod system;
|
||||
pub mod world;
|
||||
|
|
@ -0,0 +1,96 @@
|
|||
use crate::{component, world};
|
||||
|
||||
pub trait System<'a> {
|
||||
type SystemData: DynamicSystemData<'a>;
|
||||
|
||||
fn run(&mut self, sd: Self::SystemData);
|
||||
}
|
||||
|
||||
trait WorldRunner<'a> {
|
||||
fn run_world(&mut self, &'a world::World);
|
||||
}
|
||||
|
||||
impl<'a, T> WorldRunner<'a> for T
|
||||
where
|
||||
T: System<'a>,
|
||||
{
|
||||
fn run_world(&mut self, world: &'a world::World) {
|
||||
let data = T::SystemData::fetch(world);
|
||||
self.run(data);
|
||||
}
|
||||
}
|
||||
|
||||
pub trait DynamicSystemData<'a> {
|
||||
fn fetch(world: &'a world::World) -> Self;
|
||||
}
|
||||
|
||||
impl<'a, T: component::Component> DynamicSystemData<'a> for world::ReadData<'a, T> {
|
||||
fn fetch(world: &'a world::World) -> Self {
|
||||
world.components()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: component::Component> DynamicSystemData<'a> for world::ReadWriteData<'a, T> {
|
||||
fn fetch(world: &'a world::World) -> Self {
|
||||
world.components_mut()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Processor<'a> {
|
||||
world: &'a world::World,
|
||||
runners: Vec<Box<dyn WorldRunner<'a>>>,
|
||||
}
|
||||
|
||||
impl<'a> Processor<'a> {
|
||||
fn add_system<T: System<'a> + 'static>(&mut self, system: T) {
|
||||
self.runners.push(Box::new(system));
|
||||
}
|
||||
|
||||
fn run(&mut self) {
|
||||
for runner in &mut self.runners {
|
||||
runner.run_world(self.world);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::{component, system, world};
|
||||
|
||||
struct Position {
|
||||
x: u32,
|
||||
y: u32,
|
||||
z: u32,
|
||||
}
|
||||
impl component::Component for Position {}
|
||||
|
||||
struct Physics;
|
||||
impl<'a> system::System<'a> for Physics {
|
||||
type SystemData = world::ReadWriteData<'a, Position>;
|
||||
|
||||
fn run(&mut self, sd: Self::SystemData) {
|
||||
for mut p in sd.iter_mut() {
|
||||
p.z += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn processor() {
|
||||
let mut world = world::World::new();
|
||||
world.new_entity().with(Position { x: 1, y: 2, z: 3 }).build();
|
||||
world.new_entity().with(Position { x: 4, y: 5, z: 6 }).build();
|
||||
|
||||
|
||||
let mut p = system::Processor {
|
||||
world: &world,
|
||||
runners: Vec::new(),
|
||||
};
|
||||
p.add_system(Physics);
|
||||
|
||||
let positions = world.components::<Position>();
|
||||
assert_eq!(vec![3, 6], positions.iter().map(|el| el.z).collect::<Vec<u32>>());
|
||||
p.run();
|
||||
assert_eq!(vec![4, 7], positions.iter().map(|el| el.z).collect::<Vec<u32>>());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,191 @@
|
|||
use std::collections::BTreeMap;
|
||||
use std::cell::{Ref, RefCell, RefMut};
|
||||
use std::ops::Deref;
|
||||
use std::marker::PhantomData;
|
||||
use std::iter::Iterator;
|
||||
|
||||
use crate::entity;
|
||||
use crate::component;
|
||||
|
||||
pub struct ReadData<'a, T: component::Component> {
|
||||
underlying: &'a RefCell<Vec<Box<dyn component::Component>>>,
|
||||
phantom: PhantomData<&'a T>,
|
||||
}
|
||||
|
||||
impl<'a, T: component::Component> ReadData<'a, T> {
|
||||
pub fn iter(&self) -> ReadDataIter<'a, T> {
|
||||
ReadDataIter {
|
||||
underlying: Some(Ref::map(self.underlying.borrow(), |el| el.as_slice())),
|
||||
phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ReadDataIter<'a, T: component::Component> {
|
||||
underlying: Option<Ref<'a, [Box<dyn component::Component>]>>,
|
||||
phantom: PhantomData<&'a T>,
|
||||
}
|
||||
|
||||
impl <'a, T: component::Component> Iterator for ReadDataIter<'a, T> {
|
||||
type Item = Ref<'a, T>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if self.underlying.as_ref().unwrap().len() == 0 {
|
||||
return None;
|
||||
}
|
||||
let (u, n) = Ref::map_split(self.underlying.take().unwrap(), |slice| {
|
||||
let (left, right) = slice.split_at(1);
|
||||
let ptr = left[0].as_ref();
|
||||
let left = unsafe { & *(ptr as *const (dyn component::Component) as *const T) };
|
||||
|
||||
return (left, right);
|
||||
});
|
||||
self.underlying = Some(n);
|
||||
Some(u)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ReadWriteData<'a, T: component::Component> {
|
||||
underlying: &'a RefCell<Vec<Box<dyn component::Component>>>,
|
||||
phantom: PhantomData<&'a T>,
|
||||
}
|
||||
|
||||
impl<'a, T: component::Component> ReadWriteData<'a, T> {
|
||||
pub fn iter_mut(&self) -> ReadWriteDataIter<'a, T> {
|
||||
ReadWriteDataIter {
|
||||
underlying: Some(RefMut::map(self.underlying.borrow_mut(), |el| el.as_mut_slice())),
|
||||
phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ReadWriteDataIter<'a, T: component::Component> {
|
||||
underlying: Option<RefMut<'a, [Box<dyn component::Component>]>>,
|
||||
phantom: PhantomData<&'a T>,
|
||||
}
|
||||
|
||||
impl <'a, T: component::Component> Iterator for ReadWriteDataIter<'a, T> {
|
||||
type Item = RefMut<'a, T>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if self.underlying.as_ref().unwrap().len() == 0 {
|
||||
return None;
|
||||
}
|
||||
let (u, n) = RefMut::map_split(self.underlying.take().unwrap(), |slice| {
|
||||
let (left, right) = slice.split_at_mut(1);
|
||||
let ptr: &mut dyn component::Component = &mut *(left[0]);
|
||||
let left = unsafe {
|
||||
&mut *(ptr as *mut (dyn component::Component) as *mut T)
|
||||
};
|
||||
|
||||
return (left, right);
|
||||
});
|
||||
self.underlying = Some(n);
|
||||
Some(u)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pub struct World {
|
||||
entities: BTreeMap<entity::ID, entity::Entity>,
|
||||
entity_ids_by_component: BTreeMap<component::ID, Vec<entity::ID>>,
|
||||
components_by_id: BTreeMap<component::ID, RefCell<Vec<Box<dyn component::Component>>>>,
|
||||
next_id: entity::ID,
|
||||
|
||||
empty: RefCell<Vec<Box<dyn component::Component>>>,
|
||||
}
|
||||
|
||||
impl World {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
entities: BTreeMap::new(),
|
||||
entity_ids_by_component: BTreeMap::new(),
|
||||
components_by_id: BTreeMap::new(),
|
||||
next_id: 1u64,
|
||||
empty: RefCell::new(Vec::new()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_entity(&mut self) -> entity::EntityBuilder {
|
||||
let id = self.next_id;
|
||||
self.next_id += 1;
|
||||
entity::EntityBuilder::new(self, id)
|
||||
}
|
||||
|
||||
pub fn register_component_entity(
|
||||
&mut self,
|
||||
cid: component::ID,
|
||||
c: Box<dyn component::Component>,
|
||||
e: entity::Entity
|
||||
) {
|
||||
let vec = self.entity_ids_by_component.entry(cid).or_insert(vec!());
|
||||
vec.push(e.id());
|
||||
let vec = self.components_by_id.entry(cid).or_insert(RefCell::new(vec!()));
|
||||
vec.borrow_mut().push(c);
|
||||
}
|
||||
|
||||
pub fn commit(&mut self, ent: entity::Entity) {
|
||||
self.entities.insert(ent.id(), ent);
|
||||
}
|
||||
|
||||
pub fn components<'a, T: component::Component>(&'a self) -> ReadData<T> {
|
||||
let underlying = match self.components_by_id.get(&component::id::<T>()) {
|
||||
None => &self.empty,
|
||||
Some(r) => &r,
|
||||
};
|
||||
ReadData {
|
||||
underlying: underlying,
|
||||
phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn components_mut<'a, T: component::Component>(&'a self) -> ReadWriteData<T> {
|
||||
let underlying = match self.components_by_id.get(&component::id::<T>()) {
|
||||
None => &self.empty,
|
||||
Some(r) => &r,
|
||||
};
|
||||
ReadWriteData {
|
||||
underlying: underlying,
|
||||
phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::entity;
|
||||
use crate::component;
|
||||
use crate::world;
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Position {
|
||||
x: u32,
|
||||
y: u32,
|
||||
z: u32,
|
||||
}
|
||||
impl component::Component for Position {}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Name(String);
|
||||
impl component::Component for Name {}
|
||||
impl Name {
|
||||
fn new(s: &str) -> Name {
|
||||
Name(String::from(s))
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn new_list() {
|
||||
let mut world = world::World::new();
|
||||
world.new_entity().with(Name::new("foo")).with(Position { x: 1, y: 2, z: 3 }).build();
|
||||
world.new_entity().with(Name::new("bar")).with(Position { x: 4, y: 5, z: 6 }).build();
|
||||
|
||||
let mut named = world.components::<Name>().iter();
|
||||
let mut named2 = world.components::<Name>().iter();
|
||||
//assert_eq!(named.len(), 2);
|
||||
assert_eq!(String::from("foo"), named.next().unwrap().0);
|
||||
assert_eq!(String::from("foo"), named2.next().unwrap().0);
|
||||
assert_eq!(String::from("bar"), named.next().unwrap().0);
|
||||
assert_eq!(String::from("bar"), named2.next().unwrap().0);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue