use bevy_ecs::prelude::*;
#[cfg(feature = "bevy_reflect")]
use bevy_reflect::prelude::*;
use bevy_time::prelude::*;
use std::{cmp::Ordering, marker::PhantomData, time::Duration};
use crate::time_span::*;
#[derive(Debug, Default, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
pub struct TimeRunnerElasped {
now: f32,
now_period: f32,
previous: f32,
previous_period: f32,
}
impl TimeRunnerElasped {
fn update(&mut self, now: f32, now_period: f32) {
self.previous = self.now;
self.previous_period = self.now_period;
self.now = now;
self.now_period = now_period;
}
pub fn now(&self) -> f32 {
self.now
}
pub fn now_period(&self) -> f32 {
self.now_period
}
pub fn previous(&self) -> f32 {
self.previous
}
pub fn previous_period(&self) -> f32 {
self.previous_period
}
}
#[derive(Debug, Clone, PartialEq, Component)]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
#[cfg_attr(feature = "bevy_reflect", reflect(Component))]
#[cfg_attr(feature = "debug", component(on_add = on_time_runner_added))]
pub struct TimeRunner {
paused: bool,
elasped: TimeRunnerElasped,
length: Duration,
direction: TimeDirection,
time_scale: f32,
repeat: Option<(Repeat, RepeatStyle)>,
}
#[cfg(feature = "debug")]
fn on_time_runner_added(
world: bevy_ecs::world::DeferredWorld<'_>,
hook_context: bevy_ecs::lifecycle::HookContext,
) {
use bevy_log::{error, warn};
let Some(debug_info) = world.get_resource::<crate::TimeRunnerDebugInfo>() else {
return;
};
let entity = match world.get_entity(hook_context.entity) {
Ok(entity) => entity,
Err(e) => {
error!(
"Attempted to get entity {} but got error: {}",
hook_context.entity, e
);
return;
}
};
let has_any_time_step = debug_info.time_steps.iter().any(|t| entity.contains_id(*t));
if !has_any_time_step {
warn!(
"TimeRunner {} does not have any `TimeContext<T>` component. This may cause problems. If this warning is false, you can disable it by removing TimeRunnerDebugPlugin or check its documentation.",
hook_context.entity
);
}
}
#[derive(Debug, Clone, PartialEq, Component, Default)]
pub struct TimeContext<TimeCtx>
where
TimeCtx: Default + Send + Sync + 'static,
{
_time_step: PhantomData<TimeCtx>,
}
impl TimeRunner {
pub fn new(length: Duration) -> Self {
TimeRunner {
length,
..Default::default()
}
}
pub fn set_length(&mut self, duration: Duration) -> &mut Self {
self.length = duration;
self
}
pub fn length(&self) -> Duration {
self.length
}
pub fn set_paused(&mut self, paused: bool) -> &mut Self {
self.paused = paused;
self
}
pub fn paused(&self) -> bool {
self.paused
}
pub fn set_time_scale(&mut self, time_scale: f32) -> &mut Self {
self.time_scale = time_scale;
self
}
pub fn time_scale(&self) -> f32 {
self.time_scale
}
pub fn set_direction(&mut self, direction: TimeDirection) -> &mut Self {
self.direction = direction;
self
}
pub fn direction(&self) -> TimeDirection {
self.direction
}
pub fn set_repeat(&mut self, repeat: Option<(Repeat, RepeatStyle)>) -> &mut Self {
self.repeat = repeat;
self
}
pub fn repeat(&self) -> Option<(Repeat, RepeatStyle)> {
self.repeat
}
pub fn elasped(&self) -> TimeRunnerElasped {
self.elasped
}
pub fn is_completed(&self) -> bool {
let at_edge = match self.direction {
TimeDirection::Forward => {
self.elasped.now_period >= 1.0
&& self.elasped.now_period == self.elasped.previous_period
}
TimeDirection::Backward => {
self.elasped.now_period <= 0.0 && self.elasped.now == self.elasped.previous
}
};
match self.repeat {
Some((repeat, _)) => repeat.exhausted() && at_edge,
None => at_edge,
}
}
pub fn tick(&mut self, secs: f32) {
if self.paused || self.is_completed() {
return;
}
self.raw_tick(secs * self.time_scale);
}
pub fn raw_tick(&mut self, secs: f32) {
use RepeatStyle::*;
use TimeDirection::*;
assert!(!secs.is_nan(), "Tick seconds can't be Nan");
let length = self.length.as_secs_f32();
let now = self.elasped.now;
let new_elasped = match self.direction {
Forward => now + secs,
Backward => now - secs,
};
let p = period_percentage(new_elasped, length);
let repeat_count = p.floor() as i32;
let repeat_style = 'a: {
if let Some(r) = self.repeat.as_mut() {
if repeat_count != 0 {
let repeat_count = if self.direction == TimeDirection::Forward {
repeat_count
} else {
-repeat_count
};
let advances = r.0.advance_counter_by(repeat_count);
if advances != 0 {
break 'a r.1;
}
}
}
if new_elasped > length {
self.elasped.update(length, 1.);
} else if new_elasped < 0. {
self.elasped.update(0., 0.);
} else {
self.elasped.update(new_elasped, p);
};
return;
};
let new_elasped = match repeat_style {
WrapAround => saw_wave(new_elasped, length),
PingPong => triangle_wave(new_elasped, length),
};
self.elasped.update(new_elasped, p);
if repeat_style == RepeatStyle::PingPong {
let new_direction = match self.direction {
Forward => triangle_wave_direction(repeat_count),
Backward => backward_triangle_wave_direction(repeat_count),
};
self.direction = new_direction;
}
}
pub fn set_tick(&mut self, secs: f32) {
self.elasped.now = secs;
self.elasped.now_period = period_percentage(secs, self.length.as_secs_f32());
}
pub(crate) fn collaspe_elasped(&mut self) {
self.elasped.previous = self.elasped.now;
self.elasped.previous_period = self.elasped.now_period;
}
}
impl Default for TimeRunner {
fn default() -> Self {
TimeRunner {
paused: Default::default(),
elasped: Default::default(),
length: Default::default(),
direction: Default::default(),
time_scale: 1.,
repeat: Default::default(),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
pub enum Repeat {
Infinitely,
InfinitelyCounted {
times_repeated: i32,
},
Times {
#[allow(missing_docs)]
times: i32,
#[allow(missing_docs)]
times_repeated: i32,
},
}
impl Repeat {
pub fn infinitely() -> Repeat {
Repeat::Infinitely
}
pub fn infinitely_counted() -> Repeat {
Repeat::InfinitelyCounted { times_repeated: 0 }
}
pub fn times(times: i32) -> Repeat {
Repeat::Times {
times,
times_repeated: 0,
}
}
pub fn exhausted(&self) -> bool {
match self {
Repeat::Infinitely => false,
Repeat::InfinitelyCounted { .. } => false,
Repeat::Times {
times,
times_repeated,
} => times_repeated >= times,
}
}
pub fn advance_counter_by(&mut self, by: i32) -> i32 {
match self {
Repeat::Infinitely => by,
Repeat::InfinitelyCounted { times_repeated } => {
*times_repeated += by;
by
}
Repeat::Times {
times,
times_repeated,
} => {
let times_left = *times - *times_repeated;
if times_left == 0 {
return 0;
}
let times_to_advance = if times_left > by { by } else { times_left };
*times_repeated += times_to_advance;
times_to_advance
}
}
}
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
pub enum RepeatStyle {
#[default]
WrapAround,
PingPong,
}
fn saw_wave(x: f32, period: f32) -> f32 {
x.rem_euclid(period)
}
fn triangle_wave(x: f32, period: f32) -> f32 {
((x + period).rem_euclid(period * 2.) - period).abs()
}
fn triangle_wave_direction(repeats: i32) -> TimeDirection {
if repeats.rem_euclid(2) == 0 {
TimeDirection::Forward
} else {
TimeDirection::Backward
}
}
fn backward_triangle_wave_direction(repeats: i32) -> TimeDirection {
if repeats.rem_euclid(2) == 0 {
TimeDirection::Backward
} else {
TimeDirection::Forward
}
}
fn period_percentage(x: f32, period: f32) -> f32 {
x / period
}
#[derive(Debug, Clone, Copy, Component)]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
#[cfg_attr(feature = "bevy_reflect", reflect(Component))]
pub struct SkipTimeRunner;
#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
#[derive(Debug, Clone, PartialEq, Eq, Hash, Message, EntityEvent)]
pub struct TimeRunnerEnded {
pub entity: Entity,
pub current_direction: TimeDirection,
pub with_repeat: Option<Repeat>,
}
impl TimeRunnerEnded {
pub fn is_completed(&self) -> bool {
self.with_repeat
.map(|repeat| repeat.exhausted())
.unwrap_or(true)
}
}
pub fn tag_time_runner_children_with_context<TimeCtx>(
newly_created_time_runners: Query<&Children, (Added<TimeRunner>, With<TimeContext<TimeCtx>>)>,
mut commands: Commands,
) where
TimeCtx: Default + Send + Sync + 'static,
{
for children in &newly_created_time_runners {
for child_entity in children.iter() {
commands
.entity(child_entity)
.try_insert(TimeContext::<TimeCtx>::default());
}
}
}
pub fn tick_time_runner_system<TimeCtx>(
mut commands: Commands,
time: Res<Time<TimeCtx>>,
mut q_time_runner: Query<(Entity, &mut TimeRunner), With<TimeContext<TimeCtx>>>,
mut ended_writer: MessageWriter<TimeRunnerEnded>,
) where
TimeCtx: Default + Send + Sync + 'static,
{
let delta = time.delta_secs();
q_time_runner
.iter_mut()
.for_each(|(entity, mut time_runner)| {
if time_runner.paused || time_runner.is_completed() {
return;
}
let scale = time_runner.time_scale;
time_runner.raw_tick(delta * scale);
let n = time_runner.elasped().now_period;
let send_event = match time_runner.repeat {
Some((_, RepeatStyle::PingPong)) => {
(time_runner.direction == TimeDirection::Forward && n < 0.)
|| (time_runner.direction == TimeDirection::Backward && n >= 1.)
}
_ => {
(time_runner.direction == TimeDirection::Backward && n < 0.)
|| (time_runner.direction == TimeDirection::Forward && n >= 1.)
}
};
if send_event {
let event = TimeRunnerEnded {
entity,
current_direction: time_runner.direction,
with_repeat: time_runner.repeat.map(|r| r.0),
};
commands.trigger(event.clone());
ended_writer.write(event);
}
});
}
pub fn time_runner_system<TimeCtx>(
mut commands: Commands,
mut q_runner: Query<
(Entity, &mut TimeRunner, Option<&Children>),
(Without<SkipTimeRunner>, With<TimeContext<TimeCtx>>),
>,
mut q_span: Query<(Entity, Option<&mut TimeSpanProgress>, &TimeSpan)>,
q_added_skip: Query<
(Entity, &TimeRunner, Option<&Children>),
(Added<SkipTimeRunner>, With<TimeContext<TimeCtx>>),
>,
mut runner_just_completed: Local<Vec<Entity>>,
) where
TimeCtx: Default + Send + Sync + 'static,
{
use DurationQuotient::*;
use RepeatStyle::*;
use TimeDirection::*;
let mut just_completed_runners = q_runner.iter_many(&runner_just_completed);
while let Some((runner_entity, runner, children)) = just_completed_runners.fetch_next() {
if !runner.is_completed() {
continue;
}
let children = children.iter().flat_map(|a| a.into_iter());
let mut spans = q_span.iter_many_mut([&runner_entity].into_iter().chain(children));
while let Some((span_entity, _, _)) = spans.fetch_next() {
let Ok(mut entity) = commands.get_entity(span_entity) else {
continue;
};
entity.remove::<TimeSpanProgress>();
}
}
runner_just_completed.clear();
q_added_skip
.iter()
.for_each(|(runner_entity, _, children)| {
let children = children.iter().flat_map(|a| a.into_iter());
let mut spans = q_span.iter_many_mut([&runner_entity].into_iter().chain(children));
while let Some((span_entity, _, _)) = spans.fetch_next() {
let Ok(mut entity) = commands.get_entity(span_entity) else {
continue;
};
entity.remove::<TimeSpanProgress>();
}
});
q_runner
.iter_mut()
.for_each(|(runner_entity, mut runner, children)| {
if runner.is_completed() {
return;
}
let repeated =
if runner.elasped().now_period.floor() as i32 != 0 && !runner.is_completed() {
runner.repeat.map(|r| r.1)
} else {
None
};
let runner_elasped_now = runner.elasped().now;
let runner_elasped_previous = runner.elasped().previous;
let runner_direction = runner.direction;
let children = children.iter().flat_map(|a| a.into_iter());
let mut spans = q_span.iter_many_mut([&runner_entity].into_iter().chain(children));
while let Some((span_entity, time_span_progress, span)) = spans.fetch_next() {
let now_quotient = span.quotient(runner_elasped_now);
let previous_quotient = span.quotient(runner_elasped_previous);
let direction = if repeated.is_none() {
match runner_elasped_previous.total_cmp(&runner_elasped_now) {
Ordering::Less => TimeDirection::Forward,
Ordering::Equal => runner_direction,
Ordering::Greater => TimeDirection::Backward,
}
} else {
runner_direction
};
let span_in_range =
span_in_range(direction, previous_quotient, now_quotient, repeated);
if let Some(use_time) = span_in_range {
let span_max = span.max().duration().as_secs_f32();
let span_min = span.min().duration().as_secs_f32();
let span_length = span_max - span_min;
let new_now = match use_time {
UseTime::Current => runner_elasped_now - span_min,
UseTime::Min => 0.,
UseTime::Max => span_length,
};
let new_previous = runner_elasped_previous - span_min;
let new_now_percentage = if span_length > 0. {
new_now / span_length
} else {
match new_now.total_cmp(&0.) {
Ordering::Greater => f32::INFINITY,
Ordering::Equal => match runner_direction {
Forward => f32::INFINITY,
Backward => f32::NEG_INFINITY,
},
Ordering::Less => f32::NEG_INFINITY,
}
};
let new_previous_percentage = if span_length > 0. {
new_previous / span_length
} else {
match new_previous.total_cmp(&0.) {
Ordering::Greater => f32::INFINITY,
Ordering::Equal => match runner_direction {
Forward => f32::INFINITY,
Backward => f32::NEG_INFINITY,
},
Ordering::Less => f32::NEG_INFINITY,
}
};
match time_span_progress {
Some(mut time_span_progress) => {
time_span_progress.update(new_now, new_now_percentage);
}
None => {
commands.entity(span_entity).insert(TimeSpanProgress {
now_percentage: new_now_percentage,
now: new_now,
previous_percentage: new_previous_percentage,
previous: new_previous,
});
}
}
} else {
commands.entity(span_entity).remove::<TimeSpanProgress>();
}
}
runner.collaspe_elasped();
if runner.is_completed() {
runner_just_completed.push(runner_entity);
}
});
enum UseTime {
Current,
Min,
Max,
}
fn span_in_range(
direction: TimeDirection,
previous_quotient: DurationQuotient,
now_quotient: DurationQuotient,
repeated: Option<RepeatStyle>,
) -> Option<UseTime> {
match (
direction,
previous_quotient,
now_quotient,
repeated,
) {
(_, Inside, Inside, None) => {
Some(UseTime::Current)
},
| (Forward, Before, Inside, None)
| (Forward, Inside, After, None)
| (Forward, Before, After, None)
=> {
Some(UseTime::Current)
},
| (Backward, After, Inside, None)
| (Backward, Inside, Before, None)
| (Backward, After, Before, None)
=> {
Some(UseTime::Current)
},
| (Forward, Before, Before, Some(WrapAround)) | (Forward, Inside, Before, Some(WrapAround)) => {
Some(UseTime::Max)
},
| (Forward, Before, Inside, Some(WrapAround)) | (Forward, Before, After, Some(WrapAround)) | (Forward, Inside, Inside, Some(WrapAround)) | (Forward, Inside, After, Some(WrapAround)) | (Forward, After, Inside, Some(WrapAround)) | (Forward, After, After, Some(WrapAround)) => {
Some(UseTime::Current)
},
| (Backward, After, After, Some(WrapAround)) | (Backward, Inside, After, Some(WrapAround)) => {
Some(UseTime::Min)
},
| (Backward, Before, Before, Some(WrapAround)) | (Backward, Before, Inside, Some(WrapAround)) | (Backward, Inside, Before, Some(WrapAround)) | (Backward, Inside, Inside, Some(WrapAround)) | (Backward, After, Before, Some(WrapAround)) | (Backward, After, Inside, Some(WrapAround)) => {
Some(UseTime::Current)
},
| (Backward, Before, Before, Some(PingPong)) | (Backward, Before, Inside, Some(PingPong)) | (Backward, Before, After, Some(PingPong)) | (Backward, Inside, Before, Some(PingPong)) | (Backward, Inside, Inside, Some(PingPong)) | (Backward, Inside, After, Some(PingPong)) | (Backward, After, Before, Some(PingPong)) | (Backward, After, Inside, Some(PingPong)) => Some(UseTime::Current),
| (Forward, Before, Inside, Some(PingPong)) | (Forward, Before, After, Some(PingPong)) | (Forward, Inside, Before, Some(PingPong)) | (Forward, Inside, Inside, Some(PingPong)) | (Forward, Inside, After, Some(PingPong)) | (Forward, After, Before, Some(PingPong)) | (Forward, After, Inside, Some(PingPong)) | (Forward, After, After, Some(PingPong)) => Some(UseTime::Current),
_ => None,
}
}
}
#[cfg(test)]
mod test {
use bevy_ecs::system::RunSystemOnce as _;
use super::*;
fn secs(secs: f32) -> Duration {
Duration::from_secs_f32(secs)
}
#[test]
fn timer() {
let mut timer = TimeRunner::new(secs(5.));
timer.raw_tick(2.5);
assert_eq!(timer.elasped.now, 2.5);
assert_eq!(timer.elasped.now_period, 0.5);
timer.raw_tick(2.5);
assert_eq!(timer.elasped.now, 5.);
assert_eq!(timer.elasped.now_period, 1.);
timer.raw_tick(1.);
assert_eq!(timer.elasped.now, 5.);
assert_eq!(timer.elasped.now_period, 1.);
timer.set_tick(0.);
timer.raw_tick(3.);
assert_eq!(timer.elasped.now, 3.);
assert_eq!(timer.elasped.now_period, 3. / 5.);
timer.raw_tick(3.);
assert_eq!(timer.elasped.now, 5.);
assert_eq!(timer.elasped.now_period, 1.);
timer.raw_tick(1.);
assert_eq!(timer.elasped.now, 5.);
assert_eq!(timer.elasped.now_period, 1.);
}
#[test]
fn timer_backward() {
let mut timer = TimeRunner::new(secs(5.));
timer.set_direction(TimeDirection::Backward);
timer.raw_tick(1.);
assert_eq!(timer.elasped.now, 0.);
assert_eq!(timer.elasped.now_period, 0.);
timer.set_tick(5.);
timer.raw_tick(2.5);
assert_eq!(timer.elasped.now, 2.5);
assert_eq!(timer.elasped.now_period, 0.5);
timer.raw_tick(1.);
assert_eq!(timer.elasped.now, 1.5);
assert_eq!(timer.elasped.now_period, 1.5 / 5.);
timer.raw_tick(2.);
assert_eq!(timer.elasped.now, 0.);
assert_eq!(timer.elasped.now_period, 0.);
}
#[test]
fn timer_wrap_around() {
let mut timer = TimeRunner::new(secs(5.));
timer.set_repeat(Some((Repeat::Infinitely, RepeatStyle::WrapAround)));
timer.raw_tick(1.);
assert_eq!(timer.elasped.now, 1.);
assert_eq!(timer.elasped.now_period, 1. / 5.);
timer.raw_tick(2.5);
assert_eq!(timer.elasped.now, 3.5);
assert_eq!(timer.elasped.now_period, 3.5 / 5.);
timer.raw_tick(1.);
assert_eq!(timer.elasped.now, 4.5);
assert_eq!(timer.elasped.now_period, 4.5 / 5.);
timer.raw_tick(1.);
assert_eq!(timer.elasped.now, 0.5);
assert_eq!(timer.elasped.now_period, 5.5 / 5.);
timer.raw_tick(1.);
assert_eq!(timer.elasped.now, 1.5);
assert_eq!(timer.elasped.now_period, 1.5 / 5.);
timer.raw_tick(3.5);
assert_eq!(timer.elasped.now, 0.);
assert_eq!(timer.elasped.now_period, 5. / 5.);
timer.raw_tick(1.);
assert_eq!(timer.elasped.now, 1.);
assert_eq!(timer.elasped.now_period, 1. / 5.);
}
#[test]
fn timer_backward_wrap_around() {
let mut timer = TimeRunner::new(secs(5.));
timer.set_repeat(Some((Repeat::Infinitely, RepeatStyle::WrapAround)));
timer.set_direction(TimeDirection::Backward);
timer.raw_tick(1.);
assert_eq!(timer.elasped.now, 4.);
assert_eq!(timer.elasped.now_period, -1. / 5.);
timer.raw_tick(2.5);
assert_eq!(timer.elasped.now, 1.5);
assert_eq!(timer.elasped.now_period, 1.5 / 5.);
timer.raw_tick(1.);
assert_eq!(timer.elasped.now, 0.5);
assert_eq!(timer.elasped.now_period, 0.5 / 5.);
timer.raw_tick(1.);
assert_eq!(timer.elasped.now, 4.5);
assert_eq!(timer.elasped.now_period, -0.5 / 5.);
}
#[test]
fn timer_wrap_around_times() {
let mut timer = TimeRunner::new(secs(5.));
timer.set_repeat(Some((Repeat::times(2), RepeatStyle::WrapAround)));
timer.raw_tick(4.);
assert_eq!(timer.elasped.now, 4.);
assert_eq!(timer.elasped.now_period, 4. / 5.);
assert_eq!(
timer.repeat.unwrap().0,
Repeat::Times {
times: 2,
times_repeated: 0
},
);
timer.raw_tick(4.);
assert_eq!(timer.elasped.now, 3.);
assert_eq!(timer.elasped.now_period, 8. / 5.);
assert_eq!(
timer.repeat.unwrap().0,
Repeat::Times {
times: 2,
times_repeated: 1
},
);
timer.raw_tick(4.);
assert_eq!(timer.elasped.now, 2.);
assert_eq!(timer.elasped.now_period, 7. / 5.);
assert_eq!(
timer.repeat.unwrap().0,
Repeat::Times {
times: 2,
times_repeated: 2
},
);
timer.raw_tick(4.);
assert_eq!(timer.elasped.now, 5.);
assert_eq!(timer.elasped.now_period, 1.);
assert_eq!(
timer.repeat.unwrap().0,
Repeat::Times {
times: 2,
times_repeated: 2
},
);
timer.raw_tick(1.);
assert_eq!(timer.elasped.now, 5.);
assert_eq!(timer.elasped.now_period, 1.);
assert_eq!(
timer.repeat.unwrap().0,
Repeat::Times {
times: 2,
times_repeated: 2
},
);
}
#[test]
fn timer_backward_wrap_around_times() {
let mut timer = TimeRunner::new(secs(5.));
timer.set_repeat(Some((Repeat::times(2), RepeatStyle::WrapAround)));
timer.set_direction(TimeDirection::Backward);
timer.raw_tick(4.);
assert_eq!(timer.elasped.now, 1.);
assert_eq!(timer.elasped.now_period, -4. / 5.);
assert_eq!(
timer.repeat.unwrap().0,
Repeat::Times {
times: 2,
times_repeated: 1
},
);
timer.raw_tick(4.);
assert_eq!(timer.elasped.now, 2.);
assert_eq!(timer.elasped.now_period, -3. / 5.);
assert_eq!(
timer.repeat.unwrap().0,
Repeat::Times {
times: 2,
times_repeated: 2
},
);
timer.raw_tick(4.);
assert_eq!(timer.elasped.now, 0.);
assert_eq!(timer.elasped.now_period, 0. / 5.);
assert_eq!(
timer.repeat.unwrap().0,
Repeat::Times {
times: 2,
times_repeated: 2
},
);
}
#[test]
fn timer_ping_pong() {
let mut timer = TimeRunner::new(secs(5.));
timer.set_repeat(Some((Repeat::Infinitely, RepeatStyle::PingPong)));
timer.raw_tick(3.);
assert_eq!(timer.elasped.now, 3.);
assert_eq!(timer.elasped.now_period, 3. / 5.);
assert_eq!(timer.direction, TimeDirection::Forward);
timer.raw_tick(3.);
assert_eq!(timer.elasped.now, 4.);
assert_eq!(timer.elasped.now_period, 6. / 5.);
assert_eq!(timer.direction, TimeDirection::Backward);
timer.raw_tick(3.);
assert_eq!(timer.elasped.now, 1.);
assert_eq!(timer.elasped.now_period, 1. / 5.);
assert_eq!(timer.direction, TimeDirection::Backward);
timer.raw_tick(3.);
assert_eq!(timer.elasped.now, 2.);
assert_eq!(timer.elasped.now_period, -2. / 5.);
assert_eq!(timer.direction, TimeDirection::Forward);
timer.raw_tick(3.);
assert_eq!(timer.elasped.now, 5.);
assert_eq!(timer.elasped.now_period, 5. / 5.);
assert_eq!(timer.direction, TimeDirection::Backward);
timer.raw_tick(3.);
assert_eq!(timer.elasped.now, 2.);
assert_eq!(timer.elasped.now_period, 2. / 5.);
assert_eq!(timer.direction, TimeDirection::Backward);
}
#[test]
fn timer_big_tick() {
let mut world = World::default();
let mut time_runner = TimeRunner::new(secs(10.));
time_runner.tick(10.);
let mut time_span_id = Entity::PLACEHOLDER;
world
.spawn((time_runner, TimeContext::<()>::default()))
.with_children(|c| {
time_span_id = c
.spawn(TimeSpan::try_from(secs(4.)..secs(6.)).unwrap())
.id();
});
world.run_system_once(time_runner_system::<()>).unwrap();
let progress = world
.entity(time_span_id)
.get::<TimeSpanProgress>()
.expect("TimeSpanProgress should be here");
dbg!(progress);
assert_eq!(
*progress,
TimeSpanProgress {
now_percentage: 3.,
now: 6.,
previous_percentage: -2.,
previous: -4.,
}
);
}
#[test]
fn timer_zero_length_span() {
let mut world = World::default();
let mut time_runner = TimeRunner::new(secs(4.));
time_runner.tick(4.);
let mut time_span_id = Entity::PLACEHOLDER;
world
.spawn((time_runner, TimeContext::<()>::default()))
.with_children(|c| {
time_span_id = c
.spawn(TimeSpan::try_from(secs(2.)..=secs(2.)).unwrap())
.id();
});
world.run_system_once(time_runner_system::<()>).unwrap();
let progress = world
.entity(time_span_id)
.get::<TimeSpanProgress>()
.expect("TimeSpanProgress should be here");
dbg!(progress);
assert_eq!(
*progress,
TimeSpanProgress {
now_percentage: f32::INFINITY,
now: 2.,
previous_percentage: f32::NEG_INFINITY,
previous: -2.,
}
);
}
#[cfg(feature = "debug")]
#[test]
#[ignore = "This test has to be checked manually for warning"]
fn time_runner_with_no_time_steps_warning() {
let mut app = bevy_app::App::new();
app.add_plugins((
crate::TimeRunnerDebugPlugin::default(),
bevy_log::LogPlugin::default(),
));
let world = app.world_mut();
println!("This should have no warning");
world.spawn((TimeRunner::default(), TimeContext::<()>::default()));
println!("This should have warnings");
world.spawn(TimeRunner::default()); }
}