From 8b49f8324e08cd850382e5a3789eced9cd9c056e Mon Sep 17 00:00:00 2001 From: Serge Bazanski Date: Wed, 7 Apr 2021 13:55:15 +0000 Subject: [PATCH] engine, ecs: dynamically create components for entities --- engine/init.lua | 41 ++++++++-- engine/src/main.rs | 3 +- engine/src/physics/color.rs | 1 + engine/src/render/light.rs | 2 + engine/src/render/material.rs | 2 + engine/src/render/mesh.rs | 1 + engine/src/render/renderable.rs | 43 ++++++++--- engine/src/render/resource.rs | 1 + engine/src/render/vulkan/data.rs | 10 ++- engine/src/scripting.rs | 127 +++++++++++++++++++++++++------ lib/ecs/src/component.rs | 13 ++-- lib/ecs/src/entity.rs | 5 ++ lib/ecs/src/world.rs | 50 ++++++++---- 13 files changed, 236 insertions(+), 63 deletions(-) diff --git a/engine/init.lua b/engine/init.lua index e3bf17a..0eea2f7 100644 --- a/engine/init.lua +++ b/engine/init.lua @@ -1,15 +1,33 @@ -print("Hello, Lua2!") ---for k,v in pairs(components) do --- print("Component", k, v) ---end +print("Hello, Lua!") +for k,v in pairs(components) do + print("Lua Component", k, v) +end local sent = {} -sent.register = function (name, cls) +sent.register = function (cfg) + if cfg.name == nil then + error("sent.register: needs name") + end + if cfg.cls == nil then + error("sent.register: needs cls") + end + local name = cfg.name + local cls = cfg.cls + local components = cfg.components or {} + if cls.__sent_class_id ~= nil then - print("Attempting to re-register " .. name) + error(string.format("sent.register: %s already registered", name)) return end - local sent_class_id = __sent_register(name, cls) + + -- Recreate config when calling native function, to ensure no metatable + -- fuckery. + local sent_class_id = __sent_register({ + name = name, + cls = cls, + components = components, + }) + cls.__sent_class_id = sent_class_id cls.new = function(...) local arg = {...} @@ -32,7 +50,14 @@ function Test:tick() print("components " .. tostring(self.components)) end -sent.register("Test", Test) +sent.register({ + name = "Test", + cls = Test, + components = { + components.Transform.new(0, 0, 0), + }, +}) + local t1 = Test.new(123) t1:tick() local t2 = Test.new(234) diff --git a/engine/src/main.rs b/engine/src/main.rs index b56e2e9..a52e2b8 100644 --- a/engine/src/main.rs +++ b/engine/src/main.rs @@ -216,12 +216,13 @@ fn main() { env_logger::init(); let mut world = World::new(); + world.register_component_lua_bindings(Transform::bindings()); let mut renderer = render::Renderer::initialize(&mut world); let main = Main::new(&mut world, &mut renderer); let context = scripting::WorldContext::new(&world); let init = util::file::resource("//engine/init.lua").unwrap().string().unwrap(); - context.eval_init(init).unwrap(); + context.eval_init(&world, init).unwrap(); log::info!("Starting..."); diff --git a/engine/src/physics/color.rs b/engine/src/physics/color.rs index 93569ad..298e4ea 100644 --- a/engine/src/physics/color.rs +++ b/engine/src/physics/color.rs @@ -35,6 +35,7 @@ impl XYZ { } } +#[derive(Debug)] pub struct LinearF32 { pub d: f32, } diff --git a/engine/src/render/light.rs b/engine/src/render/light.rs index 36bf7cd..85c91d7 100644 --- a/engine/src/render/light.rs +++ b/engine/src/render/light.rs @@ -23,6 +23,7 @@ use crate::render::vulkan::data; /// An Omni point light, with position in 3d space, and 'color' defined in lumens per CIE XYZ /// color channel. +#[derive(Debug)] pub struct Omni { /// Luminour power/flux defined as lumens per XYZ color channel. pub color: color::XYZ, @@ -57,6 +58,7 @@ impl Omni { } } +#[derive(Debug)] pub enum Light { Omni(Omni), } diff --git a/engine/src/render/material.rs b/engine/src/render/material.rs index 81ffde5..96e07db 100644 --- a/engine/src/render/material.rs +++ b/engine/src/render/material.rs @@ -28,6 +28,7 @@ use crate::render::vulkan::data; use crate::render::vulkan::material::ChannelLayoutVulkan; use crate::util::file; +#[derive(Debug)] pub enum Texture { Color(T), ImageRef(String), @@ -55,6 +56,7 @@ impl Texture { } } +#[derive(Debug)] pub struct Material { diffuse: Texture, roughness: Texture, diff --git a/engine/src/render/mesh.rs b/engine/src/render/mesh.rs index 99fbe52..e9a5cca 100644 --- a/engine/src/render/mesh.rs +++ b/engine/src/render/mesh.rs @@ -24,6 +24,7 @@ use vulkano::sync::GpuFuture; use crate::render::vulkan::data; +#[derive(Debug)] pub struct Mesh { vertices: Arc>, indices: Arc>, diff --git a/engine/src/render/renderable.rs b/engine/src/render/renderable.rs index 49c281c..4aa0f8f 100644 --- a/engine/src/render/renderable.rs +++ b/engine/src/render/renderable.rs @@ -16,18 +16,29 @@ use std::collections::HashMap; use std::hash; +use std::cell::Ref; use cgmath as cgm; -use ecs::Component; +use ecs::{Component, ComponentLuaBindings}; use crate::render::{Light, Mesh, Material}; use crate::render::resource::{ResourceID}; +#[derive(Clone, Debug)] pub struct Transform(pub cgm::Matrix4); -pub struct TransformBindings {} +impl Component for Transform { + fn id(&self) -> ecs::component::ID { + ecs::component::component_id::() + } + fn clone_dyn(&self) -> Box { + Box::new(self.clone()) + } +} -impl ecs::ComponentLuaBindings for TransformBindings { +struct TransformBindings; + +impl ComponentLuaBindings for TransformBindings { fn globals<'a>(&self, lua: &'a mlua::Lua) -> mlua::Table<'a> { let res = lua.create_table().unwrap(); res.set("new", lua.create_function(|_, args: mlua::Variadic| { @@ -50,16 +61,20 @@ impl ecs::ComponentLuaBindings for TransformBindings { }).unwrap()).unwrap(); res } - fn id(&self) -> &'static str { + fn idstr(&self) -> &'static str { "Transform" } + fn id(&self) -> ecs::component::ID { + ecs::component::component_id::() + } + fn any_into_dyn<'a>(&self, ud: &'a mlua::AnyUserData) -> Option> { + match ud.borrow::() { + Ok(v) => Some(Box::new(Transform::clone(&v))), + Err(_) => None, + } + } } -impl Component for Transform { - fn lua_bindings(&self) -> Option> { - Some(Box::new(TransformBindings{})) - } -} impl mlua::UserData for Transform { fn add_methods<'lua, M: mlua::UserDataMethods<'lua, Self>>(methods: &mut M) { @@ -85,12 +100,22 @@ impl Transform { pub fn m4(&self) -> &cgm::Matrix4 { &self.0 } + pub fn bindings() -> Box { + Box::new(TransformBindings) + } } +#[derive(Clone, Debug)] pub enum Renderable { Light(ResourceID), Mesh(ResourceID, ResourceID), } impl Component for Renderable { + fn id(&self) -> ecs::component::ID { + ecs::component::component_id::() + } + fn clone_dyn(&self) -> Box { + Box::new(self.clone()) + } } diff --git a/engine/src/render/resource.rs b/engine/src/render/resource.rs index 5a9ac79..f97e0c8 100644 --- a/engine/src/render/resource.rs +++ b/engine/src/render/resource.rs @@ -56,6 +56,7 @@ impl Resource for Material { fn map_mut(rm: &mut Manager) -> &mut Map { &mut rm.materials } } +#[derive(Debug)] pub struct ResourceID { numerical: u64, phantom: std::marker::PhantomData, diff --git a/engine/src/render/vulkan/data.rs b/engine/src/render/vulkan/data.rs index 5c89426..ae33116 100644 --- a/engine/src/render/vulkan/data.rs +++ b/engine/src/render/vulkan/data.rs @@ -22,7 +22,7 @@ use vulkano::format::Format; use cgmath as cgm; -#[derive(Default, Copy, Clone)] +#[derive(Default, Copy, Clone, Debug)] pub struct Vertex { pos: [f32; 3], normal: [f32; 3], @@ -70,7 +70,7 @@ pub struct FragmentUniformBufferObject { pub omni_lights: [OmniLight; 4], } -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct Textures { // diffuse: RGB pub diffuse: Arc>, @@ -82,3 +82,9 @@ pub struct VertexData { pub vbuffer: Arc>, pub ibuffer: Arc>, } + +impl std::fmt::Debug for VertexData { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("VertexData").finish() + } +} diff --git a/engine/src/scripting.rs b/engine/src/scripting.rs index 2b6b19e..ea138c4 100644 --- a/engine/src/scripting.rs +++ b/engine/src/scripting.rs @@ -1,6 +1,8 @@ use std::collections::BTreeMap; use std::sync::{Arc, Mutex, atomic}; +use mlua::prelude::LuaError::RuntimeError; + fn debug_str(v: &mlua::Value) -> String { match v { mlua::Value::String(s) => s.to_str().map_or(format!("{:?}", v), |s| s.to_string()), @@ -9,10 +11,18 @@ fn debug_str(v: &mlua::Value) -> String { } } +#[derive(Debug)] +struct ComponentID { + id: ecs::component::ID, + idstr: String, +} +impl mlua::UserData for ComponentID {} + #[derive(Debug)] struct ScriptedEntityClass { name: String, - table: mlua::RegistryKey, + cls: mlua::RegistryKey, + components: Vec>, } #[derive(Debug)] @@ -24,18 +34,19 @@ impl mlua::UserData for ScriptedEntityClassID {} static GLOBAL_SCRIPTED_ENTITY_ID: atomic::AtomicU64 = atomic::AtomicU64::new(0); #[derive(Debug)] -struct ScriptedEntity { - class_name: String, - internal_id: u64, - ecs_id: Option, - table: mlua::RegistryKey, +enum ScriptedEntityStatus { + QueuedForCreation, + Exists(ecs::EntityID), + FailedToCreate, } #[derive(Debug)] -struct ScriptedEntityID { +struct ScriptedEntity { + class_name: String, internal_id: u64, + status: ScriptedEntityStatus, + table: mlua::RegistryKey, } -impl mlua::UserData for ScriptedEntityID {} impl ScriptedEntity { fn new(class_name: String, table: mlua::RegistryKey) -> Self { @@ -43,12 +54,18 @@ impl ScriptedEntity { Self { class_name, internal_id, - ecs_id: None, + status: ScriptedEntityStatus::QueuedForCreation, table, } } } +#[derive(Debug)] +struct ScriptedEntityID { + internal_id: u64, +} +impl mlua::UserData for ScriptedEntityID {} + pub struct WorldContext { // TODO(q3k): this leaks memory, right? lua: &'static mlua::Lua, @@ -89,21 +106,66 @@ impl WorldContext { } } - fn scope_sent( + fn global_set_components(&self, world: &ecs::World) -> mlua::Result<()> { + let globals = self.lua.globals(); + let components = match globals.get("components") { + Ok(c) => c, + Err(_) => { + let components = self.lua.create_table()?; + globals.set("components", components.clone())?; + components + } + }; + components.set_metatable(None); + for (idstr, id, bindings) in world.get_component_lua_bindings() { + let methods = bindings.globals(&self.lua); + methods.set("__component_component_id", ComponentID { + id, + idstr: idstr.clone(), + }); + components.set(idstr, methods); + } + Ok(()) + } + + fn scope_sent<'a, 'lua, 'scope>( &self, - scope: &mlua::Scope, - ) -> mlua::Result<()> { + scope: &mlua::Scope<'lua, 'scope>, + world: &'a ecs::World, + ) -> mlua::Result<()> + where + 'a: 'scope, + { let globals = self.lua.globals(); { let classes = self.classes.clone(); - globals.set("__sent_register", scope.create_function(move |lua, args: (mlua::String, mlua::Table)| -> mlua::Result<_> { - let name = args.0.to_str()?.to_string(); - let cls = args.1; + globals.set("__sent_register", scope.create_function(move |lua, cfg: mlua::Table| -> mlua::Result<_> { + let name: String = cfg.get("name")?; + let cls: mlua::Table = cfg.get("cls")?; + + let components: mlua::Table = cfg.get("components")?; + let components: Vec> = components + .pairs::() + .map(|pair| match pair { + Ok((_, v)) => match world.lua_any_into_dyn::<'a, '_>(&v) { + Some(b) => Ok(b), + None => Err(RuntimeError( + format!("cfg.components type error: not a component") + )), + }, + Err(err) => Err(RuntimeError( + format!("cfg.components iter error: {:?}", err) + )), + }) + .collect::>>>()?; + log::info!("Registering Scripted Entity class {} at {:?}", name, cls); + log::info!("Components: {:?}", components); let sec = ScriptedEntityClass { name: name.clone(), - table: lua.create_registry_value(cls)?, + cls: lua.create_registry_value(cls)?, + components, }; classes.lock().unwrap().insert(name.clone(), sec); Ok(ScriptedEntityClassID { @@ -119,9 +181,9 @@ impl WorldContext { let classes = classes.lock().unwrap(); let sec = match classes.get(&secid.name) { Some(el) => el, - None => return Err(mlua::prelude::LuaError::RuntimeError(format!("lost secid {:?}", secid.name))), + None => return Err(RuntimeError(format!("lost secid {:?}", secid.name))), }; - let cls: mlua::Table = lua.registry_value(&sec.table)?; + let cls: mlua::Table = lua.registry_value(&sec.cls)?; // (meta)table tree for entity objects: // @@ -169,13 +231,14 @@ impl WorldContext { Ok(()) } - pub fn eval_init(&self, val: T) -> mlua::Result<()> + pub fn eval_init(&self, world: &ecs::World, val: T) -> mlua::Result<()> where T: Into { let val: String = val.into(); self.lua.scope(|scope| { - self.scope_sent(scope)?; + self.global_set_components(world)?; + self.scope_sent(scope, world)?; self.lua.load(&val).exec() }) } @@ -185,16 +248,30 @@ impl <'system> ecs::System<'system> for WorldContext { type SystemData = ecs::ReadWriteAll<'system>; fn run(&mut self, sd: Self::SystemData) { let mut instances = self.instances.lock().unwrap(); + let classes = self.classes.lock().unwrap(); // Lazily create enqueued entities. if instances.queue.len() > 0 { let mut queue = std::mem::replace(&mut instances.queue, Vec::new()); + for mut el in queue.into_iter() { - let ecsid = sd.new_entity_lazy().build(&sd); - el.ecs_id = Some(ecsid); - instances.internal_to_ecs.insert(el.internal_id, ecsid); - log::debug!("Created sent of type {} with ECS ID {} and internal ID {}", el.class_name, ecsid, el.internal_id); - instances.ecs.insert(ecsid, el); + match classes.get(&el.class_name) { + Some(sec) => { + let mut entity = sd.new_entity_lazy(); + for component in sec.components.iter() { + entity = entity.with_dyn(component.clone_dyn()); + } + let ecsid = entity.build(&sd); + el.status = ScriptedEntityStatus::Exists(ecsid); + log::debug!("Created sent of type {} with ECS ID {} and internal ID {}", el.class_name, ecsid, el.internal_id); + instances.internal_to_ecs.insert(el.internal_id, ecsid); + instances.ecs.insert(ecsid, el); + }, + None => { + log::warn!("Failed to create entity with internal ID {}: no class {}", el.internal_id, el.class_name); + el.status = ScriptedEntityStatus::FailedToCreate; + }, + } } } } diff --git a/lib/ecs/src/component.rs b/lib/ecs/src/component.rs index 571c1dc..7d7cdc3 100644 --- a/lib/ecs/src/component.rs +++ b/lib/ecs/src/component.rs @@ -1,14 +1,17 @@ +use std::cell::Ref; + pub type ID = std::any::TypeId; pub trait LuaBindings { fn globals<'a>(&self, lua: &'a mlua::Lua) -> mlua::Table<'a>; - fn id(&self) -> &'static str; + fn idstr(&self) -> &'static str; + fn id(&self) -> ID; + fn any_into_dyn<'a>(&self, ud: &'a mlua::AnyUserData) -> Option>; } -pub trait Component: 'static { - fn lua_bindings(&self) -> Option> { - None - } +pub trait Component: std::fmt::Debug + 'static { + fn id(&self) -> ID; + fn clone_dyn(&self) -> Box; } pub fn component_id() -> ID { diff --git a/lib/ecs/src/entity.rs b/lib/ecs/src/entity.rs index c0a7b46..375cd05 100644 --- a/lib/ecs/src/entity.rs +++ b/lib/ecs/src/entity.rs @@ -53,6 +53,11 @@ impl LazyEntityBuilder { self } + pub fn with_dyn(mut self, c: Box) -> Self { + self.components.push((c.id(), c)); + self + } + pub fn build(self, w: &world::World) -> ID { for (cid, c) in self.components.into_iter() { w.enqueue_register_component_entity(cid, c, self.ent); diff --git a/lib/ecs/src/world.rs b/lib/ecs/src/world.rs index 8ebff05..6cdcab6 100644 --- a/lib/ecs/src/world.rs +++ b/lib/ecs/src/world.rs @@ -186,25 +186,40 @@ impl World { entity::LazyEntityBuilder::new(self.allocate_next_id()) } + pub fn register_component_lua_bindings( + &mut self, + bindings: Box, + ) { + let idstr = bindings.idstr(); + let cid = bindings.id(); + if let Some(_) = self.component_by_idstr.get(idstr) { + log::warn!("Ignored attempted re-registration of Lua bindings for component {} (duplicate idstr)", idstr); + return + } + if let Some(_) = self.component_lua_bindings.get(&cid) { + log::warn!("Ignored attempted re-registration of Lua bindings for component {} (duplicate ID)", idstr); + return + } + self.component_by_idstr.insert(idstr, cid); + self.component_lua_bindings.insert(cid, bindings); + } + + pub fn lua_any_into_dyn<'a, 'b>(&'a self, ud: &'a mlua::AnyUserData) -> Option> { + for (_, bindings) in self.component_lua_bindings.iter() { + if let Some(b) = bindings.any_into_dyn(ud) { + return Some(b); + } + } + None + } + pub fn register_component_entity( &mut self, cid: component::ID, c: Box, e: entity::Entity ) { - if let Some(bindings) = c.lua_bindings() { - // TODO(q3k): optimize this to not happen on every registration. - self.component_by_idstr.insert(bindings.id(), cid); - self.component_lua_bindings.insert(cid, bindings); - } - let map = self.components.entry(cid).or_insert_with(|| { - if let Some(bindings) = c.lua_bindings() { - log::info!("Registered component {}", bindings.id()); - } else { - log::warn!("Component {:?} has no .lua_bindings() defined, will not be accessible from scripting.", cid); - } - ComponentMap::new() - }); + let map = self.components.entry(cid).or_insert(ComponentMap::new()); map.insert(e.id(), c).unwrap(); } @@ -225,6 +240,15 @@ impl World { } } + pub fn get_component_lua_bindings( + &self, + ) -> Vec<(String, component::ID, &Box)> { + self.component_by_idstr.iter().filter_map(|(idstr, cid)| { + let bindings = self.component_lua_bindings.get(cid)?; + Some((idstr.to_string(), *cid, bindings)) + }).collect() + } + pub fn components<'a, T: component::Component>(&'a self) -> ReadComponent<'a, T> { ReadComponent { world: self,