From 1c33076401e47294432e8f1f3a6a1e166af22162 Mon Sep 17 00:00:00 2001 From: Serge Bazanski Date: Thu, 8 Apr 2021 11:33:54 +0000 Subject: [PATCH] engine, ecs: implement getting/setting entity component values --- engine/scene.lua | 22 ++- engine/src/render/renderable.rs | 18 ++ engine/src/scripting.rs | 320 +++++++++++++++++++++++++------- lib/ecs/src/component.rs | 6 + lib/ecs/src/world.rs | 18 +- 5 files changed, 310 insertions(+), 74 deletions(-) diff --git a/engine/scene.lua b/engine/scene.lua index 6a8facf..4cfd604 100644 --- a/engine/scene.lua +++ b/engine/scene.lua @@ -2,21 +2,31 @@ local rm = resourcemanager local cube_mesh = rm.get_mesh("cube") local cube_material = rm.get_material("test-128px") -local Test = {} +local Cube = {} -function Test:init() +function Cube:init(x, y, z) + self.components.Transform = components.Transform.new(x, y, z) end -function Test:tick() +function Cube:tick() end sent.register({ - name = "Test", - cls = Test, + name = "Cube", + cls = Cube, components = { components.Transform.new(0, 0, 0), components.Renderable.new_mesh(cube_mesh, cube_material), }, }) -Test.new() +for x=-2,2 do + for y=-2,2 do + for z=-2,2 do + if z > -2 and z < 2 and x > -2 and x < 2 then + else + Cube.new(x, y, z) + end + end + end +end diff --git a/engine/src/render/renderable.rs b/engine/src/render/renderable.rs index 237f769..5aff128 100644 --- a/engine/src/render/renderable.rs +++ b/engine/src/render/renderable.rs @@ -24,6 +24,8 @@ use ecs::{Component, ComponentLuaBindings}; use crate::render::{Light, Mesh, Material}; use crate::render::resource::{ResourceID}; +use mlua::ToLua; + #[derive(Clone, Debug)] pub struct Transform(pub cgm::Matrix4); @@ -34,6 +36,22 @@ impl Component for Transform { fn clone_dyn(&self) -> Box { Box::new(self.clone()) } + fn lua_userdata<'access, 'lua>( + &'access self, + lua: &'lua mlua::Lua, + ) -> Option> { + let ud: mlua::Value<'lua> = self.clone().to_lua(lua).unwrap(); + Some(ud) + } + fn lua_fromuserdata<'a>( + &self, + ud: &'a mlua::AnyUserData, + ) -> Option> { + match ud.borrow::() { + Ok(v) => Some(Box::new(Transform::clone(&v))), + Err(_) => None, + } + } } struct TransformBindings; diff --git a/engine/src/scripting.rs b/engine/src/scripting.rs index 6bbdc32..06138a7 100644 --- a/engine/src/scripting.rs +++ b/engine/src/scripting.rs @@ -27,7 +27,7 @@ impl mlua::UserData for ComponentID {} struct ScriptedEntityClass { name: String, cls: mlua::RegistryKey, - components: Vec>, + components: BTreeMap>, } #[derive(Debug)] @@ -40,7 +40,7 @@ static GLOBAL_SCRIPTED_ENTITY_ID: atomic::AtomicU64 = atomic::AtomicU64::new(0); #[derive(Debug)] enum ScriptedEntityStatus { - QueuedForCreation, + QueuedForCreation(BTreeMap>), Exists(ecs::EntityID), FailedToCreate, } @@ -51,21 +51,88 @@ struct ScriptedEntity { internal_id: u64, status: ScriptedEntityStatus, table: mlua::RegistryKey, + cls: mlua::RegistryKey, } impl ScriptedEntity { - fn new(class_name: String, table: mlua::RegistryKey) -> Self { + fn new( + class_name: String, + table: mlua::RegistryKey, + cls: mlua::RegistryKey, + components: BTreeMap>, + ) -> Self { let internal_id = GLOBAL_SCRIPTED_ENTITY_ID.fetch_add(1, atomic::Ordering::SeqCst) + 1; Self { class_name, internal_id, - status: ScriptedEntityStatus::QueuedForCreation, + status: ScriptedEntityStatus::QueuedForCreation(components), table, + cls, } } + + fn set_metatable( + &self, + lua: &mlua::Lua, + world: &ecs::World, + ) -> mlua::Result<()> + { + // (meta)table tree for entity objects: + // + // table: { } + // | metatable + // V + // metatable: { __index } + // | + // .-------------' + // V + // dispatch: { components.{...}, ... } + // | metadata + // V + // metametatable: { __index } + // | + // .-----------------' + // V + // cls: { init, tick, ... } + + let table: mlua::Table = lua.registry_value(&self.table)?; + let cls: mlua::Table = lua.registry_value(&self.cls)?; + + let meta = lua.create_table()?; + let dispatch = lua.create_table()?; + let metameta = lua.create_table()?; + + table.set_metatable(Some(meta.clone())); + meta.set("__index", dispatch.clone())?; + dispatch.set_metatable(Some(metameta.clone())); + metameta.set("__index", cls)?; + + table.set("__sent_id", ScriptedEntityID { + internal_id: self.internal_id, + }); + + let components = lua.create_table()?; + dispatch.set("components", components.clone())?; + + let componentsmeta = lua.create_table()?; + componentsmeta.set( + "__index", + lua.globals().get::<_, mlua::Function>("__sent_components_index")?, + )?; + componentsmeta.set( + "__newindex", + lua.globals().get::<_, mlua::Function>("__sent_components_newindex")?, + )?; + components.set_metatable(Some(componentsmeta)); + components.raw_set("__sent_id", ScriptedEntityID { + internal_id: self.internal_id, + }); + + Ok(()) + } } -#[derive(Debug)] +#[derive(Debug, Clone)] struct ScriptedEntityID { internal_id: u64, } @@ -85,6 +152,41 @@ struct InstanceData { queue: Vec, } +impl InstanceData { + fn get_mut(&mut self, internal: u64) -> Option<&mut ScriptedEntity> { + if let Some(eid) = self.internal_to_ecs.get(&internal) { + if let Some(se) = self.ecs.get_mut(eid) { + return Some(se); + } + panic!("Corrupt InstanceData: internal id {} found in internal_to_ecs, bot not in ecs", internal); + } + + for (i, el) in self.queue.iter().enumerate() { + if el.internal_id != internal { + continue + } + return Some(&mut self.queue[i]) + } + None + } + fn get(&self, internal: u64) -> Option<&ScriptedEntity> { + if let Some(eid) = self.internal_to_ecs.get(&internal) { + if let Some(se) = self.ecs.get(eid) { + return Some(se); + } + panic!("Corrupt InstanceData: internal id {} found in internal_to_ecs, bot not in ecs", internal); + } + + for el in self.queue.iter() { + if el.internal_id != internal { + continue + } + return Some(&el) + } + None + } +} + impl WorldContext { pub fn new(world: &ecs::World) -> Self { let lua = mlua::Lua::new().into_static(); @@ -152,13 +254,14 @@ impl WorldContext { /// Registers resourcemanager global in Lua, scoped to scope. // TODO(q3k): make this generic for all ECS globals. - fn scope_resourcemanager<'a, 'lua, 'scope>( - &self, - scope: &mlua::Scope<'lua, 'scope>, - world: &'a ecs::World, + fn scope_resourcemanager<'a, 'world, 'lua, 'scope>( + &'a self, + scope: &'a mlua::Scope<'lua, 'scope>, + world: &'world ecs::World, ) -> mlua::Result<()> where - 'a: 'scope + 'world: 'scope, + 'scope: 'a, { let globals = self.lua.globals(); let resourcemanager = self.lua.create_table()?; @@ -187,13 +290,14 @@ impl WorldContext { Ok(()) } - fn scope_sent<'a, 'lua, 'scope>( - &self, - scope: &mlua::Scope<'lua, 'scope>, - world: &'a ecs::World, + fn scope_sent<'a, 'world, 'lua, 'scope>( + &'a self, + scope: &'a mlua::Scope<'lua, 'scope>, + world: &'world ecs::World, ) -> mlua::Result<()> where - 'a: 'scope, + 'world: 'scope, + 'scope: 'a, { let globals = self.lua.globals(); { @@ -203,10 +307,10 @@ impl WorldContext { let cls: mlua::Table = cfg.get("cls")?; let components: mlua::Table = cfg.get("components")?; - let components: Vec> = components + let components: Vec<(String, Box)> = components .pairs::() .map(|pair| match pair { - Ok((_, v)) => match world.lua_any_into_dyn::<'a, '_>(&v) { + Ok((_, v)) => match world.lua_any_into_dyn::<'world, '_>(&v) { Some(b) => Ok(b), None => Err(RuntimeError( format!("cfg.components type error: not a component") @@ -216,7 +320,8 @@ impl WorldContext { format!("cfg.components iter error: {:?}", err) )), }) - .collect::>>>()?; + .collect::)>>>()?; + let components: BTreeMap> = components.into_iter().collect(); log::info!("Registering Scripted Entity class {} at {:?}", name, cls); log::info!("Components: {:?}", components); @@ -235,52 +340,138 @@ impl WorldContext { { 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::()?; + globals.set("__sent_components_index", scope.create_function(move |lua, args: (mlua::Table, mlua::Value)| -> mlua::Result { + let table = args.0; + let key: String = match args.1 { + mlua::Value::String(key) => key.to_str()?.to_string(), + _ => return Ok(mlua::Value::Nil), + }; + let seid: ScriptedEntityID = table.raw_get("__sent_id")?; + let classes = classes.lock().unwrap(); + let instances = instances.lock().unwrap(); + + // Get ScriptedEntity that the user requested this component for. + let se = match instances.get(seid.internal_id) { + Some(se) => se, + None => { + // This shouldn't really happen. Should we panic here? Probably not, as the + // Lua side could do some (accidentally) nasty things that could cause it, + // especially on reloads. + log::warn!("Lua code requested components for unknown entity {}", seid.internal_id); + return Err(RuntimeError(format!("unknown entity {}", seid.internal_id))); + } + }; + + // Now retrieve the ScriptedEntityClass, which contains information about the + // components that this entity has. + let sec = match classes.get(&se.class_name) { + Some(sec) => sec, + None => { + log::warn!("Lua code requested components for entity {} with unknown class {}", seid.internal_id, se.class_name); + return Err(RuntimeError(format!("unknown class {}", se.class_name))); + } + }; + + // And retrieve the component. + let component: &Box = match sec.components.get(&key) { + Some(c) => c, + None => return Ok(mlua::Value::Nil), + }; + + let ud: mlua::Value = match component.lua_userdata(lua) { + Some(ud) => ud, + None => return Err(RuntimeError(format!("unimplemented lua_userdata"))), + }; + Ok(ud) + })?)?; + } + { + let classes = self.classes.clone(); + let instances = self.instances.clone(); + globals.set("__sent_components_newindex", scope.create_function(move |lua, args: (mlua::Table, mlua::Value, mlua::AnyUserData)| -> mlua::Result { + let table = args.0; + let key: String = match args.1 { + mlua::Value::String(key) => key.to_str()?.to_string(), + _ => return Ok(mlua::Value::Nil), + }; + let value = args.2; + let seid: ScriptedEntityID = table.raw_get("__sent_id")?; + + let classes = classes.lock().unwrap(); + let mut instances = instances.lock().unwrap(); + + let se = match instances.get_mut(seid.internal_id) { + Some(se) => se, + None => { + log::warn!("Lua code requested components for unknown entity {}", seid.internal_id); + return Err(RuntimeError(format!("unknown entity {}", seid.internal_id))); + } + }; + let ecsid = match &mut se.status { + ScriptedEntityStatus::QueuedForCreation(c) => { + // Queued for creation, update in queue instead of in ECS. + let component: &Box = match c.get(&key) { + Some(c) => c, + None => return Ok(mlua::Value::Nil), + }; + let component_value = match component.lua_fromuserdata(&value) { + Some(cv) => cv, + None => return Err(RuntimeError(format!("unimplemented lua_fromuserdata"))), + }; + c.insert(key, component_value); + return Ok(mlua::Value::Nil); + }, + ScriptedEntityStatus::Exists(ecsid) => *ecsid, + ScriptedEntityStatus::FailedToCreate => return Err(RuntimeError(format!("cannot set component on failed entity"))), + }; + let sec = match classes.get(&se.class_name) { + Some(sec) => sec, + None => { + log::warn!("Lua code requested components for entity {} with unknown class {}", seid.internal_id, se.class_name); + return Err(RuntimeError(format!("unknown class {}", se.class_name))); + } + }; + let component: &Box = match sec.components.get(&key) { + Some(c) => c, + None => return Ok(mlua::Value::Nil), + }; + let component_value = match component.lua_fromuserdata(&value) { + Some(cv) => cv, + None => return Err(RuntimeError(format!("unimplemented lua_fromuserdata"))), + }; + log::info!("component_value: {:?}", component_value); + world.component_set_dyn(ecsid, component_value); + + Ok(mlua::Value::Nil) + })?)?; + } + { + let classes = self.classes.clone(); + let instances = self.instances.clone(); + globals.set("__sent_new", scope.create_function(move |lua, args: mlua::AnyUserData| { + let classes = classes.lock().unwrap(); + + let secid = args.borrow::()?; let sec = match classes.get(&secid.name) { Some(el) => el, None => return Err(RuntimeError(format!("lost secid {:?}", secid.name))), }; + let cls: mlua::Table = lua.registry_value(&sec.cls)?; - - // (meta)table tree for entity objects: - // - // table: { } - // | metatable - // V - // metatable: { __index } - // | - // .-------------' - // V - // dispatch: { components.{...}, ... } - // | metadata - // V - // metametatable: { __index } - // | - // .-----------------' - // V - // cls: { init, tick, ... } - let table = lua.create_table()?; - let meta = lua.create_table()?; - let dispatch = lua.create_table()?; - let metameta = lua.create_table()?; - table.set_metatable(Some(meta.clone())); - meta.set("__index", dispatch.clone())?; - dispatch.set_metatable(Some(metameta.clone())); - metameta.set("__index", cls)?; + let components: BTreeMap> = sec.components.iter().map(|(k, v)| { + (k.clone(), v.clone_dyn()) + }).collect(); + let sent = ScriptedEntity::new( + secid.name.clone(), + lua.create_registry_value(table.clone())?, + lua.create_registry_value(cls.clone())?, + components, + ); + sent.set_metatable(lua, world)?; - let components = lua.create_table()?; - dispatch.set("components", components.clone()); - let components_meta = lua.create_table()?; - components.set_metatable(Some(components_meta)); - - let sent = ScriptedEntity::new(secid.name.clone(), lua.create_registry_value(table.clone())?); - table.set("__sent_id", ScriptedEntityID { - internal_id: sent.internal_id, - }); instances.lock().unwrap().queue.push(sent); Ok(table) @@ -295,7 +486,7 @@ impl WorldContext { T: Into { let val: String = val.into(); - self.lua.scope(|scope| { + self.lua.scope(move |scope| { self.global_set_components(world)?; self.scope_sent(scope, world)?; self.scope_resourcemanager(scope, world)?; @@ -312,25 +503,22 @@ impl <'system> ecs::System<'system> for WorldContext { // Lazily create enqueued entities. if instances.queue.len() > 0 { - let mut queue = std::mem::replace(&mut instances.queue, Vec::new()); + let queue = std::mem::replace(&mut instances.queue, Vec::new()); for mut el in queue.into_iter() { - match classes.get(&el.class_name) { - Some(sec) => { + match el.status { + ScriptedEntityStatus::QueuedForCreation(components) => { let mut entity = sd.new_entity_lazy(); - for component in sec.components.iter() { - entity = entity.with_dyn(component.clone_dyn()); + for (_, component) in components.into_iter() { + entity = entity.with_dyn(component); } 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; - }, + } + o => panic!("ScriptedEntity in queue with unexpected status {:?}", o), } } } diff --git a/lib/ecs/src/component.rs b/lib/ecs/src/component.rs index 7d7cdc3..426720e 100644 --- a/lib/ecs/src/component.rs +++ b/lib/ecs/src/component.rs @@ -12,6 +12,12 @@ pub trait LuaBindings { pub trait Component: std::fmt::Debug + 'static { fn id(&self) -> ID; fn clone_dyn(&self) -> Box; + fn lua_userdata<'access, 'lua>(&'access self, lua: &'lua mlua::Lua) -> Option> { + None + } + fn lua_fromuserdata<'a>(&self, ud: &'a mlua::AnyUserData) -> Option> { + None + } } pub fn component_id() -> ID { diff --git a/lib/ecs/src/world.rs b/lib/ecs/src/world.rs index 6cdcab6..ed4a432 100644 --- a/lib/ecs/src/world.rs +++ b/lib/ecs/src/world.rs @@ -204,10 +204,10 @@ impl World { self.component_lua_bindings.insert(cid, bindings); } - pub fn lua_any_into_dyn<'a, 'b>(&'a self, ud: &'a mlua::AnyUserData) -> Option> { + pub fn lua_any_into_dyn<'a, 'b>(&'a self, ud: &'a mlua::AnyUserData) -> Option<(String, Box)> { for (_, bindings) in self.component_lua_bindings.iter() { if let Some(b) = bindings.any_into_dyn(ud) { - return Some(b); + return Some((bindings.idstr().into(), b)); } } None @@ -263,6 +263,20 @@ impl World { } } + pub fn component_set_dyn<'a>( + &'a self, + e: entity::ID, + c: Box + ) { + // TODO(q3k): error handling + // TODO(q3k): do not insert component data if it's missing? That can happen if we attempt + // to set_dyn before the entity/components are registered. + match self.components.get(&c.id()) { + None => panic!("Component {:?} not found", c), + Some(map) => map.insert(e, c).unwrap(), + } + } + pub fn global<'a, T: component::Global>(&'a self) -> ReadGlobal<'a, T> { ReadGlobal { world: self,