use std::marker::PhantomData;
use crate::{
Chunk, FromLua, FromLuaMulti, IntoLua, LuaError, LuaFunction, LuaResult, LuaState, LuaTable,
LuaUserdata, RefAliveToken, UserDataTrait, Value,
};
use crate::lua_api::{Lua, LuaApi};
fn typed_scope_arg<T: FromLua>(state: &mut LuaState, index: usize) -> LuaResult<T> {
let value = state.get_arg(index).unwrap_or_default();
T::from_lua(value, state).map_err(|msg| state.error(msg))
}
#[doc(hidden)]
pub trait ScopedLuaCallback<Args, R> {
fn invoke_typed(&self, state: &mut LuaState) -> LuaResult<usize>;
}
#[doc(hidden)]
pub trait ScopedLuaCallbackWith<Data, Args, R> {
fn invoke_typed_with(&self, data: &Data, state: &mut LuaState) -> LuaResult<usize>;
}
#[doc(hidden)]
pub trait ScopedLuaCallbackMutWith<Data, Args, R> {
fn invoke_typed_with_mut(&self, data: &mut Data, state: &mut LuaState) -> LuaResult<usize>;
}
struct CallbackResource<'scope> {
ptr: *mut (),
drop_fn: unsafe fn(*mut ()),
_marker: PhantomData<&'scope ()>,
}
impl<'scope> CallbackResource<'scope> {
fn new<T: 'scope>(callback: Box<T>) -> Self {
unsafe fn drop_box<T>(ptr: *mut ()) {
drop(unsafe { Box::from_raw(ptr.cast::<T>()) });
}
CallbackResource {
ptr: Box::into_raw(callback).cast::<()>(),
drop_fn: drop_box::<T>,
_marker: PhantomData,
}
}
}
impl Drop for CallbackResource<'_> {
fn drop(&mut self) {
unsafe { (self.drop_fn)(self.ptr) };
}
}
impl<Func, R> ScopedLuaCallback<(), R> for Func
where
Func: Fn() -> R,
R: IntoLua,
{
fn invoke_typed(&self, state: &mut LuaState) -> LuaResult<usize> {
match (self)().into_lua(state) {
Ok(count) => Ok(count),
Err(msg) => Err(state.error(msg)),
}
}
}
impl<Data, Func, R> ScopedLuaCallbackWith<Data, (), R> for Func
where
Func: Fn(&Data) -> R,
R: IntoLua,
{
fn invoke_typed_with(&self, data: &Data, state: &mut LuaState) -> LuaResult<usize> {
match (self)(data).into_lua(state) {
Ok(count) => Ok(count),
Err(msg) => Err(state.error(msg)),
}
}
}
impl<Data, Func, R> ScopedLuaCallbackMutWith<Data, (), R> for Func
where
Func: Fn(&mut Data) -> R,
R: IntoLua,
{
fn invoke_typed_with_mut(&self, data: &mut Data, state: &mut LuaState) -> LuaResult<usize> {
match (self)(data).into_lua(state) {
Ok(count) => Ok(count),
Err(msg) => Err(state.error(msg)),
}
}
}
macro_rules! impl_scoped_lua_callback {
($(($(($ty:ident, $value:ident) => $index:literal),+)),* $(,)?) => {
$(
impl<Func, R, $($ty),+> ScopedLuaCallback<($($ty,)+), R> for Func
where
Func: Fn($($ty),+) -> R,
R: IntoLua,
$($ty: FromLua),+
{
fn invoke_typed(&self, state: &mut LuaState) -> LuaResult<usize> {
$(
let $value = typed_scope_arg::<$ty>(state, $index)?;
)+
match (self)($($value),+).into_lua(state) {
Ok(count) => Ok(count),
Err(msg) => Err(state.error(msg)),
}
}
}
)*
};
}
impl_scoped_lua_callback!(
((A, a) => 1),
((A, a) => 1, (B, b) => 2),
((A, a) => 1, (B, b) => 2, (C, c) => 3),
((A, a) => 1, (B, b) => 2, (C, c) => 3, (D, d) => 4),
((A, a) => 1, (B, b) => 2, (C, c) => 3, (D, d) => 4, (E, e) => 5),
((A, a) => 1, (B, b) => 2, (C, c) => 3, (D, d) => 4, (E, e) => 5, (T6, t6) => 6),
((A, a) => 1, (B, b) => 2, (C, c) => 3, (D, d) => 4, (E, e) => 5, (T6, t6) => 6, (T7, t7) => 7),
((A, a) => 1, (B, b) => 2, (C, c) => 3, (D, d) => 4, (E, e) => 5, (T6, t6) => 6, (T7, t7) => 7, (T8, t8) => 8)
);
macro_rules! impl_scoped_lua_callback_with {
($(($(($ty:ident, $value:ident) => $index:literal),+)),* $(,)?) => {
$(
impl<Data, Func, R, $($ty),+> ScopedLuaCallbackWith<Data, ($($ty,)+), R> for Func
where
Func: Fn(&Data, $($ty),+) -> R,
R: IntoLua,
$($ty: FromLua),+
{
fn invoke_typed_with(&self, data: &Data, state: &mut LuaState) -> LuaResult<usize> {
$(
let $value = typed_scope_arg::<$ty>(state, $index)?;
)+
match (self)(data, $($value),+).into_lua(state) {
Ok(count) => Ok(count),
Err(msg) => Err(state.error(msg)),
}
}
}
)*
};
}
impl_scoped_lua_callback_with!(
((A, a) => 1),
((A, a) => 1, (B, b) => 2),
((A, a) => 1, (B, b) => 2, (C, c) => 3),
((A, a) => 1, (B, b) => 2, (C, c) => 3, (D, d) => 4),
((A, a) => 1, (B, b) => 2, (C, c) => 3, (D, d) => 4, (E, e) => 5),
((A, a) => 1, (B, b) => 2, (C, c) => 3, (D, d) => 4, (E, e) => 5, (T6, t6) => 6),
((A, a) => 1, (B, b) => 2, (C, c) => 3, (D, d) => 4, (E, e) => 5, (T6, t6) => 6, (T7, t7) => 7),
((A, a) => 1, (B, b) => 2, (C, c) => 3, (D, d) => 4, (E, e) => 5, (T6, t6) => 6, (T7, t7) => 7, (T8, t8) => 8)
);
macro_rules! impl_scoped_lua_callback_mut_with {
($(($(($ty:ident, $value:ident) => $index:literal),+)),* $(,)?) => {
$(
impl<Data, Func, R, $($ty),+> ScopedLuaCallbackMutWith<Data, ($($ty,)+), R> for Func
where
Func: Fn(&mut Data, $($ty),+) -> R,
R: IntoLua,
$($ty: FromLua),+
{
fn invoke_typed_with_mut(&self, data: &mut Data, state: &mut LuaState) -> LuaResult<usize> {
$(
let $value = typed_scope_arg::<$ty>(state, $index)?;
)+
match (self)(data, $($value),+).into_lua(state) {
Ok(count) => Ok(count),
Err(msg) => Err(state.error(msg)),
}
}
}
)*
};
}
impl_scoped_lua_callback_mut_with!(
((A, a) => 1),
((A, a) => 1, (B, b) => 2),
((A, a) => 1, (B, b) => 2, (C, c) => 3),
((A, a) => 1, (B, b) => 2, (C, c) => 3, (D, d) => 4),
((A, a) => 1, (B, b) => 2, (C, c) => 3, (D, d) => 4, (E, e) => 5),
((A, a) => 1, (B, b) => 2, (C, c) => 3, (D, d) => 4, (E, e) => 5, (T6, t6) => 6),
((A, a) => 1, (B, b) => 2, (C, c) => 3, (D, d) => 4, (E, e) => 5, (T6, t6) => 6, (T7, t7) => 7),
((A, a) => 1, (B, b) => 2, (C, c) => 3, (D, d) => 4, (E, e) => 5, (T6, t6) => 6, (T7, t7) => 7, (T8, t8) => 8)
);
fn scoped_expired_error() -> &'static str {
"scoped value is no longer available"
}
pub struct Scope<'scope, 'lua> {
lua: &'scope mut Lua,
ref_token: RefAliveToken,
callbacks: Vec<CallbackResource<'scope>>,
_lua: PhantomData<&'lua mut Lua>,
}
impl<'scope, 'lua> Scope<'scope, 'lua> {
pub(crate) fn new(lua: &'scope mut Lua) -> Self {
Scope {
lua,
ref_token: RefAliveToken::default(),
callbacks: Vec::new(),
_lua: PhantomData,
}
}
pub fn lua(&mut self) -> &mut Lua {
self.lua
}
pub fn globals(&mut self) -> LuaTable {
self.lua.globals()
}
pub fn load<'a>(&'a mut self, source: &str) -> Chunk<'a> {
self.lua.load(source)
}
pub fn create_function<F, Args, R>(
&mut self,
f: F,
) -> LuaResult<ScopedFunction<'scope, 'static>>
where
F: ScopedLuaCallback<Args, R> + 'static,
{
let callback = CallbackResource::new(Box::new(f));
let callback_ptr = callback.ptr as usize;
self.callbacks.push(callback);
let active = self.ref_token.clone();
let function = self.lua.create_raw_function(move |state| {
if !active.is_alive() {
return Err(state.error(scoped_expired_error().to_owned()));
}
let callback = unsafe { &*(callback_ptr as *const F) };
callback.invoke_typed(state)
})?;
Ok(ScopedFunction::new(function))
}
pub fn create_function_with<'data, Data, F, Args, R>(
&mut self,
data: &'data Data,
f: F,
) -> LuaResult<ScopedFunction<'scope, 'data>>
where
F: ScopedLuaCallbackWith<Data, Args, R> + 'static,
{
let callback = CallbackResource::new(Box::new(f));
let callback_ptr = callback.ptr as usize;
let data_ptr = (data as *const Data).cast::<()>() as usize;
self.callbacks.push(callback);
let active = self.ref_token.clone();
let function = self.lua.create_raw_function(move |state| {
if !active.is_alive() {
return Err(state.error(scoped_expired_error().to_owned()));
}
let callback = unsafe { &*(callback_ptr as *const F) };
let data = unsafe { &*(data_ptr as *const Data) };
callback.invoke_typed_with(data, state)
})?;
Ok(ScopedFunction::new(function))
}
pub fn create_function_mut_with<'data, Data, F, Args, R>(
&mut self,
data: &'data mut Data,
f: F,
) -> LuaResult<ScopedFunction<'scope, 'data>>
where
F: ScopedLuaCallbackMutWith<Data, Args, R> + 'static,
{
let callback = CallbackResource::new(Box::new(f));
let callback_ptr = callback.ptr as usize;
let data_ptr = (data as *mut Data).cast::<()>() as usize;
self.callbacks.push(callback);
let active = self.ref_token.clone();
let function = self.lua.create_raw_function(move |state| {
if !active.is_alive() {
return Err(state.error(scoped_expired_error().to_owned()));
}
let callback = unsafe { &*(callback_ptr as *const F) };
let data = unsafe { &mut *(data_ptr as *mut Data) };
callback.invoke_typed_with_mut(data, state)
})?;
Ok(ScopedFunction::new(function))
}
pub fn create_userdata_ref<T: UserDataTrait>(
&mut self,
reference: &mut T,
) -> LuaResult<ScopedUserData<'scope, T>> {
let token = self.ref_token.clone();
let ptr = reference as *mut T;
let ud = LuaUserdata::from_ref(reference, token.clone());
let value = self.lua.global_state_mut().create_userdata(ud)?;
let userdata_value = Value::new(self.lua.global_state_mut().to_ref(value));
Ok(ScopedUserData::new(userdata_value, ptr, token))
}
}
impl Drop for Scope<'_, '_> {
fn drop(&mut self) {
self.ref_token.set(false);
}
}
#[derive(Debug)]
pub struct ScopedFunction<'scope, 'data> {
inner: LuaFunction,
_marker: PhantomData<(&'scope mut (), &'data ())>,
}
impl<'scope, 'data> ScopedFunction<'scope, 'data> {
fn new(inner: LuaFunction) -> Self {
ScopedFunction {
inner,
_marker: PhantomData,
}
}
#[inline]
pub fn call<A: IntoLua, R: FromLuaMulti>(&self, args: A) -> LuaResult<R> {
self.inner.call(args)
}
#[inline]
pub fn call1<A: IntoLua, R: FromLua>(&self, args: A) -> LuaResult<R> {
self.inner.call1(args)
}
}
impl IntoLua for ScopedFunction<'_, '_> {
fn into_lua(self, state: &mut LuaState) -> Result<usize, String> {
self.inner.into_lua(state)
}
}
impl IntoLua for &ScopedFunction<'_, '_> {
fn into_lua(self, state: &mut LuaState) -> Result<usize, String> {
(&self.inner).into_lua(state)
}
}
#[derive(Debug)]
pub struct ScopedUserData<'scope, T: UserDataTrait> {
inner: Value,
ptr: *mut T,
alive_token: RefAliveToken,
_marker: PhantomData<&'scope mut T>,
}
impl<'scope, T: UserDataTrait> ScopedUserData<'scope, T> {
fn new(inner: Value, ptr: *mut T, alive_token: RefAliveToken) -> Self {
ScopedUserData {
inner,
ptr,
alive_token,
_marker: PhantomData,
}
}
pub fn get(&self) -> LuaResult<&T> {
if !self.alive_token.is_alive() {
return Err(LuaError::RuntimeError);
}
Ok(unsafe { &*self.ptr })
}
pub fn get_mut(&mut self) -> LuaResult<&mut T> {
if !self.alive_token.is_alive() {
return Err(LuaError::RuntimeError);
}
Ok(unsafe { &mut *self.ptr })
}
pub fn type_name(&self) -> LuaResult<&'static str> {
self.get().map(UserDataTrait::type_name)
}
}
impl<T: UserDataTrait> IntoLua for ScopedUserData<'_, T> {
fn into_lua(self, state: &mut LuaState) -> Result<usize, String> {
self.inner.into_lua(state)
}
}
impl<T: UserDataTrait> IntoLua for &ScopedUserData<'_, T> {
fn into_lua(self, state: &mut LuaState) -> Result<usize, String> {
(&self.inner).into_lua(state)
}
}