From 9b79d402e9468c64b0a0db112685b428aa5a3d32 Mon Sep 17 00:00:00 2001 From: William Venner Date: Sat, 22 Jan 2022 18:59:12 +0000 Subject: [PATCH] Lua functions can now return Result, Option, () or i32 --- Cargo.lock | 6 ++--- gmod-macros/Cargo.toml | 2 +- gmod-macros/src/lib.rs | 53 ++++++++++++++++++++++++++++--------- gmod/Cargo.toml | 4 +-- gmod/src/lua/import.rs | 4 ++- gmod/src/lua/lua_state.rs | 2 ++ gmod/src/lua/mod.rs | 38 ++++++++++++++++++++++++++- gmod/src/lua/returns.rs | 55 +++++++++++++++++++++++++++++++++++++++ 8 files changed, 143 insertions(+), 21 deletions(-) create mode 100644 gmod/src/lua/returns.rs diff --git a/Cargo.lock b/Cargo.lock index 1c24bdb..46bbe62 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -135,7 +135,7 @@ dependencies = [ [[package]] name = "gmod" -version = "11.1.1" +version = "12.0.0" dependencies = [ "cfg_table 1.0.0", "cstr", @@ -144,7 +144,7 @@ dependencies = [ "fn_abi", "fn_has_this", "fn_type_alias", - "gmod-macros 1.0.4", + "gmod-macros 2.0.0", "gmserverplugin", "lazy_static", "libloading", @@ -165,7 +165,7 @@ dependencies = [ [[package]] name = "gmod-macros" -version = "1.0.4" +version = "2.0.0" dependencies = [ "proc-macro2", "quote", diff --git a/gmod-macros/Cargo.toml b/gmod-macros/Cargo.toml index 346298d..dfedc2a 100644 --- a/gmod-macros/Cargo.toml +++ b/gmod-macros/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "gmod-macros" -version = "1.0.4" +version = "2.0.0" authors = ["William Venner "] edition = "2021" license = "MIT" diff --git a/gmod-macros/src/lib.rs b/gmod-macros/src/lib.rs index bba4ae8..33c24d7 100644 --- a/gmod-macros/src/lib.rs +++ b/gmod-macros/src/lib.rs @@ -6,41 +6,60 @@ extern crate quote; use proc_macro::TokenStream; use quote::ToTokens; -use syn::{ReturnType, ItemFn, Attribute}; +use syn::ItemFn; fn check_lua_function(input: &mut ItemFn) { assert!(input.sig.asyncness.is_none(), "Cannot be async"); 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!(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"); input.sig.abi = Some(syn::parse_quote!(extern "C-unwind")); } -fn no_mangle(item_fn: &mut ItemFn) { - item_fn.attrs.push(Attribute { - path: parse_quote!(no_mangle), - tokens: proc_macro2::TokenStream::new(), - style: syn::AttrStyle::Outer, - pound_token: Default::default(), - bracket_token: Default::default(), - }); +fn genericify_return(item_fn: &mut ItemFn) { + let stmts = std::mem::take(&mut item_fn.block.stmts); + let output = std::mem::replace(&mut item_fn.sig.output, parse_quote!(-> i32)); + item_fn.block.stmts = vec![syn::parse2(quote!({::gmod::lua::ValuesReturned::from((|| #output {#(#stmts);*})()).into()})).unwrap()]; } #[proc_macro_attribute] pub fn gmod13_open(_attr: TokenStream, tokens: TokenStream) -> TokenStream { 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()); - 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() } #[proc_macro_attribute] pub fn gmod13_close(_attr: TokenStream, tokens: TokenStream) -> TokenStream { let mut input = parse_macro_input!(tokens as ItemFn); + + // Make sure it's valid check_lua_function(&mut input); + // No mangling + input.attrs.push(parse_quote!(#[no_mangle])); + + // Shutdown gmcl thread if it's running #[cfg(feature = "gmcl")] { let stmts = std::mem::take(&mut input.block.stmts); input.block.stmts = vec![syn::parse2(quote!({ @@ -50,13 +69,21 @@ pub fn gmod13_close(_attr: TokenStream, tokens: TokenStream) -> TokenStream { })).unwrap()]; } - no_mangle(&mut input); + // Make the return type nice and dynamic + genericify_return(&mut input); + input.into_token_stream().into() } #[proc_macro_attribute] pub fn lua_function(_attr: TokenStream, tokens: TokenStream) -> TokenStream { let mut input = parse_macro_input!(tokens as ItemFn); + + // Make sure it's valid check_lua_function(&mut input); + + // Make the return type nice and dynamic + genericify_return(&mut input); + input.into_token_stream().into() } \ No newline at end of file diff --git a/gmod/Cargo.toml b/gmod/Cargo.toml index b99062d..56f9409 100644 --- a/gmod/Cargo.toml +++ b/gmod/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "gmod" -version = "11.1.1" +version = "12.0.0" authors = ["William Venner "] edition = "2021" license = "MIT" @@ -16,7 +16,7 @@ server-plugin = ["gmserverplugin"] gmcl = ["gmod-macros/gmcl"] [dependencies] -gmod-macros = { version = "1.0.4", path = "../gmod-macros" } +gmod-macros = { version = "2.0.0", path = "../gmod-macros" } gmserverplugin = { version = "1", optional = true } libloading = "0" diff --git a/gmod/src/lua/import.rs b/gmod/src/lua/import.rs index bdcaab3..d7947fb 100644 --- a/gmod/src/lua/import.rs +++ b/gmod/src/lua/import.rs @@ -5,7 +5,7 @@ use std::{cell::UnsafeCell, ffi::c_void}; 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 LuaSize = usize; @@ -93,6 +93,7 @@ impl LuaSharedInterface { impl std::ops::Deref for LuaSharedInterface { type Target = LuaShared; + #[inline] fn deref(&self) -> &Self::Target { #[cfg(debug_assertions)] self.debug_assertions(); @@ -101,6 +102,7 @@ impl std::ops::Deref for LuaSharedInterface { } } impl std::ops::DerefMut for LuaSharedInterface { + #[inline] fn deref_mut(&mut self) -> &mut Self::Target { #[cfg(debug_assertions)] self.debug_assertions(); diff --git a/gmod/src/lua/lua_state.rs b/gmod/src/lua/lua_state.rs index 3dc9ed2..efa6f04 100644 --- a/gmod/src/lua/lua_state.rs +++ b/gmod/src/lua/lua_state.rs @@ -522,6 +522,7 @@ impl LuaState { ptr } + #[cold] pub unsafe fn error>(&self, msg: S) -> ! { self.push_string(msg.as_ref()); (LUA_SHARED.lua_error)(*self); @@ -625,6 +626,7 @@ impl LuaState { impl std::ops::Deref for LuaState { type Target = *mut std::ffi::c_void; + #[inline(always)] fn deref(&self) -> &Self::Target { &self.0 } diff --git a/gmod/src/lua/mod.rs b/gmod/src/lua/mod.rs index c86ade9..5f77e71 100644 --- a/gmod/src/lua/mod.rs +++ b/gmod/src/lua/mod.rs @@ -1,6 +1,8 @@ #![allow(unused)] mod import; +use std::cell::Cell; + pub use import::*; mod lua_state; @@ -9,6 +11,9 @@ pub use lua_state::LuaState as State; mod push; pub use push::*; +mod returns; +pub use returns::ValuesReturned; + #[derive(Debug, Clone)] pub enum LuaError { /// 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. pub unsafe fn load() { import::LUA_SHARED.load() -} \ No newline at end of file +} + +thread_local! { + #[cfg(debug_assertions)] + static LUA: Cell> = Cell::new(None); + + #[cfg(not(debug_assertions))] + static LUA: Cell = 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)); + }) +} diff --git a/gmod/src/lua/returns.rs b/gmod/src/lua/returns.rs new file mode 100644 index 0000000..8bfed8c --- /dev/null +++ b/gmod/src/lua/returns.rs @@ -0,0 +1,55 @@ +use std::num::NonZeroI32; + +#[repr(transparent)] +pub struct ValuesReturned(pub i32); + +impl Into for ValuesReturned { + #[inline(always)] + fn into(self) -> i32 { + self.0 + } +} + +impl From for ValuesReturned { + #[inline(always)] + fn from(n: i32) -> Self { + ValuesReturned(n) + } +} + +impl From 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> for ValuesReturned { + #[inline(always)] + fn from(opt: Option) -> ValuesReturned { + ValuesReturned(match opt { + Some(vals) => i32::from(vals), + None => { + unsafe { super::state().push_nil() }; + 1 + }, + }) + } +} + +impl From> for ValuesReturned { + #[inline(always)] + fn from(res: Result) -> ValuesReturned { + match res { + Ok(vals) => ValuesReturned(vals), + Err(err) => unsafe { super::state().error(&format!("{:?}", err)) } + } + } +} \ No newline at end of file