mirror of
https://github.com/bevyengine/bevy
synced 2024-12-25 12:33:07 +00:00
992681b59b
*This PR description is an edited copy of #5007, written by @alice-i-cecile.* # Objective Follow-up to https://github.com/bevyengine/bevy/pull/2254. The `Resource` trait currently has a blanket implementation for all types that meet its bounds. While ergonomic, this results in several drawbacks: * it is possible to make confusing, silent mistakes such as inserting a function pointer (Foo) rather than a value (Foo::Bar) as a resource * it is challenging to discover if a type is intended to be used as a resource * we cannot later add customization options (see the [RFC](https://github.com/bevyengine/rfcs/blob/main/rfcs/27-derive-component.md) for the equivalent choice for Component). * dependencies can use the same Rust type as a resource in invisibly conflicting ways * raw Rust types used as resources cannot preserve privacy appropriately, as anyone able to access that type can read and write to internal values * we cannot capture a definitive list of possible resources to display to users in an editor ## Notes to reviewers * Review this commit-by-commit; there's effectively no back-tracking and there's a lot of churn in some of these commits. *ira: My commits are not as well organized :')* * I've relaxed the bound on Local to Send + Sync + 'static: I don't think these concerns apply there, so this can keep things simple. Storing e.g. a u32 in a Local is fine, because there's a variable name attached explaining what it does. * I think this is a bad place for the Resource trait to live, but I've left it in place to make reviewing easier. IMO that's best tackled with https://github.com/bevyengine/bevy/issues/4981. ## Changelog `Resource` is no longer automatically implemented for all matching types. Instead, use the new `#[derive(Resource)]` macro. ## Migration Guide Add `#[derive(Resource)]` to all types you are using as a resource. If you are using a third party type as a resource, wrap it in a tuple struct to bypass orphan rules. Consider deriving `Deref` and `DerefMut` to improve ergonomics. `ClearColor` no longer implements `Component`. Using `ClearColor` as a component in 0.8 did nothing. Use the `ClearColorConfig` in the `Camera3d` and `Camera2d` components instead. Co-authored-by: Alice <alice.i.cecile@gmail.com> Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com> Co-authored-by: devil-ira <justthecooldude@gmail.com> Co-authored-by: Carter Anderson <mcanders1@gmail.com>
160 lines
5.7 KiB
Rust
160 lines
5.7 KiB
Rust
use crate::{
|
|
app::{App, AppExit},
|
|
plugin::Plugin,
|
|
};
|
|
use bevy_ecs::event::{Events, ManualEventReader};
|
|
use bevy_ecs::prelude::Resource;
|
|
use bevy_utils::{Duration, Instant};
|
|
|
|
#[cfg(target_arch = "wasm32")]
|
|
use std::{cell::RefCell, rc::Rc};
|
|
#[cfg(target_arch = "wasm32")]
|
|
use wasm_bindgen::{prelude::*, JsCast};
|
|
|
|
/// Determines the method used to run an [`App`]'s [`Schedule`](bevy_ecs::schedule::Schedule).
|
|
///
|
|
/// It is used in the [`ScheduleRunnerSettings`].
|
|
#[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 }
|
|
}
|
|
}
|
|
|
|
/// The configuration information for the [`ScheduleRunnerPlugin`].
|
|
///
|
|
/// It gets added as a [`Resource`](bevy_ecs::system::Resource) inside of the [`ScheduleRunnerPlugin`].
|
|
#[derive(Copy, Clone, Default, Resource)]
|
|
pub struct ScheduleRunnerSettings {
|
|
/// Determines whether the [`Schedule`](bevy_ecs::schedule::Schedule) is run once or repeatedly.
|
|
pub run_mode: RunMode,
|
|
}
|
|
|
|
impl ScheduleRunnerSettings {
|
|
/// See [`RunMode::Once`].
|
|
pub fn run_once() -> Self {
|
|
ScheduleRunnerSettings {
|
|
run_mode: RunMode::Once,
|
|
}
|
|
}
|
|
|
|
/// See [`RunMode::Loop`].
|
|
pub fn run_loop(wait_duration: Duration) -> Self {
|
|
ScheduleRunnerSettings {
|
|
run_mode: RunMode::Loop {
|
|
wait: Some(wait_duration),
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Configures an [`App`] to run its [`Schedule`](bevy_ecs::schedule::Schedule) according to a given
|
|
/// [`RunMode`].
|
|
#[derive(Default)]
|
|
pub struct ScheduleRunnerPlugin;
|
|
|
|
impl Plugin for ScheduleRunnerPlugin {
|
|
fn build(&self, app: &mut App) {
|
|
let settings = app
|
|
.world
|
|
.get_resource_or_insert_with(ScheduleRunnerSettings::default)
|
|
.to_owned();
|
|
app.set_runner(move |mut app: App| {
|
|
let mut app_exit_event_reader = ManualEventReader::<AppExit>::default();
|
|
match settings.run_mode {
|
|
RunMode::Once => {
|
|
app.update();
|
|
}
|
|
RunMode::Loop { wait } => {
|
|
let mut tick = move |app: &mut App,
|
|
wait: Option<Duration>|
|
|
-> Result<Option<Duration>, AppExit> {
|
|
let start_time = Instant::now();
|
|
|
|
if let Some(app_exit_events) =
|
|
app.world.get_resource_mut::<Events<AppExit>>()
|
|
{
|
|
if let Some(exit) = app_exit_event_reader.iter(&app_exit_events).last()
|
|
{
|
|
return Err(exit.clone());
|
|
}
|
|
}
|
|
|
|
app.update();
|
|
|
|
if let Some(app_exit_events) =
|
|
app.world.get_resource_mut::<Events<AppExit>>()
|
|
{
|
|
if let Some(exit) = app_exit_event_reader.iter(&app_exit_events).last()
|
|
{
|
|
return Err(exit.clone());
|
|
}
|
|
}
|
|
|
|
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(not(target_arch = "wasm32"))]
|
|
{
|
|
while let Ok(delay) = tick(&mut app, wait) {
|
|
if let Some(delay) = delay {
|
|
std::thread::sleep(delay);
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(target_arch = "wasm32")]
|
|
{
|
|
fn set_timeout(f: &Closure<dyn FnMut()>, dur: Duration) {
|
|
web_sys::window()
|
|
.unwrap()
|
|
.set_timeout_with_callback_and_timeout_and_arguments_0(
|
|
f.as_ref().unchecked_ref(),
|
|
dur.as_millis() as i32,
|
|
)
|
|
.expect("Should register `setTimeout`.");
|
|
}
|
|
let asap = Duration::from_millis(1);
|
|
|
|
let mut rc = Rc::new(app);
|
|
let f = Rc::new(RefCell::new(None));
|
|
let g = f.clone();
|
|
|
|
let c = move || {
|
|
let mut app = Rc::get_mut(&mut rc).unwrap();
|
|
let delay = tick(&mut app, wait);
|
|
match delay {
|
|
Ok(delay) => {
|
|
set_timeout(f.borrow().as_ref().unwrap(), delay.unwrap_or(asap))
|
|
}
|
|
Err(_) => {}
|
|
}
|
|
};
|
|
*g.borrow_mut() = Some(Closure::wrap(Box::new(c) as Box<dyn FnMut()>));
|
|
set_timeout(g.borrow().as_ref().unwrap(), asap);
|
|
};
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|