use core::{num::NonZero, time::Duration};
use bevy::{prelude::*};
use cranium_bevy_plugin::CraniumPlugin;
#[derive(Resource)]
struct AutoRunHeartbeatTimeout(core::time::Duration);
impl Default for AutoRunHeartbeatTimeout {
fn default() -> Self {
Self(core::time::Duration::from_mins(5))
}
}
#[derive(Resource)]
struct AutoRunHeartbeatWrapPeriod(core::time::Duration);
impl Default for AutoRunHeartbeatWrapPeriod {
fn default() -> Self {
Self(core::time::Duration::from_hours(6))
}
}
#[derive(Default, Resource)]
struct AutoRunHeartbeatTracker {
last_tick: core::time::Duration
}
#[derive(Event)]
struct AutoRunHeartbeat;
pub fn _heartbeat(
mut commands: Commands
) {
commands.trigger(AutoRunHeartbeat);
}
fn update_heartbeat(
_trigger: On<AutoRunHeartbeat>,
timer: Res<Time<Real>>,
mut heartbeat_tracker: ResMut<AutoRunHeartbeatTracker>
) {
let now = timer.elapsed_wrapped();
heartbeat_tracker.last_tick = now;
}
fn setup_wrap_period(
wrap_period: Res<AutoRunHeartbeatWrapPeriod>,
mut timer: ResMut<Time<Real>>,
) {
let period = wrap_period.0;
timer.set_wrap_period(period);
}
fn check_heartbeat_system(
heartbeat_tracker: Res<AutoRunHeartbeatTracker>,
heartbeat_timeout: Res<AutoRunHeartbeatTimeout>,
timer: Res<Time<Real>>,
mut app_exit: MessageWriter<AppExit>,
) {
let timeout = heartbeat_timeout.0;
let last_tick = heartbeat_tracker.last_tick;
let now = timer.elapsed_wrapped();
let now = match now < last_tick {
false => now,
true => {
now + timer.wrap_period()
},
};
let delta = now - last_tick;
if delta > timeout {
bevy::log::error!(
"Cranium received no heartbeat in more than {:?}s (delta:{:?}s, last update time: {:?}s), quitting!",
timeout.as_secs(), delta.as_secs(), last_tick.as_secs()
);
app_exit.write(AppExit::Error(NonZero::new(1u8).unwrap()));
};
}
pub fn create_app() -> App {
let mut app = App::new();
app.add_plugins(CraniumPlugin);
#[cfg(feature = "logging")]
app.add_plugins(
bevy::log::LogPlugin {
level: bevy::log::Level::DEBUG,
custom_layer: |_| None,
filter: "wgpu=error,bevy_render=info,bevy_ecs=info".to_string(),
fmt_layer: |_| None,
}
);
app
}
pub fn _tick_world(app: &mut App) -> &mut App {
app.update();
app
}
struct AutoRunPlugin;
impl Plugin for AutoRunPlugin {
fn build(&self, app: &mut App) {
let timeout_seconds = option_env!("CORTEX_AUTORUN_HEARTBEAT_TIMEOUT_SECONDS")
.map(|s| s.trim().parse::<u64>().ok()).flatten()
.unwrap_or(60*5) ;
let period_seconds = option_env!("CORTEX_AUTORUN_PERIOD_SECONDS")
.map(|s| s.trim().parse::<u64>().ok()).flatten()
.unwrap_or(60*60*6) ;
app
.init_resource::<AutoRunHeartbeatTracker>()
.insert_resource(AutoRunHeartbeatTimeout(Duration::from_secs(timeout_seconds)))
.insert_resource(AutoRunHeartbeatWrapPeriod(Duration::from_secs(period_seconds)))
.add_systems(Startup, setup_wrap_period)
.add_systems(Last, check_heartbeat_system)
.add_observer(update_heartbeat)
;
}
}
pub fn configure_for_autorun(mut app: App) -> App {
let run_rate = option_env!("CORTEX_AUTORUN_RATE_MILISECONDS")
.map(|s| s.trim().parse::<u64>().ok()).flatten()
.unwrap_or(200) ;
app.add_plugins((
MinimalPlugins.set(bevy::app::ScheduleRunnerPlugin::run_loop(core::time::Duration::from_millis(run_rate))),
AutoRunPlugin,
));
app
}
pub fn autorun(mut app: App) {
app
.run();
}
pub fn create_and_autorun() {
let app = configure_for_autorun(create_app());
#[cfg(feature = "logging")]
bevy::log::info!("Created a Cranium Server app, running...");
autorun(app);
}