#[cfg(feature = "bevy_reflect")]1use bevy_reflect::Reflect;2use core::time::Duration;3use log::debug;45use crate::{real::Real, time::Time};67/// The virtual game clock representing game time.8///9/// A specialization of the [`Time`] structure. **For method documentation, see10/// [`Time<Virtual>#impl-Time<Virtual>`].**11///12/// Normally used as `Time<Virtual>`. It is automatically inserted as a resource13/// by [`TimePlugin`](crate::TimePlugin) and updated based on14/// [`Time<Real>`](Real). The virtual clock is automatically set as the default15/// generic [`Time`] resource for the update.16///17/// The virtual clock differs from real time clock in that it can be paused, sped up18/// and slowed down. It also limits how much it can advance in a single update19/// in order to prevent unexpected behavior in cases where updates do not happen20/// at regular intervals (e.g. coming back after the program was suspended a long time).21///22/// The virtual clock can be paused by calling [`pause()`](Time::pause),23/// unpaused by calling [`unpause()`](Time::unpause), or toggled by calling24/// [`toggle()`](Time::toggle). When the game clock is25/// paused [`delta()`](Time::delta) will be zero on each update, and26/// [`elapsed()`](Time::elapsed) will not grow.27/// [`effective_speed()`](Time::effective_speed) will return `0.0`. Calling28/// [`pause()`](Time::pause) will not affect value the [`delta()`](Time::delta)29/// value for the update currently being processed.30///31/// The speed of the virtual clock can be changed by calling32/// [`set_relative_speed()`](Time::set_relative_speed). A value of `2.0` means33/// that virtual clock should advance twice as fast as real time, meaning that34/// [`delta()`](Time::delta) values will be double of what35/// [`Time<Real>::delta()`](Time::delta) reports and36/// [`elapsed()`](Time::elapsed) will go twice as fast as37/// [`Time<Real>::elapsed()`](Time::elapsed). Calling38/// [`set_relative_speed()`](Time::set_relative_speed) will not affect the39/// [`delta()`](Time::delta) value for the update currently being processed.40///41/// The maximum amount of delta time that can be added by a single update can be42/// set by [`set_max_delta()`](Time::set_max_delta). This value serves a dual43/// purpose in the virtual clock.44///45/// If the game temporarily freezes due to any reason, such as disk access, a46/// blocking system call, or operating system level suspend, reporting the full47/// elapsed delta time is likely to cause bugs in game logic. Usually if a48/// laptop is suspended for an hour, it doesn't make sense to try to simulate49/// the game logic for the elapsed hour when resuming. Instead it is better to50/// lose the extra time and pretend a shorter duration of time passed. Setting51/// [`max_delta()`](Time::max_delta) to a relatively short time means that the52/// impact on game logic will be minimal.53///54/// If the game lags for some reason, meaning that it will take a longer time to55/// compute a frame than the real time that passes during the computation, then56/// we would fall behind in processing virtual time. If this situation persists,57/// and computing a frame takes longer depending on how much virtual time has58/// passed, the game would enter a "death spiral" where computing each frame59/// takes longer and longer and the game will appear to freeze. By limiting the60/// maximum time that can be added at once, we also limit the amount of virtual61/// time the game needs to compute for each frame. This means that the game will62/// run slow, and it will run slower than real time, but it will not freeze and63/// it will recover as soon as computation becomes fast again.64///65/// You should set [`max_delta()`](Time::max_delta) to a value that is66/// approximately the minimum FPS your game should have even if heavily lagged67/// for a moment. The actual FPS when lagged will be somewhat lower than this,68/// depending on how much more time it takes to compute a frame compared to real69/// time. You should also consider how stable your FPS is, as the limit will70/// also dictate how big of an FPS drop you can accept without losing time and71/// falling behind real time.72#[derive(Debug, Copy, Clone)]73#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Clone))]74pub struct Virtual {75max_delta: Duration,76paused: bool,77relative_speed: f64,78effective_speed: f64,79}8081impl Time<Virtual> {82/// The default amount of time that can added in a single update.83///84/// Equal to 250 milliseconds.85const DEFAULT_MAX_DELTA: Duration = Duration::from_millis(250);8687/// Create new virtual clock with given maximum delta step [`Duration`]88///89/// # Panics90///91/// Panics if `max_delta` is zero.92pub fn from_max_delta(max_delta: Duration) -> Self {93let mut ret = Self::default();94ret.set_max_delta(max_delta);95ret96}9798/// Returns the maximum amount of time that can be added to this clock by a99/// single update, as [`Duration`].100///101/// This is the maximum value [`Self::delta()`] will return and also to102/// maximum time [`Self::elapsed()`] will be increased by in a single103/// update.104///105/// This ensures that even if no updates happen for an extended amount of time,106/// the clock will not have a sudden, huge advance all at once. This also indirectly107/// limits the maximum number of fixed update steps that can run in a single update.108///109/// The default value is 250 milliseconds.110#[inline]111pub fn max_delta(&self) -> Duration {112self.context().max_delta113}114115/// Sets the maximum amount of time that can be added to this clock by a116/// single update, as [`Duration`].117///118/// This is the maximum value [`Self::delta()`] will return and also to119/// maximum time [`Self::elapsed()`] will be increased by in a single120/// update.121///122/// This is used to ensure that even if the game freezes for a few seconds,123/// or is suspended for hours or even days, the virtual clock doesn't124/// suddenly jump forward for that full amount, which would likely cause125/// gameplay bugs or having to suddenly simulate all the intervening time.126///127/// If no updates happen for an extended amount of time, this limit prevents128/// having a sudden, huge advance all at once. This also indirectly limits129/// the maximum number of fixed update steps that can run in a single130/// update.131///132/// The default value is 250 milliseconds. If you want to disable this133/// feature, set the value to [`Duration::MAX`].134///135/// # Panics136///137/// Panics if `max_delta` is zero.138#[inline]139pub fn set_max_delta(&mut self, max_delta: Duration) {140assert_ne!(max_delta, Duration::ZERO, "tried to set max delta to zero");141self.context_mut().max_delta = max_delta;142}143144/// Returns the speed the clock advances relative to your system clock, as [`f32`].145/// This is known as "time scaling" or "time dilation" in other engines.146#[inline]147pub fn relative_speed(&self) -> f32 {148self.relative_speed_f64() as f32149}150151/// Returns the speed the clock advances relative to your system clock, as [`f64`].152/// This is known as "time scaling" or "time dilation" in other engines.153#[inline]154pub fn relative_speed_f64(&self) -> f64 {155self.context().relative_speed156}157158/// Returns the speed the clock advanced relative to your system clock in159/// this update, as [`f32`].160///161/// Returns `0.0` if the game was paused or what the `relative_speed` value162/// was at the start of this update.163#[inline]164pub fn effective_speed(&self) -> f32 {165self.context().effective_speed as f32166}167168/// Returns the speed the clock advanced relative to your system clock in169/// this update, as [`f64`].170///171/// Returns `0.0` if the game was paused or what the `relative_speed` value172/// was at the start of this update.173#[inline]174pub fn effective_speed_f64(&self) -> f64 {175self.context().effective_speed176}177178/// Sets the speed the clock advances relative to your system clock, given as an [`f32`].179///180/// For example, setting this to `2.0` will make the clock advance twice as fast as your system181/// clock.182///183/// # Panics184///185/// Panics if `ratio` is negative or not finite.186#[inline]187pub fn set_relative_speed(&mut self, ratio: f32) {188self.set_relative_speed_f64(ratio as f64);189}190191/// Sets the speed the clock advances relative to your system clock, given as an [`f64`].192///193/// For example, setting this to `2.0` will make the clock advance twice as fast as your system194/// clock.195///196/// # Panics197///198/// Panics if `ratio` is negative or not finite.199#[inline]200pub fn set_relative_speed_f64(&mut self, ratio: f64) {201assert!(ratio.is_finite(), "tried to go infinitely fast");202assert!(ratio >= 0.0, "tried to go back in time");203self.context_mut().relative_speed = ratio;204}205206/// Stops the clock if it is running, otherwise resumes the clock.207#[inline]208pub fn toggle(&mut self) {209self.context_mut().paused ^= true;210}211212/// Stops the clock, preventing it from advancing until resumed.213#[inline]214pub fn pause(&mut self) {215self.context_mut().paused = true;216}217218/// Resumes the clock.219#[inline]220pub fn unpause(&mut self) {221self.context_mut().paused = false;222}223224/// Returns `true` if the clock is currently paused.225#[inline]226pub fn is_paused(&self) -> bool {227self.context().paused228}229230/// Returns `true` if the clock was paused at the start of this update.231#[inline]232pub fn was_paused(&self) -> bool {233self.context().effective_speed == 0.0234}235236/// Updates the elapsed duration of `self` by `raw_delta`, up to the `max_delta`.237fn advance_with_raw_delta(&mut self, raw_delta: Duration) {238let max_delta = self.context().max_delta;239let clamped_delta = if raw_delta > max_delta {240debug!(241"delta time larger than maximum delta, clamping delta to {:?} and skipping {:?}",242max_delta,243raw_delta - max_delta244);245max_delta246} else {247raw_delta248};249let effective_speed = if self.context().paused {2500.0251} else {252self.context().relative_speed253};254let delta = if effective_speed != 1.0 {255clamped_delta.mul_f64(effective_speed)256} else {257// avoid rounding when at normal speed258clamped_delta259};260self.context_mut().effective_speed = effective_speed;261self.advance_by(delta);262}263}264265impl Default for Virtual {266fn default() -> Self {267Self {268max_delta: Time::<Virtual>::DEFAULT_MAX_DELTA,269paused: false,270relative_speed: 1.0,271effective_speed: 1.0,272}273}274}275276/// Advances [`Time<Virtual>`] and [`Time`] based on the elapsed [`Time<Real>`].277///278/// The virtual time will be advanced up to the provided [`Time::max_delta`].279pub fn update_virtual_time(current: &mut Time, virt: &mut Time<Virtual>, real: &Time<Real>) {280let raw_delta = real.delta();281virt.advance_with_raw_delta(raw_delta);282*current = virt.as_generic();283}284285#[cfg(test)]286mod test {287use super::*;288289#[test]290fn test_default() {291let time = Time::<Virtual>::default();292293assert!(!time.is_paused()); // false294assert_eq!(time.relative_speed(), 1.0);295assert_eq!(time.max_delta(), Time::<Virtual>::DEFAULT_MAX_DELTA);296assert_eq!(time.delta(), Duration::ZERO);297assert_eq!(time.elapsed(), Duration::ZERO);298}299300#[test]301fn test_advance() {302let mut time = Time::<Virtual>::default();303304time.advance_with_raw_delta(Duration::from_millis(125));305306assert_eq!(time.delta(), Duration::from_millis(125));307assert_eq!(time.elapsed(), Duration::from_millis(125));308309time.advance_with_raw_delta(Duration::from_millis(125));310311assert_eq!(time.delta(), Duration::from_millis(125));312assert_eq!(time.elapsed(), Duration::from_millis(250));313314time.advance_with_raw_delta(Duration::from_millis(125));315316assert_eq!(time.delta(), Duration::from_millis(125));317assert_eq!(time.elapsed(), Duration::from_millis(375));318319time.advance_with_raw_delta(Duration::from_millis(125));320321assert_eq!(time.delta(), Duration::from_millis(125));322assert_eq!(time.elapsed(), Duration::from_millis(500));323}324325#[test]326fn test_relative_speed() {327let mut time = Time::<Virtual>::default();328329time.advance_with_raw_delta(Duration::from_millis(250));330331assert_eq!(time.relative_speed(), 1.0);332assert_eq!(time.effective_speed(), 1.0);333assert_eq!(time.delta(), Duration::from_millis(250));334assert_eq!(time.elapsed(), Duration::from_millis(250));335336time.set_relative_speed_f64(2.0);337338assert_eq!(time.relative_speed(), 2.0);339assert_eq!(time.effective_speed(), 1.0);340341time.advance_with_raw_delta(Duration::from_millis(250));342343assert_eq!(time.relative_speed(), 2.0);344assert_eq!(time.effective_speed(), 2.0);345assert_eq!(time.delta(), Duration::from_millis(500));346assert_eq!(time.elapsed(), Duration::from_millis(750));347348time.set_relative_speed_f64(0.5);349350assert_eq!(time.relative_speed(), 0.5);351assert_eq!(time.effective_speed(), 2.0);352353time.advance_with_raw_delta(Duration::from_millis(250));354355assert_eq!(time.relative_speed(), 0.5);356assert_eq!(time.effective_speed(), 0.5);357assert_eq!(time.delta(), Duration::from_millis(125));358assert_eq!(time.elapsed(), Duration::from_millis(875));359}360361#[test]362fn test_pause() {363let mut time = Time::<Virtual>::default();364365time.advance_with_raw_delta(Duration::from_millis(250));366367assert!(!time.is_paused()); // false368assert!(!time.was_paused()); // false369assert_eq!(time.relative_speed(), 1.0);370assert_eq!(time.effective_speed(), 1.0);371assert_eq!(time.delta(), Duration::from_millis(250));372assert_eq!(time.elapsed(), Duration::from_millis(250));373374time.pause();375376assert!(time.is_paused()); // true377assert!(!time.was_paused()); // false378assert_eq!(time.relative_speed(), 1.0);379assert_eq!(time.effective_speed(), 1.0);380381time.advance_with_raw_delta(Duration::from_millis(250));382383assert!(time.is_paused()); // true384assert!(time.was_paused()); // true385assert_eq!(time.relative_speed(), 1.0);386assert_eq!(time.effective_speed(), 0.0);387assert_eq!(time.delta(), Duration::ZERO);388assert_eq!(time.elapsed(), Duration::from_millis(250));389390time.unpause();391392assert!(!time.is_paused()); // false393assert!(time.was_paused()); // true394assert_eq!(time.relative_speed(), 1.0);395assert_eq!(time.effective_speed(), 0.0);396397time.advance_with_raw_delta(Duration::from_millis(250));398399assert!(!time.is_paused()); // false400assert!(!time.was_paused()); // false401assert_eq!(time.relative_speed(), 1.0);402assert_eq!(time.effective_speed(), 1.0);403assert_eq!(time.delta(), Duration::from_millis(250));404assert_eq!(time.elapsed(), Duration::from_millis(500));405}406407#[test]408fn test_max_delta() {409let mut time = Time::<Virtual>::default();410time.set_max_delta(Duration::from_millis(500));411412time.advance_with_raw_delta(Duration::from_millis(250));413414assert_eq!(time.delta(), Duration::from_millis(250));415assert_eq!(time.elapsed(), Duration::from_millis(250));416417time.advance_with_raw_delta(Duration::from_millis(500));418419assert_eq!(time.delta(), Duration::from_millis(500));420assert_eq!(time.elapsed(), Duration::from_millis(750));421422time.advance_with_raw_delta(Duration::from_millis(750));423424assert_eq!(time.delta(), Duration::from_millis(500));425assert_eq!(time.elapsed(), Duration::from_millis(1250));426427time.set_max_delta(Duration::from_secs(1));428429assert_eq!(time.max_delta(), Duration::from_secs(1));430431time.advance_with_raw_delta(Duration::from_millis(750));432433assert_eq!(time.delta(), Duration::from_millis(750));434assert_eq!(time.elapsed(), Duration::from_millis(2000));435436time.advance_with_raw_delta(Duration::from_millis(1250));437438assert_eq!(time.delta(), Duration::from_millis(1000));439assert_eq!(time.elapsed(), Duration::from_millis(3000));440}441}442443444