Create the ground works for an ECS in the engine

This commit is contained in:
SeanOMik 2023-04-14 00:22:17 -04:00
parent bbc7c8e283
commit 3fe294b8b2
Signed by: SeanOMik
GPG Key ID: 568F326C7EB33ACB
17 changed files with 268 additions and 233 deletions

0
.gitignore vendored Normal file → Executable file
View File

45
.vscode/launch.json vendored Executable file
View File

@ -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-engine'",
"cargo": {
"args": [
"build",
"--bin=lyra-engine",
"--package=lyra-engine"
],
"filter": {
"name": "lyra-engine",
"kind": "bin"
}
},
"args": [],
"cwd": "${workspaceFolder}"
},
{
"type": "lldb",
"request": "launch",
"name": "Debug unit tests in executable 'lyra-engine'",
"cargo": {
"args": [
"test",
"--no-run",
"--bin=lyra-engine",
"--package=lyra-engine"
],
"filter": {
"name": "lyra-engine",
"kind": "bin"
}
},
"args": [],
"cwd": "${workspaceFolder}"
}
]
}

3
.vscode/settings.json vendored Executable file
View File

@ -0,0 +1,3 @@
{
"nixEnvSelector.nixFile": "${workspaceRoot}/shell.nix"
}

102
Cargo.lock generated Normal file → Executable file
View File

