use crate::core::async_context::JobHandle;
use crate::core::entity::{EntityContext, EntityMap, Handle};
use crate::core::platform::lower_current_thread_priority;
use std::any::Any;
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use std::sync::mpsc::{self, Receiver, Sender};
use std::sync::{Arc, Mutex, OnceLock};
use std::thread;
pub trait Waker: Send + Sync {
fn wake(&self);
}
struct NoopWaker;
impl Waker for NoopWaker {
fn wake(&self) {}
}
pub type ForegroundJob = Box<dyn FnOnce(&mut EntityMap) + Send>;
#[derive(Clone)]
pub struct ForegroundSender {
sender: Sender<ForegroundJob>,
wake: Arc<InboxWake>,
}
struct InboxWake {
pending: AtomicBool,
waker: Mutex<Arc<dyn Waker>>,
}
impl InboxWake {
fn wake(&self) {
let waker = self.waker.lock().unwrap().clone();
waker.wake();
}
}
impl ForegroundSender {
pub fn send(&self, job: impl FnOnce(&mut EntityMap) + Send + 'static) -> bool {
let delivered = self.sender.send(Box::new(job)).is_ok();
if delivered && !self.wake.pending.swap(true, Ordering::AcqRel) {
self.wake.wake();
}
delivered
}
pub fn update<T: Any>(
&self,
handle: Handle<T>,
mutate: impl FnOnce(&mut T, &mut EntityContext<T>) + Send + 'static,
) -> bool {
self.send(move |entities| {
entities.update(handle, mutate);
})
}
}
pub struct Inbox {
receiver: Receiver<ForegroundJob>,
sender: ForegroundSender,
}
impl Inbox {
pub fn new(waker: Arc<dyn Waker>) -> Self {
let (sender, receiver) = mpsc::channel();
let wake = Arc::new(InboxWake { pending: AtomicBool::new(false), waker: Mutex::new(waker) });
Self { receiver, sender: ForegroundSender { sender, wake } }
}
pub fn sender(&self) -> ForegroundSender {
self.sender.clone()
}
pub fn set_waker(&mut self, waker: Arc<dyn Waker>) {
*self.sender.wake.waker.lock().unwrap() = waker;
if self.sender.wake.pending.load(Ordering::Acquire) {
self.sender.wake.wake();
}
}
pub fn try_next(&self) -> Option<ForegroundJob> {
if let Ok(job) = self.receiver.try_recv() {
return Some(job);
}
self.sender.wake.pending.store(false, Ordering::Release);
self.receiver.try_recv().ok()
}
}
impl Default for Inbox {
fn default() -> Self {
Self::new(Arc::new(NoopWaker))
}
}
#[derive(Clone, Default)]
pub struct CancelToken(Arc<CancelState>);
#[derive(Default)]
struct CancelState {
cancelled: AtomicBool,
waker: Mutex<Option<std::task::Waker>>,
}
impl CancelToken {
pub fn cancel(&self) {
self.0.cancelled.store(true, Ordering::Release);
let waker = self.0.waker.lock().unwrap().take();
if let Some(waker) = waker {
waker.wake();
}
}
pub fn is_cancelled(&self) -> bool {
self.0.cancelled.load(Ordering::Acquire)
}
pub(crate) fn poll_cancelled(&self, waker: &std::task::Waker) -> bool {
let mut slot = self.0.waker.lock().unwrap();
if self.is_cancelled() {
return true;
}
if slot.as_ref().is_none_or(|old| !old.will_wake(waker)) {
*slot = Some(waker.clone());
}
false
}
pub(crate) fn clear_waker(&self) {
self.0.waker.lock().unwrap().take();
}
}
#[derive(Default)]
pub struct Latest {
generation: Arc<AtomicU64>,
current: Option<JobHandle>,
}
impl Latest {
pub fn run<T: Any, R: Send + 'static>(
&mut self,
cx: &mut EntityContext<T>,
work: impl FnOnce(&CancelToken) -> R + Send + 'static,
done: impl FnOnce(&mut T, R, &mut EntityContext<T>) + Send + 'static,
) {
self.cancel();
let ticket = self.generation.load(Ordering::Acquire);
let generation = self.generation.clone();
self.current = Some(cx.job(work, move |this, result, cx| {
if generation.load(Ordering::Acquire) == ticket {
done(this, result, cx);
}
}));
}
pub fn cancel(&mut self) {
self.generation.fetch_add(1, Ordering::AcqRel);
if let Some(job) = self.current.take() {
job.cancel();
}
}
}
type Job = Box<dyn FnOnce() + Send>;
struct LazyPool {
workers: usize,
sender: OnceLock<Sender<Job>>,
}
#[derive(Clone)]
pub struct BackgroundExecutor {
pool: Arc<LazyPool>,
}
impl BackgroundExecutor {
pub fn recommended_workers() -> usize {
let cores = thread::available_parallelism().map(|n| n.get()).unwrap_or(2);
cores.saturating_sub(1).clamp(1, 4)
}
pub fn new(workers: usize) -> Self {
Self { pool: Arc::new(LazyPool { workers: workers.max(1), sender: OnceLock::new() }) }
}
fn sender(&self) -> &Sender<Job> {
self.pool.sender.get_or_init(|| {
let (sender, receiver) = mpsc::channel::<Job>();
let receiver = Arc::new(Mutex::new(receiver));
for index in 0..self.pool.workers {
let receiver = receiver.clone();
let spawned = thread::Builder::new().name(format!("aura-worker-{index}")).spawn(move || {
lower_current_thread_priority();
loop {
let job = match receiver.lock() {
Ok(guard) => guard.recv(),
Err(_) => return,
};
match job {
Ok(job) => job(),
Err(_) => return,
}
}
});
if let Err(error) = spawned {
eprintln!("no se pudo crear un hilo de trabajo: {error}");
}
}
sender
})
}
pub fn is_started(&self) -> bool {
self.pool.sender.get().is_some()
}
pub fn spawn(&self, job: impl FnOnce() + Send + 'static) {
if self.sender().send(Box::new(job)).is_err() {
eprintln!("el pool de trabajo ya terminó");
}
}
pub fn spawn_dedicated(name: &str, job: impl FnOnce() + Send + 'static) {
let spawned = thread::Builder::new().name(name.to_owned()).spawn(move || {
lower_current_thread_priority();
job();
});
if let Err(error) = spawned {
eprintln!("no se pudo crear el hilo {name}: {error}");
}
}
}
impl Default for BackgroundExecutor {
fn default() -> Self {
Self::new(Self::recommended_workers())
}
}