
The goal of `bevy_platform_support` is to provide a set of platform agnostic APIs, alongside platform-specific functionality. This is a high traffic crate (providing things like HashMap and Instant). Especially in light of https://github.com/bevyengine/bevy/discussions/18799, it deserves a friendlier / shorter name. Given that it hasn't had a full release yet, getting this change in before Bevy 0.16 makes sense. - Rename `bevy_platform_support` to `bevy_platform`.
176 lines
6.5 KiB
Rust
176 lines
6.5 KiB
Rust
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<Duration>,
|
|
},
|
|
/// 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<Duration>|
|
|
-> Result<Option<Duration>, 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<dyn FnMut()>, 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<dyn FnMut()>));
|
|
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,
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|