use std::{
future::Future,
marker::Send,
pin::Pin,
task::{Context, Poll},
};
#[cfg(feature = "asset")]
use async_asset::{notify_asset_events, AssetSubscriptions};
use bevy_app::{App, Plugin, Update};
use bevy_ecs::{resource::Resource, system::Commands, world::World};
use bevy_tasks::AsyncComputeTaskPool;
use futures::FutureExt;
#[cfg(feature = "time")]
use time::time_plugin;
use tokio::sync::{mpsc, oneshot};
#[cfg(feature = "asset")]
pub mod async_asset;
pub mod async_entity;
pub mod common_uses;
pub mod message_stream;
#[cfg(feature = "time")]
pub mod time;
pub mod prelude {
#[cfg(feature = "time")]
pub use crate::time::TimingTaskExt;
pub use crate::{
async_entity::AsyncEntityTaskExt, common_uses::CommonUsesTaskExt,
message_stream::MessageStreamTaskExt, AsyncTasksPlugin, SpawnCommandExt, SpawnTaskExt,
TaskContext,
};
}
pub struct AsyncTasksPlugin;
impl Plugin for AsyncTasksPlugin {
fn build(&self, app: &mut App) {
app.init_resource::<AsyncWork>();
app.add_systems(Update, run_async_jobs);
#[cfg(feature = "asset")]
{
app.init_resource::<AssetSubscriptions>();
app.add_systems(Update, notify_asset_events);
}
#[cfg(feature = "time")]
app.add_plugins(time_plugin);
}
}
#[derive(Resource)]
pub struct AsyncWork {
work_tx: mpsc::UnboundedSender<Job>,
work_rx: mpsc::UnboundedReceiver<Job>,
}
impl AsyncWork {
pub fn create_task_context(&self) -> TaskContext {
TaskContext {
work_queue: self.work_tx.clone(),
}
}
}
impl Default for AsyncWork {
fn default() -> Self {
let (work_tx, work_rx) = mpsc::unbounded_channel();
Self { work_tx, work_rx }
}
}
pub fn run_async_jobs(world: &mut World) {
let mut jobs = Vec::new();
let mut work = world.resource_mut::<AsyncWork>();
while let Ok(next) = work.work_rx.try_recv() {
jobs.push(next);
}
for job in jobs {
job(world);
}
}
pub trait SpawnTaskExt {
fn spawn_task<T, F>(&self, task: T)
where
T: FnOnce(TaskContext) -> F + Send + 'static,
F: Future<Output = ()> + Send + 'static;
}
impl SpawnTaskExt for World {
fn spawn_task<T, F>(&self, task: T)
where
T: FnOnce(TaskContext) -> F + Send + 'static,
F: Future<Output = ()> + Send + 'static,
{
let context = self.resource::<AsyncWork>().create_task_context();
AsyncComputeTaskPool::get().spawn(task(context)).detach();
}
}
pub trait SpawnCommandExt {
fn spawn_task<T, F>(&mut self, task: T)
where
T: FnOnce(TaskContext) -> F + Send + 'static,
F: Future<Output = ()> + Send + 'static;
}
impl SpawnCommandExt for Commands<'_, '_> {
fn spawn_task<T, F>(&mut self, task: T)
where
T: FnOnce(TaskContext) -> F + Send + 'static,
F: Future<Output = ()> + Send + 'static,
{
self.queue(move |world: &mut World| {
world.spawn_task(task);
});
}
}
#[derive(Clone)]
pub struct TaskContext {
work_queue: mpsc::UnboundedSender<Job>,
}
impl TaskContext {
#[must_use = "Ignoring `with_world` return value. Either `.await` this value or `.detach()` it to run it in parallel"]
pub fn with_world<R, F>(&self, f: F) -> WithWorld<R>
where
R: Send + 'static,
F: FnOnce(&mut World) -> R + Send + 'static,
{
WithWorld::new(f, &self.work_queue)
}
}
pub struct WithWorld<R>(oneshot::Receiver<R>);
impl<R: Send + 'static> WithWorld<R> {
fn new<F>(f: F, work_queue: &mpsc::UnboundedSender<Job>) -> Self
where
F: FnOnce(&mut World) -> R + Send + 'static,
{
let (tx, rx) = oneshot::channel();
work_queue
.send(Box::new(move |world| {
tx.send(f(world)).ok();
}))
.expect(
"Failed to send task to `run_async_jobs`. Did you remove `AsyncWork` resource?",
);
Self(rx)
}
pub fn detach(self) {}
}
impl<R> Future for WithWorld<R> {
type Output = R;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
self.0.poll_unpin(cx).map(Result::unwrap)
}
}
type Job = Box<dyn FnOnce(&mut World) + Send>;