use std::{
fmt,
sync::atomic::{AtomicPtr, Ordering},
};
use minijinja::Value as JinjaValue;
use mlua::LuaSerdeExt;
use crate::convert::{
auto_escape_to_lua,
lua_args_to_minijinja,
lua_to_minijinja,
minijinja_to_lua,
undefined_behavior_to_lua,
};
thread_local! {
static CURRENT_LUA: AtomicPtr<mlua::Lua> = const { AtomicPtr::new(std::ptr::null_mut()) };
}
trait LuaState<'env, 'render> {
fn state(&self) -> &minijinja::State<'env, 'render>;
}
#[derive(Debug)]
pub struct LuaStateRef<'scope, 'env, 'render> {
state: &'scope minijinja::State<'env, 'render>,
}
impl<'scope, 'env, 'render> LuaStateRef<'scope, 'env, 'render> {
pub(crate) fn new(state: &'scope minijinja::State<'env, 'render>) -> Self {
Self { state }
}
}
impl<'scope, 'env, 'render> LuaState<'env, 'render> for LuaStateRef<'scope, 'env, 'render> {
fn state(&self) -> &minijinja::State<'env, 'render> {
self.state
}
}
impl<'scope, 'env, 'render> fmt::Display for LuaStateRef<'scope, 'env, 'render> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "State")
}
}
impl<'scope, 'env, 'render> mlua::UserData for LuaStateRef<'scope, 'env, 'render> {
fn add_fields<F: mlua::UserDataFields<Self>>(fields: &mut F) {
fields.add_meta_field("__name", "state");
}
fn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) {
add_common_methods(methods);
}
}
#[derive(Debug)]
pub struct LuaStateMut<'scope, 'env, 'render> {
state: &'scope mut minijinja::State<'env, 'render>,
}
impl<'scope, 'env, 'render> LuaStateMut<'scope, 'env, 'render> {
pub(crate) fn new(state: &'scope mut minijinja::State<'env, 'render>) -> Self {
Self { state }
}
fn state_mut(&mut self) -> &mut minijinja::State<'env, 'render> {
self.state
}
}
impl<'scope, 'env, 'render> LuaState<'env, 'render> for LuaStateMut<'scope, 'env, 'render> {
fn state(&self) -> &minijinja::State<'env, 'render> {
self.state
}
}
impl<'scope, 'env, 'render> fmt::Display for LuaStateMut<'scope, 'env, 'render> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "State")
}
}
impl<'scope, 'env, 'render> mlua::UserData for LuaStateMut<'scope, 'env, 'render> {
fn add_fields<F: mlua::UserDataFields<Self>>(fields: &mut F) {
fields.add_meta_field("__name", "state");
}
fn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) {
add_common_methods(methods);
methods.add_method_mut(
"render_block",
|_, this, block: String| -> mlua::Result<String> {
this.state_mut()
.render_block(&block)
.map_err(mlua::Error::external)
},
);
}
}
fn add_common_methods<'env, 'render, S, M>(methods: &mut M)
where
S: LuaState<'env, 'render>,
M: mlua::UserDataMethods<S>,
'render: 'env,
{
methods.add_method("name", |_, this, _: mlua::Value| -> mlua::Result<String> {
Ok(this.state().name().to_string())
});
methods.add_method(
"auto_escape",
|_, this, _: mlua::Value| -> mlua::Result<Option<String>> {
Ok(auto_escape_to_lua(this.state().auto_escape()))
},
);
methods.add_method(
"undefined_behavior",
|_, this, _: mlua::Value| -> mlua::Result<Option<String>> {
Ok(undefined_behavior_to_lua(this.state().undefined_behavior()))
},
);
methods.add_method(
"current_block",
|_, this, _: mlua::Value| -> mlua::Result<Option<String>> {
Ok(this.state().current_block().map(|s| s.to_string()))
},
);
methods.add_method(
"lookup",
|lua, this, name: String| -> mlua::Result<Option<mlua::Value>> {
Ok(this
.state()
.lookup(&name)
.and_then(|v| minijinja_to_lua(lua, &v)))
},
);
methods.add_method(
"call_macro",
|lua, this, (name, args): (String, mlua::Variadic<mlua::Value>)| -> mlua::Result<String> {
let args: Vec<JinjaValue> = lua_args_to_minijinja(lua, args, true);
this.state()
.call_macro(&name, &args)
.map_err(mlua::Error::external)
},
);
methods.add_method(
"exports",
|_, this, _: mlua::Value| -> mlua::Result<Vec<String>> {
Ok(this
.state()
.exports()
.into_iter()
.map(|i| i.to_string())
.collect())
},
);
methods.add_method(
"known_variables",
|_, this, _: mlua::Value| -> mlua::Result<Vec<String>> {
Ok(this
.state()
.known_variables()
.into_iter()
.map(|i| i.to_string())
.collect())
},
);
methods.add_method(
"apply_filter",
|lua,
this,
(filter, args): (String, mlua::Variadic<mlua::Value>)|
-> mlua::Result<Option<mlua::Value>> {
let args: Vec<JinjaValue> = lua_args_to_minijinja(lua, args, true);
this.state()
.apply_filter(&filter, &args)
.map(|v| minijinja_to_lua(lua, &v))
.map_err(mlua::Error::external)
},
);
methods.add_method(
"perform_test",
|lua, this, (test, args): (String, mlua::Variadic<mlua::Value>)| -> mlua::Result<bool> {
let args: Vec<JinjaValue> = lua_args_to_minijinja(lua, args, true);
this.state()
.perform_test(&test, &args)
.map_err(mlua::Error::external)
},
);
methods.add_method(
"format",
|lua, this, val: mlua::Value| -> mlua::Result<String> {
let val = lua_to_minijinja(lua, &val).unwrap_or(JinjaValue::UNDEFINED);
this.state().format(val).map_err(mlua::Error::external)
},
);
methods.add_method(
"fuel_levels",
|lua, this, _: mlua::Value| -> mlua::Result<mlua::Value> {
lua.to_value(&this.state().fuel_levels())
},
);
methods.add_method(
"get_temp",
|lua, this, name: String| -> mlua::Result<Option<mlua::Value>> {
Ok(this
.state()
.get_temp(&name)
.and_then(|v| minijinja_to_lua(lua, &v)))
},
);
methods.add_method(
"set_temp",
|lua, this, (name, val): (String, mlua::Value)| -> mlua::Result<Option<mlua::Value>> {
if let Some(val) = lua_to_minijinja(lua, &val) {
Ok(this
.state()
.set_temp(&name, val)
.and_then(|v| minijinja_to_lua(lua, &v)))
} else {
Err(mlua::Error::FromLuaConversionError {
from: val.type_name(),
to: "minijinja::Value".to_string(),
message: None,
})
}
},
);
methods.add_method(
"get_or_set_temp",
|lua, this, (name, func): (String, mlua::Function)| -> mlua::Result<Option<mlua::Value>> {
let val = match this.state().get_temp(&name) {
Some(v) => v,
None => {
let val = func.call::<mlua::Value>(mlua::Value::Nil)?;
if let Some(val) = lua_to_minijinja(lua, &val) {
this.state().set_temp(&name, val.clone());
val
} else {
return Err(mlua::Error::FromLuaConversionError {
from: val.type_name(),
to: "minijinja::Value".to_string(),
message: None,
});
}
},
};
Ok(minijinja_to_lua(lua, &val))
},
);
}
pub(crate) fn with_lua<R, F: FnOnce(&mlua::Lua) -> Result<R, mlua::Error>>(
f: F,
) -> Result<R, mlua::Error> {
CURRENT_LUA.with(|handle| {
let ptr = unsafe { (handle.load(Ordering::Relaxed) as *const mlua::Lua).as_ref() };
match ptr {
Some(lua) => f(lua),
None => Err(mlua::Error::runtime(
"mlua::Lua state accessed outside of a render context.",
)),
}
})
}
pub(crate) fn bind_lua<R, F: FnOnce() -> R>(lua: &mlua::Lua, f: F) -> R {
let old_handle = CURRENT_LUA
.with(|handle| handle.swap(lua as *const mlua::Lua as *mut mlua::Lua, Ordering::Relaxed));
let rv = std::panic::catch_unwind(std::panic::AssertUnwindSafe(f));
CURRENT_LUA.with(|handle| handle.store(old_handle, Ordering::Relaxed));
match rv {
Ok(rv) => rv,
Err(payload) => std::panic::resume_unwind(payload),
}
}