Lua functions can now return Result, Option<NonZeroI32>, () or i32

This commit is contained in:
William Venner 2022-01-22 18:59:12 +00:00
parent 47492748f2
commit 9b79d402e9
8 changed files with 143 additions and 21 deletions

6
Cargo.lock generated
View File

@ -135,7 +135,7 @@ dependencies = [
[[package]] [[package]]
name = "gmod" name = "gmod"
version = "11.1.1" version = "12.0.0"
dependencies = [ dependencies = [
"cfg_table 1.0.0", "cfg_table 1.0.0",
"cstr", "cstr",
@ -144,7 +144,7 @@ dependencies = [
"fn_abi", "fn_abi",
"fn_has_this", "fn_has_this",
"fn_type_alias", "fn_type_alias",
"gmod-macros 1.0.4", "gmod-macros 2.0.0",
"gmserverplugin", "gmserverplugin",
"lazy_static", "lazy_static",
"libloading", "libloading",
@ -165,7 +165,7 @@ dependencies = [
[[package]] [[package]]
name = "gmod-macros" name = "gmod-macros"
version = "1.0.4" version = "2.0.0"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",

View File

@ -1,6 +1,6 @@
[package] [package]
name = "gmod-macros" name = "gmod-macros"
version = "1.0.4" version = "2.0.0"
authors = ["William Venner <william@venner.io>"] authors = ["William Venner <william@venner.io>"]
edition = "2021" edition = "2021"
license = "MIT" license = "MIT"

View File

@ -6,41 +6,60 @@ extern crate quote;
use proc_macro::TokenStream; use proc_macro::TokenStream;
use quote::ToTokens; use quote::ToTokens;
use syn::{ReturnType, ItemFn, Attribute}; use syn::ItemFn;
fn check_lua_function(input: &mut ItemFn) { fn check_lua_function(input: &mut ItemFn) {
assert!(input.sig.asyncness.is_none(), "Cannot be async"); assert!(input.sig.asyncness.is_none(), "Cannot be async");
assert!(input.sig.constness.is_none(), "Cannot be const"); assert!(input.sig.constness.is_none(), "Cannot be const");
assert!(input.sig.inputs.len() == 1, "There can only be one argument, and it should be a pointer to the Lua state (gmod::lua::State)"); assert!(input.sig.inputs.len() == 1, "There can only be one argument, and it should be a pointer to the Lua state (gmod::lua::State)");
assert!(matches!(&input.sig.output, ReturnType::Type(_, r#type) if r#type.to_token_stream().to_string() == "i32"), "The output must be an i32, representing the number of return values of the function");
assert!(input.sig.abi.is_none() || input.sig.abi.as_ref().and_then(|abi| abi.name.as_ref()).map(|abi| abi.value() == "C-unwind").unwrap_or(true), "Do not specify an ABI"); assert!(input.sig.abi.is_none() || input.sig.abi.as_ref().and_then(|abi| abi.name.as_ref()).map(|abi| abi.value() == "C-unwind").unwrap_or(true), "Do not specify an ABI");
input.sig.abi = Some(syn::parse_quote!(extern "C-unwind")); input.sig.abi = Some(syn::parse_quote!(extern "C-unwind"));
} }
fn no_mangle(item_fn: &mut ItemFn) { fn genericify_return(item_fn: &mut ItemFn) {
item_fn.attrs.push(Attribute { let stmts = std::mem::take(&mut item_fn.block.stmts);
path: parse_quote!(no_mangle), let output = std::mem::replace(&mut item_fn.sig.output, parse_quote!(-> i32));
tokens: proc_macro2::TokenStream::new(), item_fn.block.stmts = vec![syn::parse2(quote!({::gmod::lua::ValuesReturned::from((|| #output {#(#stmts);*})()).into()})).unwrap()];
style: syn::AttrStyle::Outer,
pound_token: Default::default(),
bracket_token: Default::default(),
});
} }
#[proc_macro_attribute] #[proc_macro_attribute]
pub fn gmod13_open(_attr: TokenStream, tokens: TokenStream) -> TokenStream { pub fn gmod13_open(_attr: TokenStream, tokens: TokenStream) -> TokenStream {
let mut input = parse_macro_input!(tokens as ItemFn); let mut input = parse_macro_input!(tokens as ItemFn);
check_lua_function(&mut input);
let lua_ident = format_ident!("{}", match &input.sig.inputs[0] {
syn::FnArg::Typed(arg) => arg.pat.to_token_stream().to_string(),
_ => unreachable!(),
});
// Capture the Lua state
input.block.stmts.insert(0, syn::parse2(quote!(::gmod::lua::__set_state__internal(#lua_ident);)).unwrap());
// Load lua_shared
input.block.stmts.insert(0, syn::parse2(quote!(#[allow(unused_unsafe)] unsafe { ::gmod::lua::load() })).unwrap()); input.block.stmts.insert(0, syn::parse2(quote!(#[allow(unused_unsafe)] unsafe { ::gmod::lua::load() })).unwrap());
no_mangle(&mut input);
// Make sure it's valid
check_lua_function(&mut input);
// No mangling
input.attrs.push(parse_quote!(#[no_mangle]));
// Make the return type nice and dynamic
genericify_return(&mut input);
input.into_token_stream().into() input.into_token_stream().into()
} }
#[proc_macro_attribute] #[proc_macro_attribute]
pub fn gmod13_close(_attr: TokenStream, tokens: TokenStream) -> TokenStream { pub fn gmod13_close(_attr: TokenStream, tokens: TokenStream) -> TokenStream {
let mut input = parse_macro_input!(tokens as ItemFn); let mut input = parse_macro_input!(tokens as ItemFn);
// Make sure it's valid
check_lua_function(&mut input); check_lua_function(&mut input);
// No mangling
input.attrs.push(parse_quote!(#[no_mangle]));
// Shutdown gmcl thread if it's running
#[cfg(feature = "gmcl")] { #[cfg(feature = "gmcl")] {
let stmts = std::mem::take(&mut input.block.stmts); let stmts = std::mem::take(&mut input.block.stmts);
input.block.stmts = vec![syn::parse2(quote!({ input.block.stmts = vec![syn::parse2(quote!({
@ -50,13 +69,21 @@ pub fn gmod13_close(_attr: TokenStream, tokens: TokenStream) -> TokenStream {
})).unwrap()]; })).unwrap()];
} }
no_mangle(&mut input); // Make the return type nice and dynamic
genericify_return(&mut input);
input.into_token_stream().into() input.into_token_stream().into()
} }
#[proc_macro_attribute] #[proc_macro_attribute]
pub fn lua_function(_attr: TokenStream, tokens: TokenStream) -> TokenStream { pub fn lua_function(_attr: TokenStream, tokens: TokenStream) -> TokenStream {
let mut input = parse_macro_input!(tokens as ItemFn); let mut input = parse_macro_input!(tokens as ItemFn);
// Make sure it's valid
check_lua_function(&mut input); check_lua_function(&mut input);
// Make the return type nice and dynamic
genericify_return(&mut input);
input.into_token_stream().into() input.into_token_stream().into()
} }

View File

@ -1,6 +1,6 @@
[package] [package]
name = "gmod" name = "gmod"
version = "11.1.1" version = "12.0.0"
authors = ["William Venner <william@venner.io>"] authors = ["William Venner <william@venner.io>"]
edition = "2021" edition = "2021"
license = "MIT" license = "MIT"
@ -16,7 +16,7 @@ server-plugin = ["gmserverplugin"]
gmcl = ["gmod-macros/gmcl"] gmcl = ["gmod-macros/gmcl"]
[dependencies] [dependencies]
gmod-macros = { version = "1.0.4", path = "../gmod-macros" } gmod-macros = { version = "2.0.0", path = "../gmod-macros" }
gmserverplugin = { version = "1", optional = true } gmserverplugin = { version = "1", optional = true }
libloading = "0" libloading = "0"

View File

@ -5,7 +5,7 @@ use std::{cell::UnsafeCell, ffi::c_void};
use libloading::{Library, Symbol}; use libloading::{Library, Symbol};
use super::{LuaError, State as LuaState, LuaDebug}; use super::{LuaError, State as LuaState, LuaDebug, returns::ValuesReturned};
pub type LuaInt = isize; pub type LuaInt = isize;
pub type LuaSize = usize; pub type LuaSize = usize;
@ -93,6 +93,7 @@ impl LuaSharedInterface {
impl std::ops::Deref for LuaSharedInterface { impl std::ops::Deref for LuaSharedInterface {
type Target = LuaShared; type Target = LuaShared;
#[inline]
fn deref(&self) -> &Self::Target { fn deref(&self) -> &Self::Target {
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
self.debug_assertions(); self.debug_assertions();
@ -101,6 +102,7 @@ impl std::ops::Deref for LuaSharedInterface {
} }
} }
impl std::ops::DerefMut for LuaSharedInterface { impl std::ops::DerefMut for LuaSharedInterface {
#[inline]
fn deref_mut(&mut self) -> &mut Self::Target { fn deref_mut(&mut self) -> &mut Self::Target {
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
self.debug_assertions(); self.debug_assertions();

View File

@ -522,6 +522,7 @@ impl LuaState {
ptr ptr
} }
#[cold]
pub unsafe fn error<S: AsRef<str>>(&self, msg: S) -> ! { pub unsafe fn error<S: AsRef<str>>(&self, msg: S) -> ! {
self.push_string(msg.as_ref()); self.push_string(msg.as_ref());
(LUA_SHARED.lua_error)(*self); (LUA_SHARED.lua_error)(*self);
@ -625,6 +626,7 @@ impl LuaState {
impl std::ops::Deref for LuaState { impl std::ops::Deref for LuaState {
type Target = *mut std::ffi::c_void; type Target = *mut std::ffi::c_void;
#[inline(always)]
fn deref(&self) -> &Self::Target { fn deref(&self) -> &Self::Target {
&self.0 &self.0
} }

View File

@ -1,6 +1,8 @@
#![allow(unused)] #![allow(unused)]
mod import; mod import;
use std::cell::Cell;
pub use import::*; pub use import::*;
mod lua_state; mod lua_state;
@ -9,6 +11,9 @@ pub use lua_state::LuaState as State;
mod push; mod push;
pub use push::*; pub use push::*;
mod returns;
pub use returns::ValuesReturned;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum LuaError { pub enum LuaError {
/// Out of memory /// Out of memory
@ -120,4 +125,35 @@ pub struct LuaDebug {
/// Loads lua_shared and imports all functions. This is already done for you if you add `#[gmod::gmod13_open]` to your `gmod13_open` function. /// Loads lua_shared and imports all functions. This is already done for you if you add `#[gmod::gmod13_open]` to your `gmod13_open` function.
pub unsafe fn load() { pub unsafe fn load() {
import::LUA_SHARED.load() import::LUA_SHARED.load()
} }
thread_local! {
#[cfg(debug_assertions)]
static LUA: Cell<Option<State>> = Cell::new(None);
#[cfg(not(debug_assertions))]
static LUA: Cell<State> = Cell::new(State(std::ptr::null_mut()));
}
/// Acquires a pointer to the Lua state for the current thread.
///
/// This will panic if called from anywhere but the main thread. This will panic if you are not using the `#[gmod13_open]` macro to open the Lua state.
///
/// This will NOT panic in release mode under these conditions and will instead cause undefined behaviour.
pub unsafe fn state() -> State {
LUA.with(|cell| {
#[cfg(debug_assertions)] {
cell.get().expect("The Lua state cannot be found in this thread. Perhaps you are calling this function from a thread other than the main thread? Perhaps you forgot to use the `#[gmod13_open]` macro?")
}
#[cfg(not(debug_assertions))] {
cell.get()
}
})
}
#[doc(hidden)]
#[allow(non_snake_case)]
pub fn __set_state__internal(state: State) {
LUA.with(|cell| {
cell.set(Some(state));
})
}

55
gmod/src/lua/returns.rs Normal file
View File

@ -0,0 +1,55 @@
use std::num::NonZeroI32;
#[repr(transparent)]
pub struct ValuesReturned(pub i32);
impl Into<i32> for ValuesReturned {
#[inline(always)]
fn into(self) -> i32 {
self.0
}
}
impl From<i32> for ValuesReturned {
#[inline(always)]
fn from(n: i32) -> Self {
ValuesReturned(n)
}
}
impl From<NonZeroI32> for ValuesReturned {
#[inline(always)]
fn from(n: NonZeroI32) -> ValuesReturned {
ValuesReturned(i32::from(n))
}
}
impl From<()> for ValuesReturned {
#[inline(always)]
fn from(_: ()) -> ValuesReturned {
ValuesReturned(0)
}
}
impl From<Option<NonZeroI32>> for ValuesReturned {
#[inline(always)]
fn from(opt: Option<NonZeroI32>) -> ValuesReturned {
ValuesReturned(match opt {
Some(vals) => i32::from(vals),
None => {
unsafe { super::state().push_nil() };
1
},
})
}
}
impl<E: std::fmt::Debug> From<Result<i32, E>> for ValuesReturned {
#[inline(always)]
fn from(res: Result<i32, E>) -> ValuesReturned {
match res {
Ok(vals) => ValuesReturned(vals),
Err(err) => unsafe { super::state().error(&format!("{:?}", err)) }
}
}
}