engine: scripted entity basics
parent
be1ff9ad9c
commit
193ead1eb7
|
@ -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();
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@ rust_library(
|
|||
],
|
||||
deps = [
|
||||
"//third_party/cargo:log",
|
||||
"//third_party/cargo:mlua",
|
||||
],
|
||||
visibility = ["//engine:__subpackages__"],
|
||||
)
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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)]
|
||||
|
|
Loading…
Reference in New Issue