use crate::{ app::{App, AppExit}, plugin::Plugin, PluginsState, }; use bevy_platform::time::Instant; use core::time::Duration; #[cfg(all(target_arch = "wasm32", feature = "web"))] use { alloc::{boxed::Box, rc::Rc}, core::cell::RefCell, wasm_bindgen::{prelude::*, JsCast}, }; /// Determines the method used to run an [`App`]'s [`Schedule`](bevy_ecs::schedule::Schedule). /// /// It is used in the [`ScheduleRunnerPlugin`]. #[derive(Copy, Clone, Debug)] pub enum RunMode { /// Indicates that the [`App`]'s schedule should run repeatedly. Loop { /// The minimum [`Duration`] to wait after a [`Schedule`](bevy_ecs::schedule::Schedule) /// has completed before repeating. A value of [`None`] will not wait. wait: Option, }, /// Indicates that the [`App`]'s schedule should run only once. Once, } impl Default for RunMode { fn default() -> Self { RunMode::Loop { wait: None } } } /// Configures an [`App`] to run its [`Schedule`](bevy_ecs::schedule::Schedule) according to a given /// [`RunMode`]. /// /// [`ScheduleRunnerPlugin`] is included in the /// [`MinimalPlugins`](https://docs.rs/bevy/latest/bevy/struct.MinimalPlugins.html) plugin group. /// /// [`ScheduleRunnerPlugin`] is *not* included in the /// [`DefaultPlugins`](https://docs.rs/bevy/latest/bevy/struct.DefaultPlugins.html) plugin group /// which assumes that the [`Schedule`](bevy_ecs::schedule::Schedule) will be executed by other means: /// typically, the `winit` event loop /// (see [`WinitPlugin`](https://docs.rs/bevy/latest/bevy/winit/struct.WinitPlugin.html)) /// executes the schedule making [`ScheduleRunnerPlugin`] unnecessary. #[derive(Default)] pub struct ScheduleRunnerPlugin { /// Determines whether the [`Schedule`](bevy_ecs::schedule::Schedule) is run once or repeatedly. pub run_mode: RunMode, } impl ScheduleRunnerPlugin { /// See [`RunMode::Once`]. pub fn run_once() -> Self { ScheduleRunnerPlugin { run_mode: RunMode::Once, } } /// See [`RunMode::Loop`]. pub fn run_loop(wait_duration: Duration) -> Self { ScheduleRunnerPlugin { run_mode: RunMode::Loop { wait: Some(wait_duration), }, } } } impl Plugin for ScheduleRunnerPlugin { fn build(&self, app: &mut App) { let run_mode = self.run_mode; app.set_runner(move |mut app: App| { let plugins_state = app.plugins_state(); if plugins_state != PluginsState::Cleaned { while app.plugins_state() == PluginsState::Adding { #[cfg(not(all(target_arch = "wasm32", feature = "web")))] bevy_tasks::tick_global_task_pools_on_main_thread(); } app.finish(); app.cleanup(); } match run_mode { RunMode::Once => { app.update(); if let Some(exit) = app.should_exit() { return exit; } AppExit::Success } RunMode::Loop { wait } => { let tick = move |app: &mut App, _wait: Option| -> Result, AppExit> { let start_time = Instant::now(); app.update(); if let Some(exit) = app.should_exit() { return Err(exit); }; let end_time = Instant::now(); if let Some(wait) = _wait { let exe_time = end_time - start_time; if exe_time < wait { return Ok(Some(wait - exe_time)); } } Ok(None) }; cfg_if::cfg_if! { if #[cfg(all(target_arch = "wasm32", feature = "web"))] { fn set_timeout(callback: &Closure, dur: Duration) { web_sys::window() .unwrap() .set_timeout_with_callback_and_timeout_and_arguments_0( callback.as_ref().unchecked_ref(), dur.as_millis() as i32, ) .expect("Should register `setTimeout`."); } let asap = Duration::from_millis(1); let exit = Rc::new(RefCell::new(AppExit::Success)); let closure_exit = exit.clone(); let mut app = Rc::new(app); let moved_tick_closure = Rc::new(RefCell::new(None)); let base_tick_closure = moved_tick_closure.clone(); let tick_app = move || { let app = Rc::get_mut(&mut app).unwrap(); let delay = tick(app, wait); match delay { Ok(delay) => set_timeout( moved_tick_closure.borrow().as_ref().unwrap(), delay.unwrap_or(asap), ), Err(code) => { closure_exit.replace(code); } } }; *base_tick_closure.borrow_mut() = Some(Closure::wrap(Box::new(tick_app) as Box)); set_timeout(base_tick_closure.borrow().as_ref().unwrap(), asap); exit.take() } else { loop { match tick(&mut app, wait) { Ok(Some(delay)) => { bevy_platform::thread::sleep(delay); } Ok(None) => continue, Err(exit) => return exit, } } } } } } }); } }