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)
- 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
/// 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
/// are **not** persistent between multiple calls to `run_return` - issuing a new call will reset
/// the control flow to `Poll`.
/// given run loop. The only exception to this is `ExitWithCode` which, once set, cannot be unset.
/// Changes are **not** persistent between multiple calls to `run_return` - issuing a new call will
/// reset the control flow to `Poll`.
///
/// [events_cleared]: crate::event::Event::RedrawEventsCleared
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
@ -86,9 +86,29 @@ pub enum ControlFlow {
/// arrives or the given time is reached.
WaitUntil(Instant),
/// 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
/// in the `control_flow` parameter being reset to `Exit`.
Exit,
/// `control_flow` cannot be changed from `ExitWithCode`, and any future attempts to do so will
/// result in the `control_flow` parameter being reset to `ExitWithCode`.
///
/// 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 {
@ -145,6 +165,11 @@ impl<T> EventLoop<T> {
///
/// 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
#[inline]
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
//! 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
//! [`ControlFlow`]`::`[`Exit`], at which point [`Event`]`::`[`LoopDestroyed`] is emitted and the
//! entire program terminates.
//! [`ControlFlow`]`::`[`ExitWithCode`] (which [`ControlFlow`]`::`[`Exit`] aliases to), at which
//! point [`Event`]`::`[`LoopDestroyed`] is emitted and the entire program terminates.
//!
//! 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
@ -114,6 +114,7 @@
//! [event_loop_run]: event_loop::EventLoop::run
//! [`ControlFlow`]: event_loop::ControlFlow
//! [`Exit`]: event_loop::ControlFlow::Exit
//! [`ExitWithCode`]: event_loop::ControlFlow::ExitWithCode
//! [`Window`]: window::Window
//! [`WindowId`]: window::WindowId
//! [`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.
///
/// 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
F: FnMut(
Event<'_, Self::UserEvent>,
@ -45,7 +50,7 @@ pub trait EventLoopExtRunReturn {
impl<T> EventLoopExtRunReturn for EventLoop<T> {
type UserEvent = T;
fn run_return<F>(&mut self, event_handler: F)
fn run_return<F>(&mut self, event_handler: F) -> i32
where
F: FnMut(
Event<'_, Self::UserEvent>,

View file

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

View file

@ -296,7 +296,7 @@ impl AppState {
};
(waiting_event_handler, event)
}
(ControlFlow::Exit, _) => bug!("unexpected `ControlFlow` `Exit`"),
(ControlFlow::ExitWithCode(_), _) => bug!("unexpected `ControlFlow` `Exit`"),
s => bug!("`EventHandler` unexpectedly woke up {:?}", s),
};
@ -451,7 +451,7 @@ impl AppState {
});
self.waker.start()
}
(_, ControlFlow::Exit) => {
(_, ControlFlow::ExitWithCode(_)) => {
// https://developer.apple.com/library/archive/qa/qa1561/_index.html
// it is not possible to quit an iOS app gracefully and programatically
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)
}
pub fn run_return<F>(&mut self, callback: F)
pub fn run_return<F>(&mut self, callback: F) -> i32
where
F: FnMut(crate::event::Event<'_, T>, &RootELW<T>, &mut ControlFlow),
{
@ -741,16 +741,13 @@ fn sticky_exit_callback<T, F>(
) where
F: FnMut(Event<'_, T>, &RootELW<T>, &mut ControlFlow),
{
// make ControlFlow::Exit sticky by providing a dummy
// control flow reference if it is already Exit.
let mut dummy = ControlFlow::Exit;
let cf = if *control_flow == ControlFlow::Exit {
&mut dummy
// make ControlFlow::ExitWithCode sticky by providing a dummy
// control flow reference if it is already ExitWithCode.
if let ControlFlow::ExitWithCode(code) = *control_flow {
callback(evt, target, &mut ControlFlow::ExitWithCode(code))
} else {
control_flow
};
// user callback
callback(evt, target, cf)
callback(evt, target, control_flow)
}
}
fn assert_is_main_thread(suggested_method: &str) {

View file

@ -206,11 +206,11 @@ impl<T: 'static> EventLoop<T> {
where
F: FnMut(Event<'_, T>, &RootEventLoopWindowTarget<T>, &mut ControlFlow) + 'static,
{
self.run_return(callback);
process::exit(0)
let exit_code = self.run_return(callback);
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
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
// communicate an error that something was terminated, but winit doesn't provide us
// 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
// user events indirectly via callback to the user.
for user_event in pending_user_events.borrow_mut().drain(..) {
@ -431,20 +432,19 @@ impl<T: 'static> EventLoop<T> {
_ => unreachable!(),
};
if let Ok(dispatched) = queue.dispatch_pending(state, |_, _, _| unimplemented!()) {
dispatched > 0
} else {
break;
match queue.dispatch_pending(state, |_, _, _| unimplemented!()) {
Ok(dispatched) => dispatched > 0,
Err(error) => break error.raw_os_error().unwrap_or(1),
}
};
match control_flow {
ControlFlow::Exit => break,
ControlFlow::ExitWithCode(code) => break code,
ControlFlow::Poll => {
// Non-blocking dispatch.
let timeout = Duration::from_millis(0);
if self.loop_dispatch(Some(timeout)).is_err() {
break;
if let Err(error) = self.loop_dispatch(Some(timeout)) {
break error.raw_os_error().unwrap_or(1);
}
callback(
@ -460,8 +460,8 @@ impl<T: 'static> EventLoop<T> {
None
};
if self.loop_dispatch(timeout).is_err() {
break;
if let Err(error) = self.loop_dispatch(timeout) {
break error.raw_os_error().unwrap_or(1);
}
callback(
@ -483,8 +483,8 @@ impl<T: 'static> EventLoop<T> {
Duration::from_millis(0)
};
if self.loop_dispatch(Some(duration)).is_err() {
break;
if let Err(error) = self.loop_dispatch(Some(duration)) {
break error.raw_os_error().unwrap_or(1);
}
let now = Instant::now();
@ -510,9 +510,10 @@ impl<T: 'static> EventLoop<T> {
}
}
}
}
};
callback(Event::LoopDestroyed, &self.window_target, &mut control_flow);
exit_code
}
#[inline]

View file

@ -258,7 +258,7 @@ impl<T: 'static> EventLoop<T> {
&self.target
}
pub fn run_return<F>(&mut self, mut callback: F)
pub fn run_return<F>(&mut self, mut callback: F) -> i32
where
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 cause = StartCause::Init;
loop {
let exit_code = loop {
sticky_exit_callback(
crate::event::Event::NewEvents(cause),
&self.target,
@ -329,7 +329,7 @@ impl<T: 'static> EventLoop<T> {
let (deadline, timeout);
match control_flow {
ControlFlow::Exit => break,
ControlFlow::ExitWithCode(code) => break code,
ControlFlow::Poll => {
cause = StartCause::Poll;
deadline = None;
@ -376,21 +376,22 @@ impl<T: 'static> EventLoop<T> {
requested_resume: deadline,
};
}
}
};
callback(
crate::event::Event::LoopDestroyed,
&self.target,
&mut control_flow,
);
exit_code
}
pub fn run<F>(mut self, callback: F) -> !
where
F: 'static + FnMut(Event<'_, T>, &RootELW<T>, &mut ControlFlow),
{
self.run_return(callback);
::std::process::exit(0);
let exit_code = self.run_return(callback);
::std::process::exit(exit_code);
}
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> {
callback: Weak<RefCell<dyn FnMut(Event<'_, T>, &RootWindowTarget<T>, &mut ControlFlow)>>,
will_exit: bool,
window_target: Rc<RootWindowTarget<T>>,
}
@ -98,25 +97,25 @@ impl<T> Debug for EventLoopHandler<T> {
impl<T> EventHandler for EventLoopHandler<T> {
fn handle_nonuser_event(&mut self, event: Event<'_, Never>, control_flow: &mut ControlFlow) {
self.with_callback(|this, mut callback| {
if let ControlFlow::ExitWithCode(code) = *control_flow {
let dummy = &mut ControlFlow::ExitWithCode(code);
(callback)(event.userify(), &this.window_target, dummy);
} else {
(callback)(event.userify(), &this.window_target, control_flow);
this.will_exit |= *control_flow == ControlFlow::Exit;
if this.will_exit {
*control_flow = ControlFlow::Exit;
}
});
}
fn handle_user_events(&mut self, control_flow: &mut ControlFlow) {
self.with_callback(|this, mut callback| {
let mut will_exit = this.will_exit;
for event in this.window_target.p.receiver.try_iter() {
if let ControlFlow::ExitWithCode(code) = *control_flow {
let dummy = &mut ControlFlow::ExitWithCode(code);
(callback)(Event::UserEvent(event), &this.window_target, dummy);
} else {
(callback)(Event::UserEvent(event), &this.window_target, control_flow);
will_exit |= *control_flow == ControlFlow::Exit;
if will_exit {
*control_flow = ControlFlow::Exit;
}
}
this.will_exit = will_exit;
});
}
}
@ -160,7 +159,10 @@ impl Handler {
}
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 {
@ -268,16 +270,20 @@ impl AppState {
) {
*HANDLER.callback.lock().unwrap() = Some(Box::new(EventLoopHandler {
callback,
will_exit: false,
window_target,
}));
}
pub fn exit() {
pub fn exit() -> i32 {
HANDLER.set_in_callback(true);
HANDLER.handle_nonuser_event(EventWrapper::StaticEvent(Event::LoopDestroyed));
HANDLER.set_in_callback(false);
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) {
@ -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.handle_nonuser_event(EventWrapper::StaticEvent(Event::NewEvents(cause)));
@ -435,7 +441,7 @@ impl AppState {
}
HANDLER.update_start_time();
match HANDLER.get_old_and_new_control_flow() {
(ControlFlow::Exit, _) | (_, ControlFlow::Exit) => (),
(ControlFlow::ExitWithCode(_), _) | (_, ControlFlow::ExitWithCode(_)) => (),
(old, new) if old == new => (),
(_, ControlFlow::Wait) => HANDLER.waker().stop(),
(_, ControlFlow::WaitUntil(instant)) => HANDLER.waker().start_at(instant),

View file

@ -155,11 +155,11 @@ impl<T> EventLoop<T> {
where
F: 'static + FnMut(Event<'_, T>, &RootWindowTarget<T>, &mut ControlFlow),
{
self.run_return(callback);
process::exit(0);
let exit_code = self.run_return(callback);
process::exit(exit_code);
}
pub fn run_return<F>(&mut self, callback: F)
pub fn run_return<F>(&mut self, callback: F) -> i32
where
F: FnMut(Event<'_, T>, &RootWindowTarget<T>, &mut ControlFlow),
{
@ -176,7 +176,7 @@ impl<T> EventLoop<T> {
self._callback = Some(Rc::clone(&callback));
autoreleasepool(|| unsafe {
let exit_code = autoreleasepool(|| unsafe {
let app = NSApp();
assert_ne!(app, nil);
@ -192,9 +192,11 @@ impl<T> EventLoop<T> {
drop(self._callback.take());
resume_unwind(panic);
}
AppState::exit();
AppState::exit()
});
drop(self._callback.take());
exit_code
}
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) {
let is_closed = *control == root::ControlFlow::Exit;
let is_closed = matches!(*control, root::ControlFlow::ExitWithCode(_));
(self.event_handler)(event, control);
@ -409,7 +409,7 @@ impl<T: 'static> Shared<T> {
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
// 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() {

View file

@ -188,11 +188,11 @@ impl<T: 'static> EventLoop<T> {
where
F: 'static + FnMut(Event<'_, T>, &RootELW<T>, &mut ControlFlow),
{
self.run_return(event_handler);
::std::process::exit(0);
let exit_code = self.run_return(event_handler);
::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
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;
unsafe {
let exit_code = unsafe {
let mut msg = mem::zeroed();
runner.poll();
'main: loop {
if 0 == winuser::GetMessageW(&mut msg, ptr::null_mut(), 0, 0) {
break 'main;
break 'main 0;
}
winuser::TranslateMessage(&msg);
winuser::DispatchMessageW(&msg);
@ -225,16 +225,20 @@ impl<T: 'static> EventLoop<T> {
panic::resume_unwind(payload);
}
if runner.control_flow() == ControlFlow::Exit && !runner.handling_events() {
break 'main;
if let ControlFlow::ExitWithCode(code) = runner.control_flow() {
if !runner.handling_events() {
break 'main code;
}
}
}
};
unsafe {
runner.loop_destroyed();
}
runner.reset_runner();
exit_code
}
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,
);
}
ControlFlow::Exit => (),
ControlFlow::ExitWithCode(_) => (),
}
}

View file

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