godot_ffi/
global.rs

1/*
2 * Copyright (c) godot-rust; Bromeon and contributors.
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at https://mozilla.org/MPL/2.0/.
6 */
7
8use std::cell::OnceCell;
9use std::sync::{Mutex, MutexGuard, PoisonError, TryLockError};
10
11/// Ergonomic global variables.
12///
13/// No more `Mutex<Option<...>>` shenanigans with lazy initialization on each use site, or `OnceLock` which limits to immutable access.
14///
15/// This type is very similar to [`once_cell::Lazy`](https://docs.rs/once_cell/latest/once_cell/sync/struct.Lazy.html) in its nature,
16/// with a minimalistic implementation. Unlike `Lazy`, it is only designed for global variables, not for local lazy initialization
17/// (following "do one thing and do it well").
18///
19/// `Global<T>` features:
20/// - `const` constructors, allowing to be used in `static` variables without `Option`.
21/// - Initialization function provided in constructor, not in each use site separately.
22/// - Ergonomic access through guards to both `&T` and `&mut T`.
23/// - Completely safe usage. Little use of `unsafe` in the implementation (for performance reasons).
24///
25/// There are two `const` methods for construction: [`new()`](Self::new) and [`default()`](Self::default).
26/// For access, you should primarily use [`lock()`](Self::lock). There is also [`try_lock()`](Self::try_lock) for special cases.
27pub struct Global<T> {
28    // When needed, this could be changed to use RwLock and separate read/write guards.
29    value: Mutex<OnceCell<T>>,
30    init_fn: fn() -> T,
31}
32
33impl<T> Global<T> {
34    /// Create `Global<T>`, providing a lazy initialization function.
35    ///
36    /// The initialization function is only called once, when the global is first accessed through [`lock()`](Self::lock).
37    pub const fn new(init_fn: fn() -> T) -> Self {
38        // Note: could be generalized to F: FnOnce() -> T + Send. See also once_cell::Lazy<T, F>.
39        Self {
40            value: Mutex::new(OnceCell::new()),
41            init_fn,
42        }
43    }
44
45    /// Create `Global<T>` with `T::default()` as initialization function.
46    ///
47    /// This is inherent rather than implementing the `Default` trait, because the latter is not `const` and thus useless in static contexts.
48    pub const fn default() -> Self
49    where
50        T: Default,
51    {
52        Self::new(T::default)
53    }
54
55    /// Returns a guard that gives shared or mutable access to the value.
56    ///
57    /// Blocks until the internal mutex is available.
58    ///
59    /// # Panics
60    /// If the initialization function panics. Once that happens, the global is considered poisoned and all future calls to `lock()` will panic.
61    /// This can currently not be recovered from.
62    pub fn lock(&self) -> GlobalGuard<'_, T> {
63        let guard = self.value.lock().unwrap_or_else(PoisonError::into_inner);
64        guard.get_or_init(self.init_fn);
65
66        // SAFETY: `get_or_init()` has already panicked if it wants to, propagating the panic to its caller,
67        // so the object is guaranteed to be initialized.
68        unsafe { GlobalGuard::new_unchecked(guard) }
69    }
70
71    /// Non-blocking access with error introspection.
72    pub fn try_lock(&self) -> Result<GlobalGuard<'_, T>, GlobalLockError<'_, T>> {
73        /// Initializes the cell and returns a guard.
74        fn init<'mutex: 'cell, 'cell, T>(
75            g: MutexGuard<'mutex, OnceCell<T>>,
76            init_fn: fn() -> T,
77        ) -> Result<GlobalGuard<'cell, T>, GlobalLockError<'cell, T>> {
78            // Initialize the cell.
79            std::panic::catch_unwind(|| g.get_or_init(init_fn))
80                .map_err(|_| GlobalLockError::InitFailed)?;
81
82            // SAFETY: `get_or_init()` has already panicked if it wants to, which has been successfully unwound,
83            // therefore the object is guaranteed to be initialized.
84            Ok(unsafe { GlobalGuard::new_unchecked(g) })
85        }
86
87        match self.value.try_lock() {
88            Ok(guard) => init(guard, self.init_fn),
89            Err(TryLockError::WouldBlock) => Err(GlobalLockError::WouldBlock),
90
91            // This is a cold branch, where the initialization function panicked.
92            Err(TryLockError::Poisoned(x)) => {
93                // We do the same things as in the hot branch.
94                let circumvent = init(x.into_inner(), self.init_fn)?;
95                Err(GlobalLockError::Poisoned { circumvent })
96            }
97        }
98    }
99}
100
101// ----------------------------------------------------------------------------------------------------------------------------------------------
102// Guards
103
104// Encapsulate private fields.
105mod global_guard {
106    use std::ops::{Deref, DerefMut};
107
108    use super::*;
109
110    /// Guard that temporarily gives access to a `Global<T>`'s inner value.
111    pub struct GlobalGuard<'a, T> {
112        // Safety invariant: `OnceCell` has been initialized.
113        mutex_guard: MutexGuard<'a, OnceCell<T>>,
114    }
115
116    impl<'a, T> GlobalGuard<'a, T> {
117        pub(super) fn new(mutex_guard: MutexGuard<'a, OnceCell<T>>) -> Option<Self> {
118            // Use an eager map instead of `mutex_guard.get().map(|_| Self { mutex_guard })`
119            // as `.get().map(…)` tries to move `mutex_guard` while borrowing an ignored value.
120            match mutex_guard.get() {
121                Some(_) => Some(Self { mutex_guard }),
122                _ => None,
123            }
124        }
125
126        /// # Safety
127        ///
128        /// The value must be initialized.
129        pub(super) unsafe fn new_unchecked(mutex_guard: MutexGuard<'a, OnceCell<T>>) -> Self {
130            debug_assert!(
131                mutex_guard.get().is_some(),
132                "safety precondition violated: cell not initialized"
133            );
134            Self::new(mutex_guard).unwrap_unchecked()
135        }
136    }
137
138    impl<T> Deref for GlobalGuard<'_, T> {
139        type Target = T;
140        fn deref(&self) -> &Self::Target {
141            // SAFETY: `GlobalGuard` guarantees that the cell is initialized.
142            unsafe { self.mutex_guard.get().unwrap_unchecked() }
143        }
144    }
145
146    impl<T> DerefMut for GlobalGuard<'_, T> {
147        fn deref_mut(&mut self) -> &mut Self::Target {
148            // SAFETY: `GlobalGuard` guarantees that the cell is initialized.
149            unsafe { self.mutex_guard.get_mut().unwrap_unchecked() }
150        }
151    }
152}
153
154pub use global_guard::GlobalGuard;
155
156/// Guard that temporarily gives access to a `Global<T>`'s inner value.
157pub enum GlobalLockError<'a, T> {
158    /// The mutex is currently locked by another thread.
159    WouldBlock,
160
161    /// A panic occurred while a lock was held. This excludes initialization errors.
162    Poisoned { circumvent: GlobalGuard<'a, T> },
163
164    /// The initialization function panicked.
165    InitFailed,
166}
167
168// ----------------------------------------------------------------------------------------------------------------------------------------------
169// Tests
170
171#[cfg(test)] #[cfg_attr(published_docs, doc(cfg(test)))]
172mod tests {
173    use std::collections::HashMap;
174
175    use super::*;
176
177    static MAP: Global<HashMap<i32, &'static str>> = Global::default();
178    static VEC: Global<Vec<i32>> = Global::new(|| vec![1, 2, 3]);
179    static FAILED: Global<()> = Global::new(|| panic!("failed"));
180    static POISON: Global<i32> = Global::new(|| 36);
181
182    #[test]
183    fn test_global_map() {
184        {
185            let mut map = MAP.lock();
186            map.insert(2, "two");
187            map.insert(3, "three");
188        }
189
190        {
191            let mut map = MAP.lock();
192            map.insert(1, "one");
193        }
194
195        let map = MAP.lock();
196        assert_eq!(map.len(), 3);
197        assert_eq!(map.get(&1), Some(&"one"));
198        assert_eq!(map.get(&2), Some(&"two"));
199        assert_eq!(map.get(&3), Some(&"three"));
200    }
201
202    #[test]
203    fn test_global_vec() {
204        {
205            let mut vec = VEC.lock();
206            vec.push(4);
207        }
208
209        let vec = VEC.lock();
210        assert_eq!(*vec, &[1, 2, 3, 4]);
211    }
212
213    #[test]
214    fn test_global_would_block() {
215        let vec = VEC.lock();
216
217        let vec2 = VEC.try_lock();
218        assert!(matches!(vec2, Err(GlobalLockError::WouldBlock)));
219    }
220
221    #[test]
222    fn test_global_init_failed() {
223        let guard = FAILED.try_lock();
224        assert!(matches!(guard, Err(GlobalLockError::InitFailed)));
225
226        // Subsequent access still returns same error.
227        let guard = FAILED.try_lock();
228        assert!(matches!(guard, Err(GlobalLockError::InitFailed)));
229    }
230
231    #[test]
232    fn test_global_poison() {
233        let result = std::panic::catch_unwind(|| {
234            let guard = POISON.lock();
235            panic!("poison injection");
236        });
237        assert!(result.is_err());
238
239        let guard = POISON.try_lock();
240        let Err(GlobalLockError::Poisoned { circumvent }) = guard else {
241            panic!("expected GlobalLockError::Poisoned");
242        };
243        assert_eq!(*circumvent, 36);
244    }
245}