use super::backend::{ScriptError, ScriptHandle, ScriptingBackend};
use mlua::{Lua, RegistryKey, StdLib};
use std::collections::HashMap;
use std::fs;
pub struct MluaBackend {
lua: Lua,
scripts: HashMap<u64, String>, event_callbacks: HashMap<String, Vec<RegistryKey>>, next_handle_id: u64,
}
impl MluaBackend {
pub fn new() -> Result<Self, ScriptError> {
let lua = Lua::new_with(
StdLib::TABLE | StdLib::STRING | StdLib::MATH | StdLib::UTF8 | StdLib::PACKAGE,
mlua::LuaOptions::default(),
)
.map_err(|e| ScriptError::RuntimeError(format!("Failed to create Lua: {}", e)))?;
Ok(Self {
lua,
scripts: HashMap::new(),
event_callbacks: HashMap::new(),
next_handle_id: 1,
})
}
#[cfg(test)]
pub fn new_unsafe() -> Result<Self, ScriptError> {
let lua = Lua::new();
Ok(Self {
lua,
scripts: HashMap::new(),
event_callbacks: HashMap::new(),
next_handle_id: 1,
})
}
pub fn lua(&self) -> &Lua {
&self.lua
}
pub fn subscribe_event(
&mut self,
event_name: String,
function_name: &str,
) -> Result<(), ScriptError> {
let callback: mlua::Function = self
.lua
.globals()
.get(function_name)
.map_err(|_| ScriptError::FunctionNotFound(function_name.to_string()))?;
let callback_ref = self.lua.create_registry_value(callback).map_err(|e| {
ScriptError::RuntimeError(format!("Failed to register callback: {}", e))
})?;
self.event_callbacks
.entry(event_name)
.or_default()
.push(callback_ref);
Ok(())
}
pub fn trigger_event(
&self,
event_name: &str,
event_data: mlua::Value,
) -> Result<(), ScriptError> {
if let Some(callbacks) = self.event_callbacks.get(event_name) {
for callback_ref in callbacks {
let callback: mlua::Function =
self.lua.registry_value(callback_ref).map_err(|e| {
ScriptError::RuntimeError(format!("Failed to get callback: {}", e))
})?;
callback.call::<_, ()>(event_data.clone()).map_err(|e| {
ScriptError::RuntimeError(format!(
"Error calling event handler for '{}': {}",
event_name, e
))
})?;
}
}
Ok(())
}
}
impl Default for MluaBackend {
fn default() -> Self {
Self::new().expect("Failed to create MLua backend")
}
}
impl ScriptingBackend for MluaBackend {
fn load_script(&mut self, path: &str) -> Result<ScriptHandle, ScriptError> {
let code = fs::read_to_string(path)
.map_err(|e| ScriptError::NotFound(format!("Failed to read {}: {}", path, e)))?;
self.lua
.load(&code)
.exec()
.map_err(|e| ScriptError::SyntaxError(format!("Syntax error in {}: {}", path, e)))?;
let handle_id = self.next_handle_id;
self.next_handle_id += 1;
let handle = ScriptHandle::new(handle_id);
self.scripts.insert(handle_id, path.to_string());
Ok(handle)
}
fn execute_chunk(&mut self, code: &str) -> Result<(), ScriptError> {
self.lua
.load(code)
.exec()
.map_err(|e| ScriptError::RuntimeError(format!("Execution error: {}", e)))
}
fn call_function(&self, _handle: ScriptHandle, func_name: &str) -> Result<(), ScriptError> {
let func: mlua::Function = self
.lua
.globals()
.get(func_name)
.map_err(|_| ScriptError::FunctionNotFound(func_name.to_string()))?;
func.call::<_, ()>(())
.map_err(|e| ScriptError::RuntimeError(format!("Error calling {}: {}", func_name, e)))
}
fn has_function(&self, _handle: ScriptHandle, func_name: &str) -> bool {
self.lua
.globals()
.get::<_, mlua::Function>(func_name)
.is_ok()
}
fn unload_script(&mut self, handle: ScriptHandle) {
self.scripts.remove(&handle.id());
}
fn backend_name(&self) -> &str {
"mlua (Lua 5.4)"
}
}