Skip to content

Commit e00effa

Browse files
committed
Include a error code in AppExit
1 parent 9d0d04c commit e00effa

File tree

4 files changed

+102
-26
lines changed

4 files changed

+102
-26
lines changed

crates/bevy_app/src/app.rs

Lines changed: 89 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,14 @@ use bevy_ecs::{
1313
#[cfg(feature = "trace")]
1414
use bevy_utils::tracing::info_span;
1515
use bevy_utils::{tracing::debug, HashMap};
16-
use std::panic::{catch_unwind, resume_unwind, AssertUnwindSafe};
1716
use std::{
1817
fmt::Debug,
1918
process::{ExitCode, Termination},
2019
};
20+
use std::{
21+
num::NonZeroU8,
22+
panic::{catch_unwind, resume_unwind, AssertUnwindSafe},
23+
};
2124
use thiserror::Error;
2225

2326
bevy_ecs::define_label!(
@@ -852,6 +855,37 @@ impl App {
852855
self.main_mut().ignore_ambiguity(schedule, a, b);
853856
self
854857
}
858+
859+
/// Attempts to determine if an [`AppExit`] was raised since the last update.
860+
///
861+
/// Will attempt to return the first [`Error`](AppExit::Error) it encounters.
862+
pub fn should_exit(&self) -> Option<AppExit> {
863+
let mut reader = ManualEventReader::default();
864+
865+
self.should_exit_manual(&mut reader)
866+
}
867+
868+
/// Several app runners in this crate keep their own [`ManualEventReader<AppExit>`].
869+
/// This exists to accommodate them.
870+
pub(crate) fn should_exit_manual(
871+
&self,
872+
reader: &mut ManualEventReader<AppExit>,
873+
) -> Option<AppExit> {
874+
let events = self.world().get_resource::<Events<AppExit>>()?;
875+
876+
let mut events = reader.read(events);
877+
878+
if events.len() != 0 {
879+
return Some(
880+
events
881+
.find(|exit| exit.is_error())
882+
.cloned()
883+
.unwrap_or(AppExit::Success),
884+
);
885+
}
886+
887+
None
888+
}
855889
}
856890

857891
type RunnerFn = Box<dyn FnOnce(App) -> AppExit>;
@@ -870,9 +904,9 @@ fn run_once(mut app: App) -> AppExit {
870904
if let Some(app_exit_events) = app.world().get_resource::<Events<AppExit>>() {
871905
if exit_code_reader
872906
.read(app_exit_events)
873-
.any(|exit| *exit == AppExit::Error)
907+
.any(AppExit::is_error)
874908
{
875-
return AppExit::Error;
909+
return AppExit::error();
876910
}
877911
}
878912

@@ -890,21 +924,62 @@ pub enum AppExit {
890924
#[default]
891925
Success,
892926
/// The [`App`] experienced an unhandleable error.
893-
Error,
927+
/// Holds the exit code we expect our app to return.
928+
Error(NonZeroU8),
929+
}
930+
931+
impl AppExit {
932+
/// Creates a [`AppExit::Error`] with a error code of 1.
933+
#[must_use]
934+
pub fn error() -> Self {
935+
Self::Error(NonZeroU8::MIN)
936+
}
937+
938+
/// Returns `true` if `self` is a [`AppExit::Success`].
939+
#[must_use]
940+
pub fn is_success(&self) -> bool {
941+
matches!(self, AppExit::Success)
942+
}
943+
944+
/// Returns `true` if `self` is a [`AppExit::Error`].
945+
#[must_use]
946+
pub fn is_error(&self) -> bool {
947+
matches!(self, AppExit::Error(_))
948+
}
949+
950+
/// Creates a [`AppExit`] from a code.
951+
///
952+
/// When code is 0 a [`AppExit::Success`] is constructed otherwise a
953+
/// [`AppExit::Error`] is constructed.
954+
#[must_use]
955+
pub fn from_code(code: u8) -> Self {
956+
match NonZeroU8::new(code) {
957+
Some(code) => Self::Error(code),
958+
None => Self::Success,
959+
}
960+
}
961+
}
962+
963+
impl From<u8> for AppExit {
964+
#[must_use]
965+
fn from(value: u8) -> Self {
966+
Self::from_code(value)
967+
}
894968
}
895969

896970
impl Termination for AppExit {
897971
fn report(self) -> std::process::ExitCode {
898972
match self {
899973
AppExit::Success => ExitCode::SUCCESS,
900-
AppExit::Error => ExitCode::FAILURE,
974+
// We leave logging an error to our users
975+
AppExit::Error(value) => ExitCode::from(value.get()),
901976
}
902977
}
903978
}
904979

905980
#[cfg(test)]
906981
mod tests {
907-
use std::marker::PhantomData;
982+
use std::{marker::PhantomData, mem};
908983

909984
use bevy_ecs::{
910985
schedule::{OnEnter, States},
@@ -1112,7 +1187,7 @@ mod tests {
11121187
/// fix: <https://github.com/bevyengine/bevy/pull/10389>
11131188
#[test]
11141189
fn regression_test_10385() {
1115-
use super::{AppExit, Res, Resource};
1190+
use super::{Res, Resource};
11161191
use crate::PreUpdate;
11171192

11181193
#[derive(Resource)]
@@ -1139,4 +1214,11 @@ mod tests {
11391214
.add_systems(PreUpdate, my_system)
11401215
.run();
11411216
}
1217+
1218+
#[test]
1219+
fn app_exit_size() {
1220+
// There wont be many of them so the size isn't a issue but
1221+
// it's nice they're so small let's keep it that way.
1222+
assert_eq!(mem::size_of::<AppExit>(), mem::size_of::<u8>());
1223+
}
11421224
}

crates/bevy_app/src/schedule_runner.rs

Lines changed: 6 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use crate::{
33
plugin::Plugin,
44
PluginsState,
55
};
6-
use bevy_ecs::event::{Events, ManualEventReader};
6+
use bevy_ecs::event::ManualEventReader;
77
use bevy_utils::{Duration, Instant};
88

99
#[cfg(target_arch = "wasm32")]
@@ -87,14 +87,8 @@ impl Plugin for ScheduleRunnerPlugin {
8787
RunMode::Once => {
8888
app.update();
8989

90-
let mut exit_code_reader = ManualEventReader::default();
91-
if let Some(app_exit_events) = app.world().get_resource::<Events<AppExit>>() {
92-
if exit_code_reader
93-
.read(app_exit_events)
94-
.any(|exit| *exit == AppExit::Error)
95-
{
96-
return AppExit::Error;
97-
}
90+
if let Some(exit) = app.should_exit_manual(&mut app_exit_event_reader) {
91+
return exit;
9892
}
9993

10094
AppExit::Success
@@ -107,12 +101,9 @@ impl Plugin for ScheduleRunnerPlugin {
107101

108102
app.update();
109103

110-
if let Some(app_exit_events) = app.world().get_resource::<Events<AppExit>>()
111-
{
112-
if let Some(exit) = app_exit_event_reader.read(app_exit_events).last() {
113-
return Err(exit.clone());
114-
}
115-
}
104+
if let Some(exit) = app.should_exit_manual(&mut app_exit_event_reader) {
105+
return Err(exit);
106+
};
116107

117108
let end_time = Instant::now();
118109

crates/bevy_render/src/pipelined_rendering.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,7 @@ fn renderer_extract(app_world: &mut World, _world: &mut World) {
193193
render_channels.send_blocking(render_app);
194194
} else {
195195
// Renderer thread panicked
196-
world.send_event(AppExit::Error);
196+
world.send_event(AppExit::error());
197197
}
198198
});
199199
});

crates/bevy_winit/src/lib.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,7 @@ pub fn winit_runner(mut app: App) -> AppExit {
281281

282282
let mut runner_state = WinitAppRunnerState::default();
283283

284+
// TODO: AppExit is effectively a u8 we could use a AtomicU8 here instead of a mutex.
284285
let mut exit_status = Arc::new(Mutex::new(AppExit::Success));
285286
let handle_exit_status = exit_status.clone();
286287

@@ -330,7 +331,7 @@ pub fn winit_runner(mut app: App) -> AppExit {
330331
// should drop the event handler. if this is not the case something funky is happening.
331332
Arc::get_mut(&mut exit_status)
332333
.map(|mutex| mutex.get_mut().unwrap().clone())
333-
.unwrap_or(AppExit::Error)
334+
.unwrap_or(AppExit::error())
334335
}
335336

336337
#[allow(clippy::too_many_arguments /* TODO: probs can reduce # of args */)]
@@ -365,11 +366,12 @@ fn handle_winit_event(
365366
}
366367
runner_state.redraw_requested = true;
367368

369+
// BEFORE_MERGE(Brezak): Consider replacing after discussion and before pr.
368370
if let Some(app_exit_events) = app.world().get_resource::<Events<AppExit>>() {
369371
let mut exit_events = app_exit_event_reader.read(app_exit_events);
370372
if exit_events.len() != 0 {
371373
*exit_status = exit_events
372-
.find(|exit| **exit == AppExit::Error)
374+
.find(|exit| exit.is_error())
373375
.cloned()
374376
.unwrap_or(AppExit::Success);
375377
event_loop.exit();
@@ -820,11 +822,12 @@ fn run_app_update_if_should(
820822
}
821823
}
822824

825+
// BEFORE_MERGE(Brezak): Consider replacing after discussion and before pr.
823826
if let Some(app_exit_events) = app.world().get_resource::<Events<AppExit>>() {
824827
let mut exit_events = app_exit_event_reader.read(app_exit_events);
825828
if exit_events.len() != 0 {
826829
*exit_status = exit_events
827-
.find(|exit| **exit == AppExit::Error)
830+
.find(|exit| exit.is_error())
828831
.cloned()
829832
.unwrap_or(AppExit::Success);
830833
event_loop.exit();

0 commit comments

Comments
 (0)