engine, ecs: implement getting/setting entity component values

master
q3k 2021-04-08 11:33:54 +00:00
parent dd941e3792
commit 1c33076401
5 changed files with 310 additions and 74 deletions

View File

@ -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

View File

@ -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;

View File

@ -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_new", scope.create_function(move |lua, args: mlua::AnyUserData| {
let secid = args.borrow::<ScriptedEntityClassID>()?;
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 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)?;
// (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<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 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),
}
}
}

View File

@ -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 {

View File

@ -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,