From 2d93a91e1dd28068ba788119d2b61c6f190bef95 Mon Sep 17 00:00:00 2001 From: Zerthox Date: Mon, 20 Jan 2025 20:29:36 +0100 Subject: [PATCH] Make globals thread safe --- arcdps/Cargo.toml | 11 ++- arcdps/src/exports/has.rs | 24 +++---- arcdps/src/exports/mod.rs | 10 ++- arcdps/src/exports/raw.rs | 28 ++++---- arcdps/src/globals.rs | 120 ++++++++++++++++++-------------- arcdps/src/lib.rs | 11 +-- arcdps/src/util.rs | 8 +++ arcdps_codegen/Cargo.toml | 2 +- arcdps_codegen/src/callbacks.rs | 16 +++-- 9 files changed, 127 insertions(+), 103 deletions(-) diff --git a/arcdps/Cargo.toml b/arcdps/Cargo.toml index be86178f7f5..e5bf79f264c 100644 --- a/arcdps/Cargo.toml +++ b/arcdps/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "arcdps" -version = "0.15.1" +version = "0.16.0" authors = ["Zerthox", "Greaka"] edition = "2021" description = "Rust bindings for ArcDPS plugins" @@ -13,22 +13,19 @@ license = "MIT" arcdps_codegen = { path = "../arcdps_codegen" } arcdps-imgui = { version = "0.8.0", features = ["tables-api"] } chrono = { version = "0.4.24", optional = true } -evtc = { path = "../evtc" } +evtc = { path = "../evtc", features = ["realtime"]} log = { version = "0.4.17", features = ["std"], optional = true } num_enum = "0.7.0" serde = { version = "1.0.160", features = ["derive"], optional = true } strum = { version = "0.26.1", features = ["derive"], optional = true } - -[dependencies.windows] -version = "0.57.0" -features = [ +windows = { version = "0.59.0", features = [ "System", "Win32_Foundation", "Win32_UI_WindowsAndMessaging", "Win32_System_LibraryLoader", "Win32_Graphics_Dxgi", "Win32_Graphics_Direct3D11", -] +] } [features] default = ["unwind"] diff --git a/arcdps/src/exports/has.rs b/arcdps/src/exports/has.rs index df31422cb28..06c3c131865 100644 --- a/arcdps/src/exports/has.rs +++ b/arcdps/src/exports/has.rs @@ -1,67 +1,67 @@ -use crate::globals::ARC_GLOBALS; +use crate::globals::ArcGlobals; /// Checks whether export `e0` was found. #[inline] pub fn has_e0_config_path() -> bool { - unsafe { ARC_GLOBALS.e0 }.is_some() + ArcGlobals::get().e0.is_some() } /// Checks whether export `e3` was found. #[inline] pub fn has_e3_log_file() -> bool { - unsafe { ARC_GLOBALS.e3 }.is_some() + ArcGlobals::get().e3.is_some() } /// Checks whether export `e5` was found. #[inline] pub fn has_e5_colors() -> bool { - unsafe { ARC_GLOBALS.e5 }.is_some() + ArcGlobals::get().e5.is_some() } /// Checks whether export `e6` was found. #[inline] pub fn has_e6_ui_settings() -> bool { - unsafe { ARC_GLOBALS.e6 }.is_some() + ArcGlobals::get().e6.is_some() } /// Checks whether export `e7` was found. #[inline] pub fn has_e7_modifiers() -> bool { - unsafe { ARC_GLOBALS.e7 }.is_some() + ArcGlobals::get().e7.is_some() } /// Checks whether export `e8` was found. #[inline] pub fn has_e8_log_window() -> bool { - unsafe { ARC_GLOBALS.e8 }.is_some() + ArcGlobals::get().e8.is_some() } /// Checks whether export `e9` was found. #[inline] pub fn has_e9_add_event() -> bool { - unsafe { ARC_GLOBALS.e9 }.is_some() + ArcGlobals::get().e9.is_some() } /// Checks whether export `e10` was found. #[inline] pub fn has_e10_add_event_combat() -> bool { - unsafe { ARC_GLOBALS.e10 }.is_some() + ArcGlobals::get().e10.is_some() } /// Checks whether export `addextension2` was found. #[inline] pub fn has_add_extension() -> bool { - unsafe { ARC_GLOBALS.add_extension }.is_some() + ArcGlobals::get().add_extension.is_some() } /// Checks whether export `freeextension2` was found. #[inline] pub fn has_free_extension() -> bool { - unsafe { ARC_GLOBALS.free_extension }.is_some() + ArcGlobals::get().free_extension.is_some() } /// Checks whether export `listextension` was found. #[inline] pub fn has_list_extension() -> bool { - unsafe { ARC_GLOBALS.list_extension }.is_some() + ArcGlobals::get().list_extension.is_some() } diff --git a/arcdps/src/exports/mod.rs b/arcdps/src/exports/mod.rs index dd389183df2..2c57da00f09 100644 --- a/arcdps/src/exports/mod.rs +++ b/arcdps/src/exports/mod.rs @@ -9,7 +9,7 @@ pub use self::has::*; use crate::{ evtc::{Event, Profession}, - globals::ARC_GLOBALS, + globals::ArcGlobals, imgui::sys::ImVec4, }; use num_enum::{IntoPrimitive, TryFromPrimitive}; @@ -32,7 +32,7 @@ use strum::{Display, EnumCount, EnumIter, IntoStaticStr, VariantNames}; /// Retrieves the ArcDPS version as string. #[inline] pub fn version() -> Option<&'static str> { - unsafe { ARC_GLOBALS.version } + ArcGlobals::get().version } /// Retrieves the config path from ArcDPS. @@ -385,8 +385,6 @@ pub enum AddExtensionResult { /// This uses version 2 (`freeextension2`) of the extension API. #[inline] pub fn free_extension(sig: u32) -> Option { - match unsafe { raw::free_extension(sig) } { - HMODULE(0) => None, - handle => Some(handle), - } + let handle = unsafe { raw::free_extension(sig) }; + (!handle.is_invalid()).then_some(handle) } diff --git a/arcdps/src/exports/raw.rs b/arcdps/src/exports/raw.rs index 25ceaed5ad0..f4b117c7303 100644 --- a/arcdps/src/exports/raw.rs +++ b/arcdps/src/exports/raw.rs @@ -1,12 +1,12 @@ //! Raw ArcDPS exports. -use crate::{evtc::Event, globals::ARC_GLOBALS, imgui::sys::ImVec4}; +use crate::{evtc::Event, globals::ArcGlobals, imgui::sys::ImVec4}; use std::{ffi::c_void, os::raw::c_char}; use windows::Win32::Foundation::HMODULE; /// Returns the handle to the ArcDPS dll. pub unsafe fn handle() -> HMODULE { - ARC_GLOBALS.handle + ArcGlobals::get().handle } /// Signature of the `e0` export. See [`e0_config_path`] for details. @@ -15,7 +15,7 @@ pub type Export0 = unsafe extern "C" fn() -> *const u16; /// Retrieves path to ArcDPS ini config file as wide char string. #[inline] pub unsafe fn e0_config_path() -> *const u16 { - ARC_GLOBALS.e0.expect("failed to find arc export e0")() + ArcGlobals::get().e0.expect("failed to find arc export e0")() } /// Signature of the `e3` export. See [`e3_log_file`] for details. @@ -24,7 +24,7 @@ pub type Export3 = unsafe extern "C" fn(string: *const c_char); /// Logs a string to `arcdps.log` file. #[inline] pub unsafe fn e3_log_file(string: *const c_char) { - ARC_GLOBALS.e3.expect("failed to find arc export e3")(string) + ArcGlobals::get().e3.expect("failed to find arc export e3")(string) } /// Signature of the `e5` export. See [`e5_colors`] for details. @@ -33,7 +33,7 @@ pub type Export5 = unsafe extern "C" fn(out: *mut [*mut ImVec4; 5]); /// Writes color array pointers to buffer. #[inline] pub unsafe fn e5_colors(buffer: *mut [*mut ImVec4; 5]) { - ARC_GLOBALS.e5.expect("failed to find arc export e5")(buffer) + ArcGlobals::get().e5.expect("failed to find arc export e5")(buffer) } /// Signature of the `e6` export. See [`e6_ui_settings`] for details. @@ -42,7 +42,7 @@ pub type Export6 = unsafe extern "C" fn() -> u64; /// Retrieves bit mask of current ArcDPS UI settings. #[inline] pub unsafe fn e6_ui_settings() -> u64 { - ARC_GLOBALS.e6.expect("failed to find arc export e6")() + ArcGlobals::get().e6.expect("failed to find arc export e6")() } /// Signature of the `e7` export. See [`e7_modifiers`] for details. @@ -51,7 +51,7 @@ pub type Export7 = unsafe extern "C" fn() -> u64; /// Retrieves modifier keys as virtual key codes. #[inline] pub unsafe fn e7_modifiers() -> u64 { - ARC_GLOBALS.e7.expect("failed to find arc export e7")() + ArcGlobals::get().e7.expect("failed to find arc export e7")() } /// Signature of the `e8` export. See [`e8_log_window`] for details. @@ -62,7 +62,7 @@ pub type Export8 = unsafe extern "C" fn(string: *const c_char); /// Colors are HTML-like: `colored text`. #[inline] pub unsafe fn e8_log_window(string: *const c_char) { - ARC_GLOBALS.e8.expect("failed to find arc export e8")(string) + ArcGlobals::get().e8.expect("failed to find arc export e8")(string) } /// Signature of the `e9` export. See [`e9_add_event`] for details. @@ -74,7 +74,7 @@ pub type Export9 = unsafe extern "C" fn(event: *const Event, sig: u32); /// Event will end up processed like ArcDPS events and logged to EVTC. #[inline] pub unsafe fn e9_add_event(event: *const Event, sig: u32) { - ARC_GLOBALS.e9.expect("failed to find arc export e9")(event, sig) + ArcGlobals::get().e9.expect("failed to find arc export e9")(event, sig) } /// Signature of the `e10` export. See [`e10_add_event_combat`] for details. @@ -88,7 +88,9 @@ pub type Export10 = unsafe extern "C" fn(event: *const Event, sig: u32); /// Contrary to [`e9_add_event`], the `skill_id` is treated as skill id and will be added to the EVTC skill table. #[inline] pub unsafe fn e10_add_event_combat(event: *const Event, sig: u32) { - ARC_GLOBALS.e10.expect("failed to find arc export e10")(event, sig) + ArcGlobals::get() + .e10 + .expect("failed to find arc export e10")(event, sig) } /// Signature of the `addextension2` export. See [`add_extension`] for details. @@ -102,7 +104,7 @@ pub type ExportAddExtension = unsafe extern "C" fn(handle: HMODULE) -> u32; /// This uses version 2 (`addextension2`) of the extension API. #[inline] pub unsafe fn add_extension(handle: HMODULE) -> u32 { - ARC_GLOBALS + ArcGlobals::get() .add_extension .expect("failed to find arc export addextension2")(handle) } @@ -120,7 +122,7 @@ pub type ExportFreeExtension = unsafe extern "C" fn(sig: u32) -> HMODULE; /// This uses version 2 (`freeextension2`) of the extension API. #[inline] pub unsafe fn free_extension(sig: u32) -> HMODULE { - ARC_GLOBALS + ArcGlobals::get() .free_extension .expect("failed to find arc export freeextension2")(sig) } @@ -136,7 +138,7 @@ pub type ExportListExtension = unsafe extern "C" fn(callback_fn: *const c_void); pub unsafe fn list_extension(callback_fn: *const c_void) { // TODO: is this sync? // TODO: bindings should check for uninitialized - ARC_GLOBALS + ArcGlobals::get() .list_extension .expect("failed to find arc export listextension")(callback_fn) } diff --git a/arcdps/src/globals.rs b/arcdps/src/globals.rs index 58702a124dd..57bec4d36f5 100644 --- a/arcdps/src/globals.rs +++ b/arcdps/src/globals.rs @@ -9,16 +9,27 @@ use crate::{ }, }, imgui, - util::exported_proc, + util::{exported_proc, Share}, }; -use std::{ffi::c_void, mem::transmute, ptr}; -use windows::Win32::{ - Foundation::HMODULE, - Graphics::{Direct3D11::ID3D11Device, Dxgi::IDXGISwapChain}, +use std::{ + ffi::c_void, + mem::transmute, + ptr::{self, NonNull}, + sync::{ + atomic::{AtomicU32, Ordering}, + OnceLock, + }, +}; +use windows::{ + core::Interface, + Win32::{ + Foundation::HMODULE, + Graphics::{Direct3D11::ID3D11Device, Dxgi::IDXGISwapChain}, + }, }; /// Global instance of ArcDPS handle & exported functions. -pub static mut ARC_GLOBALS: ArcGlobals = ArcGlobals::empty(); +pub static ARC_GLOBALS: OnceLock = OnceLock::new(); /// ArcDPS handle & exported functions. // TODO: should we move other globals from codegen here? or move this to codegen? @@ -65,29 +76,10 @@ pub struct ArcGlobals { } impl ArcGlobals { - /// Creates an empty version of ArcDPS globals. - const fn empty() -> Self { - Self { - handle: HMODULE(0), - version: None, - e0: None, - e3: None, - e5: None, - e6: None, - e7: None, - e8: None, - e9: None, - e10: None, - add_extension: None, - free_extension: None, - list_extension: None, - } - } - - /// Initializes the ArcDPS globals. - pub unsafe fn init(&mut self, handle: HMODULE, version: Option<&'static str>) { + /// Creates new ArcDPS globals. + pub unsafe fn new(handle: HMODULE, version: Option<&'static str>) -> Self { #![allow(clippy::missing_transmute_annotations)] - *self = Self { + Self { handle, version, e0: transmute(exported_proc(handle, "e0\0")), @@ -101,19 +93,37 @@ impl ArcGlobals { add_extension: transmute(exported_proc(handle, "addextension2\0")), free_extension: transmute(exported_proc(handle, "freeextension2\0")), list_extension: transmute(exported_proc(handle, "listextension\0")), - }; + } + } + + /// Initializes the ArcDPS globals. + pub unsafe fn init(handle: HMODULE, version: Option<&'static str>) -> &'static Self { + ARC_GLOBALS.get_or_init(|| Self::new(handle, version)) + } + + /// Returns the ArcDPS globals. + #[inline] + pub fn get() -> &'static Self { + Self::try_get().expect("arcdps globals not initialized") + } + + /// Tries to retrieve the ArcDPS globals. + #[inline] + pub fn try_get() -> Option<&'static Self> { + ARC_GLOBALS.get() } } +unsafe impl Send for ArcGlobals {} + +unsafe impl Sync for ArcGlobals {} + pub type MallocFn = unsafe extern "C" fn(size: usize, user_data: *mut c_void) -> *mut c_void; pub type FreeFn = unsafe extern "C" fn(ptr: *mut c_void, user_data: *mut c_void); /// ImGui context. -pub static mut IG_CONTEXT: Option = None; - -/// [`imgui::Ui`] kept in memory between renders. -pub static mut IG_UI: Option> = None; +pub static IG_CONTEXT: OnceLock> = OnceLock::new(); /// Helper to initialize ImGui. pub unsafe fn init_imgui( @@ -123,50 +133,50 @@ pub unsafe fn init_imgui( ) { imgui::sys::igSetCurrentContext(ctx); imgui::sys::igSetAllocatorFunctions(malloc, free, ptr::null_mut()); - IG_CONTEXT = Some(imgui::Context::current()); - IG_UI = Some(imgui::Ui::from_ctx(IG_CONTEXT.as_ref().unwrap_unchecked())); + IG_CONTEXT.get_or_init(|| Share(imgui::Context::current())); } /// Current DirectX version. -pub static mut D3D_VERSION: u32 = 0; +pub static D3D_VERSION: AtomicU32 = AtomicU32::new(0); /// Returns the current DirectX version. /// /// `11` for DirectX 11 and `9` for legacy DirectX 9 mode. #[inline] pub fn d3d_version() -> u32 { - unsafe { D3D_VERSION } + D3D_VERSION.load(Ordering::Relaxed) } /// DirectX 11 swap chain. -pub static mut DXGI_SWAP_CHAIN: Option = None; +pub static DXGI_SWAP_CHAIN: OnceLock>> = OnceLock::new(); /// Returns the DirectX swap chain, if available. #[inline] -pub fn dxgi_swap_chain() -> Option<&'static IDXGISwapChain> { - unsafe { DXGI_SWAP_CHAIN.as_ref() } +pub fn dxgi_swap_chain() -> Option { + DXGI_SWAP_CHAIN.get().map(|share| { + unsafe { IDXGISwapChain::from_raw_borrowed(&share.0.as_ptr()) } + .expect("invalid swap chain") + .clone() + }) } -/// Available DirectX 11 device. -pub static mut D3D11_DEVICE: Option = None; - /// Returns the DirectX 11 device, if available. #[inline] -pub fn d3d11_device() -> Option<&'static ID3D11Device> { - unsafe { D3D11_DEVICE.as_ref() } +pub fn d3d11_device() -> Option { + let swap_chain = dxgi_swap_chain()?; + unsafe { swap_chain.GetDevice() }.ok() } /// Helper to initialize DirectX information. pub unsafe fn init_dxgi(id3d: *const c_void, d3d_version: u32, name: &'static str) { - D3D_VERSION = d3d_version; - if !id3d.is_null() && d3d_version == 11 { - // referencing here prevents a crash due to drop - let swap_chain: &IDXGISwapChain = transmute(&id3d); - DXGI_SWAP_CHAIN = Some(swap_chain.clone()); - - match swap_chain.GetDevice() { - Ok(device) => D3D11_DEVICE = Some(device), - Err(err) => { + D3D_VERSION.store(d3d_version, Ordering::Relaxed); + if d3d_version == 11 { + if let Some(id3d) = NonNull::new(id3d.cast_mut()) { + let ptr = id3d.as_ptr(); + let swap_chain = + unsafe { IDXGISwapChain::from_raw_borrowed(&ptr) }.expect("invalid swap chain"); + + if let Err(err) = swap_chain.GetDevice::() { let msg = &format!("{name} error: failed to get d3d11 device: {err}"); if has_e3_log_file() { let _ = log_to_file(msg); @@ -175,6 +185,8 @@ pub unsafe fn init_dxgi(id3d: *const c_void, d3d_version: u32, name: &'static st let _ = log_to_window(msg); } } + + DXGI_SWAP_CHAIN.get_or_init(|| Share(id3d)); } } } diff --git a/arcdps/src/lib.rs b/arcdps/src/lib.rs index 0705bc6c53f..5a1910aad82 100644 --- a/arcdps/src/lib.rs +++ b/arcdps/src/lib.rs @@ -288,10 +288,11 @@ pub mod __macro { use crate::{ exports::has_e3_log_file, - globals::{init_dxgi, init_imgui, ARC_GLOBALS, IG_UI}, + globals::{init_dxgi, init_imgui, ArcGlobals, IG_CONTEXT}, imgui, panic::init_panic_hook, }; + use std::mem::ManuallyDrop; #[cfg(feature = "log")] use crate::{exports::has_e8_log_window, log::ArcDpsLogger}; @@ -310,7 +311,7 @@ pub mod __macro { name: &'static str, ) { // arc exports have to be retrieved before panic hook & logging - ARC_GLOBALS.init(arc_handle, str_from_cstr(arc_version)); + ArcGlobals::init(arc_handle, str_from_cstr(arc_version)); // only set panic hook if log file export was found if has_e3_log_file() { @@ -333,7 +334,9 @@ pub mod __macro { /// Internally used function to retrieve the [`imgui::Ui`]. #[inline] - pub unsafe fn ui() -> &'static imgui::Ui<'static> { - IG_UI.as_ref().expect("imgui ui not initialized") + pub unsafe fn ui() -> ManuallyDrop> { + ManuallyDrop::new(imgui::Ui::from_ctx( + &IG_CONTEXT.get().expect("imgui not initialized").0, + )) } } diff --git a/arcdps/src/util.rs b/arcdps/src/util.rs index 69954284216..5e2284f7a93 100644 --- a/arcdps/src/util.rs +++ b/arcdps/src/util.rs @@ -14,6 +14,14 @@ use windows::{ }, }; +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[repr(transparent)] +pub struct Share(pub T); + +unsafe impl Sync for Share {} + +unsafe impl Send for Share {} + /// Helper to convert a string pointer to a [`prim@str`]. #[inline] pub unsafe fn str_from_cstr<'a>(ptr: *const c_char) -> Option<&'a str> { diff --git a/arcdps_codegen/Cargo.toml b/arcdps_codegen/Cargo.toml index 201416dfba8..a650ad5120d 100644 --- a/arcdps_codegen/Cargo.toml +++ b/arcdps_codegen/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "arcdps_codegen" -version = "0.10.4" +version = "0.11.0" authors = ["Zerthox", "Greaka"] edition = "2021" description = "Helper macro to generate code for ArcDPS plugins" diff --git a/arcdps_codegen/src/callbacks.rs b/arcdps_codegen/src/callbacks.rs index ffcc7a3ab28..d855ec1ee1f 100644 --- a/arcdps_codegen/src/callbacks.rs +++ b/arcdps_codegen/src/callbacks.rs @@ -50,11 +50,12 @@ impl ArcDpsGen { #[no_mangle] pub unsafe extern #SYSTEM_ABI fn get_update_url() -> *const ::std::primitive::u16 { - static mut URL: ::std::vec::Vec = ::std::vec::Vec::new(); + static mut URL: ::std::vec::Vec<::std::primitive::u16> = ::std::vec::Vec::new(); if let ::std::option::Option::Some(url) = self::__UPDATE_URL() { - URL = ::arcdps::__macro::str_to_wide(url); - URL.as_ptr() + let buf = ::std::ptr::addr_of_mut!(URL); + *buf = ::arcdps::__macro::str_to_wide(url); + (*buf).as_ptr() } else { ::std::ptr::null() } @@ -128,7 +129,8 @@ impl ArcDpsGen { const __OPTIONS_WINDOWS: ::arcdps::callbacks::OptionsWindowsCallback = #safe; unsafe extern #C_ABI fn #name(window_name: *const ::arcdps::__macro::c_char) -> ::std::primitive::bool { - self::__OPTIONS_WINDOWS(::arcdps::__macro::ui(), ::arcdps::__macro::str_from_cstr(window_name)) + let ui = ::arcdps::__macro::ui(); + self::__OPTIONS_WINDOWS(&ui, ::arcdps::__macro::str_from_cstr(window_name)) } } }, @@ -146,7 +148,8 @@ impl ArcDpsGen { const __OPTIONS_END: ::arcdps::callbacks::OptionsCallback = #safe; unsafe extern #C_ABI fn #name() { - self::__OPTIONS_END(::arcdps::__macro::ui()) + let ui = ::arcdps::__macro::ui(); + self::__OPTIONS_END(&ui) } } }, @@ -164,7 +167,8 @@ impl ArcDpsGen { const __IMGUI: ::arcdps::callbacks::ImguiCallback = #safe; unsafe extern #C_ABI fn #name(loading: ::std::primitive::u32) { - self::__IMGUI(::arcdps::__macro::ui(), loading != 0) + let ui = ::arcdps::__macro::ui(); + self::__IMGUI(&ui, loading != 0) } } },