use std::time::Duration;
use bevy_time::Time;
use bevy_ecs::prelude::*;
pub struct FixedTimestepInfo {
pub step: Duration,
pub accumulator: Duration,
}
impl FixedTimestepInfo {
pub fn timestep(&self) -> Duration {
self.step
}
pub fn rate(&self) -> f64 {
1.0 / self.step.as_secs_f64()
}
pub fn remaining(&self) -> Duration {
self.accumulator
}
pub fn overstep(&self) -> f64 {
self.accumulator.as_secs_f64() / self.step.as_secs_f64()
}
}
pub struct FixedTimestepStage {
step: Duration,
accumulator: Duration,
stages: Vec<Box<dyn Stage>>,
rate_lock: (u32, f32),
lock_accum: u32,
}
impl FixedTimestepStage {
pub fn from_stage<S: Stage>(timestep: Duration, stage: S) -> Self {
Self::new(timestep).with_stage(stage)
}
pub fn new(timestep: Duration) -> Self {
Self {
step: timestep,
accumulator: Duration::default(),
stages: Vec::new(),
rate_lock: (u32::MAX, 0.0),
lock_accum: 0,
}
}
pub fn add_stage<S: Stage>(&mut self, stage: S) {
self.stages.push(Box::new(stage));
}
pub fn with_stage<S: Stage>(mut self, stage: S) -> Self {
self.add_stage(stage);
self
}
pub fn set_rate_lock(&mut self, n_frames: u32, exit_deviation: f32) {
assert!(exit_deviation > 0.0);
assert!(n_frames > 0);
self.rate_lock = (n_frames, exit_deviation);
}
pub fn with_rate_lock(mut self, n_frames: u32, exit_deviation: f32) -> Self {
self.set_rate_lock(n_frames, exit_deviation);
self
}
}
impl Stage for FixedTimestepStage {
fn run(&mut self, world: &mut World) {
self.accumulator += {
let time = world.get_resource::<Time>();
if let Some(time) = time {
time.delta()
} else {
return;
}
};
if self.lock_accum >= self.rate_lock.0 {
let overstep = self.accumulator.as_secs_f32() / self.step.as_secs_f32();
if (overstep - 1.5).abs() >= self.rate_lock.1 {
self.lock_accum = 0;
} else {
self.accumulator = self.step + self.step / 2;
}
}
let mut n_steps = 0;
while self.accumulator >= self.step {
self.accumulator -= self.step;
for stage in self.stages.iter_mut() {
world.insert_resource(FixedTimestepInfo {
step: self.step,
accumulator: self.accumulator,
});
stage.run(world);
if let Some(info) = world.remove_resource::<FixedTimestepInfo>() {
self.step = info.step;
self.accumulator = info.accumulator;
}
}
n_steps += 1;
}
if n_steps == 1 {
if self.lock_accum < self.rate_lock.0 {
self.lock_accum += 1;
}
if self.lock_accum >= self.rate_lock.0 {
self.accumulator = self.step / 2;
}
} else {
self.lock_accum = 0;
}
}
}