cocoanut 0.2.3

A minimal, declarative macOS GUI framework for Rust
use std::future::Future;
use std::pin::Pin;
use std::sync::{Arc, Mutex};
use std::task::{Context, Poll, RawWaker, RawWakerVTable, Waker};

type BoxedFuture = Pin<Box<dyn Future<Output = ()> + Send + 'static>>;

static ASYNC_TASKS: Mutex<Vec<BoxedFuture>> = Mutex::new(Vec::new());

pub fn spawn<F>(future: F)
where
    F: Future<Output = ()> + Send + 'static,
{
    let mut tasks = ASYNC_TASKS.lock().unwrap();
    tasks.push(Box::pin(future));
}

fn dummy_raw_waker() -> RawWaker {
    fn no_op(_: *const ()) {}
    fn clone(_: *const ()) -> RawWaker {
        dummy_raw_waker()
    }

    static VTABLE: RawWakerVTable = RawWakerVTable::new(clone, no_op, no_op, no_op);
    RawWaker::new(std::ptr::null(), &VTABLE)
}

#[cfg(not(test))]
pub fn block_on<F: Future>(future: F) -> F::Output {
    use objc::{class, msg_send, sel, sel_impl};

    let waker = unsafe { Waker::from_raw(dummy_raw_waker()) };
    let mut cx = Context::from_waker(&waker);
    let mut future = std::pin::pin!(future);

    loop {
        match future.as_mut().poll(&mut cx) {
            Poll::Ready(val) => return val,
            Poll::Pending => {
                let run_loop = class!(NSRunLoop);
                unsafe {
                    let mode = class!(NSDefaultRunLoopMode);
                    let date_cls = class!(NSDate);
                    let date: *mut objc::runtime::Object = msg_send![date_cls, distantPast];
                    let _: () = msg_send![run_loop, runMode: mode beforeDate: date];
                }
                std::thread::yield_now();
            }
        }
    }
}

#[cfg(test)]
pub fn block_on<F: Future>(future: F) -> F::Output {
    let waker = unsafe { Waker::from_raw(dummy_raw_waker()) };
    let mut cx = Context::from_waker(&waker);
    let mut future = std::pin::pin!(future);

    loop {
        match future.as_mut().poll(&mut cx) {
            Poll::Ready(val) => return val,
            Poll::Pending => std::thread::yield_now(),
        }
    }
}

pub fn on_click_async<F, Fut>(f: F) -> usize
where
    F: Fn() -> Fut + Send + Sync + 'static,
    Fut: Future<Output = ()> + Send + 'static,
{
    let id = crate::event::next_id();
    let f = Arc::new(f);

    crate::event::register(id, move || {
        let f = f.clone();
        std::thread::spawn(move || {
            block_on(f());
        });
    });
    id
}

#[cfg(test)]
mod tests {
    #[test]
    fn block_on_completes_ready_future() {
        let v = super::block_on(async { 41 + 1 });
        assert_eq!(v, 42);
    }

    #[test]
    fn block_on_completes_pending_then_ready() {
        use std::future::poll_fn;
        use std::sync::atomic::{AtomicUsize, Ordering};
        use std::sync::Arc;

        let n = Arc::new(AtomicUsize::new(0));
        let n2 = n.clone();
        let out = super::block_on(async move {
            poll_fn(move |cx| {
                let c = n2.fetch_add(1, Ordering::SeqCst);
                if c == 0 {
                    cx.waker().wake_by_ref();
                    std::task::Poll::Pending
                } else {
                    std::task::Poll::Ready(99)
                }
            })
            .await
        });
        assert_eq!(out, 99);
    }
}