vexide_core/sync/
lazy.rs

1use core::{
2    cell::UnsafeCell,
3    fmt::Debug,
4    mem::ManuallyDrop,
5    ops::{Deref, DerefMut},
6};
7
8use super::Once;
9
10union Data<T, I> {
11    data: ManuallyDrop<T>,
12    init: ManuallyDrop<I>,
13}
14
15/// A thread-safe value which is initialized on first access.
16///
17/// This type is a thread-safe [`LazyCell`](core::cell::LazyCell), and can be used in statics.
18///
19/// # Differences from `std::sync::LazyLock`
20///
21/// There are two possible edge cases that can cause different behavior in this type
22/// compared to its `std` counterpart:
23///
24/// - If the type is lazily initialized from within its own initialization function, a panic
25///   will occur rather than an infinite deadlock.
26/// - By extension, if the initialization function uses `block_on` to execute `async` code
27///   and another task attempts to access the underlying value of this type (through
28///   either dereferencing or using [`LazyLock::force`]) before the initialization function has
29///   returned a value, this function will panic rather than block. As such, you should generally
30///   avoid using `block_on` when creating a value inside of a `LazyLock`.
31///
32///
33///
34/// These two differences allow us to implement `LazyLock` without an actual lock at all, since
35/// we guarantee exclusive access after initialization due to the V5 being single-threaded.
36pub struct LazyLock<T, I = fn() -> T> {
37    data: UnsafeCell<Data<T, I>>,
38    once: Once,
39}
40
41unsafe impl<T: Send + Sync, I: Send> Sync for LazyLock<T, I> {}
42
43impl<T, I: FnOnce() -> T> LazyLock<T, I> {
44    /// Creates a new [`LazyLock`] with the given initializer.
45    pub const fn new(init: I) -> Self {
46        Self {
47            data: UnsafeCell::new(Data {
48                init: ManuallyDrop::new(init),
49            }),
50            once: Once::new(),
51        }
52    }
53
54    /// Consume the [`LazyLock`] and return the inner value if it has been initialized.
55    ///
56    /// # Errors
57    ///
58    /// If the inner value has not been initialized, this function returns an error
59    /// containing the initializer function.
60    pub fn into_inner(self) -> Result<T, I> {
61        let mut data = unsafe { core::ptr::read(&self.data).into_inner() };
62        match self.once.is_completed() {
63            true => Ok(unsafe { ManuallyDrop::take(&mut data.data) }),
64            false => Err(unsafe { ManuallyDrop::take(&mut data.init) }),
65        }
66    }
67
68    /// # Safety
69    ///
70    /// Caller must ensure this function is only called once.
71    unsafe fn lazy_init(&self) {
72        let initializer = unsafe { ManuallyDrop::take(&mut (*self.data.get()).init) };
73        let initialized_data = initializer();
74        unsafe {
75            self.data.get().write(Data {
76                data: ManuallyDrop::new(initialized_data),
77            });
78        }
79    }
80
81    /// Forces the evaluation of this lazy value and returns a reference to result.
82    /// This is equivalent to the `Deref` impl, but is explicit.
83    ///
84    /// # Panics
85    ///
86    /// This method will panic under two possible edge-cases:
87    ///
88    /// - It is called recursively from within its own initialization function.
89    /// - The initialization function uses `block_on` to execute `async` code,
90    ///   and another task attempts to access the underlying value of this type
91    ///   in the middle of it lazily initializing.
92    ///
93    /// This behavior differs from the standard library, which would normally either
94    /// block the current thread or deadlock forever. Since the V5 brain is a
95    /// single-core system, it was determined that panicking is a more acceptable
96    /// compromise than an unrecoverable deadlock.
97    pub fn force(&self) -> &T {
98        self.once
99            .try_call_once(|| unsafe { self.lazy_init() })
100            .unwrap();
101        unsafe { &(*self.data.get()).data }
102    }
103
104    /// Forces the evaluation of this lazy value and returns a mutable reference to
105    /// the result. This is equivalent to the `DerefMut` impl, but is explicit.
106    ///
107    /// # Panics
108    ///
109    /// This method will panic under two possible edge-cases:
110    ///
111    /// - It is called recursively from within its own initialization function.
112    /// - The initialization function uses `block_on` to execute `async` code,
113    ///   and another task attempts to access the underlying value of this type
114    ///   in the middle of it lazily initializing.
115    ///
116    /// This behavior differs from the standard library, which would normally either
117    /// block the current thread or deadlock forever. Since the V5 brain is a
118    /// single-core system, it was determined that panicking is a more acceptable
119    /// compromise than an unrecoverable deadlock.
120    pub fn force_mut(&mut self) -> &mut T {
121        self.once
122            .try_call_once(|| unsafe { self.lazy_init() })
123            .unwrap();
124        unsafe { &mut (*self.data.get()).data }
125    }
126}
127
128impl<T, I: FnOnce() -> T> Deref for LazyLock<T, I> {
129    type Target = T;
130
131    /// Dereferences the value.
132    ///
133    /// # Panics
134    ///
135    /// This method will panic under two possible edge-cases:
136    ///
137    /// - It is called recursively from within its own initialization function.
138    /// - The initialization function uses `block_on` to execute `async` code,
139    ///   and another task attempts to access the underlying value of this type
140    ///   in the middle of it lazily initializing.
141    ///
142    /// This behavior differs from the standard library, which would normally either
143    /// block the current thread or deadlock forever. Since the V5 brain is a
144    /// single-core system, it was determined that panicking is a more acceptable
145    /// compromise than an unrecoverable deadlock.
146    fn deref(&self) -> &Self::Target {
147        self.force()
148    }
149}
150
151impl<T, I: FnOnce() -> T> DerefMut for LazyLock<T, I> {
152    /// Mutably dereferences the value.
153    ///
154    /// # Panics
155    ///
156    /// This method will panic under two possible edge-cases:
157    ///
158    /// - It is called recursively from within its own initialization function.
159    /// - The initialization function uses `block_on` to execute `async` code,
160    ///   and another task attempts to access the underlying value of this type
161    ///   in the middle of it lazily initializing.
162    ///
163    /// This behavior differs from the standard library, which would normally either
164    /// block the current thread or deadlock forever. Since the V5 brain is a
165    /// single-core system, it was determined that panicking is a more acceptable
166    /// compromise than an unrecoverable deadlock.
167    fn deref_mut(&mut self) -> &mut Self::Target {
168        self.force_mut()
169    }
170}
171
172impl<T: Default> Default for LazyLock<T> {
173    fn default() -> Self {
174        Self {
175            data: UnsafeCell::new(Data {
176                init: ManuallyDrop::new(T::default),
177            }),
178            once: Once::new(),
179        }
180    }
181}
182
183impl<T: Debug, I> Debug for LazyLock<T, I> {
184    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
185        let mut struct_ = f.debug_struct("LazyLock");
186        if self.once.is_completed() {
187            struct_.field("data", unsafe { &(*self.data.get()).data });
188        } else {
189            struct_.field("data", &"Uninitialized");
190        }
191        struct_.finish_non_exhaustive()
192    }
193}
194
195impl<T, I> Drop for LazyLock<T, I> {
196    fn drop(&mut self) {
197        match self.once.is_completed() {
198            true => unsafe {
199                ManuallyDrop::drop(&mut (*self.data.get()).data);
200            },
201            false => unsafe {
202                ManuallyDrop::drop(&mut (*self.data.get()).init);
203            },
204        }
205    }
206}