#![feature(coroutines, coroutine_trait)]
use bevy::prelude::*;
use bevy::ecs::system::SystemId;
use std::any::Any;
use std::collections::HashMap;
use std::ops::Coroutine;
use std::pin::Pin;
use std::ptr::NonNull;
use std::future::Future;
pub use bevy_coroutine_system_macro::*;
pub struct CoroutinePlugin;
impl Plugin for CoroutinePlugin {
fn build(&self, app: &mut App) {
app.init_resource::<RunningCoroutines>()
.add_systems(Update, update_running_tasks);
}
}
pub trait CoroutineSystem {
fn register_coroutine<M>(&mut self, system: impl IntoSystem<(), (), M> + 'static, system_id: &'static str) -> SystemId;
}
impl CoroutineSystem for App {
fn register_coroutine<M>(&mut self, system: impl IntoSystem<(), (), M> + 'static, system_id: &'static str) -> SystemId {
let id = self.world_mut().register_system_cached(system);
self.world_mut().resource_mut::<RunningCoroutines>().register_systems.insert(system_id, id);
id
}
}
pub struct CoroutineTask<R> {
pub coroutine: Option<
Pin<
Box<
dyn Coroutine<
R,
Yield = Pin<Box<dyn Future<Output = Box<dyn Any + Send>> + Send>>,
Return = (),
> + Send,
>,
>,
>,
pub fut: Option<Pin<Box<dyn Future<Output = Box<dyn Any + Send>> + Send>>>,
}
impl<R> Default for CoroutineTask<R> {
fn default() -> Self {
Self {
coroutine: None,
fut: None,
}
}
}
pub struct CoroutineTaskInput<T> {
pub data_ptr: Option<NonNull<T>>,
pub async_result: Option<Box<dyn Any + Send>>,
}
impl<T> std::fmt::Debug for CoroutineTaskInput<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("CoroutineTaskInput")
.field("data_ptr", &self.data_ptr.is_some())
.field("async_result", &self.async_result.is_some())
.finish()
}
}
unsafe impl<T: Send> Send for CoroutineTaskInput<T> {}
impl<T> CoroutineTaskInput<T> {
pub fn data_mut(&mut self) -> &mut T {
self.data_ptr
.map(|mut ptr| unsafe { ptr.as_mut() })
.expect("TaskInput data_ptr is None")
}
pub fn result<R: 'static>(&mut self) -> R {
self.async_result
.take()
.and_then(|v| v.downcast::<R>().ok().map(|b| *b))
.expect("Failed to downcast async result")
}
}
#[derive(Resource, Default)]
pub struct RunningCoroutines {
pub systems: HashMap<&'static str, ()>,
pub register_systems: HashMap<&'static str, SystemId>,
}
fn update_running_tasks(mut commands: Commands, running_task: Res<RunningCoroutines>) {
if running_task.systems.is_empty() {
return;
}
for (system_name, system_id) in running_task.register_systems.iter() {
if running_task.systems.contains_key(system_name) {
commands.run_system(*system_id);
}
}
}
pub fn sleep(duration: std::time::Duration) -> Pin<Box<dyn Future<Output = Box<dyn Any + Send>> + Send>> {
use std::time::Instant;
struct SleepFuture {
target_time: Instant,
}
impl Future for SleepFuture {
type Output = Box<dyn Any + Send>;
fn poll(self: Pin<&mut Self>, _cx: &mut std::task::Context<'_>) -> std::task::Poll<Self::Output> {
if Instant::now() >= self.target_time {
std::task::Poll::Ready(Box::new(Instant::now()) as Box<dyn Any + Send>)
} else {
std::task::Poll::Pending
}
}
}
Box::pin(SleepFuture {
target_time: Instant::now() + duration,
})
}
pub fn next_frame() -> Pin<Box<dyn Future<Output = Box<dyn Any + Send>> + Send>> {
struct NextFrameFuture {
first_poll: bool,
}
impl Future for NextFrameFuture {
type Output = Box<dyn Any + Send>;
fn poll(mut self: Pin<&mut Self>, _cx: &mut std::task::Context<'_>) -> std::task::Poll<Self::Output> {
if self.first_poll {
self.first_poll = false;
std::task::Poll::Pending
} else {
std::task::Poll::Ready(Box::new(()) as Box<dyn Any + Send>)
}
}
}
Box::pin(NextFrameFuture {
first_poll: true,
})
}
pub fn noop() -> Pin<Box<dyn Future<Output = Box<dyn Any + Send>> + Send>> {
struct NoopFuture;
impl Future for NoopFuture {
type Output = Box<dyn Any + Send>;
fn poll(self: Pin<&mut Self>, _cx: &mut std::task::Context<'_>) -> std::task::Poll<Self::Output> {
std::task::Poll::Ready(Box::new(()) as Box<dyn Any + Send>)
}
}
Box::pin(NoopFuture)
}
struct ThreadFuture<T> {
handle: Option<std::thread::JoinHandle<T>>,
}
impl<T: Send + 'static> Future for ThreadFuture<T> {
type Output = T;
fn poll(self: Pin<&mut Self>, _cx: &mut std::task::Context<'_>) -> std::task::Poll<Self::Output> {
let this = self.get_mut();
if let Some(handle) = &this.handle {
if handle.is_finished() {
let handle = this.handle.take().unwrap();
match handle.join() {
Ok(result) => std::task::Poll::Ready(result),
Err(_) => panic!("Thread panicked"),
}
} else {
std::task::Poll::Pending
}
} else {
panic!("ThreadFuture polled after completion");
}
}
}
struct AnyFuture<T> {
inner: ThreadFuture<T>,
}
impl<T: Send + Any + 'static> Future for AnyFuture<T> {
type Output = Box<dyn Any + Send>;
fn poll(mut self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> std::task::Poll<Self::Output> {
match Pin::new(&mut self.inner).poll(cx) {
std::task::Poll::Ready(value) => std::task::Poll::Ready(Box::new(value) as Box<dyn Any + Send>),
std::task::Poll::Pending => std::task::Poll::Pending,
}
}
}
pub fn spawn_blocking_task<F, T>(task: F) -> Pin<Box<dyn Future<Output = Box<dyn Any + Send>> + Send>>
where
F: FnOnce() -> T + Send + 'static,
T: Send + Any + 'static,
{
let handle = std::thread::spawn(task);
Box::pin(AnyFuture {
inner: ThreadFuture {
handle: Some(handle),
}
})
}
#[macro_export]
#[deprecated(since = "0.2.0", note = "使用原生 yield 语法代替")]
macro_rules! yield_async {
($fut:expr) => {{
}};
}
pub mod prelude {
pub use crate::{
CoroutineSystem,
coroutine_system,
CoroutinePlugin,
sleep,
next_frame,
noop,
spawn_blocking_task,
CoroutineTask,
CoroutineTaskInput,
RunningCoroutines,
};
}