summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSerge Bazanski <q3k@q3k.org>2021-04-08 11:33:54 +0000
committerSerge Bazanski <q3k@q3k.org>2021-04-08 11:43:33 +0000
commit1c33076401e47294432e8f1f3a6a1e166af22162 (patch)
tree0eae518dcbfb4466a1c66e90b7890f83ca280a6d
parentdd941e37926ab2e7ae0ee7204929dae997d2e5b7 (diff)
downloadabrasion-1c33076401e47294432e8f1f3a6a1e166af22162.tar.gz
abrasion-1c33076401e47294432e8f1f3a6a1e166af22162.tar.bz2
abrasion-1c33076401e47294432e8f1f3a6a1e166af22162.tar.xz
abrasion-1c33076401e47294432e8f1f3a6a1e166af22162.zip
engine, ecs: implement getting/setting entity component values
-rw-r--r--engine/scene.lua22
-rw-r--r--engine/src/render/renderable.rs18
-rw-r--r--engine/src/scripting.rs320
-rw-r--r--lib/ecs/src/component.rs6
-rw-r--r--lib/ecs/src/world.rs18
5 files changed, 310 insertions, 74 deletions
diff --git a/engine/scene.lua b/engine/scene.lua
index 6a8facf1..4cfd6045 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 237f7695..5aff128b 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<f32>);
@@ -34,6 +36,22 @@ impl Component for Transform {
fn clone_dyn(&self) -> Box<dyn Component> {
Box::new(self.clone())
}
+ fn lua_userdata<'access, 'lua>(
+ &'access self,
+ lua: &'lua mlua::Lua,
+ ) -> Option<mlua::Value<'lua>> {
+ let ud: mlua::Value<'lua> = self.clone().to_lua(lua).unwrap();
+ Some(ud)
+ }
+ fn lua_fromuserdata<'a>(
+ &self,
+ ud: &'a mlua::AnyUserData,
+ ) -> Option<Box<dyn Component>> {
+ match ud.borrow::<Transform>() {
+ 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 6bbdc323..06138a7c 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<Box<dyn ecs::Component>>,
+ components: BTreeMap<String, Box<dyn ecs::Component>>,
}
#[derive(Debug)]
@@ -40,7 +40,7 @@ static GLOBAL_SCRIPTED_ENTITY_ID: atomic::AtomicU64 = atomic::AtomicU64::new(0);
#[derive(Debug)]
enum ScriptedEntityStatus {
- QueuedForCreation,
+ QueuedForCreation(BTreeMap<String, Box<dyn ecs::Component>>),
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<String, Box<dyn ecs::Component>>,
+ ) -> 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<ScriptedEntity>,
}
+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<Box<dyn ecs::Component>> = components
+ let components: Vec<(String, Box<dyn ecs::Component>)> = components
.pairs::<mlua::Integer, mlua::AnyUserData>()
.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::<mlua::Result<Vec<Box<dyn ecs::Component>>>>()?;
+ .collect::<mlua::Result<Vec<(String, Box<dyn ecs::Component>)>>>()?;
+ let components: BTreeMap<String, Box<dyn ecs::Component>> = 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_components_index", scope.create_function(move |lua, args: (mlua::Table, mlua::Value)| -> mlua::Result<mlua::Value> {
+ 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<dyn ecs::Component> = 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<mlua::Value> {
+ 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<dyn ecs::Component> = 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<dyn ecs::Component> = 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 secid = args.borrow::<ScriptedEntityClassID>()?;
let classes = classes.lock().unwrap();
+
+ let secid = args.borrow::<ScriptedEntityClassID>()?;
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)?;
+ let table = lua.create_table()?;
- // (meta)table tree for entity objects:
- //
- // table: { }
- // | metatable
- // V
- // metatable: { __index }
- // |
- // .-------------'
- // V
- // dispatch: { components.{...}, ... }
- // | metadata
- // V
- // metametatable: { __index }
- // |
- // .-----------------'
- // V
- // cls: { init, tick, ... }
+ let components: BTreeMap<String, Box<dyn ecs::Component>> = 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 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 = 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<String>
{
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 7d7cdc34..426720ea 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<dyn Component>;
+ fn lua_userdata<'access, 'lua>(&'access self, lua: &'lua mlua::Lua) -> Option<mlua::Value<'lua>> {
+ None
+ }
+ fn lua_fromuserdata<'a>(&self, ud: &'a mlua::AnyUserData) -> Option<Box<dyn Component>> {
+ None
+ }
}
pub fn component_id<T: Component>() -> ID {
diff --git a/lib/ecs/src/world.rs b/lib/ecs/src/world.rs
index 6cdcab68..ed4a4321 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<Box<dyn component::Component>> {
+ pub fn lua_any_into_dyn<'a, 'b>(&'a self, ud: &'a mlua::AnyUserData) -> Option<(String, Box<dyn component::Component>)> {
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<dyn component::Component>
+ ) {
+ // 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,