bevy/crates/bevy_app/src/schedule_runner.rs
Zachary Harrold f45e78e658
Add no_std support to bevy_app (#16874)
# Objective

- Contributes to #15460

## Solution

- Added the following features:
  - `std` (default)
  - `bevy_tasks` (default)
  - `downcast ` (default)
  - `portable-atomic`
  - `critical-section`
- `downcast` and `bevy_tasks` are now optional dependencies for
`bevy_app`.

## Testing

- CI
- Personal UEFI and Raspberry Pi Pico demo applications compile and run
against this branch

## Draft Release Notes

Bevy's application framework now supports `no_std` platforms.

Following up on `bevy_ecs` gaining `no_std` support, `bevy_app` extends
the functionality available on these targets to include the powerful
`App` and `Plugin` abstractions. With this, library authors now have the
option of making their plugins `no_std` compatible, or even offering
plugins specifically to improve Bevy on certain embedded platforms!

To start making a `no_std` compatible plugin, simply disable default
features when including `bevy_app`:

```toml
[dependencies]
bevy_app = { version = "0.16", default-features = false }
```

We encourage library authors to do this anyway, as it can also help with
compile times and binary size on all platforms.

Keep an eye out for future `no_std` updates as we continue to improve
the parity between `std` and `no_std`. We look forward to seeing what
kinds of applications are now possible with Bevy!

## Notes

- `downcast-rs` is optional as it isn't compatible with
`portable-atomic`. I will investigate making a PR upstream to add
support for this functionality, as it should be very straightforward.
- In line with the `bevy_ecs` no-std-ification, I've added documentation
to all features, and grouped them as well.
- ~~Creating this PR in draft while CI runs and so I can polish before
review.~~

---------

Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
2024-12-18 22:04:45 +00:00

184 lines
6.7 KiB
Rust

use crate::{
app::{App, AppExit},
plugin::Plugin,
PluginsState,
};
use bevy_utils::Duration;
#[cfg(any(target_arch = "wasm32", feature = "std"))]
use bevy_utils::Instant;
#[cfg(target_arch = "wasm32")]
use {
alloc::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(all(not(target_arch = "wasm32"), feature = "bevy_tasks"))]
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> {
#[cfg(any(target_arch = "wasm32", feature = "std"))]
let start_time = Instant::now();
app.update();
if let Some(exit) = app.should_exit() {
return Err(exit);
};
#[cfg(any(target_arch = "wasm32", feature = "std"))]
let end_time = Instant::now();
#[cfg(any(target_arch = "wasm32", feature = "std"))]
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(not(target_arch = "wasm32"))]
{
loop {
match tick(&mut app, wait) {
Ok(Some(_delay)) => {
#[cfg(feature = "std")]
std::thread::sleep(_delay);
}
Ok(None) => continue,
Err(exit) => return exit,
}
}
}
#[cfg(target_arch = "wasm32")]
{
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()
}
}
}
});
}
}