Make `Resource` trait opt-in, requiring `#[derive(Resource)]` V2 (#5577)
*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>
2022-08-08 21:36:35 +00:00
|
|
|
use bevy_ecs::{reflect::ReflectResource, system::Resource};
|
2022-08-17 00:21:15 +00:00
|
|
|
use bevy_reflect::{FromReflect, Reflect};
|
2020-11-22 00:38:24 +00:00
|
|
|
use bevy_utils::{Duration, Instant};
|
2019-12-03 08:30:30 +00:00
|
|
|
|
2022-10-24 14:14:25 +00:00
|
|
|
/// A clock that tracks how much it has advanced (and how much real time has elapsed) since
|
|
|
|
/// its previous update and since its creation.
|
2022-08-17 00:21:15 +00:00
|
|
|
#[derive(Resource, Reflect, FromReflect, Debug, Clone)]
|
remove blanket `Serialize + Deserialize` requirement for `Reflect` on generic types (#5197)
# Objective
Some generic types like `Option<T>`, `Vec<T>` and `HashMap<K, V>` implement `Reflect` when where their generic types `T`/`K`/`V` implement `Serialize + for<'de> Deserialize<'de>`.
This is so that in their `GetTypeRegistration` impl they can insert the `ReflectSerialize` and `ReflectDeserialize` type data structs.
This has the annoying side effect that if your struct contains a `Option<NonSerdeStruct>` you won't be able to derive reflect (https://github.com/bevyengine/bevy/issues/4054).
## Solution
- remove the `Serialize + Deserialize` bounds on wrapper types
- this means that `ReflectSerialize` and `ReflectDeserialize` will no longer be inserted even for `.register::<Option<DoesImplSerde>>()`
- add `register_type_data<T, D>` shorthand for `registry.get_mut(T).insert(D::from_type<T>())`
- require users to register their specific generic types **and the serde types** separately like
```rust
.register_type::<Option<String>>()
.register_type_data::<Option<String>, ReflectSerialize>()
.register_type_data::<Option<String>, ReflectDeserialize>()
```
I believe this is the best we can do for extensibility and convenience without specialization.
## Changelog
- `.register_type` for generic types like `Option<T>`, `Vec<T>`, `HashMap<K, V>` will no longer insert `ReflectSerialize` and `ReflectDeserialize` type data. Instead you need to register it separately for concrete generic types like so:
```rust
.register_type::<Option<String>>()
.register_type_data::<Option<String>, ReflectSerialize>()
.register_type_data::<Option<String>, ReflectDeserialize>()
```
TODO: more docs and tweaks to the scene example to demonstrate registering generic types.
2022-07-21 14:57:37 +00:00
|
|
|
#[reflect(Resource)]
|
2019-12-03 08:30:30 +00:00
|
|
|
pub struct Time {
|
2022-10-22 18:52:29 +00:00
|
|
|
startup: Instant,
|
|
|
|
first_update: Option<Instant>,
|
2020-11-28 21:08:31 +00:00
|
|
|
last_update: Option<Instant>,
|
2022-10-24 14:14:25 +00:00
|
|
|
// pausing
|
2022-10-22 18:52:29 +00:00
|
|
|
paused: bool,
|
2022-10-24 14:14:25 +00:00
|
|
|
// scaling
|
|
|
|
relative_speed: f64, // using `f64` instead of `f32` to minimize drift from rounding errors
|
2022-10-22 18:52:29 +00:00
|
|
|
delta: Duration,
|
2020-11-28 21:08:31 +00:00
|
|
|
delta_seconds: f32,
|
2022-10-22 18:52:29 +00:00
|
|
|
delta_seconds_f64: f64,
|
|
|
|
elapsed: Duration,
|
|
|
|
elapsed_seconds: f32,
|
|
|
|
elapsed_seconds_f64: f64,
|
|
|
|
raw_delta: Duration,
|
|
|
|
raw_delta_seconds: f32,
|
|
|
|
raw_delta_seconds_f64: f64,
|
|
|
|
raw_elapsed: Duration,
|
|
|
|
raw_elapsed_seconds: f32,
|
|
|
|
raw_elapsed_seconds_f64: f64,
|
|
|
|
// wrapping
|
|
|
|
wrap_period: Duration,
|
|
|
|
elapsed_wrapped: Duration,
|
|
|
|
elapsed_seconds_wrapped: f32,
|
|
|
|
elapsed_seconds_wrapped_f64: f64,
|
|
|
|
raw_elapsed_wrapped: Duration,
|
|
|
|
raw_elapsed_seconds_wrapped: f32,
|
|
|
|
raw_elapsed_seconds_wrapped_f64: f64,
|
2019-12-03 08:30:30 +00:00
|
|
|
}
|
|
|
|
|
2020-05-13 23:35:38 +00:00
|
|
|
impl Default for Time {
|
2022-10-22 18:52:29 +00:00
|
|
|
fn default() -> Self {
|
|
|
|
Self {
|
2020-05-31 04:32:47 +00:00
|
|
|
startup: Instant::now(),
|
2022-10-22 18:52:29 +00:00
|
|
|
first_update: None,
|
|
|
|
last_update: None,
|
|
|
|
paused: false,
|
2022-10-24 14:14:25 +00:00
|
|
|
relative_speed: 1.0,
|
2022-10-22 18:52:29 +00:00
|
|
|
delta: Duration::ZERO,
|
2019-12-03 08:30:30 +00:00
|
|
|
delta_seconds: 0.0,
|
2022-10-22 18:52:29 +00:00
|
|
|
delta_seconds_f64: 0.0,
|
|
|
|
elapsed: Duration::ZERO,
|
|
|
|
elapsed_seconds: 0.0,
|
|
|
|
elapsed_seconds_f64: 0.0,
|
|
|
|
raw_delta: Duration::ZERO,
|
|
|
|
raw_delta_seconds: 0.0,
|
|
|
|
raw_delta_seconds_f64: 0.0,
|
|
|
|
raw_elapsed: Duration::ZERO,
|
|
|
|
raw_elapsed_seconds: 0.0,
|
|
|
|
raw_elapsed_seconds_f64: 0.0,
|
|
|
|
wrap_period: Duration::from_secs(3600), // 1 hour
|
|
|
|
elapsed_wrapped: Duration::ZERO,
|
|
|
|
elapsed_seconds_wrapped: 0.0,
|
|
|
|
elapsed_seconds_wrapped_f64: 0.0,
|
|
|
|
raw_elapsed_wrapped: Duration::ZERO,
|
|
|
|
raw_elapsed_seconds_wrapped: 0.0,
|
|
|
|
raw_elapsed_seconds_wrapped_f64: 0.0,
|
2019-12-03 08:30:30 +00:00
|
|
|
}
|
|
|
|
}
|
2020-05-13 23:35:38 +00:00
|
|
|
}
|
2019-12-03 08:30:30 +00:00
|
|
|
|
2020-05-13 23:35:38 +00:00
|
|
|
impl Time {
|
2022-10-22 18:52:29 +00:00
|
|
|
/// Constructs a new `Time` instance with a specific startup `Instant`.
|
|
|
|
pub fn new(startup: Instant) -> Self {
|
|
|
|
Self {
|
|
|
|
startup,
|
|
|
|
..Default::default()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-02 20:36:40 +00:00
|
|
|
/// Updates the internal time measurements.
|
2022-04-24 23:15:27 +00:00
|
|
|
///
|
2022-10-22 18:52:29 +00:00
|
|
|
/// Calling this method as part of your app will most likely result in inaccurate timekeeping,
|
|
|
|
/// as the `Time` resource is ordinarily managed by the [`TimePlugin`](crate::TimePlugin).
|
2020-05-31 04:15:39 +00:00
|
|
|
pub fn update(&mut self) {
|
2022-10-22 18:52:29 +00:00
|
|
|
let now = Instant::now();
|
|
|
|
self.update_with_instant(now);
|
2020-11-28 21:08:31 +00:00
|
|
|
}
|
|
|
|
|
2022-10-22 18:52:29 +00:00
|
|
|
/// Updates time with a specified [`Instant`].
|
2022-04-24 23:15:27 +00:00
|
|
|
///
|
2022-10-22 18:52:29 +00:00
|
|
|
/// This method is provided for use in tests. Calling this method as part of your app will most
|
|
|
|
/// likely result in inaccurate timekeeping, as the `Time` resource is ordinarily managed by the
|
|
|
|
/// [`TimePlugin`](crate::TimePlugin).
|
2022-04-24 23:15:27 +00:00
|
|
|
///
|
|
|
|
/// # Examples
|
|
|
|
///
|
|
|
|
/// ```
|
2022-05-26 00:27:18 +00:00
|
|
|
/// # use bevy_time::prelude::*;
|
2022-04-24 23:15:27 +00:00
|
|
|
/// # use bevy_ecs::prelude::*;
|
|
|
|
/// # use bevy_utils::Duration;
|
|
|
|
/// # fn main () {
|
|
|
|
/// # test_health_system();
|
|
|
|
/// # }
|
Make `Resource` trait opt-in, requiring `#[derive(Resource)]` V2 (#5577)
*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>
2022-08-08 21:36:35 +00:00
|
|
|
/// #[derive(Resource)]
|
2022-04-24 23:15:27 +00:00
|
|
|
/// struct Health {
|
|
|
|
/// // Health value between 0.0 and 1.0
|
|
|
|
/// health_value: f32,
|
|
|
|
/// }
|
|
|
|
///
|
|
|
|
/// fn health_system(time: Res<Time>, mut health: ResMut<Health>) {
|
|
|
|
/// // Increase health value by 0.1 per second, independent of frame rate,
|
|
|
|
/// // but not beyond 1.0
|
|
|
|
/// health.health_value = (health.health_value + 0.1 * time.delta_seconds()).min(1.0);
|
|
|
|
/// }
|
|
|
|
///
|
|
|
|
/// // Mock time in tests
|
|
|
|
/// fn test_health_system() {
|
|
|
|
/// let mut world = World::default();
|
|
|
|
/// let mut time = Time::default();
|
|
|
|
/// time.update();
|
|
|
|
/// world.insert_resource(time);
|
|
|
|
/// world.insert_resource(Health { health_value: 0.2 });
|
|
|
|
///
|
Migrate engine to Schedule v3 (#7267)
Huge thanks to @maniwani, @devil-ira, @hymm, @cart, @superdump and @jakobhellermann for the help with this PR.
# Objective
- Followup #6587.
- Minimal integration for the Stageless Scheduling RFC: https://github.com/bevyengine/rfcs/pull/45
## Solution
- [x] Remove old scheduling module
- [x] Migrate new methods to no longer use extension methods
- [x] Fix compiler errors
- [x] Fix benchmarks
- [x] Fix examples
- [x] Fix docs
- [x] Fix tests
## Changelog
### Added
- a large number of methods on `App` to work with schedules ergonomically
- the `CoreSchedule` enum
- `App::add_extract_system` via the `RenderingAppExtension` trait extension method
- the private `prepare_view_uniforms` system now has a public system set for scheduling purposes, called `ViewSet::PrepareUniforms`
### Removed
- stages, and all code that mentions stages
- states have been dramatically simplified, and no longer use a stack
- `RunCriteriaLabel`
- `AsSystemLabel` trait
- `on_hierarchy_reports_enabled` run criteria (now just uses an ad hoc resource checking run condition)
- systems in `RenderSet/Stage::Extract` no longer warn when they do not read data from the main world
- `RunCriteriaLabel`
- `transform_propagate_system_set`: this was a nonstandard pattern that didn't actually provide enough control. The systems are already `pub`: the docs have been updated to ensure that the third-party usage is clear.
### Changed
- `System::default_labels` is now `System::default_system_sets`.
- `App::add_default_labels` is now `App::add_default_sets`
- `CoreStage` and `StartupStage` enums are now `CoreSet` and `StartupSet`
- `App::add_system_set` was renamed to `App::add_systems`
- The `StartupSchedule` label is now defined as part of the `CoreSchedules` enum
- `.label(SystemLabel)` is now referred to as `.in_set(SystemSet)`
- `SystemLabel` trait was replaced by `SystemSet`
- `SystemTypeIdLabel<T>` was replaced by `SystemSetType<T>`
- The `ReportHierarchyIssue` resource now has a public constructor (`new`), and implements `PartialEq`
- Fixed time steps now use a schedule (`CoreSchedule::FixedTimeStep`) rather than a run criteria.
- Adding rendering extraction systems now panics rather than silently failing if no subapp with the `RenderApp` label is found.
- the `calculate_bounds` system, with the `CalculateBounds` label, is now in `CoreSet::Update`, rather than in `CoreSet::PostUpdate` before commands are applied.
- `SceneSpawnerSystem` now runs under `CoreSet::Update`, rather than `CoreStage::PreUpdate.at_end()`.
- `bevy_pbr::add_clusters` is no longer an exclusive system
- the top level `bevy_ecs::schedule` module was replaced with `bevy_ecs::scheduling`
- `tick_global_task_pools_on_main_thread` is no longer run as an exclusive system. Instead, it has been replaced by `tick_global_task_pools`, which uses a `NonSend` resource to force running on the main thread.
## Migration Guide
- Calls to `.label(MyLabel)` should be replaced with `.in_set(MySet)`
- Stages have been removed. Replace these with system sets, and then add command flushes using the `apply_system_buffers` exclusive system where needed.
- The `CoreStage`, `StartupStage, `RenderStage` and `AssetStage` enums have been replaced with `CoreSet`, `StartupSet, `RenderSet` and `AssetSet`. The same scheduling guarantees have been preserved.
- Systems are no longer added to `CoreSet::Update` by default. Add systems manually if this behavior is needed, although you should consider adding your game logic systems to `CoreSchedule::FixedTimestep` instead for more reliable framerate-independent behavior.
- Similarly, startup systems are no longer part of `StartupSet::Startup` by default. In most cases, this won't matter to you.
- For example, `add_system_to_stage(CoreStage::PostUpdate, my_system)` should be replaced with
- `add_system(my_system.in_set(CoreSet::PostUpdate)`
- When testing systems or otherwise running them in a headless fashion, simply construct and run a schedule using `Schedule::new()` and `World::run_schedule` rather than constructing stages
- Run criteria have been renamed to run conditions. These can now be combined with each other and with states.
- Looping run criteria and state stacks have been removed. Use an exclusive system that runs a schedule if you need this level of control over system control flow.
- For app-level control flow over which schedules get run when (such as for rollback networking), create your own schedule and insert it under the `CoreSchedule::Outer` label.
- Fixed timesteps are now evaluated in a schedule, rather than controlled via run criteria. The `run_fixed_timestep` system runs this schedule between `CoreSet::First` and `CoreSet::PreUpdate` by default.
- Command flush points introduced by `AssetStage` have been removed. If you were relying on these, add them back manually.
- Adding extract systems is now typically done directly on the main app. Make sure the `RenderingAppExtension` trait is in scope, then call `app.add_extract_system(my_system)`.
- the `calculate_bounds` system, with the `CalculateBounds` label, is now in `CoreSet::Update`, rather than in `CoreSet::PostUpdate` before commands are applied. You may need to order your movement systems to occur before this system in order to avoid system order ambiguities in culling behavior.
- the `RenderLabel` `AppLabel` was renamed to `RenderApp` for clarity
- `App::add_state` now takes 0 arguments: the starting state is set based on the `Default` impl.
- Instead of creating `SystemSet` containers for systems that run in stages, simply use `.on_enter::<State::Variant>()` or its `on_exit` or `on_update` siblings.
- `SystemLabel` derives should be replaced with `SystemSet`. You will also need to add the `Debug`, `PartialEq`, `Eq`, and `Hash` traits to satisfy the new trait bounds.
- `with_run_criteria` has been renamed to `run_if`. Run criteria have been renamed to run conditions for clarity, and should now simply return a bool.
- States have been dramatically simplified: there is no longer a "state stack". To queue a transition to the next state, call `NextState::set`
## TODO
- [x] remove dead methods on App and World
- [x] add `App::add_system_to_schedule` and `App::add_systems_to_schedule`
- [x] avoid adding the default system set at inappropriate times
- [x] remove any accidental cycles in the default plugins schedule
- [x] migrate benchmarks
- [x] expose explicit labels for the built-in command flush points
- [x] migrate engine code
- [x] remove all mentions of stages from the docs
- [x] verify docs for States
- [x] fix uses of exclusive systems that use .end / .at_start / .before_commands
- [x] migrate RenderStage and AssetStage
- [x] migrate examples
- [x] ensure that transform propagation is exported in a sufficiently public way (the systems are already pub)
- [x] ensure that on_enter schedules are run at least once before the main app
- [x] re-enable opt-in to execution order ambiguities
- [x] revert change to `update_bounds` to ensure it runs in `PostUpdate`
- [x] test all examples
- [x] unbreak directional lights
- [x] unbreak shadows (see 3d_scene, 3d_shape, lighting, transparaency_3d examples)
- [x] game menu example shows loading screen and menu simultaneously
- [x] display settings menu is a blank screen
- [x] `without_winit` example panics
- [x] ensure all tests pass
- [x] SubApp doc test fails
- [x] runs_spawn_local tasks fails
- [x] [Fix panic_when_hierachy_cycle test hanging](https://github.com/alice-i-cecile/bevy/pull/120)
## Points of Difficulty and Controversy
**Reviewers, please give feedback on these and look closely**
1. Default sets, from the RFC, have been removed. These added a tremendous amount of implicit complexity and result in hard to debug scheduling errors. They're going to be tackled in the form of "base sets" by @cart in a followup.
2. The outer schedule controls which schedule is run when `App::update` is called.
3. I implemented `Label for `Box<dyn Label>` for our label types. This enables us to store schedule labels in concrete form, and then later run them. I ran into the same set of problems when working with one-shot systems. We've previously investigated this pattern in depth, and it does not appear to lead to extra indirection with nested boxes.
4. `SubApp::update` simply runs the default schedule once. This sucks, but this whole API is incomplete and this was the minimal changeset.
5. `time_system` and `tick_global_task_pools_on_main_thread` no longer use exclusive systems to attempt to force scheduling order
6. Implemetnation strategy for fixed timesteps
7. `AssetStage` was migrated to `AssetSet` without reintroducing command flush points. These did not appear to be used, and it's nice to remove these bottlenecks.
8. Migration of `bevy_render/lib.rs` and pipelined rendering. The logic here is unusually tricky, as we have complex scheduling requirements.
## Future Work (ideally before 0.10)
- Rename schedule_v3 module to schedule or scheduling
- Add a derive macro to states, and likely a `EnumIter` trait of some form
- Figure out what exactly to do with the "systems added should basically work by default" problem
- Improve ergonomics for working with fixed timesteps and states
- Polish FixedTime API to match Time
- Rebase and merge #7415
- Resolve all internal ambiguities (blocked on better tools, especially #7442)
- Add "base sets" to replace the removed default sets.
2023-02-06 02:04:50 +00:00
|
|
|
/// let mut schedule = Schedule::new();
|
2023-03-18 01:45:34 +00:00
|
|
|
/// schedule.add_systems(health_system);
|
2022-04-24 23:15:27 +00:00
|
|
|
///
|
|
|
|
/// // Simulate that 30 ms have passed
|
|
|
|
/// let mut time = world.resource_mut::<Time>();
|
|
|
|
/// let last_update = time.last_update().unwrap();
|
|
|
|
/// time.update_with_instant(last_update + Duration::from_millis(30));
|
|
|
|
///
|
|
|
|
/// // Run system
|
Migrate engine to Schedule v3 (#7267)
Huge thanks to @maniwani, @devil-ira, @hymm, @cart, @superdump and @jakobhellermann for the help with this PR.
# Objective
- Followup #6587.
- Minimal integration for the Stageless Scheduling RFC: https://github.com/bevyengine/rfcs/pull/45
## Solution
- [x] Remove old scheduling module
- [x] Migrate new methods to no longer use extension methods
- [x] Fix compiler errors
- [x] Fix benchmarks
- [x] Fix examples
- [x] Fix docs
- [x] Fix tests
## Changelog
### Added
- a large number of methods on `App` to work with schedules ergonomically
- the `CoreSchedule` enum
- `App::add_extract_system` via the `RenderingAppExtension` trait extension method
- the private `prepare_view_uniforms` system now has a public system set for scheduling purposes, called `ViewSet::PrepareUniforms`
### Removed
- stages, and all code that mentions stages
- states have been dramatically simplified, and no longer use a stack
- `RunCriteriaLabel`
- `AsSystemLabel` trait
- `on_hierarchy_reports_enabled` run criteria (now just uses an ad hoc resource checking run condition)
- systems in `RenderSet/Stage::Extract` no longer warn when they do not read data from the main world
- `RunCriteriaLabel`
- `transform_propagate_system_set`: this was a nonstandard pattern that didn't actually provide enough control. The systems are already `pub`: the docs have been updated to ensure that the third-party usage is clear.
### Changed
- `System::default_labels` is now `System::default_system_sets`.
- `App::add_default_labels` is now `App::add_default_sets`
- `CoreStage` and `StartupStage` enums are now `CoreSet` and `StartupSet`
- `App::add_system_set` was renamed to `App::add_systems`
- The `StartupSchedule` label is now defined as part of the `CoreSchedules` enum
- `.label(SystemLabel)` is now referred to as `.in_set(SystemSet)`
- `SystemLabel` trait was replaced by `SystemSet`
- `SystemTypeIdLabel<T>` was replaced by `SystemSetType<T>`
- The `ReportHierarchyIssue` resource now has a public constructor (`new`), and implements `PartialEq`
- Fixed time steps now use a schedule (`CoreSchedule::FixedTimeStep`) rather than a run criteria.
- Adding rendering extraction systems now panics rather than silently failing if no subapp with the `RenderApp` label is found.
- the `calculate_bounds` system, with the `CalculateBounds` label, is now in `CoreSet::Update`, rather than in `CoreSet::PostUpdate` before commands are applied.
- `SceneSpawnerSystem` now runs under `CoreSet::Update`, rather than `CoreStage::PreUpdate.at_end()`.
- `bevy_pbr::add_clusters` is no longer an exclusive system
- the top level `bevy_ecs::schedule` module was replaced with `bevy_ecs::scheduling`
- `tick_global_task_pools_on_main_thread` is no longer run as an exclusive system. Instead, it has been replaced by `tick_global_task_pools`, which uses a `NonSend` resource to force running on the main thread.
## Migration Guide
- Calls to `.label(MyLabel)` should be replaced with `.in_set(MySet)`
- Stages have been removed. Replace these with system sets, and then add command flushes using the `apply_system_buffers` exclusive system where needed.
- The `CoreStage`, `StartupStage, `RenderStage` and `AssetStage` enums have been replaced with `CoreSet`, `StartupSet, `RenderSet` and `AssetSet`. The same scheduling guarantees have been preserved.
- Systems are no longer added to `CoreSet::Update` by default. Add systems manually if this behavior is needed, although you should consider adding your game logic systems to `CoreSchedule::FixedTimestep` instead for more reliable framerate-independent behavior.
- Similarly, startup systems are no longer part of `StartupSet::Startup` by default. In most cases, this won't matter to you.
- For example, `add_system_to_stage(CoreStage::PostUpdate, my_system)` should be replaced with
- `add_system(my_system.in_set(CoreSet::PostUpdate)`
- When testing systems or otherwise running them in a headless fashion, simply construct and run a schedule using `Schedule::new()` and `World::run_schedule` rather than constructing stages
- Run criteria have been renamed to run conditions. These can now be combined with each other and with states.
- Looping run criteria and state stacks have been removed. Use an exclusive system that runs a schedule if you need this level of control over system control flow.
- For app-level control flow over which schedules get run when (such as for rollback networking), create your own schedule and insert it under the `CoreSchedule::Outer` label.
- Fixed timesteps are now evaluated in a schedule, rather than controlled via run criteria. The `run_fixed_timestep` system runs this schedule between `CoreSet::First` and `CoreSet::PreUpdate` by default.
- Command flush points introduced by `AssetStage` have been removed. If you were relying on these, add them back manually.
- Adding extract systems is now typically done directly on the main app. Make sure the `RenderingAppExtension` trait is in scope, then call `app.add_extract_system(my_system)`.
- the `calculate_bounds` system, with the `CalculateBounds` label, is now in `CoreSet::Update`, rather than in `CoreSet::PostUpdate` before commands are applied. You may need to order your movement systems to occur before this system in order to avoid system order ambiguities in culling behavior.
- the `RenderLabel` `AppLabel` was renamed to `RenderApp` for clarity
- `App::add_state` now takes 0 arguments: the starting state is set based on the `Default` impl.
- Instead of creating `SystemSet` containers for systems that run in stages, simply use `.on_enter::<State::Variant>()` or its `on_exit` or `on_update` siblings.
- `SystemLabel` derives should be replaced with `SystemSet`. You will also need to add the `Debug`, `PartialEq`, `Eq`, and `Hash` traits to satisfy the new trait bounds.
- `with_run_criteria` has been renamed to `run_if`. Run criteria have been renamed to run conditions for clarity, and should now simply return a bool.
- States have been dramatically simplified: there is no longer a "state stack". To queue a transition to the next state, call `NextState::set`
## TODO
- [x] remove dead methods on App and World
- [x] add `App::add_system_to_schedule` and `App::add_systems_to_schedule`
- [x] avoid adding the default system set at inappropriate times
- [x] remove any accidental cycles in the default plugins schedule
- [x] migrate benchmarks
- [x] expose explicit labels for the built-in command flush points
- [x] migrate engine code
- [x] remove all mentions of stages from the docs
- [x] verify docs for States
- [x] fix uses of exclusive systems that use .end / .at_start / .before_commands
- [x] migrate RenderStage and AssetStage
- [x] migrate examples
- [x] ensure that transform propagation is exported in a sufficiently public way (the systems are already pub)
- [x] ensure that on_enter schedules are run at least once before the main app
- [x] re-enable opt-in to execution order ambiguities
- [x] revert change to `update_bounds` to ensure it runs in `PostUpdate`
- [x] test all examples
- [x] unbreak directional lights
- [x] unbreak shadows (see 3d_scene, 3d_shape, lighting, transparaency_3d examples)
- [x] game menu example shows loading screen and menu simultaneously
- [x] display settings menu is a blank screen
- [x] `without_winit` example panics
- [x] ensure all tests pass
- [x] SubApp doc test fails
- [x] runs_spawn_local tasks fails
- [x] [Fix panic_when_hierachy_cycle test hanging](https://github.com/alice-i-cecile/bevy/pull/120)
## Points of Difficulty and Controversy
**Reviewers, please give feedback on these and look closely**
1. Default sets, from the RFC, have been removed. These added a tremendous amount of implicit complexity and result in hard to debug scheduling errors. They're going to be tackled in the form of "base sets" by @cart in a followup.
2. The outer schedule controls which schedule is run when `App::update` is called.
3. I implemented `Label for `Box<dyn Label>` for our label types. This enables us to store schedule labels in concrete form, and then later run them. I ran into the same set of problems when working with one-shot systems. We've previously investigated this pattern in depth, and it does not appear to lead to extra indirection with nested boxes.
4. `SubApp::update` simply runs the default schedule once. This sucks, but this whole API is incomplete and this was the minimal changeset.
5. `time_system` and `tick_global_task_pools_on_main_thread` no longer use exclusive systems to attempt to force scheduling order
6. Implemetnation strategy for fixed timesteps
7. `AssetStage` was migrated to `AssetSet` without reintroducing command flush points. These did not appear to be used, and it's nice to remove these bottlenecks.
8. Migration of `bevy_render/lib.rs` and pipelined rendering. The logic here is unusually tricky, as we have complex scheduling requirements.
## Future Work (ideally before 0.10)
- Rename schedule_v3 module to schedule or scheduling
- Add a derive macro to states, and likely a `EnumIter` trait of some form
- Figure out what exactly to do with the "systems added should basically work by default" problem
- Improve ergonomics for working with fixed timesteps and states
- Polish FixedTime API to match Time
- Rebase and merge #7415
- Resolve all internal ambiguities (blocked on better tools, especially #7442)
- Add "base sets" to replace the removed default sets.
2023-02-06 02:04:50 +00:00
|
|
|
/// schedule.run(&mut world);
|
2022-04-24 23:15:27 +00:00
|
|
|
///
|
|
|
|
/// // Check that 0.003 has been added to the health value
|
|
|
|
/// let expected_health_value = 0.2 + 0.1 * 0.03;
|
|
|
|
/// let actual_health_value = world.resource::<Health>().health_value;
|
|
|
|
/// assert_eq!(expected_health_value, actual_health_value);
|
|
|
|
/// }
|
|
|
|
/// ```
|
|
|
|
pub fn update_with_instant(&mut self, instant: Instant) {
|
2022-10-22 18:52:29 +00:00
|
|
|
let raw_delta = instant - self.last_update.unwrap_or(self.startup);
|
|
|
|
let delta = if self.paused {
|
|
|
|
Duration::ZERO
|
|
|
|
} else if self.relative_speed != 1.0 {
|
|
|
|
raw_delta.mul_f64(self.relative_speed)
|
|
|
|
} else {
|
2022-10-24 14:14:25 +00:00
|
|
|
// avoid rounding when at normal speed
|
2022-10-22 18:52:29 +00:00
|
|
|
raw_delta
|
|
|
|
};
|
|
|
|
|
|
|
|
if self.last_update.is_some() {
|
|
|
|
self.delta = delta;
|
2020-06-04 02:53:41 +00:00
|
|
|
self.delta_seconds = self.delta.as_secs_f32();
|
2022-10-22 18:52:29 +00:00
|
|
|
self.delta_seconds_f64 = self.delta.as_secs_f64();
|
|
|
|
self.raw_delta = raw_delta;
|
|
|
|
self.raw_delta_seconds = self.raw_delta.as_secs_f32();
|
|
|
|
self.raw_delta_seconds_f64 = self.raw_delta.as_secs_f64();
|
|
|
|
} else {
|
|
|
|
self.first_update = Some(instant);
|
2020-05-31 04:15:39 +00:00
|
|
|
}
|
2020-05-31 04:32:47 +00:00
|
|
|
|
2022-10-22 18:52:29 +00:00
|
|
|
self.elapsed += delta;
|
|
|
|
self.elapsed_seconds = self.elapsed.as_secs_f32();
|
|
|
|
self.elapsed_seconds_f64 = self.elapsed.as_secs_f64();
|
|
|
|
self.raw_elapsed += raw_delta;
|
|
|
|
self.raw_elapsed_seconds = self.raw_elapsed.as_secs_f32();
|
|
|
|
self.raw_elapsed_seconds_f64 = self.raw_elapsed.as_secs_f64();
|
|
|
|
|
|
|
|
self.elapsed_wrapped = duration_div_rem(self.elapsed, self.wrap_period).1;
|
|
|
|
self.elapsed_seconds_wrapped = self.elapsed_wrapped.as_secs_f32();
|
|
|
|
self.elapsed_seconds_wrapped_f64 = self.elapsed_wrapped.as_secs_f64();
|
|
|
|
self.raw_elapsed_wrapped = duration_div_rem(self.raw_elapsed, self.wrap_period).1;
|
|
|
|
self.raw_elapsed_seconds_wrapped = self.raw_elapsed_wrapped.as_secs_f32();
|
|
|
|
self.raw_elapsed_seconds_wrapped_f64 = self.raw_elapsed_wrapped.as_secs_f64();
|
|
|
|
|
2020-11-28 21:08:31 +00:00
|
|
|
self.last_update = Some(instant);
|
|
|
|
}
|
|
|
|
|
2022-10-24 14:14:25 +00:00
|
|
|
/// Returns the [`Instant`] the clock was created.
|
|
|
|
///
|
|
|
|
/// This usually represents when the app was started.
|
2022-10-22 18:52:29 +00:00
|
|
|
#[inline]
|
|
|
|
pub fn startup(&self) -> Instant {
|
|
|
|
self.startup
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns the [`Instant`] when [`update`](#method.update) was first called, if it exists.
|
2022-10-24 14:14:25 +00:00
|
|
|
///
|
|
|
|
/// This usually represents when the first app update started.
|
2022-10-22 18:52:29 +00:00
|
|
|
#[inline]
|
|
|
|
pub fn first_update(&self) -> Option<Instant> {
|
|
|
|
self.first_update
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns the [`Instant`] when [`update`](#method.update) was last called, if it exists.
|
2022-10-24 14:14:25 +00:00
|
|
|
///
|
|
|
|
/// This usually represents when the current app update started.
|
2022-10-22 18:52:29 +00:00
|
|
|
#[inline]
|
|
|
|
pub fn last_update(&self) -> Option<Instant> {
|
|
|
|
self.last_update
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns how much time has advanced since the last [`update`](#method.update), as a [`Duration`].
|
2020-11-28 21:08:31 +00:00
|
|
|
#[inline]
|
|
|
|
pub fn delta(&self) -> Duration {
|
|
|
|
self.delta
|
|
|
|
}
|
|
|
|
|
2022-10-22 18:52:29 +00:00
|
|
|
/// Returns how much time has advanced since the last [`update`](#method.update), as [`f32`] seconds.
|
2020-11-28 21:08:31 +00:00
|
|
|
#[inline]
|
|
|
|
pub fn delta_seconds(&self) -> f32 {
|
|
|
|
self.delta_seconds
|
|
|
|
}
|
|
|
|
|
2022-10-22 18:52:29 +00:00
|
|
|
/// Returns how much time has advanced since the last [`update`](#method.update), as [`f64`] seconds.
|
2020-11-28 21:08:31 +00:00
|
|
|
#[inline]
|
|
|
|
pub fn delta_seconds_f64(&self) -> f64 {
|
|
|
|
self.delta_seconds_f64
|
|
|
|
}
|
|
|
|
|
2022-10-22 18:52:29 +00:00
|
|
|
/// Returns how much time has advanced since [`startup`](#method.startup), as [`Duration`].
|
|
|
|
#[inline]
|
|
|
|
pub fn elapsed(&self) -> Duration {
|
|
|
|
self.elapsed
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns how much time has advanced since [`startup`](#method.startup), as [`f32`] seconds.
|
2022-09-23 20:15:57 +00:00
|
|
|
///
|
2022-10-22 18:52:29 +00:00
|
|
|
/// **Note:** This is a monotonically increasing value. It's precision will degrade over time.
|
|
|
|
/// If you need an `f32` but that precision loss is unacceptable,
|
|
|
|
/// use [`elapsed_seconds_wrapped`](#method.elapsed_seconds_wrapped).
|
|
|
|
#[inline]
|
|
|
|
pub fn elapsed_seconds(&self) -> f32 {
|
|
|
|
self.elapsed_seconds
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns how much time has advanced since [`startup`](#method.startup), as [`f64`] seconds.
|
2020-11-28 21:08:31 +00:00
|
|
|
#[inline]
|
2022-10-22 18:52:29 +00:00
|
|
|
pub fn elapsed_seconds_f64(&self) -> f64 {
|
|
|
|
self.elapsed_seconds_f64
|
2020-11-28 21:08:31 +00:00
|
|
|
}
|
|
|
|
|
2022-10-22 18:52:29 +00:00
|
|
|
/// Returns how much time has advanced since [`startup`](#method.startup) modulo
|
|
|
|
/// the [`wrap_period`](#method.wrap_period), as [`Duration`].
|
|
|
|
#[inline]
|
|
|
|
pub fn elapsed_wrapped(&self) -> Duration {
|
|
|
|
self.elapsed_wrapped
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns how much time has advanced since [`startup`](#method.startup) modulo
|
|
|
|
/// the [`wrap_period`](#method.wrap_period), as [`f32`] seconds.
|
2022-09-23 20:15:57 +00:00
|
|
|
///
|
2022-10-22 18:52:29 +00:00
|
|
|
/// This method is intended for applications (e.g. shaders) that require an [`f32`] value but
|
|
|
|
/// suffer from the gradual precision loss of [`elapsed_seconds`](#method.elapsed_seconds).
|
|
|
|
#[inline]
|
|
|
|
pub fn elapsed_seconds_wrapped(&self) -> f32 {
|
|
|
|
self.elapsed_seconds_wrapped
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns how much time has advanced since [`startup`](#method.startup) modulo
|
|
|
|
/// the [`wrap_period`](#method.wrap_period), as [`f64`] seconds.
|
|
|
|
#[inline]
|
|
|
|
pub fn elapsed_seconds_wrapped_f64(&self) -> f64 {
|
|
|
|
self.elapsed_seconds_wrapped_f64
|
|
|
|
}
|
|
|
|
|
2022-10-24 14:14:25 +00:00
|
|
|
/// Returns how much real time has elapsed since the last [`update`](#method.update), as a [`Duration`].
|
2022-10-22 18:52:29 +00:00
|
|
|
#[inline]
|
|
|
|
pub fn raw_delta(&self) -> Duration {
|
|
|
|
self.raw_delta
|
|
|
|
}
|
|
|
|
|
2022-10-24 14:14:25 +00:00
|
|
|
/// Returns how much real time has elapsed since the last [`update`](#method.update), as [`f32`] seconds.
|
2022-10-22 18:52:29 +00:00
|
|
|
#[inline]
|
|
|
|
pub fn raw_delta_seconds(&self) -> f32 {
|
|
|
|
self.raw_delta_seconds
|
|
|
|
}
|
|
|
|
|
2022-10-24 14:14:25 +00:00
|
|
|
/// Returns how much real time has elapsed since the last [`update`](#method.update), as [`f64`] seconds.
|
2022-10-22 18:52:29 +00:00
|
|
|
#[inline]
|
|
|
|
pub fn raw_delta_seconds_f64(&self) -> f64 {
|
|
|
|
self.raw_delta_seconds_f64
|
|
|
|
}
|
|
|
|
|
2022-10-24 14:14:25 +00:00
|
|
|
/// Returns how much real time has elapsed since [`startup`](#method.startup), as [`Duration`].
|
2022-10-22 18:52:29 +00:00
|
|
|
#[inline]
|
|
|
|
pub fn raw_elapsed(&self) -> Duration {
|
|
|
|
self.raw_elapsed
|
|
|
|
}
|
|
|
|
|
2022-10-24 14:14:25 +00:00
|
|
|
/// Returns how much real time has elapsed since [`startup`](#method.startup), as [`f32`] seconds.
|
2022-09-23 20:15:57 +00:00
|
|
|
///
|
2022-10-22 18:52:29 +00:00
|
|
|
/// **Note:** This is a monotonically increasing value. It's precision will degrade over time.
|
|
|
|
/// If you need an `f32` but that precision loss is unacceptable,
|
|
|
|
/// use [`raw_elapsed_seconds_wrapped`](#method.raw_elapsed_seconds_wrapped).
|
2022-09-23 20:15:57 +00:00
|
|
|
#[inline]
|
2022-10-22 18:52:29 +00:00
|
|
|
pub fn raw_elapsed_seconds(&self) -> f32 {
|
|
|
|
self.raw_elapsed_seconds
|
2022-09-23 20:15:57 +00:00
|
|
|
}
|
|
|
|
|
2022-10-24 14:14:25 +00:00
|
|
|
/// Returns how much real time has elapsed since [`startup`](#method.startup), as [`f64`] seconds.
|
2020-11-28 21:08:31 +00:00
|
|
|
#[inline]
|
2022-10-22 18:52:29 +00:00
|
|
|
pub fn raw_elapsed_seconds_f64(&self) -> f64 {
|
|
|
|
self.raw_elapsed_seconds_f64
|
2020-11-28 21:08:31 +00:00
|
|
|
}
|
|
|
|
|
2022-10-24 14:14:25 +00:00
|
|
|
/// Returns how much real time has elapsed since [`startup`](#method.startup) modulo
|
2022-10-22 18:52:29 +00:00
|
|
|
/// the [`wrap_period`](#method.wrap_period), as [`Duration`].
|
2020-11-28 21:08:31 +00:00
|
|
|
#[inline]
|
2022-10-22 18:52:29 +00:00
|
|
|
pub fn raw_elapsed_wrapped(&self) -> Duration {
|
|
|
|
self.raw_elapsed_wrapped
|
|
|
|
}
|
|
|
|
|
2022-10-24 14:14:25 +00:00
|
|
|
/// Returns how much real time has elapsed since [`startup`](#method.startup) modulo
|
2022-10-22 18:52:29 +00:00
|
|
|
/// the [`wrap_period`](#method.wrap_period), as [`f32`] seconds.
|
|
|
|
///
|
|
|
|
/// This method is intended for applications (e.g. shaders) that require an [`f32`] value but
|
|
|
|
/// suffer from the gradual precision loss of [`raw_elapsed_seconds`](#method.raw_elapsed_seconds).
|
|
|
|
#[inline]
|
|
|
|
pub fn raw_elapsed_seconds_wrapped(&self) -> f32 {
|
|
|
|
self.raw_elapsed_seconds_wrapped
|
|
|
|
}
|
|
|
|
|
2022-10-24 14:14:25 +00:00
|
|
|
/// Returns how much real time has elapsed since [`startup`](#method.startup) modulo
|
2022-10-22 18:52:29 +00:00
|
|
|
/// the [`wrap_period`](#method.wrap_period), as [`f64`] seconds.
|
|
|
|
#[inline]
|
|
|
|
pub fn raw_elapsed_seconds_wrapped_f64(&self) -> f64 {
|
|
|
|
self.raw_elapsed_seconds_wrapped_f64
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns the modulus used to calculate [`elapsed_wrapped`](#method.elapsed_wrapped) and
|
|
|
|
/// [`raw_elapsed_wrapped`](#method.raw_elapsed_wrapped).
|
|
|
|
///
|
|
|
|
/// **Note:** The default modulus is one hour.
|
|
|
|
#[inline]
|
|
|
|
pub fn wrap_period(&self) -> Duration {
|
|
|
|
self.wrap_period
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Sets the modulus used to calculate [`elapsed_wrapped`](#method.elapsed_wrapped) and
|
|
|
|
/// [`raw_elapsed_wrapped`](#method.raw_elapsed_wrapped).
|
|
|
|
///
|
2022-10-24 14:14:25 +00:00
|
|
|
/// **Note:** This will not take effect until the next update.
|
|
|
|
///
|
2022-10-22 18:52:29 +00:00
|
|
|
/// # Panics
|
|
|
|
///
|
2022-10-24 14:14:25 +00:00
|
|
|
/// Panics if `wrap_period` is a zero-length duration.
|
2022-10-22 18:52:29 +00:00
|
|
|
#[inline]
|
|
|
|
pub fn set_wrap_period(&mut self, wrap_period: Duration) {
|
2022-10-24 14:14:25 +00:00
|
|
|
assert!(!wrap_period.is_zero(), "division by zero");
|
2022-10-22 18:52:29 +00:00
|
|
|
self.wrap_period = wrap_period;
|
|
|
|
}
|
|
|
|
|
2022-10-24 14:14:25 +00:00
|
|
|
/// Returns the speed the clock advances relative to your system clock, as [`f32`].
|
|
|
|
/// This is known as "time scaling" or "time dilation" in other engines.
|
2022-10-22 18:52:29 +00:00
|
|
|
///
|
|
|
|
/// **Note:** This function will return zero when time is paused.
|
|
|
|
#[inline]
|
|
|
|
pub fn relative_speed(&self) -> f32 {
|
|
|
|
self.relative_speed_f64() as f32
|
|
|
|
}
|
|
|
|
|
2022-10-24 14:14:25 +00:00
|
|
|
/// Returns the speed the clock advances relative to your system clock, as [`f64`].
|
|
|
|
/// This is known as "time scaling" or "time dilation" in other engines.
|
2022-10-22 18:52:29 +00:00
|
|
|
///
|
|
|
|
/// **Note:** This function will return zero when time is paused.
|
|
|
|
#[inline]
|
|
|
|
pub fn relative_speed_f64(&self) -> f64 {
|
|
|
|
if self.paused {
|
|
|
|
0.0
|
|
|
|
} else {
|
|
|
|
self.relative_speed
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-10-24 14:14:25 +00:00
|
|
|
/// Sets the speed the clock advances relative to your system clock, given as an [`f32`].
|
|
|
|
///
|
|
|
|
/// For example, setting this to `2.0` will make the clock advance twice as fast as your system clock.
|
2022-10-22 18:52:29 +00:00
|
|
|
///
|
2022-10-24 14:14:25 +00:00
|
|
|
/// **Note:** This does not affect the `raw_*` measurements.
|
2022-10-22 18:52:29 +00:00
|
|
|
///
|
|
|
|
/// # Panics
|
|
|
|
///
|
|
|
|
/// Panics if `ratio` is negative or not finite.
|
|
|
|
#[inline]
|
|
|
|
pub fn set_relative_speed(&mut self, ratio: f32) {
|
|
|
|
self.set_relative_speed_f64(ratio as f64);
|
2019-12-03 08:30:30 +00:00
|
|
|
}
|
2020-05-31 04:32:47 +00:00
|
|
|
|
2022-10-24 14:14:25 +00:00
|
|
|
/// Sets the speed the clock advances relative to your system clock, given as an [`f64`].
|
2022-10-22 18:52:29 +00:00
|
|
|
///
|
2022-10-24 14:14:25 +00:00
|
|
|
/// For example, setting this to `2.0` will make the clock advance twice as fast as your system clock.
|
|
|
|
///
|
|
|
|
/// **Note:** This does not affect the `raw_*` measurements.
|
2022-10-22 18:52:29 +00:00
|
|
|
///
|
|
|
|
/// # Panics
|
|
|
|
///
|
|
|
|
/// Panics if `ratio` is negative or not finite.
|
|
|
|
#[inline]
|
|
|
|
pub fn set_relative_speed_f64(&mut self, ratio: f64) {
|
|
|
|
assert!(ratio.is_finite(), "tried to go infinitely fast");
|
2023-02-18 22:43:08 +00:00
|
|
|
assert!(ratio >= 0.0, "tried to go back in time");
|
2022-10-22 18:52:29 +00:00
|
|
|
self.relative_speed = ratio;
|
|
|
|
}
|
|
|
|
|
2022-10-24 14:14:25 +00:00
|
|
|
/// Stops the clock, preventing it from advancing until resumed.
|
|
|
|
///
|
2023-02-24 12:55:43 +00:00
|
|
|
/// **Note:** This does not affect the `raw_*` measurements.
|
2022-10-22 18:52:29 +00:00
|
|
|
#[inline]
|
|
|
|
pub fn pause(&mut self) {
|
|
|
|
self.paused = true;
|
|
|
|
}
|
|
|
|
|
2022-10-24 14:14:25 +00:00
|
|
|
/// Resumes the clock if paused.
|
2021-12-07 01:30:08 +00:00
|
|
|
#[inline]
|
2022-10-22 18:52:29 +00:00
|
|
|
pub fn unpause(&mut self) {
|
|
|
|
self.paused = false;
|
2020-05-31 04:32:47 +00:00
|
|
|
}
|
2022-10-22 18:52:29 +00:00
|
|
|
|
2022-10-24 14:14:25 +00:00
|
|
|
/// Returns `true` if the clock is currently paused.
|
2022-10-22 18:52:29 +00:00
|
|
|
#[inline]
|
|
|
|
pub fn is_paused(&self) -> bool {
|
|
|
|
self.paused
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn duration_div_rem(dividend: Duration, divisor: Duration) -> (u32, Duration) {
|
|
|
|
// `Duration` does not have a built-in modulo operation
|
|
|
|
let quotient = (dividend.as_nanos() / divisor.as_nanos()) as u32;
|
|
|
|
let remainder = dividend - (quotient * divisor);
|
|
|
|
(quotient, remainder)
|
2020-01-11 10:11:27 +00:00
|
|
|
}
|
2020-04-06 03:19:02 +00:00
|
|
|
|
2020-11-28 21:08:31 +00:00
|
|
|
#[cfg(test)]
|
2021-02-22 08:42:19 +00:00
|
|
|
#[allow(clippy::float_cmp)]
|
2020-11-28 21:08:31 +00:00
|
|
|
mod tests {
|
|
|
|
use super::Time;
|
|
|
|
use bevy_utils::{Duration, Instant};
|
|
|
|
|
2022-10-22 18:52:29 +00:00
|
|
|
fn assert_float_eq(a: f32, b: f32) {
|
|
|
|
assert!((a - b).abs() <= f32::EPSILON, "{a} != {b}");
|
|
|
|
}
|
|
|
|
|
2020-11-28 21:08:31 +00:00
|
|
|
#[test]
|
|
|
|
fn update_test() {
|
|
|
|
let start_instant = Instant::now();
|
2022-10-22 18:52:29 +00:00
|
|
|
let mut time = Time::new(start_instant);
|
2020-11-28 21:08:31 +00:00
|
|
|
|
2022-10-22 18:52:29 +00:00
|
|
|
// Ensure `time` was constructed correctly.
|
2020-11-28 21:08:31 +00:00
|
|
|
assert_eq!(time.startup(), start_instant);
|
2022-10-22 18:52:29 +00:00
|
|
|
assert_eq!(time.first_update(), None);
|
|
|
|
assert_eq!(time.last_update(), None);
|
|
|
|
assert_eq!(time.relative_speed(), 1.0);
|
|
|
|
assert_eq!(time.delta(), Duration::ZERO);
|
2020-11-28 21:08:31 +00:00
|
|
|
assert_eq!(time.delta_seconds(), 0.0);
|
2022-10-22 18:52:29 +00:00
|
|
|
assert_eq!(time.delta_seconds_f64(), 0.0);
|
|
|
|
assert_eq!(time.raw_delta(), Duration::ZERO);
|
|
|
|
assert_eq!(time.raw_delta_seconds(), 0.0);
|
|
|
|
assert_eq!(time.raw_delta_seconds_f64(), 0.0);
|
|
|
|
assert_eq!(time.elapsed(), Duration::ZERO);
|
|
|
|
assert_eq!(time.elapsed_seconds(), 0.0);
|
|
|
|
assert_eq!(time.elapsed_seconds_f64(), 0.0);
|
|
|
|
assert_eq!(time.raw_elapsed(), Duration::ZERO);
|
|
|
|
assert_eq!(time.raw_elapsed_seconds(), 0.0);
|
|
|
|
assert_eq!(time.raw_elapsed_seconds_f64(), 0.0);
|
|
|
|
|
|
|
|
// Update `time` and check results.
|
|
|
|
// The first update to `time` normally happens before other systems have run,
|
|
|
|
// so the first delta doesn't appear until the second update.
|
2020-11-28 21:08:31 +00:00
|
|
|
let first_update_instant = Instant::now();
|
|
|
|
time.update_with_instant(first_update_instant);
|
|
|
|
|
|
|
|
assert_eq!(time.startup(), start_instant);
|
2022-10-22 18:52:29 +00:00
|
|
|
assert_eq!(time.first_update(), Some(first_update_instant));
|
|
|
|
assert_eq!(time.last_update(), Some(first_update_instant));
|
|
|
|
assert_eq!(time.relative_speed(), 1.0);
|
|
|
|
assert_eq!(time.delta(), Duration::ZERO);
|
|
|
|
assert_eq!(time.delta_seconds(), 0.0);
|
2020-11-28 21:08:31 +00:00
|
|
|
assert_eq!(time.delta_seconds_f64(), 0.0);
|
2022-10-22 18:52:29 +00:00
|
|
|
assert_eq!(time.raw_delta(), Duration::ZERO);
|
|
|
|
assert_eq!(time.raw_delta_seconds(), 0.0);
|
|
|
|
assert_eq!(time.raw_delta_seconds_f64(), 0.0);
|
|
|
|
assert_eq!(time.elapsed(), first_update_instant - start_instant,);
|
2020-11-28 21:08:31 +00:00
|
|
|
assert_eq!(
|
2022-10-22 18:52:29 +00:00
|
|
|
time.elapsed_seconds(),
|
|
|
|
(first_update_instant - start_instant).as_secs_f32(),
|
2020-11-28 21:08:31 +00:00
|
|
|
);
|
2021-12-07 01:30:08 +00:00
|
|
|
assert_eq!(
|
2022-10-22 18:52:29 +00:00
|
|
|
time.elapsed_seconds_f64(),
|
|
|
|
(first_update_instant - start_instant).as_secs_f64(),
|
2021-12-07 01:30:08 +00:00
|
|
|
);
|
2022-10-22 18:52:29 +00:00
|
|
|
assert_eq!(time.raw_elapsed(), first_update_instant - start_instant,);
|
|
|
|
assert_eq!(
|
|
|
|
time.raw_elapsed_seconds(),
|
|
|
|
(first_update_instant - start_instant).as_secs_f32(),
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
time.raw_elapsed_seconds_f64(),
|
|
|
|
(first_update_instant - start_instant).as_secs_f64(),
|
2022-09-23 20:15:57 +00:00
|
|
|
);
|
2020-11-28 21:08:31 +00:00
|
|
|
|
2022-10-22 18:52:29 +00:00
|
|
|
// Update `time` again and check results.
|
|
|
|
// At this point its safe to use time.delta().
|
2020-11-28 21:08:31 +00:00
|
|
|
let second_update_instant = Instant::now();
|
|
|
|
time.update_with_instant(second_update_instant);
|
|
|
|
assert_eq!(time.startup(), start_instant);
|
2022-10-22 18:52:29 +00:00
|
|
|
assert_eq!(time.first_update(), Some(first_update_instant));
|
|
|
|
assert_eq!(time.last_update(), Some(second_update_instant));
|
|
|
|
assert_eq!(time.relative_speed(), 1.0);
|
|
|
|
assert_eq!(time.delta(), second_update_instant - first_update_instant);
|
2020-11-28 21:08:31 +00:00
|
|
|
assert_eq!(
|
2022-10-22 18:52:29 +00:00
|
|
|
time.delta_seconds(),
|
|
|
|
(second_update_instant - first_update_instant).as_secs_f32(),
|
2020-11-28 21:08:31 +00:00
|
|
|
);
|
2021-12-07 01:30:08 +00:00
|
|
|
assert_eq!(
|
2022-10-22 18:52:29 +00:00
|
|
|
time.delta_seconds_f64(),
|
|
|
|
(second_update_instant - first_update_instant).as_secs_f64(),
|
2021-12-07 01:30:08 +00:00
|
|
|
);
|
2022-10-22 18:52:29 +00:00
|
|
|
assert_eq!(
|
|
|
|
time.raw_delta(),
|
|
|
|
second_update_instant - first_update_instant,
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
time.raw_delta_seconds(),
|
|
|
|
(second_update_instant - first_update_instant).as_secs_f32(),
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
time.raw_delta_seconds_f64(),
|
|
|
|
(second_update_instant - first_update_instant).as_secs_f64(),
|
|
|
|
);
|
|
|
|
assert_eq!(time.elapsed(), second_update_instant - start_instant,);
|
|
|
|
assert_eq!(
|
|
|
|
time.elapsed_seconds(),
|
|
|
|
(second_update_instant - start_instant).as_secs_f32(),
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
time.elapsed_seconds_f64(),
|
|
|
|
(second_update_instant - start_instant).as_secs_f64(),
|
|
|
|
);
|
|
|
|
assert_eq!(time.raw_elapsed(), second_update_instant - start_instant,);
|
|
|
|
assert_eq!(
|
|
|
|
time.raw_elapsed_seconds(),
|
|
|
|
(second_update_instant - start_instant).as_secs_f32(),
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
time.raw_elapsed_seconds_f64(),
|
|
|
|
(second_update_instant - start_instant).as_secs_f64(),
|
2022-09-23 20:15:57 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
2022-10-22 18:52:29 +00:00
|
|
|
fn wrapping_test() {
|
2022-09-23 20:15:57 +00:00
|
|
|
let start_instant = Instant::now();
|
|
|
|
|
|
|
|
let mut time = Time {
|
|
|
|
startup: start_instant,
|
|
|
|
wrap_period: Duration::from_secs(3),
|
|
|
|
..Default::default()
|
|
|
|
};
|
|
|
|
|
2022-10-22 18:52:29 +00:00
|
|
|
assert_eq!(time.elapsed_seconds_wrapped(), 0.0);
|
2022-09-23 20:15:57 +00:00
|
|
|
|
|
|
|
time.update_with_instant(start_instant + Duration::from_secs(1));
|
2022-10-22 18:52:29 +00:00
|
|
|
assert_float_eq(time.elapsed_seconds_wrapped(), 1.0);
|
2022-09-23 20:15:57 +00:00
|
|
|
|
|
|
|
time.update_with_instant(start_instant + Duration::from_secs(2));
|
2022-10-22 18:52:29 +00:00
|
|
|
assert_float_eq(time.elapsed_seconds_wrapped(), 2.0);
|
2022-09-23 20:15:57 +00:00
|
|
|
|
|
|
|
time.update_with_instant(start_instant + Duration::from_secs(3));
|
2022-10-22 18:52:29 +00:00
|
|
|
assert_float_eq(time.elapsed_seconds_wrapped(), 0.0);
|
2022-09-23 20:15:57 +00:00
|
|
|
|
|
|
|
time.update_with_instant(start_instant + Duration::from_secs(4));
|
2022-10-22 18:52:29 +00:00
|
|
|
assert_float_eq(time.elapsed_seconds_wrapped(), 1.0);
|
2022-09-23 20:15:57 +00:00
|
|
|
}
|
|
|
|
|
2022-10-22 18:52:29 +00:00
|
|
|
#[test]
|
|
|
|
fn relative_speed_test() {
|
|
|
|
let start_instant = Instant::now();
|
|
|
|
let mut time = Time::new(start_instant);
|
|
|
|
|
|
|
|
let first_update_instant = Instant::now();
|
|
|
|
time.update_with_instant(first_update_instant);
|
|
|
|
|
|
|
|
// Update `time` again and check results.
|
|
|
|
// At this point its safe to use time.delta().
|
|
|
|
let second_update_instant = Instant::now();
|
|
|
|
time.update_with_instant(second_update_instant);
|
|
|
|
assert_eq!(time.startup(), start_instant);
|
|
|
|
assert_eq!(time.first_update(), Some(first_update_instant));
|
|
|
|
assert_eq!(time.last_update(), Some(second_update_instant));
|
|
|
|
assert_eq!(time.relative_speed(), 1.0);
|
|
|
|
assert_eq!(time.delta(), second_update_instant - first_update_instant);
|
|
|
|
assert_eq!(
|
|
|
|
time.delta_seconds(),
|
|
|
|
(second_update_instant - first_update_instant).as_secs_f32(),
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
time.delta_seconds_f64(),
|
|
|
|
(second_update_instant - first_update_instant).as_secs_f64(),
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
time.raw_delta(),
|
|
|
|
second_update_instant - first_update_instant,
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
time.raw_delta_seconds(),
|
|
|
|
(second_update_instant - first_update_instant).as_secs_f32(),
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
time.raw_delta_seconds_f64(),
|
|
|
|
(second_update_instant - first_update_instant).as_secs_f64(),
|
|
|
|
);
|
|
|
|
assert_eq!(time.elapsed(), second_update_instant - start_instant,);
|
|
|
|
assert_eq!(
|
|
|
|
time.elapsed_seconds(),
|
|
|
|
(second_update_instant - start_instant).as_secs_f32(),
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
time.elapsed_seconds_f64(),
|
|
|
|
(second_update_instant - start_instant).as_secs_f64(),
|
|
|
|
);
|
|
|
|
assert_eq!(time.raw_elapsed(), second_update_instant - start_instant,);
|
|
|
|
assert_eq!(
|
|
|
|
time.raw_elapsed_seconds(),
|
|
|
|
(second_update_instant - start_instant).as_secs_f32(),
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
time.raw_elapsed_seconds_f64(),
|
|
|
|
(second_update_instant - start_instant).as_secs_f64(),
|
|
|
|
);
|
|
|
|
|
|
|
|
// Make app time advance at 2x the rate of your system clock.
|
|
|
|
time.set_relative_speed(2.0);
|
|
|
|
|
|
|
|
// Update `time` again 1 second later.
|
|
|
|
let elapsed = Duration::from_secs(1);
|
|
|
|
let third_update_instant = second_update_instant + elapsed;
|
|
|
|
time.update_with_instant(third_update_instant);
|
|
|
|
|
|
|
|
// Since app is advancing 2x your system clock, expect time
|
|
|
|
// to have advanced by twice the amount of real time elapsed.
|
|
|
|
assert_eq!(time.startup(), start_instant);
|
|
|
|
assert_eq!(time.first_update(), Some(first_update_instant));
|
|
|
|
assert_eq!(time.last_update(), Some(third_update_instant));
|
|
|
|
assert_eq!(time.relative_speed(), 2.0);
|
|
|
|
assert_eq!(time.delta(), elapsed.mul_f32(2.0));
|
|
|
|
assert_eq!(time.delta_seconds(), elapsed.mul_f32(2.0).as_secs_f32());
|
|
|
|
assert_eq!(time.delta_seconds_f64(), elapsed.mul_f32(2.0).as_secs_f64());
|
|
|
|
assert_eq!(time.raw_delta(), elapsed);
|
|
|
|
assert_eq!(time.raw_delta_seconds(), elapsed.as_secs_f32());
|
|
|
|
assert_eq!(time.raw_delta_seconds_f64(), elapsed.as_secs_f64());
|
|
|
|
assert_eq!(
|
|
|
|
time.elapsed(),
|
|
|
|
second_update_instant - start_instant + elapsed.mul_f32(2.0),
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
time.elapsed_seconds(),
|
|
|
|
(second_update_instant - start_instant + elapsed.mul_f32(2.0)).as_secs_f32(),
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
time.elapsed_seconds_f64(),
|
|
|
|
(second_update_instant - start_instant + elapsed.mul_f32(2.0)).as_secs_f64(),
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
time.raw_elapsed(),
|
|
|
|
second_update_instant - start_instant + elapsed,
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
time.raw_elapsed_seconds(),
|
|
|
|
(second_update_instant - start_instant + elapsed).as_secs_f32(),
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
time.raw_elapsed_seconds_f64(),
|
|
|
|
(second_update_instant - start_instant + elapsed).as_secs_f64(),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn pause_test() {
|
|
|
|
let start_instant = Instant::now();
|
|
|
|
let mut time = Time::new(start_instant);
|
|
|
|
|
|
|
|
let first_update_instant = Instant::now();
|
|
|
|
time.update_with_instant(first_update_instant);
|
|
|
|
|
|
|
|
assert!(!time.is_paused());
|
|
|
|
assert_eq!(time.relative_speed(), 1.0);
|
|
|
|
|
|
|
|
time.pause();
|
|
|
|
|
|
|
|
assert!(time.is_paused());
|
|
|
|
assert_eq!(time.relative_speed(), 0.0);
|
|
|
|
|
|
|
|
let second_update_instant = Instant::now();
|
|
|
|
time.update_with_instant(second_update_instant);
|
|
|
|
assert_eq!(time.startup(), start_instant);
|
|
|
|
assert_eq!(time.first_update(), Some(first_update_instant));
|
|
|
|
assert_eq!(time.last_update(), Some(second_update_instant));
|
|
|
|
assert_eq!(time.delta(), Duration::ZERO);
|
|
|
|
assert_eq!(
|
|
|
|
time.raw_delta(),
|
|
|
|
second_update_instant - first_update_instant,
|
|
|
|
);
|
|
|
|
assert_eq!(time.elapsed(), first_update_instant - start_instant);
|
|
|
|
assert_eq!(time.raw_elapsed(), second_update_instant - start_instant);
|
|
|
|
|
|
|
|
time.unpause();
|
|
|
|
|
|
|
|
assert!(!time.is_paused());
|
|
|
|
assert_eq!(time.relative_speed(), 1.0);
|
|
|
|
|
|
|
|
let third_update_instant = Instant::now();
|
|
|
|
time.update_with_instant(third_update_instant);
|
|
|
|
assert_eq!(time.startup(), start_instant);
|
|
|
|
assert_eq!(time.first_update(), Some(first_update_instant));
|
|
|
|
assert_eq!(time.last_update(), Some(third_update_instant));
|
|
|
|
assert_eq!(time.delta(), third_update_instant - second_update_instant);
|
|
|
|
assert_eq!(
|
|
|
|
time.raw_delta(),
|
|
|
|
third_update_instant - second_update_instant,
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
time.elapsed(),
|
|
|
|
(third_update_instant - second_update_instant) + (first_update_instant - start_instant),
|
|
|
|
);
|
|
|
|
assert_eq!(time.raw_elapsed(), third_update_instant - start_instant);
|
2020-11-28 21:08:31 +00:00
|
|
|
}
|
|
|
|
}
|