Add exit code to ControlFlow::Exit (#2100)

* Add exit code to control flow and impl on linux

* Fix examples to have an exit code

* Fix doc examples to use an exit code

* Improve documentation wording on the exit code

* Add exit code example

* Add exit code on windows

* Change i32 as exit code to u8

This avoids nasty surprises with negative numbers on some unix-alikes
due to two's complement.

* Fix android usages of ControlFlow::Exit

* Fix ios usages of ControlFlow::Exit

* Fix web usages of ControlFlow::Exit

* Add macos exit code

* Add changelog note

* Document exit code on display server disconnection

* Revert "Change i32 as exit code to u8"

This reverts commit f88fba0253b45de6a2ac0c3cbcf01f50503c9396.

* Change Exit to ExitWithCode and make an Exit const

* Revert "Add exit code example"

This reverts commit fbd3d03de9c2d7516c7a63da489c99f498b710df.

* Revert "Fix doc examples to use an exit code"

This reverts commit daabcdf9ef9e16acad715c094ae442529e39fcbc.

* Revert "Fix examples to have an exit code"

This reverts commit 0df486896b8d106acf65ba83c45cc88d60d228e1.

* Fix unix-alike to use ExitWithCode instead of Exit

* Fix windows to use ExitWithCode rather than Exit

* Silence warning about non-uppercase Exit const

* Refactor exit code handling

* Fix macos Exit usage and recover original semantic

* Fix ios to use ExitWithCode instead of Exit

* Update documentation to reflect ExitWithCode

* Fix web to use ExitWithCode when needed, not Exit

* Fix android to use ExitWithCode, not Exit

* Apply documenation nits

* Apply even more documentation nits

* Move change in CHANGELOG.md under "Unreleased"

* Try to use OS error code as exit code on wayland
This commit is contained in:
multisn8 2022-01-11 01:23:20 +01:00 committed by GitHub
parent 2a2abc4843
commit a52f755ce8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 140 additions and 92 deletions

View file

@ -1,3 +1,7 @@
# Unreleased
- **Breaking:** Rename the `Exit` variant of `ControlFlow` to `ExitWithCode`, which holds a value to control the exit code after running. Add an `Exit` constant which aliases to `ExitWithCode(0)` instead to avoid major breakage. This shouldn't affect most existing programs.
# 0.26.1 (2022-01-05) # 0.26.1 (2022-01-05)
- Fix linking to the `ColorSync` framework on macOS 10.7, and in newer Rust versions. - Fix linking to the `ColorSync` framework on macOS 10.7, and in newer Rust versions.

View file

@ -64,9 +64,9 @@ impl<T> fmt::Debug for EventLoopWindowTarget<T> {
/// ///
/// ## Persistency /// ## Persistency
/// Almost every change is persistent between multiple calls to the event loop closure within a /// Almost every change is persistent between multiple calls to the event loop closure within a
/// given run loop. The only exception to this is `Exit` which, once set, cannot be unset. Changes /// given run loop. The only exception to this is `ExitWithCode` which, once set, cannot be unset.
/// are **not** persistent between multiple calls to `run_return` - issuing a new call will reset /// Changes are **not** persistent between multiple calls to `run_return` - issuing a new call will
/// the control flow to `Poll`. /// reset the control flow to `Poll`.
/// ///
/// [events_cleared]: crate::event::Event::RedrawEventsCleared /// [events_cleared]: crate::event::Event::RedrawEventsCleared
#[derive(Copy, Clone, Debug, PartialEq, Eq)] #[derive(Copy, Clone, Debug, PartialEq, Eq)]
@ -86,9 +86,29 @@ pub enum ControlFlow {
/// arrives or the given time is reached. /// arrives or the given time is reached.
WaitUntil(Instant), WaitUntil(Instant),
/// Send a `LoopDestroyed` event and stop the event loop. This variant is *sticky* - once set, /// Send a `LoopDestroyed` event and stop the event loop. This variant is *sticky* - once set,
/// `control_flow` cannot be changed from `Exit`, and any future attempts to do so will result /// `control_flow` cannot be changed from `ExitWithCode`, and any future attempts to do so will
/// in the `control_flow` parameter being reset to `Exit`. /// result in the `control_flow` parameter being reset to `ExitWithCode`.
Exit, ///
/// The contained number will be used as exit code. The [`Exit`] constant is a shortcut for this
/// with exit code 0.
///
/// ## Platform-specific
///
/// - **Android / iOS / WASM**: The supplied exit code is unused.
/// - **Unix**: On most Unix-like platforms, only the 8 least significant bits will be used,
/// which can cause surprises with negative exit values (`-42` would end up as `214`). See
/// [`std::process::exit`].
///
/// [`Exit`]: ControlFlow::Exit
ExitWithCode(i32),
}
impl ControlFlow {
/// Alias for [`ExitWithCode`]`(0)`.
///
/// [`ExitWithCode`]: ControlFlow::ExitWithCode
#[allow(non_upper_case_globals)]
pub const Exit: Self = Self::ExitWithCode(0);
} }
impl Default for ControlFlow { impl Default for ControlFlow {
@ -145,6 +165,11 @@ impl<T> EventLoop<T> {
/// ///
/// Any values not passed to this function will *not* be dropped. /// Any values not passed to this function will *not* be dropped.
/// ///
/// ## Platform-specific
///
/// - **X11 / Wayland**: The program terminates with exit code 1 if the display server
/// disconnects.
///
/// [`ControlFlow`]: crate::event_loop::ControlFlow /// [`ControlFlow`]: crate::event_loop::ControlFlow
#[inline] #[inline]
pub fn run<F>(self, event_handler: F) -> ! pub fn run<F>(self, event_handler: F) -> !

View file

@ -31,8 +31,8 @@
//! You can retrieve events by calling [`EventLoop::run`][event_loop_run]. This function will //! You can retrieve events by calling [`EventLoop::run`][event_loop_run]. This function will
//! dispatch events for every [`Window`] that was created with that particular [`EventLoop`], and //! dispatch events for every [`Window`] that was created with that particular [`EventLoop`], and
//! will run until the `control_flow` argument given to the closure is set to //! will run until the `control_flow` argument given to the closure is set to
//! [`ControlFlow`]`::`[`Exit`], at which point [`Event`]`::`[`LoopDestroyed`] is emitted and the //! [`ControlFlow`]`::`[`ExitWithCode`] (which [`ControlFlow`]`::`[`Exit`] aliases to), at which
//! entire program terminates. //! point [`Event`]`::`[`LoopDestroyed`] is emitted and the entire program terminates.
//! //!
//! Winit no longer uses a `EventLoop::poll_events() -> impl Iterator<Event>`-based event loop //! Winit no longer uses a `EventLoop::poll_events() -> impl Iterator<Event>`-based event loop
//! model, since that can't be implemented properly on some platforms (e.g web, iOS) and works poorly on //! model, since that can't be implemented properly on some platforms (e.g web, iOS) and works poorly on
@ -114,6 +114,7 @@
//! [event_loop_run]: event_loop::EventLoop::run //! [event_loop_run]: event_loop::EventLoop::run
//! [`ControlFlow`]: event_loop::ControlFlow //! [`ControlFlow`]: event_loop::ControlFlow
//! [`Exit`]: event_loop::ControlFlow::Exit //! [`Exit`]: event_loop::ControlFlow::Exit
//! [`ExitWithCode`]: event_loop::ControlFlow::ExitWithCode
//! [`Window`]: window::Window //! [`Window`]: window::Window
//! [`WindowId`]: window::WindowId //! [`WindowId`]: window::WindowId
//! [`WindowBuilder`]: window::WindowBuilder //! [`WindowBuilder`]: window::WindowBuilder

View file

@ -33,7 +33,12 @@ pub trait EventLoopExtRunReturn {
/// underlying OS APIs, which cannot be hidden by `winit` without severe stability repercussions. /// underlying OS APIs, which cannot be hidden by `winit` without severe stability repercussions.
/// ///
/// You are strongly encouraged to use `run`, unless the use of this is absolutely necessary. /// You are strongly encouraged to use `run`, unless the use of this is absolutely necessary.
fn run_return<F>(&mut self, event_handler: F) ///
/// ## Platform-specific
///
/// - **Unix-alikes** (**X11** or **Wayland**): This function returns `1` upon disconnection from
/// the display server.
fn run_return<F>(&mut self, event_handler: F) -> i32
where where
F: FnMut( F: FnMut(
Event<'_, Self::UserEvent>, Event<'_, Self::UserEvent>,
@ -45,7 +50,7 @@ pub trait EventLoopExtRunReturn {
impl<T> EventLoopExtRunReturn for EventLoop<T> { impl<T> EventLoopExtRunReturn for EventLoop<T> {
type UserEvent = T; type UserEvent = T;
fn run_return<F>(&mut self, event_handler: F) fn run_return<F>(&mut self, event_handler: F) -> i32
where where
F: FnMut( F: FnMut(
Event<'_, Self::UserEvent>, Event<'_, Self::UserEvent>,

View file

@ -73,10 +73,10 @@ pub struct EventLoop<T: 'static> {
macro_rules! call_event_handler { macro_rules! call_event_handler {
( $event_handler:expr, $window_target:expr, $cf:expr, $event:expr ) => {{ ( $event_handler:expr, $window_target:expr, $cf:expr, $event:expr ) => {{
if $cf != ControlFlow::Exit { if let ControlFlow::ExitWithCode(code) = $cf {
$event_handler($event, $window_target, &mut $cf); $event_handler($event, $window_target, &mut ControlFlow::ExitWithCode(code));
} else { } else {
$event_handler($event, $window_target, &mut ControlFlow::Exit); $event_handler($event, $window_target, &mut $cf);
} }
}}; }};
} }
@ -103,11 +103,11 @@ impl<T: 'static> EventLoop<T> {
F: 'static F: 'static
+ FnMut(event::Event<'_, T>, &event_loop::EventLoopWindowTarget<T>, &mut ControlFlow), + FnMut(event::Event<'_, T>, &event_loop::EventLoopWindowTarget<T>, &mut ControlFlow),
{ {
self.run_return(event_handler); let exit_code = self.run_return(event_handler);
::std::process::exit(0); ::std::process::exit(exit_code);
} }
pub fn run_return<F>(&mut self, mut event_handler: F) pub fn run_return<F>(&mut self, mut event_handler: F) -> i32
where where
F: FnMut(event::Event<'_, T>, &event_loop::EventLoopWindowTarget<T>, &mut ControlFlow), F: FnMut(event::Event<'_, T>, &event_loop::EventLoopWindowTarget<T>, &mut ControlFlow),
{ {
@ -339,7 +339,7 @@ impl<T: 'static> EventLoop<T> {
); );
match control_flow { match control_flow {
ControlFlow::Exit => { ControlFlow::ExitWithCode(code) => {
self.first_event = poll( self.first_event = poll(
self.looper self.looper
.poll_once_timeout(Duration::from_millis(0)) .poll_once_timeout(Duration::from_millis(0))
@ -349,7 +349,7 @@ impl<T: 'static> EventLoop<T> {
start: Instant::now(), start: Instant::now(),
requested_resume: None, requested_resume: None,
}; };
break 'event_loop; break 'event_loop code;
} }
ControlFlow::Poll => { ControlFlow::Poll => {
self.first_event = poll( self.first_event = poll(

View file

@ -296,7 +296,7 @@ impl AppState {
}; };
(waiting_event_handler, event) (waiting_event_handler, event)
} }
(ControlFlow::Exit, _) => bug!("unexpected `ControlFlow` `Exit`"), (ControlFlow::ExitWithCode(_), _) => bug!("unexpected `ControlFlow` `Exit`"),
s => bug!("`EventHandler` unexpectedly woke up {:?}", s), s => bug!("`EventHandler` unexpectedly woke up {:?}", s),
}; };
@ -451,7 +451,7 @@ impl AppState {
}); });
self.waker.start() self.waker.start()
} }
(_, ControlFlow::Exit) => { (_, ControlFlow::ExitWithCode(_)) => {
// https://developer.apple.com/library/archive/qa/qa1561/_index.html // https://developer.apple.com/library/archive/qa/qa1561/_index.html
// it is not possible to quit an iOS app gracefully and programatically // it is not possible to quit an iOS app gracefully and programatically
warn!("`ControlFlow::Exit` ignored on iOS"); warn!("`ControlFlow::Exit` ignored on iOS");

View file

@ -655,7 +655,7 @@ impl<T: 'static> EventLoop<T> {
x11_or_wayland!(match self; EventLoop(evlp) => evlp.create_proxy(); as EventLoopProxy) x11_or_wayland!(match self; EventLoop(evlp) => evlp.create_proxy(); as EventLoopProxy)
} }
pub fn run_return<F>(&mut self, callback: F) pub fn run_return<F>(&mut self, callback: F) -> i32
where where
F: FnMut(crate::event::Event<'_, T>, &RootELW<T>, &mut ControlFlow), F: FnMut(crate::event::Event<'_, T>, &RootELW<T>, &mut ControlFlow),
{ {
@ -741,16 +741,13 @@ fn sticky_exit_callback<T, F>(
) where ) where
F: FnMut(Event<'_, T>, &RootELW<T>, &mut ControlFlow), F: FnMut(Event<'_, T>, &RootELW<T>, &mut ControlFlow),
{ {
// make ControlFlow::Exit sticky by providing a dummy // make ControlFlow::ExitWithCode sticky by providing a dummy
// control flow reference if it is already Exit. // control flow reference if it is already ExitWithCode.
let mut dummy = ControlFlow::Exit; if let ControlFlow::ExitWithCode(code) = *control_flow {
let cf = if *control_flow == ControlFlow::Exit { callback(evt, target, &mut ControlFlow::ExitWithCode(code))
&mut dummy
} else { } else {
control_flow callback(evt, target, control_flow)
}; }
// user callback
callback(evt, target, cf)
} }
fn assert_is_main_thread(suggested_method: &str) { fn assert_is_main_thread(suggested_method: &str) {

View file

@ -206,11 +206,11 @@ impl<T: 'static> EventLoop<T> {
where where
F: FnMut(Event<'_, T>, &RootEventLoopWindowTarget<T>, &mut ControlFlow) + 'static, F: FnMut(Event<'_, T>, &RootEventLoopWindowTarget<T>, &mut ControlFlow) + 'static,
{ {
self.run_return(callback); let exit_code = self.run_return(callback);
process::exit(0) process::exit(exit_code);
} }
pub fn run_return<F>(&mut self, mut callback: F) pub fn run_return<F>(&mut self, mut callback: F) -> i32
where where
F: FnMut(Event<'_, T>, &RootEventLoopWindowTarget<T>, &mut ControlFlow), F: FnMut(Event<'_, T>, &RootEventLoopWindowTarget<T>, &mut ControlFlow),
{ {
@ -235,7 +235,8 @@ impl<T: 'static> EventLoop<T> {
// really an option. Instead we inform that the event loop got destroyed. We may // really an option. Instead we inform that the event loop got destroyed. We may
// communicate an error that something was terminated, but winit doesn't provide us // communicate an error that something was terminated, but winit doesn't provide us
// with an API to do that via some event. // with an API to do that via some event.
loop { // Still, we set the exit code to the error's OS error code, or to 1 if not possible.
let exit_code = loop {
// Handle pending user events. We don't need back buffer, since we can't dispatch // Handle pending user events. We don't need back buffer, since we can't dispatch
// user events indirectly via callback to the user. // user events indirectly via callback to the user.
for user_event in pending_user_events.borrow_mut().drain(..) { for user_event in pending_user_events.borrow_mut().drain(..) {
@ -431,20 +432,19 @@ impl<T: 'static> EventLoop<T> {
_ => unreachable!(), _ => unreachable!(),
}; };
if let Ok(dispatched) = queue.dispatch_pending(state, |_, _, _| unimplemented!()) { match queue.dispatch_pending(state, |_, _, _| unimplemented!()) {
dispatched > 0 Ok(dispatched) => dispatched > 0,
} else { Err(error) => break error.raw_os_error().unwrap_or(1),
break;
} }
}; };
match control_flow { match control_flow {
ControlFlow::Exit => break, ControlFlow::ExitWithCode(code) => break code,
ControlFlow::Poll => { ControlFlow::Poll => {
// Non-blocking dispatch. // Non-blocking dispatch.
let timeout = Duration::from_millis(0); let timeout = Duration::from_millis(0);
if self.loop_dispatch(Some(timeout)).is_err() { if let Err(error) = self.loop_dispatch(Some(timeout)) {
break; break error.raw_os_error().unwrap_or(1);
} }
callback( callback(
@ -460,8 +460,8 @@ impl<T: 'static> EventLoop<T> {
None None
}; };
if self.loop_dispatch(timeout).is_err() { if let Err(error) = self.loop_dispatch(timeout) {
break; break error.raw_os_error().unwrap_or(1);
} }
callback( callback(
@ -483,8 +483,8 @@ impl<T: 'static> EventLoop<T> {
Duration::from_millis(0) Duration::from_millis(0)
}; };
if self.loop_dispatch(Some(duration)).is_err() { if let Err(error) = self.loop_dispatch(Some(duration)) {
break; break error.raw_os_error().unwrap_or(1);
} }
let now = Instant::now(); let now = Instant::now();
@ -510,9 +510,10 @@ impl<T: 'static> EventLoop<T> {
} }
} }
} }
} };
callback(Event::LoopDestroyed, &self.window_target, &mut control_flow); callback(Event::LoopDestroyed, &self.window_target, &mut control_flow);
exit_code
} }
#[inline] #[inline]

View file

@ -258,7 +258,7 @@ impl<T: 'static> EventLoop<T> {
&self.target &self.target
} }
pub fn run_return<F>(&mut self, mut callback: F) pub fn run_return<F>(&mut self, mut callback: F) -> i32
where where
F: FnMut(Event<'_, T>, &RootELW<T>, &mut ControlFlow), F: FnMut(Event<'_, T>, &RootELW<T>, &mut ControlFlow),
{ {
@ -266,7 +266,7 @@ impl<T: 'static> EventLoop<T> {
let mut events = Events::with_capacity(8); let mut events = Events::with_capacity(8);
let mut cause = StartCause::Init; let mut cause = StartCause::Init;
loop { let exit_code = loop {
sticky_exit_callback( sticky_exit_callback(
crate::event::Event::NewEvents(cause), crate::event::Event::NewEvents(cause),
&self.target, &self.target,
@ -329,7 +329,7 @@ impl<T: 'static> EventLoop<T> {
let (deadline, timeout); let (deadline, timeout);
match control_flow { match control_flow {
ControlFlow::Exit => break, ControlFlow::ExitWithCode(code) => break code,
ControlFlow::Poll => { ControlFlow::Poll => {
cause = StartCause::Poll; cause = StartCause::Poll;
deadline = None; deadline = None;
@ -376,21 +376,22 @@ impl<T: 'static> EventLoop<T> {
requested_resume: deadline, requested_resume: deadline,
}; };
} }
} };
callback( callback(
crate::event::Event::LoopDestroyed, crate::event::Event::LoopDestroyed,
&self.target, &self.target,
&mut control_flow, &mut control_flow,
); );
exit_code
} }
pub fn run<F>(mut self, callback: F) -> ! pub fn run<F>(mut self, callback: F) -> !
where where
F: 'static + FnMut(Event<'_, T>, &RootELW<T>, &mut ControlFlow), F: 'static + FnMut(Event<'_, T>, &RootELW<T>, &mut ControlFlow),
{ {
self.run_return(callback); let exit_code = self.run_return(callback);
::std::process::exit(0); ::std::process::exit(exit_code);
} }
fn drain_events<F>(&mut self, callback: &mut F, control_flow: &mut ControlFlow) fn drain_events<F>(&mut self, callback: &mut F, control_flow: &mut ControlFlow)

View file

@ -62,7 +62,6 @@ pub trait EventHandler: Debug {
struct EventLoopHandler<T: 'static> { struct EventLoopHandler<T: 'static> {
callback: Weak<RefCell<dyn FnMut(Event<'_, T>, &RootWindowTarget<T>, &mut ControlFlow)>>, callback: Weak<RefCell<dyn FnMut(Event<'_, T>, &RootWindowTarget<T>, &mut ControlFlow)>>,
will_exit: bool,
window_target: Rc<RootWindowTarget<T>>, window_target: Rc<RootWindowTarget<T>>,
} }
@ -98,25 +97,25 @@ impl<T> Debug for EventLoopHandler<T> {
impl<T> EventHandler for EventLoopHandler<T> { impl<T> EventHandler for EventLoopHandler<T> {
fn handle_nonuser_event(&mut self, event: Event<'_, Never>, control_flow: &mut ControlFlow) { fn handle_nonuser_event(&mut self, event: Event<'_, Never>, control_flow: &mut ControlFlow) {
self.with_callback(|this, mut callback| { self.with_callback(|this, mut callback| {
(callback)(event.userify(), &this.window_target, control_flow); if let ControlFlow::ExitWithCode(code) = *control_flow {
this.will_exit |= *control_flow == ControlFlow::Exit; let dummy = &mut ControlFlow::ExitWithCode(code);
if this.will_exit { (callback)(event.userify(), &this.window_target, dummy);
*control_flow = ControlFlow::Exit; } else {
(callback)(event.userify(), &this.window_target, control_flow);
} }
}); });
} }
fn handle_user_events(&mut self, control_flow: &mut ControlFlow) { fn handle_user_events(&mut self, control_flow: &mut ControlFlow) {
self.with_callback(|this, mut callback| { self.with_callback(|this, mut callback| {
let mut will_exit = this.will_exit;
for event in this.window_target.p.receiver.try_iter() { for event in this.window_target.p.receiver.try_iter() {
(callback)(Event::UserEvent(event), &this.window_target, control_flow); if let ControlFlow::ExitWithCode(code) = *control_flow {
will_exit |= *control_flow == ControlFlow::Exit; let dummy = &mut ControlFlow::ExitWithCode(code);
if will_exit { (callback)(Event::UserEvent(event), &this.window_target, dummy);
*control_flow = ControlFlow::Exit; } else {
(callback)(Event::UserEvent(event), &this.window_target, control_flow);
} }
} }
this.will_exit = will_exit;
}); });
} }
} }
@ -160,7 +159,10 @@ impl Handler {
} }
fn should_exit(&self) -> bool { fn should_exit(&self) -> bool {
*self.control_flow.lock().unwrap() == ControlFlow::Exit matches!(
*self.control_flow.lock().unwrap(),
ControlFlow::ExitWithCode(_)
)
} }
fn get_control_flow_and_update_prev(&self) -> ControlFlow { fn get_control_flow_and_update_prev(&self) -> ControlFlow {
@ -268,16 +270,20 @@ impl AppState {
) { ) {
*HANDLER.callback.lock().unwrap() = Some(Box::new(EventLoopHandler { *HANDLER.callback.lock().unwrap() = Some(Box::new(EventLoopHandler {
callback, callback,
will_exit: false,
window_target, window_target,
})); }));
} }
pub fn exit() { pub fn exit() -> i32 {
HANDLER.set_in_callback(true); HANDLER.set_in_callback(true);
HANDLER.handle_nonuser_event(EventWrapper::StaticEvent(Event::LoopDestroyed)); HANDLER.handle_nonuser_event(EventWrapper::StaticEvent(Event::LoopDestroyed));
HANDLER.set_in_callback(false); HANDLER.set_in_callback(false);
HANDLER.callback.lock().unwrap().take(); HANDLER.callback.lock().unwrap().take();
if let ControlFlow::ExitWithCode(code) = HANDLER.get_old_and_new_control_flow().1 {
code
} else {
0
}
} }
pub fn launched(app_delegate: &Object) { pub fn launched(app_delegate: &Object) {
@ -332,7 +338,7 @@ impl AppState {
} }
} }
} }
ControlFlow::Exit => StartCause::Poll, //panic!("unexpected `ControlFlow::Exit`"), ControlFlow::ExitWithCode(_) => StartCause::Poll, //panic!("unexpected `ControlFlow::Exit`"),
}; };
HANDLER.set_in_callback(true); HANDLER.set_in_callback(true);
HANDLER.handle_nonuser_event(EventWrapper::StaticEvent(Event::NewEvents(cause))); HANDLER.handle_nonuser_event(EventWrapper::StaticEvent(Event::NewEvents(cause)));
@ -435,7 +441,7 @@ impl AppState {
} }
HANDLER.update_start_time(); HANDLER.update_start_time();
match HANDLER.get_old_and_new_control_flow() { match HANDLER.get_old_and_new_control_flow() {
(ControlFlow::Exit, _) | (_, ControlFlow::Exit) => (), (ControlFlow::ExitWithCode(_), _) | (_, ControlFlow::ExitWithCode(_)) => (),
(old, new) if old == new => (), (old, new) if old == new => (),
(_, ControlFlow::Wait) => HANDLER.waker().stop(), (_, ControlFlow::Wait) => HANDLER.waker().stop(),
(_, ControlFlow::WaitUntil(instant)) => HANDLER.waker().start_at(instant), (_, ControlFlow::WaitUntil(instant)) => HANDLER.waker().start_at(instant),

View file

@ -155,11 +155,11 @@ impl<T> EventLoop<T> {
where where
F: 'static + FnMut(Event<'_, T>, &RootWindowTarget<T>, &mut ControlFlow), F: 'static + FnMut(Event<'_, T>, &RootWindowTarget<T>, &mut ControlFlow),
{ {
self.run_return(callback); let exit_code = self.run_return(callback);
process::exit(0); process::exit(exit_code);
} }
pub fn run_return<F>(&mut self, callback: F) pub fn run_return<F>(&mut self, callback: F) -> i32
where where
F: FnMut(Event<'_, T>, &RootWindowTarget<T>, &mut ControlFlow), F: FnMut(Event<'_, T>, &RootWindowTarget<T>, &mut ControlFlow),
{ {
@ -176,7 +176,7 @@ impl<T> EventLoop<T> {
self._callback = Some(Rc::clone(&callback)); self._callback = Some(Rc::clone(&callback));
autoreleasepool(|| unsafe { let exit_code = autoreleasepool(|| unsafe {
let app = NSApp(); let app = NSApp();
assert_ne!(app, nil); assert_ne!(app, nil);
@ -192,9 +192,11 @@ impl<T> EventLoop<T> {
drop(self._callback.take()); drop(self._callback.take());
resume_unwind(panic); resume_unwind(panic);
} }
AppState::exit(); AppState::exit()
}); });
drop(self._callback.take()); drop(self._callback.take());
exit_code
} }
pub fn create_proxy(&self) -> Proxy<T> { pub fn create_proxy(&self) -> Proxy<T> {

View file

@ -84,7 +84,7 @@ impl<T: 'static> Runner<T> {
} }
fn handle_single_event(&mut self, event: Event<'_, T>, control: &mut root::ControlFlow) { fn handle_single_event(&mut self, event: Event<'_, T>, control: &mut root::ControlFlow) {
let is_closed = *control == root::ControlFlow::Exit; let is_closed = matches!(*control, root::ControlFlow::ExitWithCode(_));
(self.event_handler)(event, control); (self.event_handler)(event, control);
@ -409,7 +409,7 @@ impl<T: 'static> Shared<T> {
RunnerEnum::Destroyed => return, RunnerEnum::Destroyed => return,
} }
let is_closed = *control == root::ControlFlow::Exit; let is_closed = matches!(*control, root::ControlFlow::ExitWithCode(_));
// Don't take events out of the queue if the loop is closed or the runner doesn't exist // Don't take events out of the queue if the loop is closed or the runner doesn't exist
// If the runner doesn't exist and this method recurses, it will recurse infinitely // If the runner doesn't exist and this method recurses, it will recurse infinitely
@ -456,7 +456,7 @@ impl<T: 'static> Shared<T> {
), ),
} }
} }
root::ControlFlow::Exit => State::Exit, root::ControlFlow::ExitWithCode(_) => State::Exit,
}; };
match *self.0.runner.borrow_mut() { match *self.0.runner.borrow_mut() {

View file

@ -188,11 +188,11 @@ impl<T: 'static> EventLoop<T> {
where where
F: 'static + FnMut(Event<'_, T>, &RootELW<T>, &mut ControlFlow), F: 'static + FnMut(Event<'_, T>, &RootELW<T>, &mut ControlFlow),
{ {
self.run_return(event_handler); let exit_code = self.run_return(event_handler);
::std::process::exit(0); ::std::process::exit(exit_code);
} }
pub fn run_return<F>(&mut self, mut event_handler: F) pub fn run_return<F>(&mut self, mut event_handler: F) -> i32
where where
F: FnMut(Event<'_, T>, &RootELW<T>, &mut ControlFlow), F: FnMut(Event<'_, T>, &RootELW<T>, &mut ControlFlow),
{ {
@ -209,13 +209,13 @@ impl<T: 'static> EventLoop<T> {
let runner = &self.window_target.p.runner_shared; let runner = &self.window_target.p.runner_shared;
unsafe { let exit_code = unsafe {
let mut msg = mem::zeroed(); let mut msg = mem::zeroed();
runner.poll(); runner.poll();
'main: loop { 'main: loop {
if 0 == winuser::GetMessageW(&mut msg, ptr::null_mut(), 0, 0) { if 0 == winuser::GetMessageW(&mut msg, ptr::null_mut(), 0, 0) {
break 'main; break 'main 0;
} }
winuser::TranslateMessage(&msg); winuser::TranslateMessage(&msg);
winuser::DispatchMessageW(&msg); winuser::DispatchMessageW(&msg);
@ -225,16 +225,20 @@ impl<T: 'static> EventLoop<T> {
panic::resume_unwind(payload); panic::resume_unwind(payload);
} }
if runner.control_flow() == ControlFlow::Exit && !runner.handling_events() { if let ControlFlow::ExitWithCode(code) = runner.control_flow() {
break 'main; if !runner.handling_events() {
break 'main code;
}
} }
} }
} };
unsafe { unsafe {
runner.loop_destroyed(); runner.loop_destroyed();
} }
runner.reset_runner(); runner.reset_runner();
exit_code
} }
pub fn create_proxy(&self) -> EventLoopProxy<T> { pub fn create_proxy(&self) -> EventLoopProxy<T> {
@ -763,7 +767,7 @@ unsafe fn process_control_flow<T: 'static>(runner: &EventLoopRunner<T>) {
Box::into_raw(WaitUntilInstantBox::new(until)) as LPARAM, Box::into_raw(WaitUntilInstantBox::new(until)) as LPARAM,
); );
} }
ControlFlow::Exit => (), ControlFlow::ExitWithCode(_) => (),
} }
} }

