use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use bevy_ecs::prelude::*;
use bevy_ecs::query::QueryFilter;
use bevy_tasks::futures::check_ready;
use bevy_tasks::{ConditionalSend, ConditionalSendFuture, Task, TaskPool};
use crossbeam_channel::Receiver;
use futures::{Stream, StreamExt};
use super::stage::{self, Effect};
const SETTLE_FRAMES: u32 = 4;
#[derive(Resource, Clone)]
pub struct Wake {
wake: Arc<dyn Fn() + Send + Sync>,
woken: Arc<AtomicBool>,
}
impl Wake {
pub fn new(wake: impl Fn() + Send + Sync + 'static) -> Self {
Self {
wake: Arc::new(wake),
woken: Arc::new(AtomicBool::new(false)),
}
}
pub fn wake(&self) {
self.woken.store(true, Ordering::Release);
(self.wake)();
}
}
impl Default for Wake {
fn default() -> Self {
Self::new(|| {})
}
}
pub(crate) fn run_settle_frames(wake: Res<Wake>, mut left: Local<u32>) {
if wake.woken.swap(false, Ordering::AcqRel) {
*left = SETTLE_FRAMES;
}
if *left > 0 {
*left -= 1;
(wake.wake)();
}
}
type Finish = Box<dyn FnOnce(EntityWorldMut) + Send>;
#[derive(Component)]
pub struct Running(Work);
enum Work {
Task(Task<Finish>),
Stream {
items: Receiver<Finish>,
task: Task<()>,
},
}
impl Running {
pub fn spawn<T: Send + Sync + 'static>(
pool: &TaskPool,
wake: &Wake,
work: impl ConditionalSendFuture<Output = T> + 'static,
) -> Self {
Self::spawn_into::<Done<T>, T>(pool, wake, work)
}
pub fn spawn_into<C: Component + From<T>, T: Send + 'static>(
pool: &TaskPool,
wake: &Wake,
work: impl ConditionalSendFuture<Output = T> + 'static,
) -> Self {
Self::finishing(pool, wake, work, |mut entity, output| {
entity.insert(C::from(output));
})
}
pub fn answering<T: Into<stage::Outcome> + Send + 'static>(
pool: &TaskPool,
wake: &Wake,
work: impl ConditionalSendFuture<Output = T> + 'static,
) -> Self {
Self::finishing(pool, wake, work, |mut entity, output| {
let outcome: stage::Outcome = output.into();
entity.insert_if_new(outcome);
})
}
pub fn stream<T: Send + Sync + 'static>(
pool: &TaskPool,
wake: &Wake,
items: impl Stream<Item = T> + ConditionalSend + 'static,
) -> Self {
let (sender, received) = crossbeam_channel::unbounded();
let wake = wake.clone();
let task = pool.spawn(async move {
let mut items = std::pin::pin!(items);
while let Some(item) = items.next().await {
let insert = move |mut entity: EntityWorldMut| {
entity.insert(Done(item));
};
if sender.send(Box::new(insert) as Finish).is_err() {
break;
}
wake.wake();
}
wake.wake();
});
Self(Work::Stream {
items: received,
task,
})
}
pub(crate) fn finishing<T: Send + 'static>(
pool: &TaskPool,
wake: &Wake,
work: impl ConditionalSendFuture<Output = T> + 'static,
finish: fn(EntityWorldMut, T),
) -> Self {
let wake = wake.clone();
Self(Work::Task(pool.spawn(async move {
let output = work.await;
wake.wake();
Box::new(move |entity: EntityWorldMut| finish(entity, output)) as Finish
})))
}
pub fn is_stream(&self) -> bool {
matches!(self.0, Work::Stream { .. })
}
pub(crate) async fn cancel(self) {
match self.0 {
Work::Task(task) => {
task.cancel().await;
}
Work::Stream { task, .. } => {
task.cancel().await;
}
}
}
}
#[derive(Component)]
pub struct Done<T: Send + Sync + 'static>(pub T);
impl<T: Send + Sync + 'static> From<T> for Done<T> {
fn from(output: T) -> Self {
Self(output)
}
}
#[derive(SystemSet, Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct PollTasks;
pub(crate) fn poll_tasks(
tasks: Query<(Entity, &mut Running), Without<Effect>>,
commands: Commands,
) {
finish(tasks, commands);
}
pub(crate) fn arrive(tasks: Query<(Entity, &mut Running), With<Effect>>, commands: Commands) {
finish(tasks, commands);
}
fn finish<F: QueryFilter>(mut tasks: Query<(Entity, &mut Running), F>, mut commands: Commands) {
for (entity, mut running) in &mut tasks {
match &mut running.bypass_change_detection().0 {
Work::Task(task) => {
if let Some(finish) = check_ready(task) {
commands.entity(entity).remove::<Running>().queue(finish);
}
}
Work::Stream { items, task } => {
let ended = check_ready(task).is_some();
let mut entity = commands.entity(entity);
for insert in items.try_iter() {
entity.queue(insert);
}
if ended {
entity.remove::<Running>();
}
}
}
}
}