use std::future::{Future, poll_fn};
use std::pin::Pin;
use std::sync::Arc;
use std::task::{Context, Wake, Waker};
use valo::ImageContext;
use valo_codec::{Decoder, ImageLoader};
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum DecodeExecution {
#[cfg_attr(
not(all(feature = "image-worker", not(target_arch = "wasm32"))),
default
)]
Local,
#[cfg(all(feature = "image-worker", not(target_arch = "wasm32")))]
#[default]
Worker,
}
pub fn create_image_loader(
images: ImageContext,
execution: DecodeExecution,
) -> std::io::Result<ImageLoader> {
let decoders: Vec<Box<dyn Decoder>> = vec![
#[cfg(all(feature = "image-apple", any(target_os = "macos", target_os = "ios")))]
Box::new(valo_codec_apple::AppleDecoder::default()),
#[cfg(feature = "image-software")]
Box::new(valo_codec_software::SoftwareDecoder),
];
match execution {
DecodeExecution::Local => Ok(ImageLoader::new(images, decoders)),
#[cfg(all(feature = "image-worker", not(target_arch = "wasm32")))]
DecodeExecution::Worker => ImageLoader::with_worker(images, decoders),
}
}
struct EventLoopWake {
continuation: Waker,
wake_host: Arc<dyn Fn() + Send + Sync>,
}
impl Wake for EventLoopWake {
fn wake(self: Arc<Self>) {
self.wake_by_ref();
}
fn wake_by_ref(self: &Arc<Self>) {
self.continuation.wake_by_ref();
(self.wake_host)();
}
}
pub(crate) fn with_event_loop_wake<'a, T: 'a>(
future: impl Future<Output = T> + 'a,
wake_host: impl Fn() + Send + Sync + 'static,
) -> Pin<Box<dyn Future<Output = T> + 'a>> {
let mut future = Box::pin(future);
let wake_host: Arc<dyn Fn() + Send + Sync> = Arc::new(wake_host);
Box::pin(poll_fn(move |context| {
let waker = Waker::from(Arc::new(EventLoopWake {
continuation: context.waker().clone(),
wake_host: wake_host.clone(),
}));
future.as_mut().poll(&mut Context::from_waker(&waker))
}))
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::{
Mutex,
atomic::{AtomicBool, Ordering},
mpsc,
};
use std::task::Poll;
use std::time::Duration;
struct ReadyTask(Arc<AtomicBool>);
impl Wake for ReadyTask {
fn wake(self: Arc<Self>) {
self.0.store(true, Ordering::Release);
}
}
#[test]
fn completion_queues_the_continuation_before_waking_an_idle_host() {
let queued = Arc::new(AtomicBool::new(false));
let completed = Arc::new(AtomicBool::new(false));
let saved_waker = Arc::new(Mutex::new(None::<Waker>));
let (send, receive) = mpsc::channel();
let saved = saved_waker.clone();
let ready = completed.clone();
let queued_at_wake = queued.clone();
let mut future = with_event_loop_wake(
poll_fn(move |context| {
*saved.lock().unwrap() = Some(context.waker().clone());
if ready.load(Ordering::Acquire) {
Poll::Ready(42)
} else {
Poll::Pending
}
}),
move || {
assert!(queued_at_wake.load(Ordering::Acquire));
send.send(()).unwrap();
},
);
let waker = Waker::from(Arc::new(ReadyTask(queued)));
assert!(
future
.as_mut()
.poll(&mut Context::from_waker(&waker))
.is_pending()
);
let worker = std::thread::spawn(move || {
completed.store(true, Ordering::Release);
saved_waker.lock().unwrap().take().unwrap().wake();
});
receive
.recv_timeout(Duration::from_secs(5))
.expect("completion wakes the host without a redraw timer");
worker.join().unwrap();
assert_eq!(
future.as_mut().poll(&mut Context::from_waker(&waker)),
Poll::Ready(42)
);
}
}