@ -268,6 +268,12 @@ dependencies = [
"syn",
]
[[package]]
name = "atom"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c9ff149ed9780025acfdb36862d35b28856bb693ceb451259a7164442f22fdc3"
[[package]]
name = "atomic-waker"
version = "1.1.0"
@ -546,6 +552,16 @@ dependencies = [
"scopeguard",
]
[[package]]
name = "crossbeam-queue"
version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d1cfb3ea8a53f37c40dea2c7bedcbd88bdfae54f5e2175d6ecaff1c988353add"
dependencies = [
"cfg-if",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.14"
@ -826,6 +842,16 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df"
[[package]]
name = "hibitset"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93a1bb8316a44459a7d14253c4d28dd7395cbd23cc04a68c46e851b8e46d64b1"
dependencies = [
"atom",
"rayon",
]
[[package]]
name = "image"
version = "0.24.5"
@ -967,7 +993,7 @@ dependencies = [
"cgmath",
"image",
"instant",
"shipyard",
"specs",
"tobj",
"tracing",
"tracing-log",
@ -1059,6 +1085,12 @@ dependencies = [
"windows-sys 0.45.0",
]
[[package]]
name = "mopa"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a785740271256c230f57462d3b83e52f998433a7062fc18f96d5999474a9f915"
[[package]]
name = "naga"
version = "0.11.0"
@ -1531,28 +1563,37 @@ dependencies = [
]
[[package]]
name = "shipyard"
version = "0.6.2"
name = "shred"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3511ae730f2e1c3d62a9025e2f9b2acbf130968057f1b3caab6d74a54a5e0e56"
checksum = "102269e720bb814df57e136161cad841f2b6f411e003ac748fc48aaf2363bea3"
dependencies = [
"arrayvec",
"hashbrown",
"lock_api",
"mopa",
"rayon",
"shipyard_proc",
"shred-derive",
"smallvec",
"tynm",
]
[[package]]
name = "shipyard_proc"
version = "0.3.0"
name = "shred-derive"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3eb847f4b9582e468198b5cfb5731b65cc67fe5e535acc9cbf3c11703d15f08c"
checksum = "d5404c36bd155e41a54276ab6aafedad2fb627e5e5849d36ec439c9ddc044a2f"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "shrev"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a5ea33232fdcf1bf691ca33450e5a94dde13e1a8cbb8caabc5e4f9d761e10b1a"
[[package]]
name = "signal-hook"
version = "0.3.15"
@ -1625,6 +1666,34 @@ dependencies = [
"winapi",
]
[[package]]
name = "specs"
version = "0.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ea85dac2880f84d4025ff5ace80cda6d8bc43bc88b6a389b9277fcf894b51e9"
dependencies = [
"crossbeam-queue",
"hashbrown",
"hibitset",
"log",
"rayon",
"shred",
"shrev",
"specs-derive",
"tuple_utils",
]
[[package]]
name = "specs-derive"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e23e09360f3d2190fec4222cd9e19d3158d5da948c0d1ea362df617dd103511"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "spirv"
version = "0.2.0+1.5.4"
@ -1812,6 +1881,21 @@ version = "0.18.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0609f771ad9c6155384897e1df4d948e692667cc0588548b68eb44d052b27633"
[[package]]
name = "tuple_utils"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cffaaf9392ef73cd30828797152476aaa2fa37a17856934fa63d4843f34290e9"
[[package]]
name = "tynm"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd59ecde1e694c0495c8b92bdd0f9a70e3b489cd8180fc6a18f6ea1c1026f3b2"
dependencies = [
"nom",
]
[[package]]
name = "unicode-ident"
version = "1.0.6"

4
Cargo.toml Normal file → Executable file
View File

@ -6,7 +6,6 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
shipyard = "0.6.2"
winit = "0.28.1"
#winit-modular = "0.1.1"
tracing = "0.1.37"
@ -23,4 +22,5 @@ tobj = { version = "3.2.1", features = [
"async",
]}
instant = "0.1"
async-trait = "0.1.65"
async-trait = "0.1.65"
specs = { version = "0.18.0", features = [ "derive" ] }

0
shell.nix Normal file → Executable file
View File

3
src/controls.rs Executable file
View File

@ -0,0 +1,3 @@
pub struct Controls {
}

View File

@ -1,6 +0,0 @@
use std::any::Any;
pub trait Component {
fn as_any(&self) -> &dyn Any;
fn as_any_mut(&mut self) -> &mut dyn Any;
}

View File

@ -1,12 +0,0 @@
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Entity {
pub handle: i32,
}
impl Entity {
pub fn new(handle: i32) -> Self {
Self {
handle
}
}
}

View File

@ -1,3 +0,0 @@
pub mod entity;
pub mod registry;
pub mod component;

View File

@ -1,131 +0,0 @@
use std::{collections::HashMap, cell::{RefCell, RefMut, Ref}, rc::Rc, borrow::BorrowMut, any::Any, ops::Deref};
use super::{entity::Entity, component::Component};
pub trait Registry {
/// Create an entity
fn create_entity(&mut self) -> Entity;
/// Remove the entity from the registry. Its components will also be destroyed
fn destroy_entity(&mut self, entity: Entity);
/// Add a component to an entity
fn entity_add_component<C: Component + 'static>(&mut self, entity: Entity, component: C) -> Option<Rc<RefCell<C>>>; // TODO: Return result if the component is already added.
/// Remove a component from an entity
fn entity_remove_component<C: Component + 'static>(&mut self, entity: Entity) -> bool;
fn entity_has_component<C: Component + 'static>(&self, entity: Entity) -> bool;
/// Get all the components that an entity has
fn entity_get_components(&self, entity: Entity) -> Option<Vec<Rc<RefCell<dyn Component>>>>;
/// Get a component of a specific type.
fn entity_get_component<C: Component + 'static>(&self, entity: Entity) -> Option<RefMut<'_, C>>;
}
pub struct BasicRegistry {
next_handle: i32,
map: HashMap<Entity, Vec<Rc<RefCell<dyn Component>>>>,
}
impl BasicRegistry {
pub fn new() -> Self {
Self {
next_handle: 0,
map: HashMap::new(),
}
}
}
impl Registry for BasicRegistry {
fn create_entity(&mut self) -> Entity {
//self.map.insert()
let entity = Entity::new(self.next_handle);
self.next_handle += 1;
self.map.insert(entity, Vec::new());
entity
}
fn destroy_entity(&mut self, entity: Entity) {
self.map.remove(&entity);
}
fn entity_add_component<C: Component + 'static>(&mut self, entity: Entity, component: C) -> Option<Rc<RefCell<C>>> {
if let Some(components) = self.map.get_mut(&entity) {
let rc = Rc::new(RefCell::new(component));
let clone = Rc::clone(&rc);
components.push(rc);
return Some(clone);
}
None
}
fn entity_remove_component<C: Component + 'static>(&mut self, entity: Entity) -> bool {
if let Some(components) = self.map.get_mut(&entity) {
let mut removed = false;
components.retain(|component| {
if removed {
return true;
}
let borrowed_comp = component.deref().borrow_mut();
let any_comp = borrowed_comp.as_any();
if any_comp.is::<C>() {
removed = true;
false
} else {
true
}
});
return removed;
}
false
}
fn entity_has_component<C: Component + 'static>(&self, entity: Entity) -> bool {
if let Some(components) = self.map.get(&entity) {
for component in components.iter() {
let borrowed_comp = component.deref().borrow_mut();
let any_comp = borrowed_comp.as_any();
if any_comp.is::<C>() {
return true;
}
}
}
false
}
fn entity_get_components(&self, entity: Entity) -> Option<Vec<Rc<RefCell<dyn Component>>>> {
if let Some(components) = self.map.get(&entity) {
let mut cloned_components = Vec::new();
for component in components.iter() {
cloned_components.push(Rc::clone(&component));
}
return Some(cloned_components);
}
None
}
fn entity_get_component<C: Component + 'static>(&self, entity: Entity) -> Option<RefMut<'_, C>> {
if let Some(components) = self.map.get(&entity) {
for component in components.iter() {
let borrowed_comp = component.deref().borrow_mut();
let any_comp = borrowed_comp.as_any();
if any_comp.is::<C>() {
return Some(RefMut::map(borrowed_comp, |c| c.as_any_mut().downcast_mut().unwrap()));
}
}
}
None
}
}

