use std::sync::OnceLock;
use std::sync::atomic::{AtomicBool, Ordering};
use std::time::{Duration, Instant};
use bevy_app::prelude::*;
use bevy_ecs::prelude::*;
use bevy_log::warn;
use rig_ecs::tasks::Wake;
use super::exit::{Exit, ExitReason, SIGNAL_EXIT_CODE};
const FORCE_AFTER: Duration = Duration::from_secs(2);
static SIGNALS: Signals = Signals::new();
struct Signals {
first: OnceLock<Instant>,
asked: AtomicBool,
}
impl Signals {
const fn new() -> Self {
Self {
first: OnceLock::new(),
asked: AtomicBool::new(false),
}
}
fn signal(&self) -> bool {
self.first.get_or_init(Instant::now).elapsed() >= FORCE_AFTER
}
fn take(&self) -> bool {
self.first.get().is_some() && !self.asked.swap(true, Ordering::AcqRel)
}
}
static WAKE: OnceLock<Wake> = OnceLock::new();
pub struct ExitOnSignalPlugin;
impl Plugin for ExitOnSignalPlugin {
fn build(&self, app: &mut App) {
if let Err(failure) = ctrlc::try_set_handler(on_signal) {
warn!("signals will not exit cleanly: {failure}");
}
app.add_systems(PostStartup, |wake: Option<Res<Wake>>| {
if let Some(wake) = wake {
WAKE.get_or_init(|| wake.clone());
}
})
.add_systems(First, exit_on_signal);
}
}
fn on_signal() {
if SIGNALS.signal() {
std::process::exit(SIGNAL_EXIT_CODE.into());
}
if let Some(wake) = WAKE.get() {
wake.wake();
}
}
fn exit_on_signal(mut commands: Commands) {
if SIGNALS.take() {
commands.trigger(Exit(ExitReason::Signal));
}
}
#[cfg(test)]
mod tests;