use bevy_time::Time;
use bevy_utils::Duration;
use bevy_utils::HashMap;
use bevy_ecs::prelude::*;
pub type TimestepName = &'static str;
#[derive(Default)]
#[derive(Resource)]
pub struct FixedTimesteps {
info: HashMap<TimestepName, FixedTimestepInfo>,
current: Option<TimestepName>,
}
impl FixedTimesteps {
pub fn get(&self, label: TimestepName) -> Option<&FixedTimestepInfo> {
self.info.get(label)
}
pub fn get_current(&self) -> Option<&FixedTimestepInfo> {
self.current.as_ref().and_then(|label| self.info.get(label))
}
pub fn current(&self) -> &FixedTimestepInfo {
self.get_current()
.expect("FixedTimesteps::current can only be used when running inside a fixed timestep.")
}
pub fn get_single(&self) -> Option<&FixedTimestepInfo> {
if self.info.len() != 1 {
return None;
}
self.info.values().next()
}
pub fn single(&self) -> &FixedTimestepInfo {
self.get_single().expect("Expected exactly one fixed timestep.")
}
pub fn get_mut(&mut self, label: TimestepName) -> Option<&mut FixedTimestepInfo> {
self.info.get_mut(label)
}
pub fn get_current_mut(&mut self) -> Option<&mut FixedTimestepInfo> {
self.current.as_ref().and_then(|label| self.info.get_mut(label))
}
pub fn current_mut(&mut self) -> &mut FixedTimestepInfo {
self.get_current_mut()
.expect("FixedTimesteps::current can only be used when running inside a fixed timestep.")
}
pub fn get_single_mut(&mut self) -> Option<&mut FixedTimestepInfo> {
if self.info.len() != 1 {
return None;
}
self.info.values_mut().next()
}
pub fn single_mut(&mut self) -> &mut FixedTimestepInfo {
self.get_single_mut().expect("Expected exactly one fixed timestep.")
}
}
pub struct FixedTimestepInfo {
pub step: Duration,
pub accumulator: Duration,
pub paused: bool,
}
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 fn pause(&mut self) {
self.paused = true;
}
pub fn unpause(&mut self) {
self.paused = false;
}
pub fn toggle_pause(&mut self) {
self.paused = !self.paused;
}
}
pub struct FixedTimestepStage {
step: Duration,
accumulator: Duration,
paused: bool,
label: TimestepName,
stages: Vec<Box<dyn Stage>>,
rate_lock: (u32, f32),
lock_accum: u32,
}
impl FixedTimestepStage {
pub fn from_stage<S: Stage>(timestep: Duration, label: TimestepName, stage: S) -> Self {
Self::new(timestep, label).with_stage(stage)
}
pub fn new(timestep: Duration, label: TimestepName) -> Self {
Self {
step: timestep,
accumulator: Duration::default(),
paused: false,
label,
stages: Vec::new(),
rate_lock: (u32::MAX, 0.0),
lock_accum: 0,
}
}
pub fn paused(mut self) -> Self {
self.paused = true;
self
}
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
}
fn store_fixedtimestepinfo(&self, world: &mut World) {
if let Some(mut timesteps) = world.get_resource_mut::<FixedTimesteps>() {
timesteps.current = Some(self.label);
if let Some(mut info) = timesteps.info.get_mut(&self.label) {
info.step = self.step;
info.accumulator = self.accumulator;
info.paused = self.paused;
} else {
timesteps.info.insert(self.label, FixedTimestepInfo {
step: self.step,
accumulator: self.accumulator,
paused: self.paused,
});
}
} else {
let mut timesteps = FixedTimesteps::default();
timesteps.current = Some(self.label);
timesteps.info.insert(self.label, FixedTimestepInfo {
step: self.step,
accumulator: self.accumulator,
paused: self.paused,
});
world.insert_resource(timesteps);
}
}
}
impl Stage for FixedTimestepStage {
fn run(&mut self, world: &mut World) {
if let Some(timesteps) = world.get_resource::<FixedTimesteps>() {
if let Some(info) = timesteps.info.get(&self.label) {
self.step = info.step;
self.paused = info.paused;
}
}
if self.paused {
return;
}
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;
self.store_fixedtimestepinfo(world);
for stage in self.stages.iter_mut() {
stage.run(world);
if let Some(timesteps) = world.get_resource::<FixedTimesteps>() {
if let Some(info) = timesteps.info.get(&self.label) {
self.step = info.step;
self.accumulator = info.accumulator;
self.paused = info.paused;
}
}
}
n_steps += 1;
}
if let Some(mut timesteps) = world.get_resource_mut::<FixedTimesteps>() {
timesteps.current = None;
}
if n_steps == 0 {
self.store_fixedtimestepinfo(world);
}
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;
}
}
}
#[derive(Debug, Clone)]
pub struct FixedTimestepStageLabel(pub TimestepName);
impl StageLabel for FixedTimestepStageLabel {
fn as_str(&self) -> &'static str {
self.0
}
}
#[cfg(feature = "app")]
pub mod app {
use bevy_utils::Duration;
use bevy_ecs::prelude::*;
use bevy_ecs::schedule::IntoSystemDescriptor;
use bevy_app::{App, CoreStage};
use super::{FixedTimestepStage, FixedTimestepStageLabel, TimestepName};
pub trait AppLooplessFixedTimestepExt {
fn add_fixed_timestep(&mut self, timestep: Duration, label: TimestepName) -> &mut App;
fn add_fixed_timestep_before_stage(&mut self, stage: impl StageLabel, timestep: Duration, label: TimestepName) -> &mut App;
fn add_fixed_timestep_after_stage(&mut self, stage: impl StageLabel, timestep: Duration, label: TimestepName) -> &mut App;
fn add_fixed_timestep_child_stage(&mut self, timestep_name: TimestepName) -> &mut App;
fn add_fixed_timestep_custom_child_stage(&mut self, timestep_name: TimestepName, stage: impl Stage) -> &mut App;
fn add_fixed_timestep_system<Params>(&mut self, timestep_name: TimestepName, substage_i: usize, system: impl IntoSystemDescriptor<Params>) -> &mut App;
fn add_fixed_timestep_system_set(&mut self, timestep_name: TimestepName, substage_i: usize, system_set: SystemSet) -> &mut App;
fn get_fixed_timestep_stage(&self, timestep_name: TimestepName) -> &FixedTimestepStage;
fn get_fixed_timestep_stage_mut(&mut self, timestep_name: TimestepName) -> &mut FixedTimestepStage;
fn get_fixed_timestep_child_substage<S: Stage>(&self, timestep_name: TimestepName, substage_i: usize) -> &S;
fn get_fixed_timestep_child_substage_mut<S: Stage>(&mut self, timestep_name: TimestepName, substage_i: usize) -> &mut S;
}
impl AppLooplessFixedTimestepExt for App {
fn add_fixed_timestep(&mut self, timestep: Duration, label: TimestepName) -> &mut App {
self.add_fixed_timestep_before_stage(CoreStage::Update, timestep, label)
}
fn add_fixed_timestep_before_stage(&mut self, stage: impl StageLabel, timestep: Duration, label: TimestepName) -> &mut App {
let ftstage = FixedTimestepStage::from_stage(timestep, label, SystemStage::parallel());
ftstage.store_fixedtimestepinfo(&mut self.world);
self.add_stage_before(
stage,
FixedTimestepStageLabel(label),
ftstage
)
}
fn add_fixed_timestep_after_stage(&mut self, stage: impl StageLabel, timestep: Duration, label: TimestepName) -> &mut App {
let ftstage = FixedTimestepStage::from_stage(timestep, label, SystemStage::parallel());
ftstage.store_fixedtimestepinfo(&mut self.world);
self.add_stage_after(
stage,
FixedTimestepStageLabel(label),
ftstage
)
}
fn add_fixed_timestep_child_stage(&mut self, timestep_name: TimestepName) -> &mut App {
let stage = self.schedule.get_stage_mut::<FixedTimestepStage>(
FixedTimestepStageLabel(timestep_name)
).expect("Fixed Timestep Stage not found");
stage.add_stage(SystemStage::parallel());
self
}
fn add_fixed_timestep_custom_child_stage(&mut self, timestep_name: TimestepName, custom_stage: impl Stage) -> &mut App {
let stage = self.schedule.get_stage_mut::<FixedTimestepStage>(
FixedTimestepStageLabel(timestep_name)
).expect("Fixed Timestep Stage not found");
stage.add_stage(custom_stage);
self
}
fn add_fixed_timestep_system<Params>(&mut self, timestep_name: TimestepName, substage_i: usize, system: impl IntoSystemDescriptor<Params>) -> &mut App {
let stage = self.schedule.get_stage_mut::<FixedTimestepStage>(
FixedTimestepStageLabel(timestep_name)
).expect("Fixed Timestep Stage not found");
let substage = stage.stages.get_mut(substage_i)
.expect("Fixed Timestep sub-stage not found")
.downcast_mut::<SystemStage>()
.expect("Fixed Timestep sub-stage is not a SystemStage");
substage.add_system(system);
self
}
fn add_fixed_timestep_system_set(&mut self, timestep_name: TimestepName, substage_i: usize, system_set: SystemSet) -> &mut App {
let stage = self.schedule.get_stage_mut::<FixedTimestepStage>(
FixedTimestepStageLabel(timestep_name)
).expect("Fixed Timestep Stage not found");
let substage = stage.stages.get_mut(substage_i)
.expect("Fixed Timestep sub-stage not found")
.downcast_mut::<SystemStage>()
.expect("Fixed Timestep sub-stage is not a SystemStage");
substage.add_system_set(system_set);
self
}
fn get_fixed_timestep_stage(&self, timestep_name: TimestepName) -> &FixedTimestepStage {
self.schedule.get_stage::<FixedTimestepStage>(
FixedTimestepStageLabel(timestep_name)
).expect("Fixed Timestep Stage not found")
}
fn get_fixed_timestep_stage_mut(&mut self, timestep_name: TimestepName) -> &mut FixedTimestepStage {
self.schedule.get_stage_mut::<FixedTimestepStage>(
FixedTimestepStageLabel(timestep_name)
).expect("Fixed Timestep Stage not found")
}
fn get_fixed_timestep_child_substage<S: Stage>(&self, timestep_name: TimestepName, substage_i: usize) -> &S {
let stage = self.get_fixed_timestep_stage(timestep_name);
stage.stages.get(substage_i)
.expect("Fixed Timestep sub-stage not found")
.downcast_ref::<S>()
.expect("Fixed Timestep sub-stage is not the requested type")
}
fn get_fixed_timestep_child_substage_mut<S: Stage>(&mut self, timestep_name: TimestepName, substage_i: usize) -> &mut S {
let stage = self.get_fixed_timestep_stage_mut(timestep_name);
stage.stages.get_mut(substage_i)
.expect("Fixed Timestep sub-stage not found")
.downcast_mut::<S>()
.expect("Fixed Timestep sub-stage is not the requested type")
}
}
}
pub mod schedule {
use bevy_utils::Duration;
use bevy_ecs::prelude::*;
use bevy_ecs::schedule::IntoSystemDescriptor;
use super::{FixedTimestepStage, FixedTimestepStageLabel, TimestepName};
pub trait ScheduleLooplessFixedTimestepExt {
fn add_fixed_timestep_before_stage(&mut self, stage: impl StageLabel, timestep: Duration, label: TimestepName) -> &mut Schedule;
fn add_fixed_timestep_after_stage(&mut self, stage: impl StageLabel, timestep: Duration, label: TimestepName) -> &mut Schedule;
fn add_fixed_timestep_child_stage(&mut self, timestep_name: TimestepName) -> &mut Schedule;
fn add_fixed_timestep_custom_child_stage(&mut self, timestep_name: TimestepName, stage: impl Stage) -> &mut Schedule;
fn add_fixed_timestep_system<Params>(&mut self, timestep_name: TimestepName, substage_i: usize, system: impl IntoSystemDescriptor<Params>) -> &mut Schedule;
fn add_fixed_timestep_system_set(&mut self, timestep_name: TimestepName, substage_i: usize, system_set: SystemSet) -> &mut Schedule;
fn get_fixed_timestep_stage(&self, timestep_name: TimestepName) -> &FixedTimestepStage;
fn get_fixed_timestep_stage_mut(&mut self, timestep_name: TimestepName) -> &mut FixedTimestepStage;
fn get_fixed_timestep_child_substage<S: Stage>(&self, timestep_name: TimestepName, substage_i: usize) -> &S;
fn get_fixed_timestep_child_substage_mut<S: Stage>(&mut self, timestep_name: TimestepName, substage_i: usize) -> &mut S;
}
impl ScheduleLooplessFixedTimestepExt for Schedule {
fn add_fixed_timestep_before_stage(&mut self, stage: impl StageLabel, timestep: Duration, label: TimestepName) -> &mut Schedule {
self.add_stage_before(
stage,
FixedTimestepStageLabel(label),
FixedTimestepStage::from_stage(timestep, label, SystemStage::parallel())
)
}
fn add_fixed_timestep_after_stage(&mut self, stage: impl StageLabel, timestep: Duration, label: TimestepName) -> &mut Schedule {
self.add_stage_after(
stage,
FixedTimestepStageLabel(label),
FixedTimestepStage::from_stage(timestep, label, SystemStage::parallel())
)
}
fn add_fixed_timestep_child_stage(&mut self, timestep_name: TimestepName) -> &mut Schedule {
let stage = self.get_stage_mut::<FixedTimestepStage>(
FixedTimestepStageLabel(timestep_name)
).expect("Fixed Timestep Stage not found");
stage.add_stage(SystemStage::parallel());
self
}
fn add_fixed_timestep_custom_child_stage(&mut self, timestep_name: TimestepName, custom_stage: impl Stage) -> &mut Schedule {
let stage = self.get_stage_mut::<FixedTimestepStage>(
FixedTimestepStageLabel(timestep_name)
).expect("Fixed Timestep Stage not found");
stage.add_stage(custom_stage);
self
}
fn add_fixed_timestep_system<Params>(&mut self, timestep_name: TimestepName, substage_i: usize, system: impl IntoSystemDescriptor<Params>) -> &mut Schedule {
let stage = self.get_stage_mut::<FixedTimestepStage>(
FixedTimestepStageLabel(timestep_name)
).expect("Fixed Timestep Stage not found");
let substage = stage.stages.get_mut(substage_i)
.expect("Fixed Timestep sub-stage not found")
.downcast_mut::<SystemStage>()
.expect("Fixed Timestep sub-stage is not a SystemStage");
substage.add_system(system);
self
}
fn add_fixed_timestep_system_set(&mut self, timestep_name: TimestepName, substage_i: usize, system_set: SystemSet) -> &mut Schedule {
let stage = self.get_stage_mut::<FixedTimestepStage>(
FixedTimestepStageLabel(timestep_name)
).expect("Fixed Timestep Stage not found");
let substage = stage.stages.get_mut(substage_i)
.expect("Fixed Timestep sub-stage not found")
.downcast_mut::<SystemStage>()
.expect("Fixed Timestep sub-stage is not a SystemStage");
substage.add_system_set(system_set);
self
}
fn get_fixed_timestep_stage(&self, timestep_name: TimestepName) -> &FixedTimestepStage {
self.get_stage::<FixedTimestepStage>(
FixedTimestepStageLabel(timestep_name)
).expect("Fixed Timestep Stage not found")
}
fn get_fixed_timestep_stage_mut(&mut self, timestep_name: TimestepName) -> &mut FixedTimestepStage {
self.get_stage_mut::<FixedTimestepStage>(
FixedTimestepStageLabel(timestep_name)
).expect("Fixed Timestep Stage not found")
}
fn get_fixed_timestep_child_substage<S: Stage>(&self, timestep_name: TimestepName, substage_i: usize) -> &S {
let stage = self.get_fixed_timestep_stage(timestep_name);
stage.stages.get(substage_i)
.expect("Fixed Timestep sub-stage not found")
.downcast_ref::<S>()
.expect("Fixed Timestep sub-stage is not the requested type")
}
fn get_fixed_timestep_child_substage_mut<S: Stage>(&mut self, timestep_name: TimestepName, substage_i: usize) -> &mut S {
let stage = self.get_fixed_timestep_stage_mut(timestep_name);
stage.stages.get_mut(substage_i)
.expect("Fixed Timestep sub-stage not found")
.downcast_mut::<S>()
.expect("Fixed Timestep sub-stage is not the requested type")
}
}
}