use std::time::{Duration, Instant, SystemTime};
use bevy::time::{DelayedCommandQueue, Time, Virtual};
use bevy_app::PluginsState;
use bevy_app::prelude::*;
use bevy_ecs::prelude::*;
use bevy_ecs::schedule::SingleThreadedExecutor;
use rig_ecs::tasks::Wake;
use crate::clock::{Due, KeepAwake, WakeAt};
const FRAME: Duration = Duration::from_millis(16);
const IDLE: Duration = Duration::from_secs(1);
const MAX_FRAME_GAP: Duration = Duration::from_secs(60);
pub(crate) fn finish_plugins(app: &mut App) {
if app.plugins_state() != PluginsState::Cleaned {
while app.plugins_state() == PluginsState::Adding {
bevy_tasks::tick_global_task_pools_on_main_thread();
}
app.finish();
app.cleanup();
}
}
pub struct RunnerPlugin;
impl Plugin for RunnerPlugin {
fn build(&self, app: &mut App) {
let (sender, wakes) = crossbeam_channel::bounded(1);
app.insert_resource(Wake::new(move || {
sender.try_send(()).ok();
}));
app.set_runner(move |mut app: App| {
finish_plugins(&mut app);
let mut due = NextFrame::new(app.world_mut());
loop {
let started = Instant::now();
app.update();
if let Some(exit) = app.should_exit() {
return exit;
}
let sleep = due.after(app.world()).saturating_sub(started.elapsed());
wakes.recv_timeout(sleep).ok();
if let Some(rest) = FRAME.checked_sub(started.elapsed()) {
std::thread::sleep(rest);
}
}
});
}
fn finish(&self, app: &mut App) {
if let Some(mut time) = app.world_mut().get_resource_mut::<Time<Virtual>>() {
time.set_max_delta(MAX_FRAME_GAP);
}
}
fn cleanup(&self, app: &mut App) {
if let Some(mut schedules) = app.world_mut().get_resource_mut::<Schedules>() {
for (_, schedule) in schedules.iter_mut() {
schedule.set_executor(SingleThreadedExecutor::new());
}
}
}
}
struct NextFrame {
delayed: QueryState<&'static DelayedCommandQueue>,
awake: QueryState<&'static KeepAwake>,
alarms: QueryState<&'static WakeAt, Without<Due>>,
}
impl NextFrame {
fn new(world: &mut World) -> Self {
Self {
delayed: world.query(),
awake: world.query(),
alarms: world.query_filtered(),
}
}
fn after(&mut self, world: &World) -> Duration {
let delayed = world.get_resource::<Time>().and_then(|time| {
self.delayed
.iter(world)
.map(|queue| queue.submit_at.saturating_sub(time.elapsed()))
.min()
});
let now = SystemTime::now();
let alarms = self.alarms.iter(world).map(|alarm| alarm.left(now));
self.awake
.iter(world)
.map(|awake| awake.0)
.chain(delayed)
.chain(alarms)
.fold(IDLE, Duration::min)
}
}
#[cfg(test)]
mod tests;