use std::fmt;
use std::time::Duration;
use bevy::ecs::change_detection::Tick;
use bevy::ecs::message::MessageRegistry;
use bevy::ecs::world::World;
use bevy::time::{Fixed, Time, Virtual, run_fixed_main_schedule};
use crate::core::{ClockSource, SocietyClock, SocietyRng, SocietySettings};
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct FastForward {
pub ticks: u64,
pub from_days: f64,
pub to_days: f64,
}
#[derive(Debug, Clone, PartialEq)]
pub enum FastForwardError {
NoSociety,
NotStarted,
BadDays(f32),
HostClock,
NoTimeStep,
}
impl fmt::Display for FastForwardError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::NoSociety => write!(f, "no SocietyClock or SocietySettings: add SocietyPlugins"),
Self::NotStarted => write!(
f,
"the society has not started: run one app update before fast-forwarding"
),
Self::BadDays(d) => write!(
f,
"cannot fast-forward {d} days: need a finite, non-negative count whose ticks fit a u64"
),
Self::HostClock => write!(
f,
"the society clock follows the host (ClockSource::Host); a fast-forward cannot advance it"
),
Self::NoTimeStep => write!(
f,
"there is no fixed clock (add TimePlugin), or the fixed timestep and \
SocietySettings::tick_secs are not both positive and finite"
),
}
}
}
impl std::error::Error for FastForwardError {}
pub fn fast_forward(world: &mut World, days: f32) -> Result<FastForward, FastForwardError> {
if !days.is_finite() || days < 0.0 {
return Err(FastForwardError::BadDays(days));
}
let (tick_secs, seconds_per_day, clock) = {
let settings = world
.get_resource::<SocietySettings>()
.ok_or(FastForwardError::NoSociety)?;
(settings.tick_secs, settings.seconds_per_day, settings.clock)
};
let start = *world
.get_resource::<SocietyClock>()
.ok_or(FastForwardError::NoSociety)?;
if !world.contains_resource::<SocietyRng>() {
return Err(FastForwardError::NotStarted);
}
if clock == ClockSource::Host {
return Err(FastForwardError::HostClock);
}
let (Some(&fixed), Some(&virtual_time), Some(&generic_time)) = (
world.get_resource::<Time<Fixed>>(),
world.get_resource::<Time<Virtual>>(),
world.get_resource::<Time>(),
) else {
return Err(FastForwardError::NoTimeStep);
};
let timestep = fixed.timestep();
if !(tick_secs.is_finite() && tick_secs > 0.0 && !timestep.is_zero()) {
return Err(FastForwardError::NoTimeStep);
}
let wanted = (f64::from(days) * f64::from(seconds_per_day) / f64::from(tick_secs)).ceil();
let target = (wanted < u64::MAX as f64)
.then(|| start.tick.checked_add(wanted as u64))
.flatten()
.ok_or(FastForwardError::BadDays(days))?;
world
.resource_mut::<Time<Virtual>>()
.advance_by(Duration::ZERO);
while world.resource::<SocietyClock>().tick < target {
world
.resource_mut::<Time<Fixed>>()
.accumulate_overstep(timestep);
run_fixed_main_schedule(world);
world.resource_scope(
|world, mut registry: bevy::ecs::change_detection::Mut<MessageRegistry>| {
registry.run_updates(world, Tick::new(0));
},
);
}
*world.resource_mut::<Time<Virtual>>() = virtual_time;
*world.resource_mut::<Time>() = generic_time;
let end = *world.resource::<SocietyClock>();
Ok(FastForward {
ticks: end.tick - start.tick,
from_days: start.now_days,
to_days: end.now_days,
})
}