View file

@ -241,10 +241,10 @@ impl<T> EventLoopRunner<T> {
let mut event_handler = self.event_handler.take() let mut event_handler = self.event_handler.take()
.expect("either event handler is re-entrant (likely), or no event handler is registered (very unlikely)"); .expect("either event handler is re-entrant (likely), or no event handler is registered (very unlikely)");
if control_flow != ControlFlow::Exit { if let ControlFlow::ExitWithCode(code) = control_flow {
event_handler(event, &mut control_flow); event_handler(event, &mut ControlFlow::ExitWithCode(code));
} else { } else {
event_handler(event, &mut ControlFlow::Exit); event_handler(event, &mut control_flow);
} }
assert!(self.event_handler.replace(Some(event_handler)).is_none()); assert!(self.event_handler.replace(Some(event_handler)).is_none());
@ -372,10 +372,12 @@ impl<T> EventLoopRunner<T> {
let start_cause = match (init, self.control_flow()) { let start_cause = match (init, self.control_flow()) {
(true, _) => StartCause::Init, (true, _) => StartCause::Init,
(false, ControlFlow::Poll) => StartCause::Poll, (false, ControlFlow::Poll) => StartCause::Poll,
(false, ControlFlow::Exit) | (false, ControlFlow::Wait) => StartCause::WaitCancelled { (false, ControlFlow::ExitWithCode(_)) | (false, ControlFlow::Wait) => {
requested_resume: None, StartCause::WaitCancelled {
start: self.last_events_cleared.get(), requested_resume: None,
}, start: self.last_events_cleared.get(),
}
}
(false, ControlFlow::WaitUntil(requested_resume)) => { (false, ControlFlow::WaitUntil(requested_resume)) => {
if Instant::now() < requested_resume { if Instant::now() < requested_resume {
StartCause::WaitCancelled { StartCause::WaitCancelled {