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}