95
src/game.rs Normal file → Executable file
View File

@ -1,10 +1,10 @@
use std::{sync::Arc, ops::Deref};
use std::{sync::Arc};
use async_std::{task::block_on, sync::Mutex};
use shipyard::{World, Workload};
use tracing::{metadata::LevelFilter, info, debug, Level, warn};
/* use tracing_subscriber::{filter::FilterFn, util::SubscriberInitExt};
use tracing_subscriber::prelude::__tracing_subscriber_SubscriberExt; */
use specs::{Dispatcher, World};
use tracing::{metadata::LevelFilter, info, debug, warn};
use tracing_subscriber::{
layer::{Layer, SubscriberExt},
filter::FilterFn,
@ -15,16 +15,22 @@ use winit::{window::{WindowBuilder, Window}, event::{Event, WindowEvent, Keyboar
use crate::{renderer::{Renderer, BasicRenderer}, input_event::InputEvent};
struct GameLoop {
struct GameLoop<'a, 'b> {
window: Arc<Window>,
renderer: Box<dyn Renderer>,
world: Arc<Mutex<World>>,
system_dispatchers: Vec<Dispatcher<'a, 'b>>,
}
impl GameLoop {
pub async fn new(window: &Arc<Window>) -> Self {
impl<'a, 'b> GameLoop<'a, 'b> {
pub async fn new(window: Arc<Window>, world: Arc<Mutex<World>>, system_dispatchers: Vec<Dispatcher<'a, 'b>>) -> GameLoop<'a, 'b> {
Self {
window: Arc::clone(&window),
renderer: Box::new(BasicRenderer::create_with_window(Arc::clone(window)).await),
renderer: Box::new(BasicRenderer::create_with_window(window).await),
world,
system_dispatchers,
}
}
@ -37,8 +43,10 @@ impl GameLoop {
}
async fn update(&mut self) {
todo!()
let world = self.world.lock().await;
for dispatcher in self.system_dispatchers.iter_mut() {
dispatcher.dispatch(&world);
}
}
async fn input_update(&mut self, event: &InputEvent) -> Option<ControlFlow> {
@ -112,6 +120,8 @@ impl GameLoop {
},
Event::RedrawRequested(window_id) if window_id == self.window.id() => {
self.update().await;
match self.renderer.as_mut().render().await {
Ok(_) => {}
// Reconfigure the surface if lost
@ -131,20 +141,23 @@ impl GameLoop {
}
}
pub struct Game {
pub struct Game<'a, 'b> {
world: Option<Arc<Mutex<World>>>,
system_dispatchers: Option<Vec<Dispatcher<'a, 'b>>>,
}
impl Game {
/* fn lyra_engine_log_filter() {
impl<'a, 'b> Default for Game<'a, 'b> {
fn default() -> Self {
Self {
world: None,
system_dispatchers: None,
}
}
}
} */
pub async fn initialize() -> Self {
impl<'a, 'b> Game<'static, 'static> {
pub async fn initialize() -> Game<'static, 'static> {
let filter = FilterFn::new(|metadata| {
//metadata.
//println!("Metadata: {:?}", metadata);
metadata.module_path()
.unwrap_or_else(|| metadata.target())
.starts_with("lyra_engine") && (LevelFilter::INFO >= metadata.level().to_owned())
@ -156,36 +169,42 @@ impl Game {
.with(layer.with_filter(filter))
.init();
Self {
}
Self::default()
}
fn update() {
todo!()
pub fn with_world(&mut self, world: World) -> &mut Self {
self.world = Some(Arc::new(Mutex::new(world)));
self
}
fn input_update() {
todo!()
pub fn with_world_arc(&mut self, world: Arc<Mutex<World>>) -> &mut Self {
self.world = Some(world);
self
}
fn render_window() {
todo!()
pub fn with_dispatchers(&mut self, dispatchers: Vec<Dispatcher<'static, 'static>>) -> &mut Self {
self.system_dispatchers = Some(dispatchers);
self
}
fn render_item() {
todo!()
}
pub fn with_dispatcher(&mut self, dispatcher: Dispatcher<'static, 'static>) -> &mut Self {
self.system_dispatchers.get_or_insert_with(|| Vec::new()).push(dispatcher);
fn exit() {
todo!()
self
}
pub async fn run(&mut self) {
let event_loop = EventLoop::new();
let window = WindowBuilder::new().build(&event_loop).unwrap();
let world = self.world.take().expect("ECS World was never given to Game!");
let system_dispatchers = self.system_dispatchers
.take().unwrap_or_else(|| Vec::new());
let mut g_loop = GameLoop::new(&Arc::new(window)).await;
let event_loop = EventLoop::new();
let window = Arc::new(WindowBuilder::new().build(&event_loop).unwrap());
let mut g_loop = GameLoop::new(Arc::clone(&window), world, system_dispatchers).await;
event_loop.run(move |event, _, control_flow| {
g_loop.run_sync(event, control_flow);

0
src/input_event.rs Normal file → Executable file
View File

97
src/main.rs Normal file → Executable file
View File

@ -1,22 +1,23 @@
mod system;
mod game;
mod renderer;
//mod ecs;
mod input_event;
use game::Game;
use specs::*;
use shipyard::{Component, World, EntitiesViewMut, ViewMut, Get, Workload};
use shipyard::IntoWorkload;
//use ecs::{registry::Registry, component::Component};
#[derive(Component, Debug)]
#[derive(Component, Debug, Default)]
struct Point2d {
x: i32,
y: i32,
}
impl std::fmt::Display for Point2d {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "(x={}, y={})", self.x, self.y)
}
}
impl Point2d {
pub fn new(x: i32, y: i32) -> Self {
Self {
@ -26,37 +27,69 @@ impl Point2d {
}
}
#[derive(Component, Debug, Default)]
struct Point3d {
x: i32,
y: i32,
z: i32,
}
impl std::fmt::Display for Point3d {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "(x={}, y={}, z={})", self.x, self.y, self.z)
}
}
impl Point3d {
pub fn new(x: i32, y: i32, z: i32) -> Self {
Self {
x,
y,
z,
}
}
}
struct TestingSystem;
impl<'a> System<'a> for TestingSystem {
// These are the resources required for execution.
// You can also define a struct and `#[derive(SystemData)]`,
// see the `full` example.
type SystemData = (ReadStorage<'a, Point2d>,);
fn run(&mut self, (point,): Self::SystemData) {
// The `.join()` combines multiple component storages,
// so we get access to all entities which have
// both a position and a velocity.
for (point,) in (&point,).join() {
//point.0 += vel.0;
println!("Entity at: {}", point);
}
}
}
#[async_std::main]
async fn main() {
let mut world = World::new();
world.register::<Point2d>();
world.register::<Point3d>();
let entity = world.add_entity(Point2d::new(10, 10));
let entity2 = world.add_entity(Point2d::new(846, 2188));
world.create_entity()
.with(Point2d::new(10, 10))
.with(Point3d::new(50, 50, 50))
.build();
world.run(
|mut vm_point: ViewMut<Point2d>| {
let mut p = (&mut vm_point).get(entity).unwrap();
println!("Point: {:?}", p);
p.x = 100;
println!("Point after: {:?}", p);
drop(p);
let mut dispatcher = DispatcherBuilder::new().with(TestingSystem, "testing_system", &[]).build();
dispatcher.setup(&mut world);
let mut p = (&mut vm_point).get(entity2).unwrap();
println!("Point: {:?}", p);
p.x = 1000;
println!("Point after: {:?}", p);
//dispatcher.dispatch(&mut world);
//let mut vm_
//println!("Point2d: {:?}", vm_point.get);
/* let empty_entity = entities.add_entity((), ());
let single_component = entities.add_entity(&mut vm_pos, Pos::new());
let multiple_components =
entities.add_entity((&mut vm_pos, &mut vm_vel), (Pos::new(), Vel::new())); */
},
);
Game::initialize().await
.with_world(world)
.with_dispatcher(dispatcher)
.run().await;
let mut game = Game::initialize().await;
game.run().await;
/* let mut game = Game::initialize().await;
game.run().await; */
}

0
src/renderer.rs Normal file → Executable file
View File

0
src/shader.wgsl Normal file → Executable file
View File

0
src/system.rs Normal file → Executable file
View File