engine, ecs: implement getting/setting entity component values
parent
dd941e3792
commit
1c33076401
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Reference in New Issue