use std::{future::Future, marker::PhantomData};
use bevy_app::{App, Plugin, Update};
use bevy_ecs::{
event::{Event, EventWriter},
schedule::{IntoSystemConfigs, SystemSet},
system::{Commands, ResMut, Resource},
world::{Command, Mut, World},
};
use bevy_tasks::AsyncComputeTaskPool;
use pollable::{PollableTask, SpawnPollableExt};
mod pollable;
pub trait ComputeInBackgroundCommandExt {
fn compute_in_background<F, T>(&mut self, future: F)
where
F: Future<Output = T> + Send + 'static,
T: Send + Sync + 'static;
}
pub struct BackgroundComputePlugin<T>(PhantomData<fn() -> T>);
#[derive(SystemSet)]
pub struct BackgroundComputeCheck<T>(PhantomData<T>);
struct ComputeInBackground<F, T>(F)
where
F: Future<Output = T> + Send + 'static,
T: Send + Sync + 'static;
#[derive(Resource)]
struct BackgroundTasks<T> {
tasks: Vec<PollableTask<T>>,
}
#[derive(Event)]
pub struct BackgroundComputeComplete<T>(pub T)
where
T: Send + Sync + 'static;
impl<'w, 's> ComputeInBackgroundCommandExt for Commands<'w, 's> {
fn compute_in_background<F, T>(&mut self, future: F)
where
F: Future<Output = T> + Send + 'static,
T: Send + Sync + 'static,
{
self.add(ComputeInBackground(future))
}
}
impl<T> Default for BackgroundComputePlugin<T> {
fn default() -> Self {
Self(PhantomData)
}
}
impl<T> Default for BackgroundComputeCheck<T> {
fn default() -> Self {
Self(PhantomData)
}
}
mod impls {
use super::BackgroundComputeCheck;
use std::fmt::Debug;
use std::hash::Hash;
impl<T> Clone for BackgroundComputeCheck<T> {
fn clone(&self) -> Self {
Self(self.0)
}
}
impl<T> Debug for BackgroundComputeCheck<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "BackgroundComputeCheck<{:?}>", self.0)
}
}
impl<T> PartialEq for BackgroundComputeCheck<T> {
fn eq(&self, other: &Self) -> bool {
self.0 == other.0
}
}
impl<T> Eq for BackgroundComputeCheck<T> {}
impl<T> Hash for BackgroundComputeCheck<T> {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.0.hash(state);
}
}
}
impl<T> Plugin for BackgroundComputePlugin<T>
where
T: Send + Sync + 'static,
{
fn build(&self, app: &mut App) {
app.add_event::<BackgroundComputeComplete<T>>()
.insert_resource(BackgroundTasks::<T> { tasks: vec![] })
.add_systems(
Update,
background_compute_check_system::<T>.in_set(BackgroundComputeCheck::<T>::default()),
);
}
}
impl<F, T> Command for ComputeInBackground<F, T>
where
F: Future<Output = T> + Send + 'static,
T: Send + Sync + 'static,
{
fn apply(self, world: &mut World) {
world.resource_scope(|_, mut holder: Mut<BackgroundTasks<T>>| {
let func = self.0;
holder
.tasks
.push(AsyncComputeTaskPool::get().spawn_pollable(func));
});
}
}
fn background_compute_check_system<T>(
mut holder: ResMut<BackgroundTasks<T>>,
mut event_writer: EventWriter<BackgroundComputeComplete<T>>,
) where
T: Send + Sync + 'static,
{
holder.tasks.retain(|pollable| {
if let Some(value) = pollable.poll() {
event_writer.send(BackgroundComputeComplete(value));
false
} else {
true
}
})
}