use std::collections::VecDeque;
pub struct Microtask {
callback: Box<dyn FnOnce()>,
}
impl Microtask {
#[inline]
pub fn new(callback: impl FnOnce() + 'static) -> Self {
Self {
callback: Box::new(callback),
}
}
#[inline]
pub fn run(self) {
(self.callback)();
}
}
pub struct MicrotaskQueue {
queue: VecDeque<Microtask>,
}
impl MicrotaskQueue {
#[inline]
#[must_use]
pub fn new() -> Self {
Self {
queue: VecDeque::new(),
}
}
#[inline]
pub fn enqueue(&mut self, microtask: Microtask) {
self.queue.push_back(microtask);
}
#[inline]
pub fn queue_microtask(&mut self, callback: impl FnOnce() + 'static) {
self.enqueue(Microtask::new(callback));
}
pub fn drain(&mut self) -> usize {
const MAX_DRAIN: usize = 10_000;
let mut executed = 0;
while let Some(microtask) = self.queue.pop_front() {
microtask.run();
executed += 1;
if executed >= MAX_DRAIN {
debug_assert!(
false,
"microtask drain exceeded {MAX_DRAIN} — possible infinite loop"
);
break;
}
}
executed
}
#[inline]
#[must_use]
pub fn len(&self) -> usize {
self.queue.len()
}
#[inline]
#[must_use]
pub fn is_empty(&self) -> bool {
self.queue.is_empty()
}
}
impl Default for MicrotaskQueue {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::cell::Cell;
use std::rc::Rc;
#[test]
fn empty_drain_returns_zero() {
let mut q = MicrotaskQueue::new();
assert_eq!(q.drain(), 0);
assert!(q.is_empty());
}
#[test]
fn single_microtask_executes() {
let called = Rc::new(Cell::new(false));
let mut q = MicrotaskQueue::new();
let c = called.clone();
q.queue_microtask(move || c.set(true));
assert_eq!(q.len(), 1);
assert_eq!(q.drain(), 1);
assert!(called.get());
assert!(q.is_empty());
}
#[test]
fn drain_order_is_fifo() {
let log = Rc::new(std::cell::RefCell::new(Vec::new()));
let mut q = MicrotaskQueue::new();
for i in 0..5 {
let l = log.clone();
q.queue_microtask(move || l.borrow_mut().push(i));
}
q.drain();
assert_eq!(*log.borrow(), vec![0, 1, 2, 3, 4]);
}
#[test]
fn drain_includes_newly_enqueued() {
let log = Rc::new(std::cell::RefCell::new(Vec::new()));
let mut q = MicrotaskQueue::new();
let l1 = log.clone();
q.queue_microtask(move || {
l1.borrow_mut().push(1);
});
let l2 = log.clone();
q.queue_microtask(move || {
l2.borrow_mut().push(2);
});
assert_eq!(q.drain(), 2);
assert_eq!(*log.borrow(), vec![1, 2]);
let log2 = Rc::new(std::cell::RefCell::new(Vec::new()));
let mut q2 = MicrotaskQueue::new();
let l = log2.clone();
q2.queue_microtask(move || l.borrow_mut().push("first"));
let l = log2.clone();
q2.queue_microtask(move || l.borrow_mut().push("second"));
assert_eq!(q2.drain(), 2);
assert_eq!(*log2.borrow(), vec!["first", "second"]);
}
#[test]
fn enqueue_via_struct() {
let called = Rc::new(Cell::new(false));
let mut q = MicrotaskQueue::new();
let c = called.clone();
q.enqueue(Microtask::new(move || c.set(true)));
q.drain();
assert!(called.get());
}
#[test]
fn len_and_is_empty() {
let mut q = MicrotaskQueue::new();
assert!(q.is_empty());
assert_eq!(q.len(), 0);
q.queue_microtask(|| {});
assert!(!q.is_empty());
assert_eq!(q.len(), 1);
q.drain();
assert!(q.is_empty());
}
}