diff --git a/engine/src/main.rs b/engine/src/main.rs index 080ca33..98f7a07 100644 --- a/engine/src/main.rs +++ b/engine/src/main.rs @@ -214,17 +214,58 @@ impl<'a> ecs::System <'a> for Main { fn main() { env_logger::init(); + let mut world = World::new(); let mut renderer = render::Renderer::initialize(&mut world); let main = Main::new(&mut world, &mut renderer); - let context = scripting::WorldContext::new(); - context.eval("print(\"Hello, Lua!\", 1337)").unwrap(); + let context = scripting::WorldContext::new(&world); + context.eval_init(r#" + print("Hello, Lua!") + --for k,v in pairs(components) do + -- print("Component", k, v) + --end + + local sent = {} + sent.register = function (name, cls) + if cls.__sent_class_id ~= nil then + print("Attempting to re-register " .. name) + return + end + local sent_class_id = __sent_register(name, cls) + cls.__sent_class_id = sent_class_id + cls.new = function(...) + local arg = {...} + local res = __sent_new(sent_class_id) + if res.init ~= nil then + res:init(unpack(arg)) + end + return res + end + end + + local Test = {} + + function Test:init(val) + self.val = val + end + + function Test:tick() + print("tick! " .. tostring(self.val)) + end + + sent.register("Test", Test) + local t1 = Test.new(123) + t1:tick() + local t2 = Test.new(234) + t2:tick() + "#).unwrap(); log::info!("Starting..."); let mut p = Processor::new(&world); p.add_system(main); + p.add_system(context); p.add_system(renderer); let start = time::Instant::now(); diff --git a/engine/src/render/renderable.rs b/engine/src/render/renderable.rs index c1def93..49c281c 100644 --- a/engine/src/render/renderable.rs +++ b/engine/src/render/renderable.rs @@ -25,12 +25,52 @@ use crate::render::resource::{ResourceID}; pub struct Transform(pub cgm::Matrix4); -impl Component for Transform { +pub struct TransformBindings {} + +impl ecs::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| { + let args: Vec = args.iter().map(|el| *el as f32).collect(); + let t = match args.len() { + 0 => Transform::at(0., 0., 0.), + 3 => Transform::at(args[0], args[1], args[2]), + 16 => Transform(cgm::Matrix4::new( + // Matrix4::new takes column-wise arguments, this api takes them row-wise. + args[0], args[4], args[8], args[12], + args[1], args[5], args[9], args[13], + args[2], args[6], args[10], args[14], + args[3], args[7], args[11], args[15], + )), + _ => { + return Err(mlua::prelude::LuaError::RuntimeError("Transform.new must be called with 0, 3 ,or 16 arguments".to_string())); + }, + }; + Ok(t) + }).unwrap()).unwrap(); + res + } fn id(&self) -> &'static str { "Transform" } } +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) { + methods.add_method("xyzw", |_, this, _: ()| { + // TODO(q3k): lua wrappers for cgmath + let xyzw = this.xyzw(); + Ok(vec![xyzw.z, xyzw.y, xyzw.z, xyzw.w]) + }); + } +} + impl Transform { pub fn at(x: f32, y: f32, z: f32) -> Self { Transform(cgm::Matrix4::from_translation(cgm::Vector3::new(x, y, z))) @@ -53,7 +93,4 @@ pub enum Renderable { } impl Component for Renderable { - fn id(&self) -> &'static str { - "Renderable" - } } diff --git a/engine/src/scripting.rs b/engine/src/scripting.rs index b47aa45..f140b3f 100644 --- a/engine/src/scripting.rs +++ b/engine/src/scripting.rs @@ -1,6 +1,5 @@ -pub struct WorldContext { - lua: mlua::Lua, -} +use std::collections::BTreeMap; +use std::sync::{Arc, Mutex, atomic}; fn debug_str(v: &mlua::Value) -> String { match v { @@ -10,9 +9,63 @@ fn debug_str(v: &mlua::Value) -> String { } } +pub struct WorldContext { + // TODO(q3k): this leaks memory, right? + lua: &'static mlua::Lua, + classes: Arc>>, + instances: Arc>, +} + +#[derive(Debug)] +struct InstanceData { + ecs: BTreeMap, + queue: Vec, +} + +#[derive(Debug)] +struct ScriptedEntityClass { + name: String, + table: mlua::RegistryKey, +} + +#[derive(Debug)] +struct ScriptedEntityClassID { + name: String, +} +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, +} + +#[derive(Debug)] +struct ScriptedEntityID { + ecs_id: Option, + internal_id: u64, +} +impl mlua::UserData for ScriptedEntityID {} + +impl ScriptedEntity { + fn new(class_name: String, table: mlua::RegistryKey) -> Self { + let internal_id = GLOBAL_SCRIPTED_ENTITY_ID.fetch_add(1, atomic::Ordering::SeqCst) + 1; + Self { + class_name, + internal_id, + ecs_id: None, + table, + } + } +} + impl WorldContext { - pub fn new() -> Self { - let lua = mlua::Lua::new(); + pub fn new(world: &ecs::World) -> Self { + let lua = mlua::Lua::new().into_static(); log::info!("Lua WorldContext created."); lua.globals().set("print", lua.create_function(|_, vals: mlua::Variadic| -> mlua::Result<()> { let msg: Vec = vals.iter().map(|val| debug_str(val)).collect(); @@ -20,12 +73,86 @@ impl WorldContext { Ok(()) }).unwrap()).unwrap(); + let classes = Arc::new(Mutex::new(BTreeMap::new())); + let instances = Arc::new(Mutex::new(InstanceData { + ecs: BTreeMap::new(), + queue: Vec::new(), + })); + + lua.globals().set("sent", lua.create_table().unwrap()).unwrap(); + Self { lua, + classes, + instances, } } - pub fn eval(&self, val: &str) -> mlua::Result<()> { - self.lua.load(val).exec() + fn scope_sent( + &self, + scope: &mlua::Scope, + ) -> mlua::Result<()> { + 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; + log::info!("Registering Scripted Entity class {} at {:?}", name, cls); + + let sec = ScriptedEntityClass { + name: name.clone(), + table: lua.create_registry_value(cls)?, + }; + classes.lock().unwrap().insert(name.clone(), sec); + Ok(ScriptedEntityClassID { + name: name, + }) + })?)?; + } + { + let classes = self.classes.clone(); + let instances = self.instances.clone(); + globals.set("__sent_new", scope.create_function(move |lua, args: mlua::AnyUserData| { + let secid = args.borrow::()?; + 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))), + }; + let cls: mlua::Table = lua.registry_value(&sec.table)?; + + let table = lua.create_table()?; + let meta = lua.create_table()?; + meta.set("__index", cls)?; + table.set_metatable(Some(meta)); + + let sent = ScriptedEntity::new(secid.name.clone(), lua.create_registry_value(table.clone())?); + table.set("__sent_id", ScriptedEntityID { + ecs_id: sent.ecs_id, + internal_id: sent.internal_id, + }); + instances.lock().unwrap().queue.push(sent); + + Ok(table) + })?)?; + } + + Ok(()) + } + + pub fn eval_init(&self, val: &str) -> mlua::Result<()> + where + { + self.lua.scope(|scope| { + self.scope_sent(scope)?; + self.lua.load(val).exec() + }) + } +} + +impl <'system> ecs::System<'system> for WorldContext { + type SystemData = ecs::ReadWriteAll<'system>; + fn run(&mut self, sd: Self::SystemData) { } } diff --git a/lib/ecs/BUILD b/lib/ecs/BUILD index e3f789c..ba90a5e 100644 --- a/lib/ecs/BUILD +++ b/lib/ecs/BUILD @@ -16,6 +16,7 @@ rust_library( ], deps = [ "//third_party/cargo:log", + "//third_party/cargo:mlua", ], visibility = ["//engine:__subpackages__"], ) diff --git a/lib/ecs/src/component.rs b/lib/ecs/src/component.rs index 7f9ab17..571c1dc 100644 --- a/lib/ecs/src/component.rs +++ b/lib/ecs/src/component.rs @@ -1,9 +1,13 @@ 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; +} pub trait Component: 'static { - fn id(&self) -> &'static str { - "" + fn lua_bindings(&self) -> Option> { + None } } diff --git a/lib/ecs/src/lib.rs b/lib/ecs/src/lib.rs index c1da7e8..4947a26 100644 --- a/lib/ecs/src/lib.rs +++ b/lib/ecs/src/lib.rs @@ -8,10 +8,12 @@ pub mod world; pub use entity::ID as EntityID; pub use component::{Component, Global}; +pub use component::LuaBindings as ComponentLuaBindings; pub use world::{ World, ReadComponent, ReadWriteComponent, ReadGlobal, ReadWriteGlobal, + ReadWriteAll, }; pub use system::{ System, Join, Processor, diff --git a/lib/ecs/src/system.rs b/lib/ecs/src/system.rs index 8623bfe..6a527e3 100644 --- a/lib/ecs/src/system.rs +++ b/lib/ecs/src/system.rs @@ -7,6 +7,7 @@ use crate::{ ReadComponent, ReadComponentIter, ReadWriteComponent, ReadWriteComponentIter, ReadGlobal, ReadWriteGlobal, + ReadWriteAll, World, } }; @@ -81,6 +82,12 @@ impl<'a, T: component::Global> Access<'a> for ReadWriteGlobal<'a, T> { } } +impl<'a> Access<'a> for ReadWriteAll<'a> { + fn fetch(world: &'a World) -> Self { + world.all() + } +} + macro_rules! impl_access_tuple { ( $($ty:ident),* ) => { impl <'a, $($ty),*> Access<'a> for ( $($ty,)* ) @@ -181,7 +188,12 @@ mod test { component::Global, system, system::Join, - world::{ReadComponent, ReadWriteComponent, ReadGlobal, ReadWriteGlobal, World}, + world::{ + ReadComponent, ReadWriteComponent, + ReadGlobal, ReadWriteGlobal, + ReadWriteAll, + World, + }, }; #[derive(Clone,Debug,Default)] diff --git a/lib/ecs/src/world.rs b/lib/ecs/src/world.rs index d3c9a86..d5571bf 100644 --- a/lib/ecs/src/world.rs +++ b/lib/ecs/src/world.rs @@ -132,8 +132,21 @@ impl<'a, T: component::Global> ReadWriteGlobal<'a, T> { } } +/// ReadWriteAll gives access to all components/entities/globals within a world. Using it in a +/// system means that no other system can run in parallel, and limits performance. This should only +/// be used when absolutely necessary (eg. for scripting systems). +pub struct ReadWriteAll<'a> { + world: &'a World, +} + +impl<'a> ReadWriteAll<'a> { +} + + pub struct World { components: BTreeMap, + component_by_idstr: BTreeMap<&'static str, component::ID>, + component_lua_bindings: BTreeMap>, globals: GlobalMap, next_id: entity::ID, } @@ -142,6 +155,8 @@ impl World { pub fn new() -> Self { Self { components: BTreeMap::new(), + component_by_idstr: BTreeMap::new(), + component_lua_bindings: BTreeMap::new(), globals: GlobalMap::new(), next_id: 1u64, } @@ -159,9 +174,16 @@ impl World { 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 c.id() == "" { - log::warn!("Component type {:?} has no .id() defined, will not be accessible from scripting.", cid); + 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() }); @@ -196,9 +218,19 @@ impl World { } } + pub fn all<'a>(&'a self) -> ReadWriteAll<'a> { + ReadWriteAll { + world: self, + } + } + pub fn set_global(&self, r: T) { self.globals.set(r).unwrap(); } + + pub fn lua_components(&self) -> &BTreeMap> { + &self.component_lua_bindings + } } #[cfg(test)]