use std::collections::BTreeMap; use std::sync::{Arc, Mutex, RwLock, atomic}; use std::io::Read; use engine_render as render; use engine_util as util; use crate::globals::Time; use mlua::prelude::LuaError::RuntimeError; use mlua::ToLua; fn debug_str(v: &mlua::Value) -> String { match v { mlua::Value::String(s) => s.to_str().map_or(format!("{:?}", v), |s| s.to_string()), mlua::Value::Integer(i) => format!("{}", i), _ => format!("{:?}", v), } } #[derive(Debug)] struct ComponentID { id: ecs::component::ID, idstr: String, } impl mlua::UserData for ComponentID {} #[derive(Debug)] struct ScriptedEntityClass { name: String, cls: mlua::RegistryKey, components: BTreeMap>, } #[derive(Debug)] struct ScriptedEntityClassID { name: String, } impl mlua::UserData for ScriptedEntityClassID {} static GLOBAL_SCRIPTED_ENTITY_ID: atomic::AtomicU64 = atomic::AtomicU64::new(0); #[derive(Debug)] enum ScriptedEntityStatus { QueuedForCreation(BTreeMap>), Exists(ecs::EntityID), FailedToCreate, } #[derive(Debug)] struct ScriptedEntity { class_name: String, internal_id: u64, status: ScriptedEntityStatus, table: mlua::RegistryKey, } impl ScriptedEntity { fn new( class_name: String, table: mlua::RegistryKey, components: BTreeMap>, ) -> Self { let internal_id = GLOBAL_SCRIPTED_ENTITY_ID.fetch_add(1, atomic::Ordering::SeqCst) + 1; Self { class_name, internal_id, status: ScriptedEntityStatus::QueuedForCreation(components), table, } } } #[derive(Debug, Clone)] struct ScriptedEntityID { internal_id: u64, } impl mlua::UserData for ScriptedEntityID {} pub struct WorldContext { // TODO(q3k): this leaks memory, right? lua: &'static mlua::Lua, classes: Arc>>, instances: Arc>, } #[derive(Debug)] struct InstanceData { ecs: BTreeMap, internal_to_ecs: BTreeMap, queue: Vec, } 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(); log::info!("Lua WorldContext created."); lua.globals().set("print", lua.create_function(|_, vals: mlua::Variadic| -> mlua::Result<()> { let msg: Vec = vals.iter().map(|val| debug_str(val)).collect(); log::info!("[Lua] {}", msg.join("\t")); Ok(()) }).unwrap()).unwrap(); let classes = Arc::new(Mutex::new(BTreeMap::new())); let instances = Arc::new(RwLock::new(InstanceData { ecs: BTreeMap::new(), internal_to_ecs: BTreeMap::new(), queue: Vec::new(), })); lua.globals().set("sent", lua.create_table().unwrap()).unwrap(); let loaders = lua.create_table().unwrap(); loaders.set(1, lua.create_function(move |lua, name: String| -> mlua::Result { log::debug!("require({})", name); let path = name.clone(); match util::file::resource(path.clone()) { Err(e) => Ok(format!("util::file::resource({}) failed: {:?}", path, e).to_lua(&lua)?), Ok(mut reader) => { let mut data: Vec = Vec::new(); match reader.read_to_end(&mut data) { Err(e) => Ok(format!("util::file::reasource read failed: {:?}", e).to_lua(&lua)?), Ok(_) => lua.load(&data).set_name(&path)?.into_function()?.to_lua(&lua), } }, } }).unwrap()).unwrap(); lua.globals().get::<_, mlua::Table>("package").unwrap().set("loaders", loaders).unwrap(); Self { lua, classes, instances, } } fn global_set_components(&self, world: &ecs::World) -> mlua::Result<()> { let globals = self.lua.globals(); let components = match globals.get("components") { Ok(c) => c, Err(_) => { let components = self.lua.create_table()?; globals.set("components", components.clone())?; components } }; components.set_metatable(None); for (idstr, id, bindings) in world.get_component_lua_bindings() { let methods = bindings.globals(&self.lua); methods.set("__component_component_id", ComponentID { id, idstr: idstr.clone(), }); components.set(idstr, methods); } Ok(()) } /// Registers resourcemanager global in Lua, scoped to scope. // TODO(q3k): make this generic for all ECS globals. fn scope_resourcemanager<'a, 'world, 'lua, 'scope>( &'a self, scope: &'a mlua::Scope<'lua, 'scope>, world: &'world ecs::World, ) -> mlua::Result<()> where 'world: 'scope, 'scope: 'a, { let globals = self.lua.globals(); let resourcemanager = self.lua.create_table()?; globals.set("resourcemanager", resourcemanager.clone()); { let rm = world.global::(); let rm = rm.get(); resourcemanager.set("get_mesh", scope.create_function(move |lua, name: String| -> mlua::Result { match rm.by_label::(&name) { None => Ok(mlua::Value::Nil), Some(r) => Ok(r.to_lua(&lua)?), } })?)?; } { let rm = world.global::(); let rm = rm.get(); resourcemanager.set("get_material", scope.create_function(move |lua, name: String| -> mlua::Result { match rm.by_label::(&name) { None => Ok(mlua::Value::Nil), Some(r) => Ok(r.to_lua(&lua)?), } })?)?; } Ok(()) } fn scope_time<'a, 'world, 'lua, 'scope>( &'a self, scope: &'a mlua::Scope<'lua, 'scope>, world: &'world ecs::World, ) -> mlua::Result<()> where 'world: 'scope, 'scope: 'a, { let globals = self.lua.globals(); let time = world.global::