1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
use core::cell::{RefCell, UnsafeCell};
use core::future::Future;
use core::pin::Pin;
use core::sync::atomic::{AtomicBool, Ordering};
use core::task::{Context, Poll};
use spin::Mutex;
pub mod single {
use super::*;
/// A Single-Core, single-producer, single-consumer (SPSC) signaling primitive for asynchronous synchronization.
///
/// `Signal` allows one asynchronous task to notify another task and optionally pass data.
/// In a single-core, cooperative multitasking environment like UEFI, it acts as a
/// lightweight "oneshot" channel without the need for complex locking or heap-allocated wakers.
///
/// # Design
/// The signal uses a `RefCell` to store data. The `wait()` method produces a `Future`
/// that polls the internal state until the data is provided via `signal()`.
///
/// # Examples
///
/// ```rust
/// // Defined as a static or shared resource
/// static ASSET_LOADED: Signal<TextureHandle> = Signal::new();
///
/// async fn background_loader() {
/// let texture = load_texture_gop("logo.bmp").await;
/// // Notify the renderer that the texture is ready
/// ASSET_LOADED.signal(texture);
/// }
///
/// async fn renderer_task() {
/// // Suspend execution until the signal is triggered
/// let texture = ASSET_LOADED.wait().await;
/// draw_to_screen(texture);
/// }
/// ```
#[derive(Debug)]
pub struct Signal<Data> (RefCell<Option<Data>>);
/// A `Future` that resolves when the associated [`Signal`] receives data.
///
/// Created by the [`Signal::wait`] method.
#[derive(Debug)]
pub struct _Signal<'bemly_, Data> (&'bemly_ Signal<Data>);
impl<Data> Signal<Data> {
/// Creates a new, empty signal.
pub const fn new() -> Self { Self(RefCell::new(None)) }
/// Triggers the signal and stores the provided data.
///
/// This will make the associated [`_Signal`] future resolve on its next poll.
pub fn signal(&self, value: Data) { *self.0.borrow_mut() = Some(value) }
/// Returns a future that resolves to the data when the signal is triggered.
///
/// Note: The data is "consumed" (taken out) once the future resolves.
pub fn wait(&self) -> _Signal<'_, Data> { _Signal(self) }
/// Checks if the signal has been triggered without consuming the data.
pub fn is_triggered(&self) -> bool { self.0.borrow().is_some() }
}
impl<Data> Future for _Signal<'_, Data> {
type Output = Data;
/// Polls the internal state of the signal.
///
/// Returns [`Poll::Ready`] if data is present, consuming it from the signal.
/// Otherwise, returns [`Poll::Pending`].
fn poll(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<Self::Output> {
match self.0.0.borrow_mut().take() {
Some(data) => Poll::Ready(data),
None => Poll::Pending,
}
}
}
/// A low-level, zero-overhead signaling primitive for cross-task communication.
/// Single-core, multi-asynchronous, Unsafe, Fast
///
/// `UnsafeSignal` allows one asynchronous task to wait for a specific piece of data
/// or a "trigger" event sent by another task or an interrupt handler.
///
/// # Safety
/// This type is marked as `Sync` to allow static definition. However, it uses
/// `UnsafeCell` internally without locking. It is designed for single-threaded
/// executors (like the UEFI executor) where re-entrancy is managed by the scheduler.
#[derive(Debug)]
pub struct UnsafeSignal<Data> (UnsafeCell<Option<Data>>);
/// A handle returned by [`UnsafeSignal::wait`], implementing [`Future`].
#[derive(Debug)]
pub struct _UnsafeSignal<'bemly_, Data> (&'bemly_ UnsafeSignal<Data>);
unsafe impl<Data> Sync for UnsafeSignal<Data> {}
impl<Data> UnsafeSignal<Data> {
/// Creates a new, untriggered signal.
pub const fn new() -> Self { Self(UnsafeCell::new(None)) }
/// Triggers the signal by providing a value.
///
/// Any task currently `.await`-ing the associated [`_UnsafeSignal`] future
/// will be resolved on its next poll.
pub fn signal(&self, value: Data) { unsafe { *self.0.get() = Some(value) } }
/// Checks if the signal has been triggered without consuming the data.
pub fn is_triggered(&self) -> bool { unsafe { (*self.0.get()).is_some() } }
/// Returns a future that resolves when the signal is triggered.
///
/// # Example
/// ```rust
/// static ASYNC_EVENT: UnsafeSignal<u32> = UnsafeSignal::new();
///
/// // In Task A:
/// async fn wait_for_event() {
/// let data = ASYNC_EVENT.wait().await;
/// println!("Received: {}", data);
/// }
///
/// // In Task B (or Sync context):
/// fn trigger_event() {
/// ASYNC_EVENT.signal(42);
/// }
/// ```
pub fn wait(&self) -> _UnsafeSignal<'_, Data> { _UnsafeSignal(self) }
}
impl<Data> Future for _UnsafeSignal<'_, Data> {
type Output = Data;
/// Polls the signal to check if data is available.
///
/// If the signal has been triggered, the data is taken from the signal
/// (resetting it to an empty state) and returned as [`Poll::Ready`].
fn poll(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<Self::Output> {
let data_ptr = self.0.0.get();
unsafe {
if (*data_ptr).is_some() {
// Takes the data out, leaving None in its place.
Poll::Ready((*data_ptr).take().expect("Signal data is None"))
} else { Poll::Pending }
}
}
}
}
pub mod multiple {
use super::*;
/// A cross-task/cross-core synchronization primitive for passing data between producers and consumers.
///
/// `MultiCoreSignal` acts as a high-performance, single-slot mailbox. It allows one task
/// (or core) to notify another task that data is ready. It uses an `AtomicBool` for
/// fast "dirty" checks before acquiring a heavier `Mutex` lock, minimizing contention.
///
/// # Design
/// This signal is **reset-on-read**. When the consumer `.await`s the signal and successfully
/// receives the data, the internal state is cleared, and subsequent polls will return
/// `Poll::Pending` until the producer calls `signal()` again.
///
/// # Examples
///
/// ```rust,no_run
/// static RENDER_SIGNAL: MultiCoreSignal<u64> = MultiCoreSignal::new();
///
/// /// Producer: Runs physics calculations and notifies the renderer
/// async fn physics_task() {
/// let mut frame_count = 0;
/// loop {
/// do_physics_calc();
/// frame_count += 1;
///
/// // Update the signal with the new frame index
/// RENDER_SIGNAL.signal(frame_count);
///
/// Yield.await;
/// }
/// }
///
/// /// Consumer: Waits for the signal and renders the frame
/// async fn render_task() {
/// loop {
/// // Execution suspends here until RENDER_SIGNAL.signal() is called
/// let frame = RENDER_SIGNAL.wait().await;
///
/// draw_frame(frame);
///
/// // Yield once to allow other tasks to process before next wait
/// Yield.await;
/// }
/// }
/// ```
#[derive(Debug)]
pub struct MultiCoreSignal<Data> {
data: Mutex<Option<Data>>,
has_data: AtomicBool,
}
/// A handle representing the "wait" state of a [`MultiCoreSignal`].
///
/// This struct implements [`Future`], allowing it to be integrated into
/// an asynchronous executor.
#[derive(Debug)]
pub struct _MultiCoreSignal<'bemly_, Data> (&'bemly_ MultiCoreSignal<Data>);
impl<Data> MultiCoreSignal<Data> {
/// Creates a new, empty `MultiCoreSignal`.
pub const fn new() -> Self { Self { data: Mutex::new(None), has_data: AtomicBool::new(false) } }
/// Deposits data into the signal and notifies any waiting consumers.
///
/// If data already exists in the slot and hasn't been read yet,
/// it will be overwritten (Last-Write-Wins).
pub fn signal(&self, data: Data) {
*self.data.lock() = Some(data);
self.has_data.store(true, Ordering::Release)
}
/// Returns a future that resolves when data is signaled.
pub fn wait(&self) -> _MultiCoreSignal<'_, Data> { _MultiCoreSignal(self) }
/// Manually clears the signal data and resets the notification flag.
pub fn reset(&self) {
self.has_data.store(false, Ordering::Relaxed);
*self.data.lock() = None
}
}
impl<'bemly_, Data> Future for _MultiCoreSignal<'bemly_, Data> {
type Output = Data;
/// Attempts to consume the data from the signal.
///
/// If the `has_data` flag is set, it attempts to lock the internal data.
/// If data is found, it is taken out (leaving `None`), the flag is reset,
/// and `Poll::Ready(data)` is returned.
fn poll(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<Self::Output> {
// Fast atomic check (Acquire) to avoid unnecessary Mutex locking
if !self.0.has_data.load(Ordering::Acquire) { return Poll::Pending }
match self.0.data.lock().take() {
Some(data) => {
self.0.has_data.store(false, Ordering::Relaxed);
Poll::Ready(data)
},
// Fallback in case another consumer/core stole the data between the check and the lock
None => Poll::Pending,
}
}
}
}