|
| 1 | +use std::sync::atomic::{AtomicBool, Ordering}; |
| 2 | + |
| 3 | +use bevy_ecs::event::EventWriter; |
| 4 | + |
| 5 | +use crate::{App, AppExit, Plugin, Update}; |
| 6 | + |
| 7 | +pub use ctrlc; |
| 8 | + |
| 9 | +/// Indicates that all [`App`]'s should exit. |
| 10 | +static SHOULD_EXIT: AtomicBool = AtomicBool::new(false); |
| 11 | + |
| 12 | +/// Gracefully handles `Ctrl+C` by emitting a [`AppExit`] event. This plugin is part of the `DefaultPlugins`. |
| 13 | +/// |
| 14 | +/// ```no_run |
| 15 | +/// # use bevy_app::{App, NoopPluginGroup as MinimalPlugins, PluginGroup, TerminalCtrlCHandlerPlugin}; |
| 16 | +/// fn main() { |
| 17 | +/// App::new() |
| 18 | +/// .add_plugins(MinimalPlugins) |
| 19 | +/// .add_plugins(TerminalCtrlCHandlerPlugin) |
| 20 | +/// .run(); |
| 21 | +/// } |
| 22 | +/// ``` |
| 23 | +/// |
| 24 | +/// If you want to setup your own `Ctrl+C` handler, you should call the |
| 25 | +/// [`TerminalCtrlCHandlerPlugin::gracefully_exit`] function in your handler if you want bevy to gracefully exit. |
| 26 | +/// ```no_run |
| 27 | +/// # use bevy_app::{App, NoopPluginGroup as DefaultPlugins, PluginGroup, TerminalCtrlCHandlerPlugin, ctrlc}; |
| 28 | +/// fn main() { |
| 29 | +/// // Your own `Ctrl+C` handler |
| 30 | +/// ctrlc::set_handler(move || { |
| 31 | +/// // Other clean up code ... |
| 32 | +/// |
| 33 | +/// TerminalCtrlCHandlerPlugin::gracefully_exit(); |
| 34 | +/// }); |
| 35 | +/// |
| 36 | +/// App::new() |
| 37 | +/// .add_plugins(DefaultPlugins) |
| 38 | +/// .run(); |
| 39 | +/// } |
| 40 | +/// ``` |
| 41 | +#[derive(Default)] |
| 42 | +pub struct TerminalCtrlCHandlerPlugin; |
| 43 | + |
| 44 | +impl TerminalCtrlCHandlerPlugin { |
| 45 | + /// Sends the [`AppExit`] event to all apps using this plugin to make them gracefully exit. |
| 46 | + pub fn gracefully_exit() { |
| 47 | + SHOULD_EXIT.store(true, Ordering::Relaxed); |
| 48 | + } |
| 49 | + |
| 50 | + /// Sends a [`AppExit`] event when the user presses `Ctrl+C` on the terminal. |
| 51 | + fn exit_on_flag(mut events: EventWriter<AppExit>) { |
| 52 | + if SHOULD_EXIT.load(Ordering::Relaxed) { |
| 53 | + events.send(AppExit::from_code(130)); |
| 54 | + } |
| 55 | + } |
| 56 | +} |
| 57 | + |
| 58 | +impl Plugin for TerminalCtrlCHandlerPlugin { |
| 59 | + fn build(&self, app: &mut App) { |
| 60 | + let result = ctrlc::try_set_handler(move || { |
| 61 | + Self::gracefully_exit(); |
| 62 | + }); |
| 63 | + match result { |
| 64 | + Ok(()) => {} |
| 65 | + Err(ctrlc::Error::MultipleHandlers) => { |
| 66 | + bevy_utils::tracing::info!("Skipping installing `Ctrl+C` handler as one was already installed. Please call `TerminalCtrlCHandlerPlugin::gracefully_exit` in your own `Ctrl+C` handler if you want Bevy to gracefully exit on `Ctrl+C`."); |
| 67 | + } |
| 68 | + Err(err) => bevy_utils::tracing::warn!("Failed to set `Ctrl+C` handler: {err}"), |
| 69 | + } |
| 70 | + |
| 71 | + app.add_systems(Update, TerminalCtrlCHandlerPlugin::exit_on_flag); |
| 72 | + } |
| 73 | +} |
0 commit comments