use std::collections::VecDeque;
use std::sync::{Arc, Mutex, Weak};
struct Segment<T> {
producer: spsc::Producer<Arc<T>>,
consumer: spsc::Consumer<Arc<T>>,
}
struct Pending<T> {
segments: VecDeque<Segment<T>>,
closed: bool,
}
struct Queue<T> {
pending: Mutex<Pending<T>>,
wake: Arc<dyn Fn() + Send + Sync>,
}
pub struct EventQueue<T>(Arc<Queue<T>>);
impl<T> EventQueue<T> {
pub fn new(wake: impl Fn() + Send + Sync + 'static) -> Self {
Self(Arc::new(Queue {
pending: Mutex::new(Pending {
segments: VecDeque::new(),
closed: false,
}),
wake: Arc::new(wake),
}))
}
pub fn target(&self) -> EventTarget<T> {
EventTarget(Arc::downgrade(&self.0))
}
pub fn take_pending(&mut self) -> EventBatch<T> {
EventBatch(std::mem::take(&mut self.0.pending.lock().unwrap().segments))
}
}
impl<T> Drop for EventQueue<T> {
fn drop(&mut self) {
let pending = {
let mut state = self.0.pending.lock().unwrap();
state.closed = true;
std::mem::take(&mut state.segments)
};
drop(pending);
}
}
pub struct EventBatch<T>(VecDeque<Segment<T>>);
impl<T> Iterator for EventBatch<T> {
type Item = Arc<T>;
fn next(&mut self) -> Option<Self::Item> {
while let Some(segment) = self.0.front() {
if let Some(value) = segment.consumer.try_pop() {
return Some(value);
}
self.0.pop_front();
}
None
}
}
pub struct EventTarget<T>(Weak<Queue<T>>);
impl<T> Clone for EventTarget<T> {
fn clone(&self) -> Self {
Self(Weak::clone(&self.0))
}
}
impl<T> EventTarget<T> {
pub fn publish(&self, value: Arc<T>) -> Result<(), Arc<T>> {
let Some(queue) = self.0.upgrade() else {
return Err(value);
};
let needs_wake = {
let mut pending = queue.pending.lock().unwrap();
if pending.closed {
return Err(value);
}
let empty = pending.segments.is_empty();
let value = match pending.segments.back() {
Some(segment) => match segment.producer.try_push(value) {
None => return Ok(()),
Some(value) => value,
},
None => value,
};
let (producer, consumer) = spsc::make(64);
assert!(producer.try_push(value).is_none());
pending.segments.push_back(Segment { producer, consumer });
empty
};
if needs_wake {
let wake = Arc::clone(&queue.wake);
drop(nagoya::spawn(async move { wake() }));
}
Ok(())
}
}