From 8d0bacec529d1e81324a64f167ad3e3adbb4d8ec Mon Sep 17 00:00:00 2001 From: William Venner Date: Thu, 21 Oct 2021 15:05:01 +0100 Subject: [PATCH] Replace lazy_static Lua Shared struct with a much faster single-threaded abstraction --- Cargo.lock | 4 ++-- README.md | 9 ++++++++ gmod-macros/Cargo.toml | 2 +- gmod-macros/src/lib.rs | 1 + gmod/Cargo.toml | 4 ++-- gmod/src/lib.rs | 1 + gmod/src/lua/import.rs | 49 +++++++++++++++++++++++++++++++++++++++--- gmod/src/lua/mod.rs | 6 ++++++ 8 files changed, 68 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 586811c..e1e17e3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -109,7 +109,7 @@ dependencies = [ [[package]] name = "gmod" -version = "5.0.0" +version = "6.0.0" dependencies = [ "cfg_table", "cstr", @@ -127,7 +127,7 @@ dependencies = [ [[package]] name = "gmod-macros" -version = "0.1.0" +version = "1.0.0" dependencies = [ "proc-macro2", "quote", diff --git a/README.md b/README.md index 1dbf421..ea34148 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,15 @@ A swiss army knife for creating binary modules for Garry's Mod in Rust. # Example +### rust-toolchain.toml + +Because we're using the [`C-unwind`](https://rust-lang.github.io/rfcs/2797-project-ffi-unwind.html) ABI, this crate must be used on a [Nightly Rust](https://doc.rust-lang.org/book/appendix-07-nightly-rust.html) compiler. + +```toml +[toolchain] +channel = "nightly" +``` + ### Cargo.toml ```toml diff --git a/gmod-macros/Cargo.toml b/gmod-macros/Cargo.toml index f5de247..32439eb 100644 --- a/gmod-macros/Cargo.toml +++ b/gmod-macros/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "gmod-macros" -version = "0.1.0" +version = "1.0.0" authors = ["William Venner "] edition = "2018" license = "MIT" diff --git a/gmod-macros/src/lib.rs b/gmod-macros/src/lib.rs index dfd9808..0123957 100644 --- a/gmod-macros/src/lib.rs +++ b/gmod-macros/src/lib.rs @@ -21,6 +21,7 @@ fn check_lua_function(input: &mut ItemFn) { pub fn gmod13_open(_attr: TokenStream, tokens: TokenStream) -> TokenStream { let mut input = parse_macro_input!(tokens as ItemFn); check_lua_function(&mut input); + input.block.stmts.insert(0, syn::parse2(quote!(#[allow(unused_unsafe)] unsafe { ::gmod::lua::load() })).unwrap()); TokenStream::from(quote!(#[no_mangle] #input)) } diff --git a/gmod/Cargo.toml b/gmod/Cargo.toml index e2d06df..77994b6 100644 --- a/gmod/Cargo.toml +++ b/gmod/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "gmod" -version = "5.0.0" +version = "6.0.0" authors = ["William Venner "] edition = "2018" license = "MIT" @@ -16,7 +16,7 @@ skidscan = "0" cstr = "0" lazy_static = "1" ctor = "0" -gmod-macros = { version = "0.1.0", path = "../gmod-macros" } +gmod-macros = { version = "1.0.0", path = "../gmod-macros" } fn_type_alias = "0" fn_abi = "2" diff --git a/gmod/src/lib.rs b/gmod/src/lib.rs index 9245247..aca47c7 100644 --- a/gmod/src/lib.rs +++ b/gmod/src/lib.rs @@ -1,5 +1,6 @@ #![doc = include_str!("../../README.md")] #![feature(c_unwind)] +#![feature(thread_id_value)] pub use libloading; pub use detour; diff --git a/gmod/src/lua/import.rs b/gmod/src/lua/import.rs index 6e83cd9..49f1ef7 100644 --- a/gmod/src/lua/import.rs +++ b/gmod/src/lua/import.rs @@ -1,4 +1,7 @@ -use std::ffi::c_void; +#[cfg(debug_assertions)] +use std::sync::atomic::AtomicI64; + +use std::{cell::UnsafeCell, ffi::c_void}; use libloading::{Library, Symbol}; @@ -70,9 +73,49 @@ impl LuaError { } } -lazy_static::lazy_static! { - pub static ref LUA_SHARED: LuaShared = LuaShared::import(); +#[cfg_attr(not(debug_assertions), repr(transparent))] +pub struct LuaSharedInterface(UnsafeCell<*mut LuaShared>, #[cfg(debug_assertions)] AtomicI64); +impl LuaSharedInterface { + #[cfg(debug_assertions)] + fn debug_assertions(&self) { + assert!(!unsafe { *self.0.get() }.is_null(), "The Lua state has not been initialized yet. Add `#[gmod::gmod13_open]` to your module's gmod13_open function to fix this. You can also manually load the Lua state with `gmod::load_lua_state()` or `gmod::set_lua_state(*mut c_void)`"); + + let thread_id = u64::from(std::thread::current().id().as_u64()) as i64; + match self.1.compare_exchange(-1, thread_id, std::sync::atomic::Ordering::SeqCst, std::sync::atomic::Ordering::SeqCst) { + Ok(-1) => {}, // This is the first thread to use this Lua state. + Ok(_) => unreachable!(), + Err(remembered_thread_id) => assert_eq!(thread_id, remembered_thread_id, "Tried to access the Lua state from another thread! The Lua state is NOT thread-safe, and should only be accessed from the main thread.") + } + } + + pub(super) unsafe fn load(&self) { + *self.0.get() = Box::leak(Box::new(LuaShared::import())); + } + + pub(super) unsafe fn set(&self, ptr: *mut c_void) { + *self.0.get() = ptr as *mut LuaShared; + } } +impl std::ops::Deref for LuaSharedInterface { + type Target = LuaShared; + + fn deref(&self) -> &Self::Target { + #[cfg(debug_assertions)] + self.debug_assertions(); + + unsafe { &**self.0.get() } + } +} +impl std::ops::DerefMut for LuaSharedInterface { + fn deref_mut(&mut self) -> &mut Self::Target { + #[cfg(debug_assertions)] + self.debug_assertions(); + + unsafe { &mut **self.0.get_mut() } + } +} + +pub static mut LUA_SHARED: LuaSharedInterface = LuaSharedInterface(UnsafeCell::new(std::ptr::null_mut()), #[cfg(debug_assertions)] AtomicI64::new(-1)); pub struct LuaShared { pub lual_newstate: Symbol<'static, unsafe extern "C-unwind" fn() -> LuaState>, diff --git a/gmod/src/lua/mod.rs b/gmod/src/lua/mod.rs index 50915f9..9656111 100644 --- a/gmod/src/lua/mod.rs +++ b/gmod/src/lua/mod.rs @@ -93,4 +93,10 @@ pub struct LuaDebug { pub lastlinedefined: i32, pub short_src: [std::os::raw::c_char; LUA_IDSIZE], pub i_ci: i32 +} + +#[inline(always)] +/// 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