Files
galaxy/crates/galaxy_util/src/sync.rs
T

93 lines
3.0 KiB
Rust

//! Synchronization utilities.
use std::future::Future;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use event_listener::Event;
#[cfg(test)]
#[path = "sync_tests.rs"]
mod tests;
/// A set-once asynchronous condition variable.
///
/// Generally, a [condition variable](http://www.cs.cornell.edu/courses/cs3110/2012fa/recitations/rec16.html)
/// lets tasks wait until some condition becomes true (for example, we might want to wait for the
/// user to have logged in, or for the initial load of Warp Drive objects to have finished). When
/// the condition becomes true, one or all of the waiting tasks can wake up and do their work.
///
/// This [`Condition`] implementation models the simpler case where a condition becomes true and
/// is then *always* true (unless reset explicitly). This allows waiting for something to happen at
/// least once. If a task starts waiting before the condition is met, it will block, but if the
/// condition is already true, it continues immediately.
///
/// This is useful when regular UI framework events are not suitable because they do not tell us if
/// the event had *already* happened - a task that subscribed too late would block forever.
///
/// Also see [`std::sync::Condvar`].
#[derive(Debug, Clone)]
pub struct Condition {
// This is more or less the reference example for event-listener:
// https://github.com/smol-rs/event-listener
flag: Arc<AtomicBool>,
event: Arc<Event>,
}
impl Condition {
pub fn new() -> Self {
Self {
flag: Arc::new(AtomicBool::new(false)),
event: Arc::new(Event::new()),
}
}
/// Mark the condition as true.
pub fn set(&self) {
self.flag.store(true, Ordering::SeqCst);
self.event.notify(usize::MAX);
}
/// Reset the condition to false so that future [`wait`](Self::wait) calls
/// will block until [`set`](Self::set) is called again.
pub fn reset(&self) {
self.flag.store(false, Ordering::SeqCst);
}
/// Returns `true` if the condition has already been set.
pub fn is_set(&self) -> bool {
self.flag.load(Ordering::SeqCst)
}
/// Asynchronously wait for the condition to be true.
pub fn wait(&self) -> impl Future<Output = ()> {
let flag = self.flag.clone();
let event = self.event.clone();
async move {
// Loop in case of spurious wakeups.
loop {
// Check if the condition has already been set.
if flag.load(Ordering::SeqCst) {
break;
}
let listener = event.listen();
// Check the flag again after creating the listener, in case it was set while we
// started listening.
if flag.load(Ordering::SeqCst) {
break;
}
listener.await;
}
}
}
}
impl Default for Condition {
fn default() -> Self {
Self::new()
}
}