nexus/
globals.rs

1use crate::{api::AddonApi, imgui};
2use std::{
3    fmt, mem, ptr,
4    sync::{Mutex, OnceLock},
5};
6
7#[cfg(feature = "panic")]
8use crate::panic::init_panic_hook;
9
10#[cfg(feature = "log")]
11use crate::logger::NexusLogger;
12
13static ADDON_API: OnceLock<&'static AddonApi> = OnceLock::new();
14
15static IMGUI_CTX: OnceLock<ContextWrapper> = OnceLock::new();
16
17thread_local! { static IMGUI_UI: imgui::Ui<'static> = imgui::Ui::from_ctx(&IMGUI_CTX.get().expect("imgui context not initialized").0); }
18
19/// Initializes globals.
20///
21/// Any calls after the initial one will result in a panic.
22/// A call to this is inserted automatically by the [`export`](crate::export) macro.
23///
24/// # Safety
25/// The passed pointer must be a valid [`AddonApi`] with `'static` lifetime.
26pub unsafe fn init(
27    api: *const AddonApi,
28    addon_name: &'static str,
29    _log_filter: Option<&'static str>,
30) {
31    let api = api.as_ref().expect("no addon api supplied");
32    ADDON_API
33        .set(api)
34        .expect("addon api initialized multiple times");
35
36    // panic hook
37    #[cfg(feature = "panic")]
38    init_panic_hook(addon_name);
39
40    // init logger
41    #[cfg(feature = "log")]
42    NexusLogger::set_logger(addon_name, _log_filter);
43
44    // setup imgui
45    imgui::sys::igSetCurrentContext(api.imgui_context);
46    imgui::sys::igSetAllocatorFunctions(api.imgui_malloc, api.imgui_free, ptr::null_mut());
47    IMGUI_CTX
48        .set(imgui::Context::current().into())
49        .expect("imgui context initialized multiple times");
50}
51
52/// Actions to be performed on addon unload.
53static UNLOAD_ACTIONS: Mutex<Vec<Box<dyn FnOnce() + Send>>> = Mutex::new(Vec::new());
54
55/// Adds a new action to be perform on unload.
56#[inline]
57pub fn on_unload(action: impl FnOnce() + Send + 'static) {
58    UNLOAD_ACTIONS.lock().unwrap().push(Box::new(action));
59}
60
61/// Cleans up during addon unload.
62///
63/// A call to this is inserted automatically by the [`export`](crate::export) macro.
64///
65/// # Safety
66/// This may perform not thread-safe operations and leave globals in an invalid state.
67pub unsafe fn deinit() {
68    // perform stored unload actions
69    let mut guard = UNLOAD_ACTIONS.lock().unwrap();
70    let vec: Vec<_> = mem::take(&mut guard);
71    for action in vec {
72        action();
73    }
74}
75
76/// Returns the Nexus [`AddonApi`] instance.
77///
78/// Panics if called before initialization.
79#[inline]
80pub fn addon_api() -> &'static AddonApi {
81    ADDON_API.get().expect("addon api not initialized")
82}
83
84/// Retrieves the [`imgui::Ui`] for rendering a frame.
85///
86/// # Safety
87/// The [`imgui::Ui`] should only be accessed in render thread.
88#[inline]
89pub unsafe fn with_ui<R>(body: impl FnOnce(&imgui::Ui<'static>) -> R) -> R {
90    IMGUI_UI.with(body)
91}
92
93/// Helper to store [`imgui::Context`] as a global.
94#[repr(transparent)]
95struct ContextWrapper(pub imgui::Context);
96
97impl From<imgui::Context> for ContextWrapper {
98    #[inline]
99    fn from(ctx: imgui::Context) -> Self {
100        Self(ctx)
101    }
102}
103
104impl fmt::Debug for ContextWrapper {
105    #[inline]
106    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
107        self.0.fmt(f)
108    }
109}
110
111unsafe impl Send for ContextWrapper {}
112
113unsafe impl Sync for ContextWrapper {}