Fix game crashing when using overridden stdout in gmod13_close

This commit is contained in:
William Venner 2021-12-30 18:18:23 +00:00
parent d87cf56251
commit 169ab1211a
5 changed files with 56 additions and 10 deletions

6
Cargo.lock generated
View File

@ -135,7 +135,7 @@ dependencies = [
[[package]] [[package]]
name = "gmod" name = "gmod"
version = "10.2.1" version = "10.2.2"
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.2", "gmod-macros 1.0.3",
"gmserverplugin", "gmserverplugin",
"lazy_static", "lazy_static",
"libloading", "libloading",
@ -165,7 +165,7 @@ dependencies = [
[[package]] [[package]]
name = "gmod-macros" name = "gmod-macros"
version = "1.0.2" version = "1.0.3"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",

View File

@ -1,12 +1,15 @@
[package] [package]
name = "gmod-macros" name = "gmod-macros"
version = "1.0.2" version = "1.0.3"
authors = ["William Venner <william@venner.io>"] authors = ["William Venner <william@venner.io>"]
edition = "2018" edition = "2018"
license = "MIT" license = "MIT"
description = "Proc macros for gmod-rs" description = "Proc macros for gmod-rs"
repository = "https://github.com/WilliamVenner/gmod-rs" repository = "https://github.com/WilliamVenner/gmod-rs"
[features]
gmcl = []
[lib] [lib]
proc-macro = true proc-macro = true

View File

@ -40,6 +40,16 @@ pub fn gmod13_open(_attr: TokenStream, tokens: TokenStream) -> TokenStream {
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);
check_lua_function(&mut input); check_lua_function(&mut input);
#[cfg(feature = "gmcl")] {
let stmts = std::mem::take(&mut input.block.stmts);
input.block.stmts = vec![syn::parse2(quote!({
let ret = {#(#stmts);*};
::gmod::gmcl::restore_stdout();
ret
})).unwrap()];
}
no_mangle(&mut input); no_mangle(&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 = "10.2.1" version = "10.2.2"
authors = ["William Venner <william@venner.io>"] authors = ["William Venner <william@venner.io>"]
edition = "2018" edition = "2018"
license = "MIT" license = "MIT"
@ -13,10 +13,10 @@ categories = ["api-bindings", "external-ffi-bindings", "game-development", "deve
default = ["hax"] default = ["hax"]
hax = ["ctor", "skidscan", "detour", "fn_type_alias", "fn_abi", "cfg_table", "null_fn", "fn_has_this"] hax = ["ctor", "skidscan", "detour", "fn_type_alias", "fn_abi", "cfg_table", "null_fn", "fn_has_this"]
server-plugin = ["gmserverplugin"] server-plugin = ["gmserverplugin"]
gmcl = [] gmcl = ["gmod-macros/gmcl"]
[dependencies] [dependencies]
gmod-macros = { version = "1.0.2", path = "../gmod-macros" } gmod-macros = { version = "1.0.3", path = "../gmod-macros" }
gmserverplugin = { version = "1", optional = true } gmserverplugin = { version = "1", optional = true }
libloading = "0" libloading = "0"

View File

@ -1,9 +1,28 @@
use std::{sync::{Arc, Mutex, TryLockError}, time::Duration, os::raw::c_char}; use std::{sync::{Arc, Mutex, TryLockError, atomic::AtomicBool}, time::Duration, os::raw::c_char, thread::JoinHandle};
lazy_static::lazy_static! {
static ref STDOUT_OVERRIDE_THREAD: Mutex<Option<JoinHandle<()>>> = Mutex::new(None);
}
static SHUTDOWN_FLAG: AtomicBool = AtomicBool::new(false);
/// This function will **permanently** redirect stdout to the client console. /// This function will **permanently** redirect stdout to the client console.
/// ///
/// This allows for `println!()` and friends to print to the client console. /// This allows for `println!()` and friends to print to the client console.
///
/// # IMPORTANT
///
/// You must undo this action when your module is unloaded or the game will crash.
///
/// This will be done automatically for you if you use the `#[gmod13_close]` attribute macro, otherwise, please call `gmod::gmcl::restore_stdout()` in your custom `gmod13_close` function.
pub fn override_stdout() { pub fn override_stdout() {
let mut join_handle = STDOUT_OVERRIDE_THREAD.lock().unwrap();
if join_handle.is_some() {
// We don't need to override twice
return;
}
unsafe { unsafe {
let (_lib, _path) = crate::open_library!("tier0").expect("Failed to open tier0.dll"); let (_lib, _path) = crate::open_library!("tier0").expect("Failed to open tier0.dll");
@ -28,7 +47,7 @@ pub fn override_stdout() {
let output_buf_ref = output_buf.clone(); let output_buf_ref = output_buf.clone();
// This is actually a really dumb implementation, but appears to be the only way, unfortunately. // This is actually a really dumb implementation, but appears to be the only way, unfortunately.
std::thread::spawn(move || loop { join_handle.replace(std::thread::spawn(move || loop {
match output_buf.try_lock() { match output_buf.try_lock() {
Ok(mut data) => if !data.is_empty() { Ok(mut data) => if !data.is_empty() {
data.push(0); // cheeky data.push(0); // cheeky
@ -43,9 +62,23 @@ pub fn override_stdout() {
continue continue
} }
} }
if SHUTDOWN_FLAG.load(std::sync::atomic::Ordering::Relaxed) {
break;
}
std::thread::sleep(Duration::from_millis(250)); std::thread::sleep(Duration::from_millis(250));
}); }));
std::io::set_output_capture(Some(output_buf_ref)); std::io::set_output_capture(Some(output_buf_ref));
}; };
}
/// Undoes `gmod::gmcl::override_stdout`. You must call this function in a custom `gmod13_close` function (you are not using the crate's provided `#[gmod13_close]` attribute macro) if you override stdout.
pub fn restore_stdout() {
SHUTDOWN_FLAG.store(true, std::sync::atomic::Ordering::Release);
if let Some(join_handle) = STDOUT_OVERRIDE_THREAD.lock().unwrap().take() {
let _ = join_handle.join();
}
std::io::set_output_capture(None);
} }