engine: scripted entity basics

master
q3k 2021-04-05 16:34:12 +00:00
parent be1ff9ad9c
commit 193ead1eb7
8 changed files with 274 additions and 18 deletions

View File

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

View File

@ -25,12 +25,52 @@ use crate::render::resource::{ResourceID};
pub struct Transform(pub cgm::Matrix4<f32>);
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<mlua::Number>| {
let args: Vec<f32> = 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<Box<dyn ecs::ComponentLuaBindings>> {
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"
}
}

View File

@ -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<Mutex<BTreeMap<String, ScriptedEntityClass>>>,
instances: Arc<Mutex<InstanceData>>,
}
#[derive(Debug)]
struct InstanceData {
ecs: BTreeMap<ecs::EntityID, ScriptedEntity>,
queue: Vec<ScriptedEntity>,
}
#[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<ecs::EntityID>,
table: mlua::RegistryKey,
}
#[derive(Debug)]
struct ScriptedEntityID {
ecs_id: Option<ecs::EntityID>,
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::Value>| -> mlua::Result<()> {
let msg: Vec<String> = 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::<ScriptedEntityClassID>()?;
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) {
}
}

View File

@ -16,6 +16,7 @@ rust_library(
],
deps = [
"//third_party/cargo:log",
"//third_party/cargo:mlua",
],
visibility = ["//engine:__subpackages__"],
)

View File

@ -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<Box<dyn LuaBindings>> {
None
}
}

View File

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

View File

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

View File

@ -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::ID, ComponentMap>,
component_by_idstr: BTreeMap<&'static str, component::ID>,
component_lua_bindings: BTreeMap<component::ID, Box<dyn component::LuaBindings>>,
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<dyn component::Component>,
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<T: component::Global>(&self, r: T) {
self.globals.set(r).unwrap();
}
pub fn lua_components(&self) -> &BTreeMap<component::ID, Box<dyn component::LuaBindings>> {
&self.component_lua_bindings
}
}
#[cfg(test)]