use std::cell::RefCell;
use std::rc::Rc;
use crate::ScheduleFlush;
pub struct DeferredScheduler {
pending: RefCell<Vec<Box<dyn FnOnce()>>>,
}
impl DeferredScheduler {
#[must_use]
pub fn new() -> Rc<Self> {
Rc::new(Self {
pending: RefCell::new(Vec::new()),
})
}
pub fn drain(&self) {
let cbs: Vec<Box<dyn FnOnce()>> = self.pending.borrow_mut().drain(..).collect();
for cb in cbs {
cb();
}
}
#[must_use]
pub fn pending_count(&self) -> usize {
self.pending.borrow().len()
}
}
impl Default for DeferredScheduler {
fn default() -> Self {
Self {
pending: RefCell::new(Vec::new()),
}
}
}
impl ScheduleFlush for DeferredScheduler {
fn schedule(&self, callback: Box<dyn FnOnce()>) {
self.pending.borrow_mut().push(callback);
}
}
#[cfg(test)]
mod tests {
use std::cell::Cell;
use std::rc::Rc;
use super::*;
#[test]
fn deferred_scheduler_buffers_and_drains() {
let sched = DeferredScheduler::new();
let calls = Rc::new(Cell::new(0u32));
let c1 = calls.clone();
sched.schedule(Box::new(move || c1.set(c1.get() + 1)));
let c2 = calls.clone();
sched.schedule(Box::new(move || c2.set(c2.get() + 2)));
assert_eq!(sched.pending_count(), 2);
assert_eq!(calls.get(), 0);
sched.drain();
assert_eq!(calls.get(), 3);
assert_eq!(sched.pending_count(), 0);
}
#[test]
fn drain_empty_is_noop() {
let sched = DeferredScheduler::new();
sched.drain(); assert_eq!(sched.pending_count(), 0);
}
#[test]
fn default_scheduler_has_no_pending() {
let sched = DeferredScheduler::default();
assert_eq!(sched.pending_count(), 0);
}
#[test]
fn reentrant_schedule_during_drain_deferred_to_next_drain() {
let sched = DeferredScheduler::new();
let calls = Rc::new(Cell::new(0u32));
let sched_weak = Rc::downgrade(&sched);
let c1 = calls.clone();
sched.schedule(Box::new(move || {
c1.set(1);
if let Some(s) = sched_weak.upgrade() {
s.schedule(Box::new(|| {}));
}
}));
assert_eq!(sched.pending_count(), 1);
sched.drain();
assert_eq!(calls.get(), 1);
assert_eq!(
sched.pending_count(),
1,
"re-entrant callback should be pending"
);
sched.drain();
assert_eq!(sched.pending_count(), 0);
}
}