use std::future::Future;
use std::marker::PhantomData;
use std::sync::Arc;
use smol::Task;
const MAX_POLL_ITERATIONS: usize = 1000;
pub struct ForegroundExecutor {
inner: smol::LocalExecutor<'static>,
redraw_requester: RedrawRequester,
_not_send: PhantomData<*const ()>,
}
impl ForegroundExecutor {
pub fn new(redraw_requester: RedrawRequester) -> Self {
Self {
inner: smol::LocalExecutor::new(),
redraw_requester,
_not_send: PhantomData,
}
}
pub fn spawn_local<F, T>(&self, future: F) -> Task<T>
where
F: Future<Output = T> + 'static,
T: 'static,
{
let req = self.redraw_requester.clone();
self.inner.spawn(async move {
let out = future.await;
req.request();
out
})
}
pub fn try_tick(&self) -> bool {
self.inner.try_tick()
}
pub fn poll(&self) {
for _ in 0..MAX_POLL_ITERATIONS {
if !self.inner.try_tick() {
break;
}
}
}
}
#[derive(Clone)]
pub struct BackgroundExecutor {
redraw_requester: RedrawRequester,
}
impl BackgroundExecutor {
pub fn new(redraw_requester: RedrawRequester) -> Self {
Self { redraw_requester }
}
pub fn spawn<F, T>(&self, future: F) -> Task<T>
where
F: Future<Output = T> + Send + 'static,
T: Send + 'static,
{
let req = self.redraw_requester.clone();
smol::spawn(async move {
let out = future.await;
req.request();
out
})
}
#[cfg(feature = "tokio")]
pub fn spawn_compat<F, T>(&self, future: F) -> Task<T>
where
F: Future<Output = T> + Send + 'static,
T: Send + 'static,
{
let req = self.redraw_requester.clone();
smol::spawn(async move {
let out = async_compat::Compat::new(future).await;
req.request();
out
})
}
pub fn spawn_blocking<F, T>(&self, f: F) -> Task<T>
where
F: FnOnce() -> T + Send + 'static,
T: Send + 'static,
{
let req = self.redraw_requester.clone();
smol::spawn(async move {
let out = smol::unblock(f).await;
req.request();
out
})
}
}
#[derive(Clone)]
pub struct RedrawRequester(Arc<dyn Fn() + Send + Sync>);
impl RedrawRequester {
pub fn new<F>(wake_fn: F) -> Self
where
F: Fn() + Send + Sync + 'static,
{
Self(Arc::new(wake_fn))
}
pub fn noop() -> Self {
Self(Arc::new(|| {}))
}
pub fn request(&self) {
(self.0)();
}
}
pub struct Executor {
pub foreground: ForegroundExecutor,
pub background: BackgroundExecutor,
}
impl Executor {
pub fn new(redraw_requester: RedrawRequester) -> Self {
Self {
foreground: ForegroundExecutor::new(redraw_requester.clone()),
background: BackgroundExecutor::new(redraw_requester),
}
}
pub fn poll_foreground(&self) {
self.foreground.poll();
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::cell::Cell;
use std::rc::Rc;
use std::sync::atomic::{AtomicUsize, Ordering};
fn assert_not_send<T>() {}
fn assert_send<T: Send>() {}
fn assert_sync<T: Sync>() {}
#[test]
fn foreground_executor_is_not_send() {
assert_not_send::<ForegroundExecutor>();
}
#[test]
fn background_executor_is_send_sync_clone() {
assert_send::<BackgroundExecutor>();
assert_sync::<BackgroundExecutor>();
let _ = |e: BackgroundExecutor| e.clone();
}
#[test]
fn redraw_requester_is_send_sync_clone() {
assert_send::<RedrawRequester>();
assert_sync::<RedrawRequester>();
let _ = |r: RedrawRequester| r.clone();
}
#[test]
fn spawn_local_increments_counter() {
let counter = Rc::new(Cell::new(0));
let counter_clone = counter.clone();
let req = RedrawRequester::noop();
let executor = ForegroundExecutor::new(req);
let _task = executor.spawn_local(async move {
counter_clone.set(counter_clone.get() + 1);
});
executor.poll();
assert_eq!(counter.get(), 1);
}
#[test]
fn spawn_local_requests_redraw() {
let redraw_count = Arc::new(AtomicUsize::new(0));
let redraw_count_clone = redraw_count.clone();
let req = RedrawRequester::new(move || {
redraw_count_clone.fetch_add(1, Ordering::SeqCst);
});
let executor = ForegroundExecutor::new(req);
let _task = executor.spawn_local(async {});
executor.poll();
assert_eq!(redraw_count.load(Ordering::SeqCst), 1);
}
#[test]
fn poll_returns_immediately_when_empty() {
let req = RedrawRequester::noop();
let executor = ForegroundExecutor::new(req);
assert!(!executor.try_tick());
executor.poll();
}
#[test]
fn background_spawn_runs_future() {
let counter = Arc::new(AtomicUsize::new(0));
let counter_clone = counter.clone();
let req = RedrawRequester::noop();
let executor = BackgroundExecutor::new(req);
let task = executor.spawn(async move {
counter_clone.fetch_add(1, Ordering::SeqCst);
42
});
let result = smol::block_on(task);
assert_eq!(result, 42);
assert_eq!(counter.load(Ordering::SeqCst), 1);
}
#[test]
fn background_spawn_requests_redraw() {
let redraw_count = Arc::new(AtomicUsize::new(0));
let redraw_count_clone = redraw_count.clone();
let req = RedrawRequester::new(move || {
redraw_count_clone.fetch_add(1, Ordering::SeqCst);
});
let executor = BackgroundExecutor::new(req);
let task = executor.spawn(async { 1 + 1 });
smol::block_on(task);
assert_eq!(redraw_count.load(Ordering::SeqCst), 1);
}
#[test]
fn background_spawn_blocking_runs_closure() {
let req = RedrawRequester::noop();
let executor = BackgroundExecutor::new(req);
let task = executor.spawn_blocking(|| {
std::thread::sleep(std::time::Duration::from_millis(10));
"done"
});
let result = smol::block_on(task);
assert_eq!(result, "done");
}
#[test]
fn executor_aggregate_polls_foreground() {
let counter = Rc::new(Cell::new(0));
let counter_clone = counter.clone();
let req = RedrawRequester::noop();
let executor = Executor::new(req);
let _task = executor.foreground.spawn_local(async move {
counter_clone.set(counter_clone.get() + 1);
});
executor.poll_foreground();
assert_eq!(counter.get(), 1);
}
}