# Objective Closes #19564. The current `Event` trait looks like this: ```rust pub trait Event: Send + Sync + 'static { type Traversal: Traversal<Self>; const AUTO_PROPAGATE: bool = false; fn register_component_id(world: &mut World) -> ComponentId { ... } fn component_id(world: &World) -> Option<ComponentId> { ... } } ``` The `Event` trait is used by both buffered events (`EventReader`/`EventWriter`) and observer events. If they are observer events, they can optionally be targeted at specific `Entity`s or `ComponentId`s, and can even be propagated to other entities. However, there has long been a desire to split the trait semantically for a variety of reasons, see #14843, #14272, and #16031 for discussion. Some reasons include: - It's very uncommon to use a single event type as both a buffered event and targeted observer event. They are used differently and tend to have distinct semantics. - A common footgun is using buffered events with observers or event readers with observer events, as there is no type-level error that prevents this kind of misuse. - #19440 made `Trigger::target` return an `Option<Entity>`. This *seriously* hurts ergonomics for the general case of entity observers, as you need to `.unwrap()` each time. If we could statically determine whether the event is expected to have an entity target, this would be unnecessary. There's really two main ways that we can categorize events: push vs. pull (i.e. "observer event" vs. "buffered event") and global vs. targeted: | | Push | Pull | | ------------ | --------------- | --------------------------- | | **Global** | Global observer | `EventReader`/`EventWriter` | | **Targeted** | Entity observer | - | There are many ways to approach this, each with their tradeoffs. Ultimately, we kind of want to split events both ways: - A type-level distinction between observer events and buffered events, to prevent people from using the wrong kind of event in APIs - A statically designated entity target for observer events to avoid accidentally using untargeted events for targeted APIs This PR achieves these goals by splitting event traits into `Event`, `EntityEvent`, and `BufferedEvent`, with `Event` being the shared trait implemented by all events. ## `Event`, `EntityEvent`, and `BufferedEvent` `Event` is now a very simple trait shared by all events. ```rust pub trait Event: Send + Sync + 'static { // Required for observer APIs fn register_component_id(world: &mut World) -> ComponentId { ... } fn component_id(world: &World) -> Option<ComponentId> { ... } } ``` You can call `trigger` for *any* event, and use a global observer for listening to the event. ```rust #[derive(Event)] struct Speak { message: String, } // ... app.add_observer(|trigger: On<Speak>| { println!("{}", trigger.message); }); // ... commands.trigger(Speak { message: "Y'all like these reworked events?".to_string(), }); ``` To allow an event to be targeted at entities and even propagated further, you can additionally implement the `EntityEvent` trait: ```rust pub trait EntityEvent: Event { type Traversal: Traversal<Self>; const AUTO_PROPAGATE: bool = false; } ``` This lets you call `trigger_targets`, and to use targeted observer APIs like `EntityCommands::observe`: ```rust #[derive(Event, EntityEvent)] #[entity_event(traversal = &'static ChildOf, auto_propagate)] struct Damage { amount: f32, } // ... let enemy = commands.spawn((Enemy, Health(100.0))).id(); // Spawn some armor as a child of the enemy entity. // When the armor takes damage, it will bubble the event up to the enemy. let armor_piece = commands .spawn((ArmorPiece, Health(25.0), ChildOf(enemy))) .observe(|trigger: On<Damage>, mut query: Query<&mut Health>| { // Note: `On::target` only exists because this is an `EntityEvent`. let mut health = query.get(trigger.target()).unwrap(); health.0 -= trigger.amount(); }); commands.trigger_targets(Damage { amount: 10.0 }, armor_piece); ``` > [!NOTE] > You *can* still also trigger an `EntityEvent` without targets using `trigger`. We probably *could* make this an either-or thing, but I'm not sure that's actually desirable. To allow an event to be used with the buffered API, you can implement `BufferedEvent`: ```rust pub trait BufferedEvent: Event {} ``` The event can then be used with `EventReader`/`EventWriter`: ```rust #[derive(Event, BufferedEvent)] struct Message(String); fn write_hello(mut writer: EventWriter<Message>) { writer.write(Message("I hope these examples are alright".to_string())); } fn read_messages(mut reader: EventReader<Message>) { // Process all buffered events of type `Message`. for Message(message) in reader.read() { println!("{message}"); } } ``` In summary: - Need a basic event you can trigger and observe? Derive `Event`! - Need the event to be targeted at an entity? Derive `EntityEvent`! - Need the event to be buffered and support the `EventReader`/`EventWriter` API? Derive `BufferedEvent`! ## Alternatives I'll now cover some of the alternative approaches I have considered and briefly explored. I made this section collapsible since it ended up being quite long :P <details> <summary>Expand this to see alternatives</summary> ### 1. Unified `Event` Trait One option is not to have *three* separate traits (`Event`, `EntityEvent`, `BufferedEvent`), and to instead just use associated constants on `Event` to determine whether an event supports targeting and buffering or not: ```rust pub trait Event: Send + Sync + 'static { type Traversal: Traversal<Self>; const AUTO_PROPAGATE: bool = false; const TARGETED: bool = false; const BUFFERED: bool = false; fn register_component_id(world: &mut World) -> ComponentId { ... } fn component_id(world: &World) -> Option<ComponentId> { ... } } ``` Methods can then use bounds like `where E: Event<TARGETED = true>` or `where E: Event<BUFFERED = true>` to limit APIs to specific kinds of events. This would keep everything under one `Event` trait, but I don't think it's necessarily a good idea. It makes APIs harder to read, and docs can't easily refer to specific types of events. You can also create weird invariants: what if you specify `TARGETED = false`, but have `Traversal` and/or `AUTO_PROPAGATE` enabled? ### 2. `Event` and `Trigger` Another option is to only split the traits between buffered events and observer events, since that is the main thing people have been asking for, and they have the largest API difference. If we did this, I think we would need to make the terms *clearly* separate. We can't really use `Event` and `BufferedEvent` as the names, since it would be strange that `BufferedEvent` doesn't implement `Event`. Something like `ObserverEvent` and `BufferedEvent` could work, but it'd be more verbose. For this approach, I would instead keep `Event` for the current `EventReader`/`EventWriter` API, and call the observer event a `Trigger`, since the "trigger" terminology is already used in the observer context within Bevy (both as a noun and a verb). This is also what a long [bikeshed on Discord](https://discord.com/channels/691052431525675048/749335865876021248/1298057661878898791) seemed to land on at the end of last year. ```rust // For `EventReader`/`EventWriter` pub trait Event: Send + Sync + 'static {} // For observers pub trait Trigger: Send + Sync + 'static { type Traversal: Traversal<Self>; const AUTO_PROPAGATE: bool = false; const TARGETED: bool = false; fn register_component_id(world: &mut World) -> ComponentId { ... } fn component_id(world: &World) -> Option<ComponentId> { ... } } ``` The problem is that "event" is just a really good term for something that "happens". Observers are rapidly becoming the more prominent API, so it'd be weird to give them the `Trigger` name and leave the good `Event` name for the less common API. So, even though a split like this seems neat on the surface, I think it ultimately wouldn't really work. We want to keep the `Event` name for observer events, and there is no good alternative for the buffered variant. (`Message` was suggested, but saying stuff like "sends a collision message" is weird.) ### 3. `GlobalEvent` + `TargetedEvent` What if instead of focusing on the buffered vs. observed split, we *only* make a distinction between global and targeted events? ```rust // A shared event trait to allow global observers to work pub trait Event: Send + Sync + 'static { fn register_component_id(world: &mut World) -> ComponentId { ... } fn component_id(world: &World) -> Option<ComponentId> { ... } } // For buffered events and non-targeted observer events pub trait GlobalEvent: Event {} // For targeted observer events pub trait TargetedEvent: Event { type Traversal: Traversal<Self>; const AUTO_PROPAGATE: bool = false; } ``` This is actually the first approach I implemented, and it has the neat characteristic that you can only use non-targeted APIs like `trigger` with a `GlobalEvent` and targeted APIs like `trigger_targets` with a `TargetedEvent`. You have full control over whether the entity should or should not have a target, as they are fully distinct at the type-level. However, there's a few problems: - There is no type-level indication of whether a `GlobalEvent` supports buffered events or just non-targeted observer events - An `Event` on its own does literally nothing, it's just a shared trait required to make global observers accept both non-targeted and targeted events - If an event is both a `GlobalEvent` and `TargetedEvent`, global observers again have ambiguity on whether an event has a target or not, undermining some of the benefits - The names are not ideal ### 4. `Event` and `EntityEvent` We can fix some of the problems of Alternative 3 by accepting that targeted events can also be used in non-targeted contexts, and simply having the `Event` and `EntityEvent` traits: ```rust // For buffered events and non-targeted observer events pub trait Event: Send + Sync + 'static { fn register_component_id(world: &mut World) -> ComponentId { ... } fn component_id(world: &World) -> Option<ComponentId> { ... } } // For targeted observer events pub trait EntityEvent: Event { type Traversal: Traversal<Self>; const AUTO_PROPAGATE: bool = false; } ``` This is essentially identical to this PR, just without a dedicated `BufferedEvent`. The remaining major "problem" is that there is still zero type-level indication of whether an `Event` event *actually* supports the buffered API. This leads us to the solution proposed in this PR, using `Event`, `EntityEvent`, and `BufferedEvent`. </details> ## Conclusion The `Event` + `EntityEvent` + `BufferedEvent` split proposed in this PR aims to solve all the common problems with Bevy's current event model while keeping the "weirdness" factor minimal. It splits in terms of both the push vs. pull *and* global vs. targeted aspects, while maintaining a shared concept for an "event". ### Why I Like This - The term "event" remains as a single concept for all the different kinds of events in Bevy. - Despite all event types being "events", they use fundamentally different APIs. Instead of assuming that you can use an event type with any pattern (when only one is typically supported), you explicitly opt in to each one with dedicated traits. - Using separate traits for each type of event helps with documentation and clearer function signatures. - I can safely make assumptions on expected usage. - If I see that an event is an `EntityEvent`, I can assume that I can use `observe` on it and get targeted events. - If I see that an event is a `BufferedEvent`, I can assume that I can use `EventReader` to read events. - If I see both `EntityEvent` and `BufferedEvent`, I can assume that both APIs are supported. In summary: This allows for a unified concept for events, while limiting the different ways to use them with opt-in traits. No more guess-work involved when using APIs. ### Problems? - Because `BufferedEvent` implements `Event` (for more consistent semantics etc.), you can still use all buffered events for non-targeted observers. I think this is fine/good. The important part is that if you see that an event implements `BufferedEvent`, you know that the `EventReader`/`EventWriter` API should be supported. Whether it *also* supports other APIs is secondary. - I currently only support `trigger_targets` for an `EntityEvent`. However, you can technically target components too, without targeting any entities. I consider that such a niche and advanced use case that it's not a huge problem to only support it for `EntityEvent`s, but we could also split `trigger_targets` into `trigger_entities` and `trigger_components` if we wanted to (or implement components as entities :P). - You can still trigger an `EntityEvent` *without* targets. I consider this correct, since `Event` implements the non-targeted behavior, and it'd be weird if implementing another trait *removed* behavior. However, it does mean that global observers for entity events can technically return `Entity::PLACEHOLDER` again (since I got rid of the `Option<Entity>` added in #19440 for ergonomics). I think that's enough of an edge case that it's not a huge problem, but it is worth keeping in mind. - ~~Deriving both `EntityEvent` and `BufferedEvent` for the same type currently duplicates the `Event` implementation, so you instead need to manually implement one of them.~~ Changed to always requiring `Event` to be derived. ## Related Work There are plans to implement multi-event support for observers, especially for UI contexts. [Cart's example](https://github.com/bevyengine/bevy/issues/14649#issuecomment-2960402508) API looked like this: ```rust // Truncated for brevity trigger: Trigger<( OnAdd<Pressed>, OnRemove<Pressed>, OnAdd<InteractionDisabled>, OnRemove<InteractionDisabled>, OnInsert<Hovered>, )>, ``` I believe this shouldn't be in conflict with this PR. If anything, this PR might *help* achieve the multi-event pattern for entity observers with fewer footguns: by statically enforcing that all of these events are `EntityEvent`s in the context of `EntityCommands::observe`, we can avoid misuse or weird cases where *some* events inside the trigger are targeted while others are not.
546 lines
20 KiB
Rust
546 lines
20 KiB
Rust
use bevy_ecs::resource::Resource;
|
|
use core::time::Duration;
|
|
#[cfg(feature = "bevy_reflect")]
|
|
use {
|
|
bevy_ecs::reflect::ReflectResource,
|
|
bevy_reflect::{std_traits::ReflectDefault, Reflect},
|
|
};
|
|
|
|
/// A generic clock resource that tracks how much it has advanced since its
|
|
/// previous update and since its creation.
|
|
///
|
|
/// Multiple instances of this resource are inserted automatically by
|
|
/// [`TimePlugin`](crate::TimePlugin):
|
|
///
|
|
/// - [`Time<Real>`](crate::real::Real) tracks real wall-clock time elapsed.
|
|
/// - [`Time<Virtual>`](crate::virt::Virtual) tracks virtual game time that may
|
|
/// be paused or scaled.
|
|
/// - [`Time<Fixed>`](crate::fixed::Fixed) tracks fixed timesteps based on
|
|
/// virtual time.
|
|
/// - [`Time`] is a generic clock that corresponds to "current" or "default"
|
|
/// time for systems. It contains [`Time<Virtual>`](crate::virt::Virtual)
|
|
/// except inside the [`FixedMain`](bevy_app::FixedMain) schedule when it
|
|
/// contains [`Time<Fixed>`](crate::fixed::Fixed).
|
|
///
|
|
/// The time elapsed since the previous time this clock was advanced is saved as
|
|
/// [`delta()`](Time::delta) and the total amount of time the clock has advanced
|
|
/// is saved as [`elapsed()`](Time::elapsed). Both are represented as exact
|
|
/// [`Duration`] values with fixed nanosecond precision. The clock does not
|
|
/// support time moving backwards, but it can be updated with [`Duration::ZERO`]
|
|
/// which will set [`delta()`](Time::delta) to zero.
|
|
///
|
|
/// These values are also available in seconds as `f32` via
|
|
/// [`delta_secs()`](Time::delta_secs) and
|
|
/// [`elapsed_secs()`](Time::elapsed_secs), and also in seconds as `f64`
|
|
/// via [`delta_secs_f64()`](Time::delta_secs_f64) and
|
|
/// [`elapsed_secs_f64()`](Time::elapsed_secs_f64).
|
|
///
|
|
/// Since [`elapsed_secs()`](Time::elapsed_secs) will grow constantly and
|
|
/// is `f32`, it will exhibit gradual precision loss. For applications that
|
|
/// require an `f32` value but suffer from gradual precision loss there is
|
|
/// [`elapsed_secs_wrapped()`](Time::elapsed_secs_wrapped) available. The
|
|
/// same wrapped value is also available as [`Duration`] and `f64` for
|
|
/// consistency. The wrap period is by default 1 hour, and can be set by
|
|
/// [`set_wrap_period()`](Time::set_wrap_period).
|
|
///
|
|
/// # Accessing clocks
|
|
///
|
|
/// By default, any systems requiring current [`delta()`](Time::delta) or
|
|
/// [`elapsed()`](Time::elapsed) should use `Res<Time>` to access the default
|
|
/// time configured for the program. By default, this refers to
|
|
/// [`Time<Virtual>`](crate::virt::Virtual) except during the
|
|
/// [`FixedMain`](bevy_app::FixedMain) schedule when it refers to
|
|
/// [`Time<Fixed>`](crate::fixed::Fixed). This ensures your system can be used
|
|
/// either in [`Update`](bevy_app::Update) or
|
|
/// [`FixedUpdate`](bevy_app::FixedUpdate) schedule depending on what is needed.
|
|
///
|
|
/// ```
|
|
/// # use bevy_ecs::prelude::*;
|
|
/// # use bevy_time::prelude::*;
|
|
/// #
|
|
/// fn ambivalent_system(time: Res<Time>) {
|
|
/// println!("this how I see time: delta {:?}, elapsed {:?}", time.delta(), time.elapsed());
|
|
/// }
|
|
/// ```
|
|
///
|
|
/// If your system needs to react based on real time (wall clock time), like for
|
|
/// user interfaces, it should use `Res<Time<Real>>`. The
|
|
/// [`delta()`](Time::delta) and [`elapsed()`](Time::elapsed) values will always
|
|
/// correspond to real time and will not be affected by pause, time scaling or
|
|
/// other tweaks.
|
|
///
|
|
/// ```
|
|
/// # use bevy_ecs::prelude::*;
|
|
/// # use bevy_time::prelude::*;
|
|
/// #
|
|
/// fn real_time_system(time: Res<Time<Real>>) {
|
|
/// println!("this will always be real time: delta {:?}, elapsed {:?}", time.delta(), time.elapsed());
|
|
/// }
|
|
/// ```
|
|
///
|
|
/// If your system specifically needs to access fixed timestep clock, even when
|
|
/// placed in `Update` schedule, you should use `Res<Time<Fixed>>`. The
|
|
/// [`delta()`](Time::delta) and [`elapsed()`](Time::elapsed) values will
|
|
/// correspond to the latest fixed timestep that has been run.
|
|
///
|
|
/// ```
|
|
/// # use bevy_ecs::prelude::*;
|
|
/// # use bevy_time::prelude::*;
|
|
/// #
|
|
/// fn fixed_time_system(time: Res<Time<Fixed>>) {
|
|
/// println!("this will always be the last executed fixed timestep: delta {:?}, elapsed {:?}", time.delta(), time.elapsed());
|
|
/// }
|
|
/// ```
|
|
///
|
|
/// Finally, if your system specifically needs to know the current virtual game
|
|
/// time, even if placed inside [`FixedUpdate`](bevy_app::FixedUpdate), for
|
|
/// example to know if the game is [`was_paused()`](Time::was_paused) or to use
|
|
/// [`effective_speed()`](Time::effective_speed), you can use
|
|
/// `Res<Time<Virtual>>`. However, if the system is placed in
|
|
/// [`FixedUpdate`](bevy_app::FixedUpdate), extra care must be used because your
|
|
/// system might be run multiple times with the same [`delta()`](Time::delta)
|
|
/// and [`elapsed()`](Time::elapsed) values as the virtual game time has not
|
|
/// changed between the iterations.
|
|
///
|
|
/// ```
|
|
/// # use bevy_ecs::prelude::*;
|
|
/// # use bevy_time::prelude::*;
|
|
/// #
|
|
/// fn fixed_time_system(time: Res<Time<Virtual>>) {
|
|
/// println!("this will be virtual time for this update: delta {:?}, elapsed {:?}", time.delta(), time.elapsed());
|
|
/// println!("also the relative speed of the game is now {}", time.effective_speed());
|
|
/// }
|
|
/// ```
|
|
///
|
|
/// If you need to change the settings for any of the clocks, for example to
|
|
/// [`pause()`](Time::pause) the game, you should use `ResMut<Time<Virtual>>`.
|
|
///
|
|
/// ```
|
|
/// # use bevy_ecs::prelude::*;
|
|
/// # use bevy_time::prelude::*;
|
|
/// #
|
|
/// #[derive(Event, BufferedEvent)]
|
|
/// struct PauseEvent(bool);
|
|
///
|
|
/// fn pause_system(mut time: ResMut<Time<Virtual>>, mut events: EventReader<PauseEvent>) {
|
|
/// for ev in events.read() {
|
|
/// if ev.0 {
|
|
/// time.pause();
|
|
/// } else {
|
|
/// time.unpause();
|
|
/// }
|
|
/// }
|
|
/// }
|
|
/// ```
|
|
///
|
|
/// # Adding custom clocks
|
|
///
|
|
/// New custom clocks can be created by creating your own struct as a context
|
|
/// and passing it to [`new_with()`](Time::new_with). These clocks can be
|
|
/// inserted as resources as normal and then accessed by systems. You can use
|
|
/// the [`advance_by()`](Time::advance_by) or [`advance_to()`](Time::advance_to)
|
|
/// methods to move the clock forwards based on your own logic.
|
|
///
|
|
/// If you want to add methods for your time instance and they require access to
|
|
/// both your context and the generic time part, it's probably simplest to add a
|
|
/// custom trait for them and implement it for `Time<Custom>`.
|
|
///
|
|
/// Your context struct will need to implement the [`Default`] trait because
|
|
/// [`Time`] structures support reflection. It also makes initialization trivial
|
|
/// by being able to call `app.init_resource::<Time<Custom>>()`.
|
|
///
|
|
/// You can also replace the "generic" `Time` clock resource if the "default"
|
|
/// time for your game should not be the default virtual time provided. You can
|
|
/// get a "generic" snapshot of your clock by calling `as_generic()` and then
|
|
/// overwrite the [`Time`] resource with it. The default systems added by
|
|
/// [`TimePlugin`](crate::TimePlugin) will overwrite the [`Time`] clock during
|
|
/// [`First`](bevy_app::First) and [`FixedUpdate`](bevy_app::FixedUpdate)
|
|
/// schedules.
|
|
///
|
|
/// ```
|
|
/// # use bevy_ecs::prelude::*;
|
|
/// # use bevy_time::prelude::*;
|
|
/// # use bevy_platform::time::Instant;
|
|
/// #
|
|
/// #[derive(Debug)]
|
|
/// struct Custom {
|
|
/// last_external_time: Instant,
|
|
/// }
|
|
///
|
|
/// impl Default for Custom {
|
|
/// fn default() -> Self {
|
|
/// Self {
|
|
/// last_external_time: Instant::now(),
|
|
/// }
|
|
/// }
|
|
/// }
|
|
///
|
|
/// trait CustomTime {
|
|
/// fn update_from_external(&mut self, instant: Instant);
|
|
/// }
|
|
///
|
|
/// impl CustomTime for Time<Custom> {
|
|
/// fn update_from_external(&mut self, instant: Instant) {
|
|
/// let delta = instant - self.context().last_external_time;
|
|
/// self.advance_by(delta);
|
|
/// self.context_mut().last_external_time = instant;
|
|
/// }
|
|
/// }
|
|
/// ```
|
|
#[derive(Resource, Debug, Copy, Clone)]
|
|
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Resource, Default))]
|
|
pub struct Time<T: Default = ()> {
|
|
context: T,
|
|
wrap_period: Duration,
|
|
delta: Duration,
|
|
delta_secs: f32,
|
|
delta_secs_f64: f64,
|
|
elapsed: Duration,
|
|
elapsed_secs: f32,
|
|
elapsed_secs_f64: f64,
|
|
elapsed_wrapped: Duration,
|
|
elapsed_secs_wrapped: f32,
|
|
elapsed_secs_wrapped_f64: f64,
|
|
}
|
|
|
|
impl<T: Default> Time<T> {
|
|
const DEFAULT_WRAP_PERIOD: Duration = Duration::from_secs(3600); // 1 hour
|
|
|
|
/// Create a new clock from context with [`Self::delta`] and [`Self::elapsed`] starting from
|
|
/// zero.
|
|
pub fn new_with(context: T) -> Self {
|
|
Self {
|
|
context,
|
|
..Default::default()
|
|
}
|
|
}
|
|
|
|
/// Advance this clock by adding a `delta` duration to it.
|
|
///
|
|
/// The added duration will be returned by [`Self::delta`] and
|
|
/// [`Self::elapsed`] will be increased by the duration. Adding
|
|
/// [`Duration::ZERO`] is allowed and will set [`Self::delta`] to zero.
|
|
pub fn advance_by(&mut self, delta: Duration) {
|
|
self.delta = delta;
|
|
self.delta_secs = self.delta.as_secs_f32();
|
|
self.delta_secs_f64 = self.delta.as_secs_f64();
|
|
self.elapsed += delta;
|
|
self.elapsed_secs = self.elapsed.as_secs_f32();
|
|
self.elapsed_secs_f64 = self.elapsed.as_secs_f64();
|
|
self.elapsed_wrapped = duration_rem(self.elapsed, self.wrap_period);
|
|
self.elapsed_secs_wrapped = self.elapsed_wrapped.as_secs_f32();
|
|
self.elapsed_secs_wrapped_f64 = self.elapsed_wrapped.as_secs_f64();
|
|
}
|
|
|
|
/// Advance this clock to a specific `elapsed` time.
|
|
///
|
|
/// [`Self::delta()`] will return the amount of time the clock was advanced
|
|
/// and [`Self::elapsed()`] will be the `elapsed` value passed in. Cannot be
|
|
/// used to move time backwards.
|
|
///
|
|
/// # Panics
|
|
///
|
|
/// Panics if `elapsed` is less than `Self::elapsed()`.
|
|
pub fn advance_to(&mut self, elapsed: Duration) {
|
|
assert!(
|
|
elapsed >= self.elapsed,
|
|
"tried to move time backwards to an earlier elapsed moment"
|
|
);
|
|
self.advance_by(elapsed - self.elapsed);
|
|
}
|
|
|
|
/// Returns the modulus used to calculate [`elapsed_wrapped`](#method.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).
|
|
///
|
|
/// **Note:** This will not take effect until the next update.
|
|
///
|
|
/// # Panics
|
|
///
|
|
/// Panics if `wrap_period` is a zero-length duration.
|
|
#[inline]
|
|
pub fn set_wrap_period(&mut self, wrap_period: Duration) {
|
|
assert!(!wrap_period.is_zero(), "division by zero");
|
|
self.wrap_period = wrap_period;
|
|
}
|
|
|
|
/// Returns how much time has advanced since the last [`update`](#method.update), as a
|
|
/// [`Duration`].
|
|
#[inline]
|
|
pub fn delta(&self) -> Duration {
|
|
self.delta
|
|
}
|
|
|
|
/// Returns how much time has advanced since the last [`update`](#method.update), as [`f32`]
|
|
/// seconds.
|
|
#[inline]
|
|
pub fn delta_secs(&self) -> f32 {
|
|
self.delta_secs
|
|
}
|
|
|
|
/// Returns how much time has advanced since the last [`update`](#method.update), as [`f64`]
|
|
/// seconds.
|
|
#[inline]
|
|
pub fn delta_secs_f64(&self) -> f64 {
|
|
self.delta_secs_f64
|
|
}
|
|
|
|
/// 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.
|
|
///
|
|
/// **Note:** This is a monotonically increasing value. Its precision will degrade over time.
|
|
/// If you need an `f32` but that precision loss is unacceptable,
|
|
/// use [`elapsed_secs_wrapped`](#method.elapsed_secs_wrapped).
|
|
#[inline]
|
|
pub fn elapsed_secs(&self) -> f32 {
|
|
self.elapsed_secs
|
|
}
|
|
|
|
/// Returns how much time has advanced since [`startup`](#method.startup), as [`f64`] seconds.
|
|
#[inline]
|
|
pub fn elapsed_secs_f64(&self) -> f64 {
|
|
self.elapsed_secs_f64
|
|
}
|
|
|
|
/// 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.
|
|
///
|
|
/// This method is intended for applications (e.g. shaders) that require an [`f32`] value but
|
|
/// suffer from the gradual precision loss of [`elapsed_secs`](#method.elapsed_secs).
|
|
#[inline]
|
|
pub fn elapsed_secs_wrapped(&self) -> f32 {
|
|
self.elapsed_secs_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_secs_wrapped_f64(&self) -> f64 {
|
|
self.elapsed_secs_wrapped_f64
|
|
}
|
|
|
|
/// Returns a reference to the context of this specific clock.
|
|
#[inline]
|
|
pub fn context(&self) -> &T {
|
|
&self.context
|
|
}
|
|
|
|
/// Returns a mutable reference to the context of this specific clock.
|
|
#[inline]
|
|
pub fn context_mut(&mut self) -> &mut T {
|
|
&mut self.context
|
|
}
|
|
|
|
/// Returns a copy of this clock as fully generic clock without context.
|
|
#[inline]
|
|
pub fn as_generic(&self) -> Time<()> {
|
|
Time {
|
|
context: (),
|
|
wrap_period: self.wrap_period,
|
|
delta: self.delta,
|
|
delta_secs: self.delta_secs,
|
|
delta_secs_f64: self.delta_secs_f64,
|
|
elapsed: self.elapsed,
|
|
elapsed_secs: self.elapsed_secs,
|
|
elapsed_secs_f64: self.elapsed_secs_f64,
|
|
elapsed_wrapped: self.elapsed_wrapped,
|
|
elapsed_secs_wrapped: self.elapsed_secs_wrapped,
|
|
elapsed_secs_wrapped_f64: self.elapsed_secs_wrapped_f64,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<T: Default> Default for Time<T> {
|
|
fn default() -> Self {
|
|
Self {
|
|
context: Default::default(),
|
|
wrap_period: Self::DEFAULT_WRAP_PERIOD,
|
|
delta: Duration::ZERO,
|
|
delta_secs: 0.0,
|
|
delta_secs_f64: 0.0,
|
|
elapsed: Duration::ZERO,
|
|
elapsed_secs: 0.0,
|
|
elapsed_secs_f64: 0.0,
|
|
elapsed_wrapped: Duration::ZERO,
|
|
elapsed_secs_wrapped: 0.0,
|
|
elapsed_secs_wrapped_f64: 0.0,
|
|
}
|
|
}
|
|
}
|
|
|
|
fn duration_rem(dividend: Duration, divisor: Duration) -> Duration {
|
|
// `Duration` does not have a built-in modulo operation
|
|
let quotient = (dividend.as_nanos() / divisor.as_nanos()) as u32;
|
|
dividend - (quotient * divisor)
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod test {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_initial_state() {
|
|
let time: Time = Time::default();
|
|
|
|
assert_eq!(time.wrap_period(), Time::<()>::DEFAULT_WRAP_PERIOD);
|
|
assert_eq!(time.delta(), Duration::ZERO);
|
|
assert_eq!(time.delta_secs(), 0.0);
|
|
assert_eq!(time.delta_secs_f64(), 0.0);
|
|
assert_eq!(time.elapsed(), Duration::ZERO);
|
|
assert_eq!(time.elapsed_secs(), 0.0);
|
|
assert_eq!(time.elapsed_secs_f64(), 0.0);
|
|
assert_eq!(time.elapsed_wrapped(), Duration::ZERO);
|
|
assert_eq!(time.elapsed_secs_wrapped(), 0.0);
|
|
assert_eq!(time.elapsed_secs_wrapped_f64(), 0.0);
|
|
}
|
|
|
|
#[test]
|
|
fn test_advance_by() {
|
|
let mut time: Time = Time::default();
|
|
|
|
time.advance_by(Duration::from_millis(250));
|
|
|
|
assert_eq!(time.delta(), Duration::from_millis(250));
|
|
assert_eq!(time.delta_secs(), 0.25);
|
|
assert_eq!(time.delta_secs_f64(), 0.25);
|
|
assert_eq!(time.elapsed(), Duration::from_millis(250));
|
|
assert_eq!(time.elapsed_secs(), 0.25);
|
|
assert_eq!(time.elapsed_secs_f64(), 0.25);
|
|
|
|
time.advance_by(Duration::from_millis(500));
|
|
|
|
assert_eq!(time.delta(), Duration::from_millis(500));
|
|
assert_eq!(time.delta_secs(), 0.5);
|
|
assert_eq!(time.delta_secs_f64(), 0.5);
|
|
assert_eq!(time.elapsed(), Duration::from_millis(750));
|
|
assert_eq!(time.elapsed_secs(), 0.75);
|
|
assert_eq!(time.elapsed_secs_f64(), 0.75);
|
|
|
|
time.advance_by(Duration::ZERO);
|
|
|
|
assert_eq!(time.delta(), Duration::ZERO);
|
|
assert_eq!(time.delta_secs(), 0.0);
|
|
assert_eq!(time.delta_secs_f64(), 0.0);
|
|
assert_eq!(time.elapsed(), Duration::from_millis(750));
|
|
assert_eq!(time.elapsed_secs(), 0.75);
|
|
assert_eq!(time.elapsed_secs_f64(), 0.75);
|
|
}
|
|
|
|
#[test]
|
|
fn test_advance_to() {
|
|
let mut time: Time = Time::default();
|
|
|
|
time.advance_to(Duration::from_millis(250));
|
|
|
|
assert_eq!(time.delta(), Duration::from_millis(250));
|
|
assert_eq!(time.delta_secs(), 0.25);
|
|
assert_eq!(time.delta_secs_f64(), 0.25);
|
|
assert_eq!(time.elapsed(), Duration::from_millis(250));
|
|
assert_eq!(time.elapsed_secs(), 0.25);
|
|
assert_eq!(time.elapsed_secs_f64(), 0.25);
|
|
|
|
time.advance_to(Duration::from_millis(750));
|
|
|
|
assert_eq!(time.delta(), Duration::from_millis(500));
|
|
assert_eq!(time.delta_secs(), 0.5);
|
|
assert_eq!(time.delta_secs_f64(), 0.5);
|
|
assert_eq!(time.elapsed(), Duration::from_millis(750));
|
|
assert_eq!(time.elapsed_secs(), 0.75);
|
|
assert_eq!(time.elapsed_secs_f64(), 0.75);
|
|
|
|
time.advance_to(Duration::from_millis(750));
|
|
|
|
assert_eq!(time.delta(), Duration::ZERO);
|
|
assert_eq!(time.delta_secs(), 0.0);
|
|
assert_eq!(time.delta_secs_f64(), 0.0);
|
|
assert_eq!(time.elapsed(), Duration::from_millis(750));
|
|
assert_eq!(time.elapsed_secs(), 0.75);
|
|
assert_eq!(time.elapsed_secs_f64(), 0.75);
|
|
}
|
|
|
|
#[test]
|
|
#[should_panic]
|
|
fn test_advance_to_backwards_panics() {
|
|
let mut time: Time = Time::default();
|
|
|
|
time.advance_to(Duration::from_millis(750));
|
|
|
|
time.advance_to(Duration::from_millis(250));
|
|
}
|
|
|
|
#[test]
|
|
fn test_wrapping() {
|
|
let mut time: Time = Time::default();
|
|
time.set_wrap_period(Duration::from_secs(3));
|
|
|
|
time.advance_by(Duration::from_secs(2));
|
|
|
|
assert_eq!(time.elapsed_wrapped(), Duration::from_secs(2));
|
|
assert_eq!(time.elapsed_secs_wrapped(), 2.0);
|
|
assert_eq!(time.elapsed_secs_wrapped_f64(), 2.0);
|
|
|
|
time.advance_by(Duration::from_secs(2));
|
|
|
|
assert_eq!(time.elapsed_wrapped(), Duration::from_secs(1));
|
|
assert_eq!(time.elapsed_secs_wrapped(), 1.0);
|
|
assert_eq!(time.elapsed_secs_wrapped_f64(), 1.0);
|
|
|
|
time.advance_by(Duration::from_secs(2));
|
|
|
|
assert_eq!(time.elapsed_wrapped(), Duration::ZERO);
|
|
assert_eq!(time.elapsed_secs_wrapped(), 0.0);
|
|
assert_eq!(time.elapsed_secs_wrapped_f64(), 0.0);
|
|
|
|
time.advance_by(Duration::new(3, 250_000_000));
|
|
|
|
assert_eq!(time.elapsed_wrapped(), Duration::from_millis(250));
|
|
assert_eq!(time.elapsed_secs_wrapped(), 0.25);
|
|
assert_eq!(time.elapsed_secs_wrapped_f64(), 0.25);
|
|
}
|
|
|
|
#[test]
|
|
fn test_wrapping_change() {
|
|
let mut time: Time = Time::default();
|
|
time.set_wrap_period(Duration::from_secs(5));
|
|
|
|
time.advance_by(Duration::from_secs(8));
|
|
|
|
assert_eq!(time.elapsed_wrapped(), Duration::from_secs(3));
|
|
assert_eq!(time.elapsed_secs_wrapped(), 3.0);
|
|
assert_eq!(time.elapsed_secs_wrapped_f64(), 3.0);
|
|
|
|
time.set_wrap_period(Duration::from_secs(2));
|
|
|
|
assert_eq!(time.elapsed_wrapped(), Duration::from_secs(3));
|
|
assert_eq!(time.elapsed_secs_wrapped(), 3.0);
|
|
assert_eq!(time.elapsed_secs_wrapped_f64(), 3.0);
|
|
|
|
time.advance_by(Duration::ZERO);
|
|
|
|
// Time will wrap to modulo duration from full `elapsed()`, not to what
|
|
// is left in `elapsed_wrapped()`. This test of values is here to ensure
|
|
// that we notice if we change that behavior.
|
|
assert_eq!(time.elapsed_wrapped(), Duration::from_secs(0));
|
|
assert_eq!(time.elapsed_secs_wrapped(), 0.0);
|
|
assert_eq!(time.elapsed_secs_wrapped_f64(), 0.0);
|
|
}
|
|
}
|