use std::sync::Arc;
use futures::future::BoxFuture;
pub struct Callback {
pub callback: Box<dyn Fn() -> BoxFuture<'static, ()>>,
}
impl Callback {
pub async fn call(&self) {
(self.callback)().await
}
}
pub trait BlockingIfSync<Marker> {
fn to_callback(self) -> Callback;
}
impl<F: Fn() + Send + Sync + 'static> BlockingIfSync<()> for F {
fn to_callback(self) -> Callback {
let cb = Arc::new(self);
Callback {
callback: Box::new(move || {
let cb = Arc::clone(&cb);
Box::pin(async move {
_ = tokio::task::spawn_blocking(move || (cb)()).await;
})
}),
}
}
}
struct AsyncMarker;
impl<F, Fut> BlockingIfSync<AsyncMarker> for F
where
F: Fn() -> Fut + Send + Sync + 'static,
Fut: Future<Output = ()> + Send + Sync + 'static,
<Fut as Future>::Output: Send,
{
fn to_callback(self) -> Callback {
let cb = Arc::new(self);
Callback {
callback: Box::new(move || {
let cb = Arc::clone(&cb);
Box::pin(async move { (cb)().await })
}),
}
}
}
pub fn schedule<Marker, MaybeAsync: BlockingIfSync<Marker>>(cb: MaybeAsync) -> Callback {
cb.to_callback()
}
#[cfg(test)]
mod tests {
use tokio::sync::mpsc::channel;
use super::*;
async fn test_async() {
println!("testing");
}
async fn test_sync() {
println!("testing");
}
#[tokio::test]
async fn test_async_callback() {
let (tx, rx) = channel::<()>(1);
{
let tx = tx.clone();
let cb = schedule(move || {
let tx = tx.clone();
async move {
_ = tx.send(());
}
});
}
{
let tx = tx.clone();
let cb = schedule(move || {
_ = tx.send(());
});
}
let cb = schedule(test_sync);
let cb = schedule(test_async);
}
}
I would like to remove the usage of arc. I would also like to support FnMut instead, but I don't see how I can make this work without Arc<Mutex> due to the nested closures and the nature of FnMut. These callbacks are intended to be ran from inside one task in a single loop, so there won't be multiple threads calling a given cb at a given time.