hreads 0.5.0

Multithreading primitives.
Documentation
use std::sync::{
    Arc, Mutex,
    mpsc::{Sender, channel},
};

use anyhow::Result;
use log::warn;

use crate::main_thread::is_main_thread;

type Callback = Box<dyn FnOnce() + Send>;
type Callbacks = Mutex<Vec<Callback>>;
type SignalledCallbacks = Mutex<Vec<(Sender<()>, Callback)>>;

static CALLBACKS: Callbacks = Callbacks::new(vec![]);
static SIGNALLED: SignalledCallbacks = SignalledCallbacks::new(vec![]);

pub fn from_main<T, A>(action: A) -> T
where
    A: FnOnce() -> T + Send + 'static,
    T: Send + 'static, {
    if is_main_thread() {
        return action();
    }

    let result = Arc::<Mutex<Option<T>>>::default();

    let (sender, receiver) = channel::<()>();

    let capture = result.clone();
    SIGNALLED.lock().unwrap().push((
        sender,
        Box::new(move || {
            let mut res = capture.lock().unwrap();
            *res = action().into();
        }),
    ));

    receiver.recv().expect("Failed to receive result in on_main");

    result.lock().unwrap().take().unwrap()
}

pub fn wait_for_next_frame() {
    assert!(
        !is_main_thread(),
        "Waiting for next frame on main thread does nothing"
    );
    from_main(|| {});
}

pub fn on_main(action: impl FnOnce() + Send + 'static) {
    if is_main_thread() {
        action();
    } else {
        CALLBACKS.lock().unwrap().push(Box::new(action));
    }
}

pub fn ok_main(action: impl FnOnce() + Send + 'static) -> Result<()> {
    on_main(action);
    Ok(())
}

pub fn on_main_sync(action: impl FnOnce() + Send + 'static) {
    if is_main_thread() {
        action();
    } else {
        let (sender, receiver) = channel::<()>();
        SIGNALLED.lock().unwrap().push((sender, Box::new(action)));
        while receiver.try_recv().is_err() {}
    }
}

pub fn after(delay: f32, action: impl FnOnce() + Send + 'static) {
    crate::spawn(async move {
        crate::sleep(delay).await;
        CALLBACKS.lock().unwrap().push(Box::new(action));
    });
}

pub fn invoke_dispatched() {
    let Ok(mut callback) = CALLBACKS.try_lock() else {
        warn!("Failed to lock CALLBACKS");
        return;
    };

    for action in callback.drain(..) {
        action();
    }
    drop(callback);

    let Ok(mut signalled) = SIGNALLED.try_lock() else {
        warn!("Failed to lock SIGNALLED");
        return;
    };

    for (signal, action) in signalled.drain(..) {
        action();
        signal.send(()).unwrap();
    }
}