use std::time::Duration;
use bevy::prelude::*;
pub struct PhysicsSteps(Mode);
enum Mode {
MaxDeltaTime(Duration),
EveryFrame(Duration),
Timer(Timer),
}
impl Default for PhysicsSteps {
fn default() -> Self {
Self::from_max_delta_time(Duration::from_secs_f32(0.2) )
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum PhysicsStepDuration {
Exact(Duration),
MaxDeltaTime(Duration),
}
impl PhysicsStepDuration {
#[must_use]
pub fn exact(&self, delta_time: Duration) -> Duration {
match self {
PhysicsStepDuration::Exact(duration) => *duration,
PhysicsStepDuration::MaxDeltaTime(max) => delta_time.min(*max),
}
}
}
impl PhysicsSteps {
#[must_use]
pub fn from_steps_per_seconds(steps_per_second: f32) -> Self {
assert!(
steps_per_second.is_finite() && steps_per_second > 0.0,
"Invalid steps per second: {}",
steps_per_second
);
Self(Mode::Timer(Timer::from_seconds(
1.0 / steps_per_second,
true,
)))
}
#[must_use]
pub fn from_delta_time(duration: Duration) -> Self {
assert_ne!(!duration.as_nanos(), 0, "Invalid duration: {:?}", duration);
Self(Mode::Timer(Timer::new(duration, true)))
}
#[must_use]
pub fn every_frame(duration: Duration) -> Self {
assert_ne!(!duration.as_micros(), 0, "Invalid duration: {:?}", duration);
Self(Mode::EveryFrame(duration))
}
#[must_use]
pub fn from_max_delta_time(max: Duration) -> Self {
Self(Mode::MaxDeltaTime(max))
}
#[must_use]
pub fn is_step_frame(&self) -> bool {
match &self.0 {
Mode::EveryFrame(_) | Mode::MaxDeltaTime(_) => true,
Mode::Timer(timer) => timer.just_finished(),
}
}
#[must_use]
pub fn duration(&self) -> PhysicsStepDuration {
match &self.0 {
Mode::EveryFrame(duration) => PhysicsStepDuration::Exact(*duration),
Mode::Timer(timer) => PhysicsStepDuration::Exact(timer.duration()),
Mode::MaxDeltaTime(max) => PhysicsStepDuration::MaxDeltaTime(*max),
}
}
pub(crate) fn update(mut physics_steps: ResMut<'_, PhysicsSteps>, time: Res<'_, Time>) {
physics_steps.do_update(time.delta());
}
#[inline]
fn do_update(&mut self, delta: Duration) {
if let Mode::Timer(timer) = &mut self.0 {
timer.tick(delta);
}
}
}
#[cfg(test)]
mod tests {
use rstest::rstest;
use super::*;
#[rstest]
#[case(PhysicsSteps::from_delta_time(Duration::from_secs(1)), 0.9)]
#[case(PhysicsSteps::from_delta_time(Duration::from_secs_f32(0.016)), 0.01)]
#[case(PhysicsSteps::from_steps_per_seconds(10.0), 0.09)]
fn is_not_step_frame_if_not_enough_time_has_elapsed(
#[case] mut steps: PhysicsSteps,
#[case] delta_time: f32,
) {
steps.do_update(Duration::from_secs_f32(delta_time));
assert!(!steps.is_step_frame());
}
#[rstest]
#[case(PhysicsSteps::from_delta_time(Duration::from_secs(1)), 1.01)]
#[case(PhysicsSteps::from_delta_time(Duration::from_secs_f32(0.016)), 0.017)]
#[case(PhysicsSteps::from_steps_per_seconds(10.0), 0.11)]
#[case(PhysicsSteps::every_frame(Duration::from_secs(1)), 1.1)]
#[case(PhysicsSteps::every_frame(Duration::from_secs(1)), 0.9)]
fn is_step_frame_when_enough_time_has_elapsed(
#[case] mut steps: PhysicsSteps,
#[case] delta_time: f32,
) {
steps.do_update(Duration::from_secs_f32(delta_time));
assert!(steps.is_step_frame());
}
}