mirror of
https://github.com/bevyengine/bevy
synced 2024-11-21 20:23:28 +00:00
Make AppExit
more specific about exit reason. (#13022)
# Objective Closes #13017. ## Solution - Make `AppExit` a enum with a `Success` and `Error` variant. - Make `App::run()` return a `AppExit` if it ever returns. - Make app runners return a `AppExit` to signal if they encountered a error. --- ## Changelog ### Added - [`App::should_exit`](https://example.org/) - [`AppExit`](https://docs.rs/bevy/latest/bevy/app/struct.AppExit.html) to the `bevy` and `bevy_app` preludes, ### Changed - [`AppExit`](https://docs.rs/bevy/latest/bevy/app/struct.AppExit.html) is now a enum with 2 variants (`Success` and `Error`). - The app's [runner function](https://docs.rs/bevy/latest/bevy/app/struct.App.html#method.set_runner) now has to return a `AppExit`. - [`App::run()`](https://docs.rs/bevy/latest/bevy/app/struct.App.html#method.run) now also returns the `AppExit` produced by the runner function. ## Migration Guide - Replace all usages of [`AppExit`](https://docs.rs/bevy/latest/bevy/app/struct.AppExit.html) with `AppExit::Success` or `AppExit::Failure`. - Any custom app runners now need to return a `AppExit`. We suggest you return a `AppExit::Error` if any `AppExit` raised was a Error. You can use the new [`App::should_exit`](https://example.org/) method. - If not exiting from `main` any other way. You should return the `AppExit` from `App::run()` so the app correctly returns a error code if anything fails e.g. ```rust fn main() -> AppExit { App::new() //Your setup here... .run() } ``` --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
This commit is contained in:
parent
a50223622f
commit
de875fdc4c
12 changed files with 248 additions and 60 deletions
|
@ -4,7 +4,7 @@ use crate::{
|
|||
};
|
||||
pub use bevy_derive::AppLabel;
|
||||
use bevy_ecs::{
|
||||
event::event_update_system,
|
||||
event::{event_update_system, ManualEventReader},
|
||||
intern::Interned,
|
||||
prelude::*,
|
||||
schedule::{ScheduleBuildSettings, ScheduleLabel},
|
||||
|
@ -13,8 +13,14 @@ use bevy_ecs::{
|
|||
#[cfg(feature = "trace")]
|
||||
use bevy_utils::tracing::info_span;
|
||||
use bevy_utils::{tracing::debug, HashMap};
|
||||
use std::fmt::Debug;
|
||||
use std::panic::{catch_unwind, resume_unwind, AssertUnwindSafe};
|
||||
use std::{
|
||||
fmt::Debug,
|
||||
process::{ExitCode, Termination},
|
||||
};
|
||||
use std::{
|
||||
num::NonZeroU8,
|
||||
panic::{catch_unwind, resume_unwind, AssertUnwindSafe},
|
||||
};
|
||||
use thiserror::Error;
|
||||
|
||||
bevy_ecs::define_label!(
|
||||
|
@ -151,7 +157,7 @@ impl App {
|
|||
/// # Panics
|
||||
///
|
||||
/// Panics if not all plugins have been built.
|
||||
pub fn run(&mut self) {
|
||||
pub fn run(&mut self) -> AppExit {
|
||||
#[cfg(feature = "trace")]
|
||||
let _bevy_app_run_span = info_span!("bevy_app").entered();
|
||||
if self.is_building_plugins() {
|
||||
|
@ -160,7 +166,7 @@ impl App {
|
|||
|
||||
let runner = std::mem::replace(&mut self.runner, Box::new(run_once));
|
||||
let app = std::mem::replace(self, App::empty());
|
||||
(runner)(app);
|
||||
(runner)(app)
|
||||
}
|
||||
|
||||
/// Sets the function that will be called when the app is run.
|
||||
|
@ -177,17 +183,20 @@ impl App {
|
|||
/// ```
|
||||
/// # use bevy_app::prelude::*;
|
||||
/// #
|
||||
/// fn my_runner(mut app: App) {
|
||||
/// fn my_runner(mut app: App) -> AppExit {
|
||||
/// loop {
|
||||
/// println!("In main loop");
|
||||
/// app.update();
|
||||
/// if let Some(exit) = app.should_exit() {
|
||||
/// return exit;
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// App::new()
|
||||
/// .set_runner(my_runner);
|
||||
/// ```
|
||||
pub fn set_runner(&mut self, f: impl FnOnce(App) + 'static) -> &mut Self {
|
||||
pub fn set_runner(&mut self, f: impl FnOnce(App) -> AppExit + 'static) -> &mut Self {
|
||||
self.runner = Box::new(f);
|
||||
self
|
||||
}
|
||||
|
@ -849,11 +858,44 @@ impl App {
|
|||
self.main_mut().ignore_ambiguity(schedule, a, b);
|
||||
self
|
||||
}
|
||||
|
||||
/// Attempts to determine if an [`AppExit`] was raised since the last update.
|
||||
///
|
||||
/// Will attempt to return the first [`Error`](AppExit::Error) it encounters.
|
||||
/// This should be called after every [`update()`](App::update) otherwise you risk
|
||||
/// dropping possible [`AppExit`] events.
|
||||
pub fn should_exit(&self) -> Option<AppExit> {
|
||||
let mut reader = ManualEventReader::default();
|
||||
|
||||
self.should_exit_manual(&mut reader)
|
||||
}
|
||||
|
||||
/// Several app runners in this crate keep their own [`ManualEventReader<AppExit>`].
|
||||
/// This exists to accommodate them.
|
||||
pub(crate) fn should_exit_manual(
|
||||
&self,
|
||||
reader: &mut ManualEventReader<AppExit>,
|
||||
) -> Option<AppExit> {
|
||||
let events = self.world().get_resource::<Events<AppExit>>()?;
|
||||
|
||||
let mut events = reader.read(events);
|
||||
|
||||
if events.len() != 0 {
|
||||
return Some(
|
||||
events
|
||||
.find(|exit| exit.is_error())
|
||||
.cloned()
|
||||
.unwrap_or(AppExit::Success),
|
||||
);
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
type RunnerFn = Box<dyn FnOnce(App)>;
|
||||
type RunnerFn = Box<dyn FnOnce(App) -> AppExit>;
|
||||
|
||||
fn run_once(mut app: App) {
|
||||
fn run_once(mut app: App) -> AppExit {
|
||||
while app.plugins_state() == PluginsState::Adding {
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
bevy_tasks::tick_global_task_pools_on_main_thread();
|
||||
|
@ -862,27 +904,99 @@ fn run_once(mut app: App) {
|
|||
app.cleanup();
|
||||
|
||||
app.update();
|
||||
|
||||
let mut exit_code_reader = ManualEventReader::default();
|
||||
if let Some(app_exit_events) = app.world().get_resource::<Events<AppExit>>() {
|
||||
if exit_code_reader
|
||||
.read(app_exit_events)
|
||||
.any(AppExit::is_error)
|
||||
{
|
||||
return AppExit::error();
|
||||
}
|
||||
}
|
||||
|
||||
AppExit::Success
|
||||
}
|
||||
|
||||
/// An event that indicates the [`App`] should exit. If one or more of these are present at the
|
||||
/// end of an update, the [runner](App::set_runner) will end and ([maybe](App::run)) return
|
||||
/// control to the caller.
|
||||
/// An event that indicates the [`App`] should exit. If one or more of these are present at the end of an update,
|
||||
/// the [runner](App::set_runner) will end and ([maybe](App::run)) return control to the caller.
|
||||
///
|
||||
/// This event can be used to detect when an exit is requested. Make sure that systems listening
|
||||
/// for this event run before the current update ends.
|
||||
#[derive(Event, Debug, Clone, Default)]
|
||||
pub struct AppExit;
|
||||
///
|
||||
/// # Portability
|
||||
/// This type is roughly meant to map to a standard definition of a process exit code (0 means success, not 0 means error). Due to portability concerns
|
||||
/// (see [`ExitCode`](https://doc.rust-lang.org/std/process/struct.ExitCode.html) and [`process::exit`](https://doc.rust-lang.org/std/process/fn.exit.html#))
|
||||
/// we only allow error codes between 1 and [255](u8::MAX).
|
||||
#[derive(Event, Debug, Clone, Default, PartialEq, Eq)]
|
||||
pub enum AppExit {
|
||||
/// [`App`] exited without any problems.
|
||||
#[default]
|
||||
Success,
|
||||
/// The [`App`] experienced an unhandleable error.
|
||||
/// Holds the exit code we expect our app to return.
|
||||
Error(NonZeroU8),
|
||||
}
|
||||
|
||||
impl AppExit {
|
||||
/// Creates a [`AppExit::Error`] with a error code of 1.
|
||||
#[must_use]
|
||||
pub const fn error() -> Self {
|
||||
Self::Error(NonZeroU8::MIN)
|
||||
}
|
||||
|
||||
/// Returns `true` if `self` is a [`AppExit::Success`].
|
||||
#[must_use]
|
||||
pub const fn is_success(&self) -> bool {
|
||||
matches!(self, AppExit::Success)
|
||||
}
|
||||
|
||||
/// Returns `true` if `self` is a [`AppExit::Error`].
|
||||
#[must_use]
|
||||
pub const fn is_error(&self) -> bool {
|
||||
matches!(self, AppExit::Error(_))
|
||||
}
|
||||
|
||||
/// Creates a [`AppExit`] from a code.
|
||||
///
|
||||
/// When `code` is 0 a [`AppExit::Success`] is constructed otherwise a
|
||||
/// [`AppExit::Error`] is constructed.
|
||||
#[must_use]
|
||||
pub const fn from_code(code: u8) -> Self {
|
||||
match NonZeroU8::new(code) {
|
||||
Some(code) => Self::Error(code),
|
||||
None => Self::Success,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u8> for AppExit {
|
||||
#[must_use]
|
||||
fn from(value: u8) -> Self {
|
||||
Self::from_code(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl Termination for AppExit {
|
||||
fn report(self) -> std::process::ExitCode {
|
||||
match self {
|
||||
AppExit::Success => ExitCode::SUCCESS,
|
||||
// We leave logging an error to our users
|
||||
AppExit::Error(value) => ExitCode::from(value.get()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::marker::PhantomData;
|
||||
use std::{marker::PhantomData, mem};
|
||||
|
||||
use bevy_ecs::{
|
||||
schedule::{OnEnter, States},
|
||||
system::Commands,
|
||||
};
|
||||
|
||||
use crate::{App, Plugin};
|
||||
use crate::{App, AppExit, Plugin};
|
||||
|
||||
struct PluginA;
|
||||
impl Plugin for PluginA {
|
||||
|
@ -1089,13 +1203,15 @@ mod tests {
|
|||
#[derive(Resource)]
|
||||
struct MyState {}
|
||||
|
||||
fn my_runner(mut app: App) {
|
||||
fn my_runner(mut app: App) -> AppExit {
|
||||
let my_state = MyState {};
|
||||
app.world_mut().insert_resource(my_state);
|
||||
|
||||
for _ in 0..5 {
|
||||
app.update();
|
||||
}
|
||||
|
||||
AppExit::Success
|
||||
}
|
||||
|
||||
fn my_system(_: Res<MyState>) {
|
||||
|
@ -1108,4 +1224,11 @@ mod tests {
|
|||
.add_systems(PreUpdate, my_system)
|
||||
.run();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn app_exit_size() {
|
||||
// There wont be many of them so the size isn't a issue but
|
||||
// it's nice they're so small let's keep it that way.
|
||||
assert_eq!(mem::size_of::<AppExit>(), mem::size_of::<u8>());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,7 +28,7 @@ pub use sub_app::*;
|
|||
pub mod prelude {
|
||||
#[doc(hidden)]
|
||||
pub use crate::{
|
||||
app::App,
|
||||
app::{App, AppExit},
|
||||
main_schedule::{
|
||||
First, FixedFirst, FixedLast, FixedPostUpdate, FixedPreUpdate, FixedUpdate, Last, Main,
|
||||
PostStartup, PostUpdate, PreStartup, PreUpdate, SpawnScene, Startup, StateTransition,
|
||||
|
|
|
@ -3,7 +3,7 @@ use crate::{
|
|||
plugin::Plugin,
|
||||
PluginsState,
|
||||
};
|
||||
use bevy_ecs::event::{Events, ManualEventReader};
|
||||
use bevy_ecs::event::ManualEventReader;
|
||||
use bevy_utils::{Duration, Instant};
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
|
@ -84,7 +84,15 @@ impl Plugin for ScheduleRunnerPlugin {
|
|||
|
||||
let mut app_exit_event_reader = ManualEventReader::<AppExit>::default();
|
||||
match run_mode {
|
||||
RunMode::Once => app.update(),
|
||||
RunMode::Once => {
|
||||
app.update();
|
||||
|
||||
if let Some(exit) = app.should_exit_manual(&mut app_exit_event_reader) {
|
||||
return exit;
|
||||
}
|
||||
|
||||
AppExit::Success
|
||||
}
|
||||
RunMode::Loop { wait } => {
|
||||
let mut tick = move |app: &mut App,
|
||||
wait: Option<Duration>|
|
||||
|
@ -93,14 +101,9 @@ impl Plugin for ScheduleRunnerPlugin {
|
|||
|
||||
app.update();
|
||||
|
||||
if let Some(app_exit_events) =
|
||||
app.world_mut().get_resource_mut::<Events<AppExit>>()
|
||||
{
|
||||
if let Some(exit) = app_exit_event_reader.read(&app_exit_events).last()
|
||||
{
|
||||
return Err(exit.clone());
|
||||
}
|
||||
}
|
||||
if let Some(exit) = app.should_exit_manual(&mut app_exit_event_reader) {
|
||||
return Err(exit);
|
||||
};
|
||||
|
||||
let end_time = Instant::now();
|
||||
|
||||
|
@ -116,40 +119,54 @@ impl Plugin for ScheduleRunnerPlugin {
|
|||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
{
|
||||
while let Ok(delay) = tick(&mut app, wait) {
|
||||
if let Some(delay) = delay {
|
||||
std::thread::sleep(delay);
|
||||
loop {
|
||||
match tick(&mut app, wait) {
|
||||
Ok(Some(delay)) => std::thread::sleep(delay),
|
||||
Ok(None) => continue,
|
||||
Err(exit) => return exit,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
{
|
||||
fn set_timeout(f: &Closure<dyn FnMut()>, dur: Duration) {
|
||||
fn set_timeout(callback: &Closure<dyn FnMut()>, dur: Duration) {
|
||||
web_sys::window()
|
||||
.unwrap()
|
||||
.set_timeout_with_callback_and_timeout_and_arguments_0(
|
||||
f.as_ref().unchecked_ref(),
|
||||
callback.as_ref().unchecked_ref(),
|
||||
dur.as_millis() as i32,
|
||||
)
|
||||
.expect("Should register `setTimeout`.");
|
||||
}
|
||||
let asap = Duration::from_millis(1);
|
||||
|
||||
let mut rc = Rc::new(app);
|
||||
let f = Rc::new(RefCell::new(None));
|
||||
let g = f.clone();
|
||||
let exit = Rc::new(RefCell::new(AppExit::Success));
|
||||
let closure_exit = exit.clone();
|
||||
|
||||
let c = move || {
|
||||
let app = Rc::get_mut(&mut rc).unwrap();
|
||||
let mut app = Rc::new(app);
|
||||
let moved_tick_closure = Rc::new(RefCell::new(None));
|
||||
let base_tick_closure = moved_tick_closure.clone();
|
||||
|
||||
let tick_app = move || {
|
||||
let app = Rc::get_mut(&mut app).unwrap();
|
||||
let delay = tick(app, wait);
|
||||
if let Ok(delay) = delay {
|
||||
set_timeout(f.borrow().as_ref().unwrap(), delay.unwrap_or(asap));
|
||||
match delay {
|
||||
Ok(delay) => set_timeout(
|
||||
moved_tick_closure.borrow().as_ref().unwrap(),
|
||||
delay.unwrap_or(asap),
|
||||
),
|
||||
Err(code) => {
|
||||
closure_exit.replace(code);
|
||||
}
|
||||
}
|
||||
};
|
||||
*g.borrow_mut() = Some(Closure::wrap(Box::new(c) as Box<dyn FnMut()>));
|
||||
set_timeout(g.borrow().as_ref().unwrap(), asap);
|
||||
};
|
||||
*base_tick_closure.borrow_mut() =
|
||||
Some(Closure::wrap(Box::new(tick_app) as Box<dyn FnMut()>));
|
||||
set_timeout(base_tick_closure.borrow().as_ref().unwrap(), asap);
|
||||
|
||||
exit.take()
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -37,7 +37,7 @@ fn ci_testing_exit_after(
|
|||
) {
|
||||
if let Some(exit_after) = ci_testing_config.exit_after {
|
||||
if *current_frame > exit_after {
|
||||
app_exit_events.send(AppExit);
|
||||
app_exit_events.send(AppExit::Success);
|
||||
info!("Exiting after {} frames. Test successful!", exit_after);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -193,7 +193,7 @@ fn renderer_extract(app_world: &mut World, _world: &mut World) {
|
|||
render_channels.send_blocking(render_app);
|
||||
} else {
|
||||
// Renderer thread panicked
|
||||
world.send_event(AppExit);
|
||||
world.send_event(AppExit::error());
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
@ -13,7 +13,7 @@ use bevy_ecs::prelude::*;
|
|||
pub fn exit_on_all_closed(mut app_exit_events: EventWriter<AppExit>, windows: Query<&Window>) {
|
||||
if windows.is_empty() {
|
||||
bevy_utils::tracing::info!("No windows are open, exiting");
|
||||
app_exit_events.send(AppExit);
|
||||
app_exit_events.send(AppExit::Success);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -28,7 +28,7 @@ pub fn exit_on_primary_closed(
|
|||
) {
|
||||
if windows.is_empty() {
|
||||
bevy_utils::tracing::info!("Primary window was closed, exiting");
|
||||
app_exit_events.send(AppExit);
|
||||
app_exit_events.send(AppExit::Success);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -19,6 +19,8 @@ mod winit_config;
|
|||
pub mod winit_event;
|
||||
mod winit_windows;
|
||||
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use approx::relative_eq;
|
||||
use bevy_a11y::AccessibilityRequested;
|
||||
use bevy_utils::Instant;
|
||||
|
@ -263,7 +265,7 @@ type UserEvent = RequestRedraw;
|
|||
///
|
||||
/// Overriding the app's [runner](bevy_app::App::runner) while using `WinitPlugin` will bypass the
|
||||
/// `EventLoop`.
|
||||
pub fn winit_runner(mut app: App) {
|
||||
pub fn winit_runner(mut app: App) -> AppExit {
|
||||
if app.plugins_state() == PluginsState::Ready {
|
||||
app.finish();
|
||||
app.cleanup();
|
||||
|
@ -279,6 +281,10 @@ pub fn winit_runner(mut app: App) {
|
|||
|
||||
let mut runner_state = WinitAppRunnerState::default();
|
||||
|
||||
// TODO: AppExit is effectively a u8 we could use a AtomicU8 here instead of a mutex.
|
||||
let mut exit_status = Arc::new(Mutex::new(AppExit::Success));
|
||||
let handle_exit_status = exit_status.clone();
|
||||
|
||||
// prepare structures to access data in the world
|
||||
let mut app_exit_event_reader = ManualEventReader::<AppExit>::default();
|
||||
let mut redraw_event_reader = ManualEventReader::<RequestRedraw>::default();
|
||||
|
@ -298,6 +304,8 @@ pub fn winit_runner(mut app: App) {
|
|||
let mut winit_events = Vec::default();
|
||||
// set up the event loop
|
||||
let event_handler = move |event, event_loop: &EventLoopWindowTarget<UserEvent>| {
|
||||
let mut exit_status = handle_exit_status.lock().unwrap();
|
||||
|
||||
handle_winit_event(
|
||||
&mut app,
|
||||
&mut app_exit_event_reader,
|
||||
|
@ -307,6 +315,7 @@ pub fn winit_runner(mut app: App) {
|
|||
&mut focused_windows_state,
|
||||
&mut redraw_event_reader,
|
||||
&mut winit_events,
|
||||
&mut exit_status,
|
||||
event,
|
||||
event_loop,
|
||||
);
|
||||
|
@ -317,6 +326,12 @@ pub fn winit_runner(mut app: App) {
|
|||
if let Err(err) = event_loop.run(event_handler) {
|
||||
error!("winit event loop returned an error: {err}");
|
||||
}
|
||||
|
||||
// We should be the only ones holding this `Arc` since the event loop exiting cleanly
|
||||
// should drop the event handler. if this is not the case something funky is happening.
|
||||
Arc::get_mut(&mut exit_status)
|
||||
.map(|mutex| mutex.get_mut().unwrap().clone())
|
||||
.unwrap_or(AppExit::error())
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments /* TODO: probs can reduce # of args */)]
|
||||
|
@ -334,6 +349,7 @@ fn handle_winit_event(
|
|||
focused_windows_state: &mut SystemState<(Res<WinitSettings>, Query<&Window>)>,
|
||||
redraw_event_reader: &mut ManualEventReader<RequestRedraw>,
|
||||
winit_events: &mut Vec<WinitEvent>,
|
||||
exit_status: &mut AppExit,
|
||||
event: Event<UserEvent>,
|
||||
event_loop: &EventLoopWindowTarget<UserEvent>,
|
||||
) {
|
||||
|
@ -350,8 +366,14 @@ fn handle_winit_event(
|
|||
}
|
||||
runner_state.redraw_requested = true;
|
||||
|
||||
// TODO: Replace with `App::should_exit()`
|
||||
if let Some(app_exit_events) = app.world().get_resource::<Events<AppExit>>() {
|
||||
if app_exit_event_reader.read(app_exit_events).last().is_some() {
|
||||
let mut exit_events = app_exit_event_reader.read(app_exit_events);
|
||||
if exit_events.len() != 0 {
|
||||
*exit_status = exit_events
|
||||
.find(|exit| exit.is_error())
|
||||
.cloned()
|
||||
.unwrap_or(AppExit::Success);
|
||||
event_loop.exit();
|
||||
return;
|
||||
}
|
||||
|
@ -411,6 +433,7 @@ fn handle_winit_event(
|
|||
app_exit_event_reader,
|
||||
redraw_event_reader,
|
||||
winit_events,
|
||||
exit_status,
|
||||
);
|
||||
if runner_state.active != ActiveState::Suspended {
|
||||
event_loop.set_control_flow(ControlFlow::Poll);
|
||||
|
@ -638,6 +661,7 @@ fn handle_winit_event(
|
|||
app_exit_event_reader,
|
||||
redraw_event_reader,
|
||||
winit_events,
|
||||
exit_status,
|
||||
);
|
||||
}
|
||||
_ => {}
|
||||
|
@ -738,6 +762,7 @@ fn run_app_update_if_should(
|
|||
app_exit_event_reader: &mut ManualEventReader<AppExit>,
|
||||
redraw_event_reader: &mut ManualEventReader<RequestRedraw>,
|
||||
winit_events: &mut Vec<WinitEvent>,
|
||||
exit_status: &mut AppExit,
|
||||
) {
|
||||
runner_state.reset_on_update();
|
||||
|
||||
|
@ -797,9 +822,16 @@ fn run_app_update_if_should(
|
|||
}
|
||||
}
|
||||
|
||||
// TODO: Replace with `App::should_exit()`
|
||||
if let Some(app_exit_events) = app.world().get_resource::<Events<AppExit>>() {
|
||||
if app_exit_event_reader.read(app_exit_events).last().is_some() {
|
||||
let mut exit_events = app_exit_event_reader.read(app_exit_events);
|
||||
if exit_events.len() != 0 {
|
||||
*exit_status = exit_events
|
||||
.find(|exit| exit.is_error())
|
||||
.cloned()
|
||||
.unwrap_or(AppExit::Success);
|
||||
event_loop.exit();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
//! This example demonstrates you can create a custom runner (to update an app manually). It reads
|
||||
//! lines from stdin and prints them from within the ecs.
|
||||
|
||||
use bevy::prelude::*;
|
||||
use bevy::{app::AppExit, prelude::*};
|
||||
use std::io;
|
||||
|
||||
#[derive(Resource)]
|
||||
struct Input(String);
|
||||
|
||||
fn my_runner(mut app: App) {
|
||||
fn my_runner(mut app: App) -> AppExit {
|
||||
println!("Type stuff into the console");
|
||||
for line in io::stdin().lines() {
|
||||
{
|
||||
|
@ -15,17 +15,30 @@ fn my_runner(mut app: App) {
|
|||
input.0 = line.unwrap();
|
||||
}
|
||||
app.update();
|
||||
|
||||
if let Some(exit) = app.should_exit() {
|
||||
return exit;
|
||||
}
|
||||
}
|
||||
|
||||
AppExit::Success
|
||||
}
|
||||
|
||||
fn print_system(input: Res<Input>) {
|
||||
println!("You typed: {}", input.0);
|
||||
}
|
||||
|
||||
fn main() {
|
||||
fn exit_system(input: Res<Input>, mut exit_event: EventWriter<AppExit>) {
|
||||
if input.0 == "exit" {
|
||||
exit_event.send(AppExit::Success);
|
||||
}
|
||||
}
|
||||
|
||||
// AppExit implements `Termination` so we can return it from main.
|
||||
fn main() -> AppExit {
|
||||
App::new()
|
||||
.insert_resource(Input(String::new()))
|
||||
.set_runner(my_runner)
|
||||
.add_systems(Update, print_system)
|
||||
.run();
|
||||
.add_systems(Update, (print_system, exit_system))
|
||||
.run()
|
||||
}
|
||||
|
|
|
@ -130,10 +130,10 @@ fn game_over_system(
|
|||
) {
|
||||
if let Some(ref player) = game_state.winning_player {
|
||||
println!("{player} won the game!");
|
||||
app_exit_events.send(AppExit);
|
||||
app_exit_events.send(AppExit::Success);
|
||||
} else if game_state.current_round == game_rules.max_rounds {
|
||||
println!("Ran out of rounds. Nobody wins!");
|
||||
app_exit_events.send(AppExit);
|
||||
app_exit_events.send(AppExit::Success);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -334,7 +334,7 @@ fn quit(
|
|||
.distance(cursor_world_pos)
|
||||
< BEVY_LOGO_RADIUS
|
||||
{
|
||||
app_exit.send(AppExit);
|
||||
app_exit.send(AppExit::Success);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -789,7 +789,7 @@ mod menu {
|
|||
if *interaction == Interaction::Pressed {
|
||||
match menu_button_action {
|
||||
MenuButtonAction::Quit => {
|
||||
app_exit_events.send(AppExit);
|
||||
app_exit_events.send(AppExit::Success);
|
||||
}
|
||||
MenuButtonAction::Play => {
|
||||
game_state.set(GameState::Game);
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
//! An example that illustrates how Time is handled in ECS.
|
||||
|
||||
use bevy::app::AppExit;
|
||||
use bevy::prelude::*;
|
||||
|
||||
use std::io::{self, BufRead};
|
||||
|
@ -30,7 +31,7 @@ fn help() {
|
|||
println!(" u: Unpause");
|
||||
}
|
||||
|
||||
fn runner(mut app: App) {
|
||||
fn runner(mut app: App) -> AppExit {
|
||||
banner();
|
||||
help();
|
||||
let stdin = io::stdin();
|
||||
|
@ -78,6 +79,8 @@ fn runner(mut app: App) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
AppExit::Success
|
||||
}
|
||||
|
||||
fn print_real_time(time: Res<Time<Real>>) {
|
||||
|
|
Loading…
Reference in a new issue