use std::{ffi::CStr, fmt::Display, mem};
use crate::lua::{
self, FromLuaMulti, ToLuaMulti, Value, ffi,
traits::{FromLua, ToLua},
types::{Callback, MaybeSend},
};
#[derive(Clone, Debug)]
pub struct Function(pub(crate) Value);
impl Function {
pub fn call<R: FromLuaMulti>(
&self,
state: &lua::State,
args: impl ToLuaMulti,
) -> lua::Result<R> {
let stack_start = ffi::lua_gettop(state.0);
#[allow(clippy::needless_borrow)]
(&self.0).push_to_stack(state); let nargs = args.push_to_stack_multi(state);
match ffi::lua_pcall(state.0, nargs, ffi::LUA_MULTRET, 0) {
ffi::LUA_OK => {}
res => return Err(state.pop_error(res)),
}
let nresults = ffi::lua_gettop(state.0) - stack_start;
R::try_from_stack_multi(state, stack_start + 1, nresults).map(|(v, _)| v)
}
}
const CLOSURE_GC_METATABLE_NAME: &CStr = gmodx_macros::unique_id!(cstr);
impl lua::State {
pub fn create_function<F, Marker>(&self, func: F) -> Function
where
F: IntoLuaFunction<Marker>,
{
let callback = func.into_callback();
self.create_function_impl(callback)
}
pub(crate) fn create_function_impl(&self, func: Callback) -> Function {
let callback_ptr =
ffi::lua_newuserdata(self.0, mem::size_of::<Callback>()) as *mut Callback;
debug_assert_eq!(
(callback_ptr as usize) % mem::align_of::<Callback>(),
0,
"Lua userdata has insufficient alignment for Callback"
);
unsafe {
callback_ptr.write(func);
}
if ffi::luaL_newmetatable(self.0, CLOSURE_GC_METATABLE_NAME.as_ptr()) {
extern "C-unwind" fn gc_rust_function(state: *mut lua::ffi::lua_State) -> i32 {
let l = lua::State(state);
let data_ptr = ffi::lua_touserdata(l.0, 1) as *mut Callback;
if !data_ptr.is_null() {
unsafe {
std::ptr::drop_in_place(data_ptr);
}
}
0
}
ffi::lua_pushcclosure(self.0, Some(gc_rust_function), 0);
ffi::lua_setfield(self.0, -2, c"__gc".as_ptr());
}
ffi::lua_setmetatable(self.0, -2);
ffi::lua_pushcclosure(self.0, Some(rust_closure_callback), 1);
Function(Value::pop_from_stack(self))
}
}
extern "C-unwind" fn rust_closure_callback(state: *mut ffi::lua_State) -> i32 {
{
let l = lua::State(state);
let data_ptr = ffi::lua_touserdata(l.0, ffi::lua_upvalueindex(1)) as *const Callback;
if !data_ptr.is_null() {
let func = unsafe { &*data_ptr };
match func(&l) {
Ok(v) => return v,
Err(err) => {
let err_str = err.to_string();
ffi::lua_pushlstring(l.0, err_str.as_ptr() as *const i8, err_str.len());
drop(err_str); }
}
} else {
ffi::lua_pushstring(l.0, c"attempt to call a nil value".as_ptr());
}
}
ffi::lua_error(state);
}
impl ToLua for Function {
fn push_to_stack(self, state: &lua::State) {
self.0.push_to_stack(state);
}
fn to_value(self, _: &lua::State) -> Value {
self.0
}
}
impl ToLua for &Function {
fn push_to_stack(self, state: &lua::State) {
#[allow(clippy::needless_borrow)]
(&self.0).push_to_stack(state);
}
fn to_value(self, _: &lua::State) -> Value {
self.0.clone()
}
}
impl FromLua for Function {
fn try_from_stack(state: &lua::State, index: i32) -> lua::Result<Self> {
match ffi::lua_type(state.0, index) {
ffi::LUA_TFUNCTION => Ok(Function(Value::from_stack(state, index))),
_ => Err(state.type_error(index, "function")),
}
}
}
pub trait IntoLuaCallbackResult {
type Value: ToLuaMulti;
fn into_callback_result(self) -> Result<Self::Value, String>;
}
impl<T, E> IntoLuaCallbackResult for Result<T, E>
where
T: ToLuaMulti,
E: Display,
{
type Value = T;
#[inline(always)]
fn into_callback_result(self) -> Result<T, String> {
self.map_err(|e| e.to_string())
}
}
impl<T> IntoLuaCallbackResult for T
where
T: ToLuaMulti,
{
type Value = Self;
#[inline(always)]
fn into_callback_result(self) -> Result<Self, String> {
Ok(self)
}
}
pub trait IntoLuaFunction<Marker> {
fn into_callback(self) -> Callback;
}
macro_rules! impl_into_lua_function {
($($name:ident),*) => {
impl<FF, $($name,)* RR, Ret> IntoLuaFunction<($($name,)*)> for FF
where
FF: Fn(&lua::State, $($name,)*) -> Ret + MaybeSend + 'static,
$($name: FromLuaMulti,)*
Ret: IntoLuaCallbackResult<Value = RR>,
RR: ToLuaMulti,
{
fn into_callback(self) -> Callback {
#[allow(unused)]
#[allow(non_snake_case)]
Box::new(move |state: &lua::State| {
let nargs = ffi::lua_gettop(state.0);
let mut index = 1;
let mut remaining = nargs;
$(
let ($name, consumed) = $name::try_from_stack_multi(state, index, remaining)?;
index += consumed;
remaining -= consumed;
)*
let ret = self(state, $($name,)*).into_callback_result()?;
Ok(ret.push_to_stack_multi(state))
})
}
}
};
}
impl_into_lua_function!();
impl_into_lua_function!(A);
impl_into_lua_function!(A, B);
impl_into_lua_function!(A, B, C);
impl_into_lua_function!(A, B, C, D);
impl_into_lua_function!(A, B, C, D, E);
impl_into_lua_function!(A, B, C, D, E, F);
impl_into_lua_function!(A, B, C, D, E, F, G);
impl_into_lua_function!(A, B, C, D, E, F, G, H);
impl_into_lua_function!(A, B, C, D, E, F, G, H, I);
impl_into_lua_function!(A, B, C, D, E, F, G, H, I, J);
impl_into_lua_function!(A, B, C, D, E, F, G, H, I, J, K);
impl_into_lua_function!(A, B, C, D, E, F, G, H, I, J, K, L);
impl_into_lua_function!(A, B, C, D, E, F, G, H, I, J, K, L, M);
impl_into_lua_function!(A, B, C, D, E, F, G, H, I, J, K, L, M, N);
impl_into_lua_function!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O);
impl_into_lua_function!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P);