Re-work event loop run() API so it can return a Result

This re-works the portable `run()` API that consumes the `EventLoop` and
runs the loop on the calling thread until the app exits.

This can be supported across _all_ platforms and compared to the
previous `run() -> !` API is now able to return a `Result` status on all
platforms except iOS and Web. Fixes: #2709

By moving away from `run() -> !` we stop calling `std::process::exit()`
internally as a means to kill the process without returning which means
it's possible to return an exit status and applications can return from
their `main()` function normally.

This also fixes Android support where an Activity runs in a thread but
we can't assume to have full ownership of the process (other services
could be running in separate threads).

Additionally all examples have generally been updated so that `main()`
returns a `Result` from `run()`

Fixes: #2709
This commit is contained in:
Robert Bragg 2023-04-11 12:50:52 +01:00 committed by Kirill Chibisov
parent a6f414d732
commit 0d366ffbda
39 changed files with 99 additions and 123 deletions

View file

@ -3,7 +3,7 @@
mod fill;
#[cfg(any(x11_platform, macos_platform, windows_platform))]
fn main() {
fn main() -> Result<(), impl std::error::Error> {
use std::collections::HashMap;
use raw_window_handle::HasRawWindowHandle;

View file

@ -27,7 +27,7 @@ enum Mode {
const WAIT_TIME: time::Duration = time::Duration::from_millis(100);
const POLL_SLEEP_TIME: time::Duration = time::Duration::from_millis(100);
fn main() {
fn main() -> Result<(), impl std::error::Error> {
SimpleLogger::new().init().unwrap();
println!("Press '1' to switch to Wait mode.");
@ -122,5 +122,5 @@ fn main() {
}
_ => (),
}
});
})
}

View file

@ -10,7 +10,7 @@ use winit::{
#[path = "util/fill.rs"]
mod fill;
fn main() {
fn main() -> Result<(), impl std::error::Error> {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
@ -54,7 +54,7 @@ fn main() {
}
_ => (),
}
});
})
}
const CURSORS: &[CursorIcon] = &[

View file

@ -11,7 +11,7 @@ use winit::{
#[path = "util/fill.rs"]
mod fill;
fn main() {
fn main() -> Result<(), impl std::error::Error> {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
@ -73,5 +73,5 @@ fn main() {
Event::RedrawRequested(_) => fill::fill_window(&window),
_ => (),
}
});
})
}

View file

@ -1,7 +1,7 @@
#![allow(clippy::single_match)]
#[cfg(not(wasm_platform))]
fn main() {
fn main() -> Result<(), impl std::error::Error> {
use simple_logger::SimpleLogger;
use winit::{
event::{Event, WindowEvent},
@ -52,7 +52,7 @@ fn main() {
}
_ => (),
}
});
})
}
#[cfg(wasm_platform)]

View file

@ -11,7 +11,7 @@ use winit::{
#[path = "util/fill.rs"]
mod fill;
fn main() {
fn main() -> Result<(), impl std::error::Error> {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
@ -69,7 +69,7 @@ fn main() {
}
}
_ => (),
});
})
}
fn name_windows(window_id: WindowId, switched: bool, window_1: &Window, window_2: &Window) {

View file

@ -12,7 +12,7 @@ use winit::platform::macos::WindowExtMacOS;
#[path = "util/fill.rs"]
mod fill;
fn main() {
fn main() -> Result<(), impl std::error::Error> {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
@ -131,5 +131,5 @@ fn main() {
}
_ => {}
}
});
})
}

View file

@ -11,7 +11,7 @@ use winit::{
#[path = "util/fill.rs"]
mod fill;
fn main() {
fn main() -> Result<(), impl std::error::Error> {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
@ -87,5 +87,5 @@ fn main() {
}
_ => (),
}
});
})
}

View file

@ -13,7 +13,7 @@ use winit::{
#[path = "util/fill.rs"]
mod fill;
fn main() {
fn main() -> Result<(), impl std::error::Error> {
SimpleLogger::new()
.with_level(LevelFilter::Trace)
.init()
@ -105,5 +105,5 @@ fn main() {
}
_ => (),
}
});
})
}

View file

@ -17,7 +17,7 @@ fn main() {
}
#[cfg(any(target_os = "macos", target_os = "windows", target_os = "linux"))]
fn main() {
fn main() -> Result<(), impl std::error::Error> {
#[path = "util/fill.rs"]
mod fill;
@ -61,5 +61,5 @@ fn main() {
}
_ => (),
};
});
})
}

View file

@ -10,7 +10,7 @@ use winit::{
#[path = "util/fill.rs"]
mod fill;
fn main() {
fn main() -> Result<(), impl std::error::Error> {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
@ -64,5 +64,5 @@ In other words, the deltas indicate the direction in which to move the content (
}
_ => (),
}
});
})
}

View file

@ -1,7 +1,7 @@
#![allow(clippy::single_match)]
#[cfg(not(wasm_platform))]
fn main() {
fn main() -> Result<(), impl std::error::Error> {
use std::{collections::HashMap, sync::mpsc, thread, time::Duration};
use simple_logger::SimpleLogger;

View file

@ -13,7 +13,7 @@ use winit::{
#[path = "util/fill.rs"]
mod fill;
fn main() {
fn main() -> Result<(), impl std::error::Error> {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();

View file

@ -10,7 +10,7 @@ use winit::{
#[path = "util/fill.rs"]
mod fill;
fn main() {
fn main() -> Result<(), impl std::error::Error> {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
@ -41,5 +41,5 @@ fn main() {
}
_ => (),
}
});
})
}

View file

@ -1,7 +1,7 @@
#![allow(clippy::single_match)]
#[cfg(not(wasm_platform))]
fn main() {
fn main() -> Result<(), impl std::error::Error> {
use std::{sync::Arc, thread, time};
use simple_logger::SimpleLogger;
@ -49,7 +49,7 @@ fn main() {
}
_ => (),
}
});
})
}
#[cfg(wasm_platform)]

View file

@ -12,7 +12,7 @@ use winit::{
#[path = "util/fill.rs"]
mod fill;
fn main() {
fn main() -> Result<(), impl std::error::Error> {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
@ -53,5 +53,5 @@ fn main() {
}
_ => (),
};
});
})
}

View file

@ -1,9 +1,5 @@
//! Demonstrates the use of startup notifications on Linux.
fn main() {
example::main();
}
#[cfg(any(x11_platform, wayland_platform))]
#[path = "./util/fill.rs"]
mod fill;
@ -21,7 +17,7 @@ mod example {
};
use winit::window::{Window, WindowBuilder, WindowId};
pub(super) fn main() {
pub(super) fn main() -> Result<(), impl std::error::Error> {
// Create the event loop and get the activation token.
let event_loop = EventLoop::new();
let mut current_token = match event_loop.read_token_from_env() {
@ -115,13 +111,16 @@ mod example {
}
flow.set_wait();
});
})
}
}
#[cfg(not(any(x11_platform, wayland_platform)))]
mod example {
pub(super) fn main() {
println!("This example is only supported on X11 and Wayland platforms.");
}
#[cfg(any(x11_platform, wayland_platform))]
fn main() -> Result<(), impl std::error::Error> {
example::main()
}
#[cfg(not(any(x11_platform, wayland_platform)))]
fn main() {
println!("This example is only supported on X11 and Wayland platforms.");
}

View file

@ -11,7 +11,7 @@ use winit::{
#[path = "util/fill.rs"]
mod fill;
fn main() {
fn main() -> Result<(), impl std::error::Error> {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
@ -75,5 +75,5 @@ fn main() {
}
_ => (),
}
});
})
}

View file

@ -16,7 +16,7 @@ use winit::{
#[path = "util/fill.rs"]
mod fill;
fn main() {
fn main() -> Result<(), impl std::error::Error> {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
@ -47,5 +47,5 @@ fn main() {
}
_ => (),
}
});
})
}

View file

@ -8,7 +8,7 @@ use winit::{
#[path = "util/fill.rs"]
mod fill;
fn main() {
fn main() -> Result<(), impl std::error::Error> {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
@ -47,5 +47,5 @@ fn main() {
} else if let Event::RedrawRequested(_) = event {
fill::fill_window(&window);
}
});
})
}

View file

@ -10,7 +10,7 @@ use winit::{
#[path = "util/fill.rs"]
mod fill;
fn main() {
fn main() -> Result<(), impl std::error::Error> {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
@ -36,5 +36,5 @@ fn main() {
}
_ => (),
}
});
})
}

View file

@ -7,7 +7,7 @@ use winit::{
window::{Fullscreen, WindowBuilder},
};
pub fn main() {
pub fn main() -> Result<(), impl std::error::Error> {
let event_loop = EventLoop::new();
let builder = WindowBuilder::new().with_title("A fantastic window!");
@ -56,7 +56,7 @@ pub fn main() {
}
_ => (),
}
});
})
}
#[cfg(wasm_platform)]
@ -72,7 +72,7 @@ mod wasm {
console_log::init_with_level(log::Level::Debug).expect("error initializing logger");
#[allow(clippy::main_recursion)]
super::main();
let _ = super::main();
}
pub fn insert_canvas_and_create_log_list(window: &Window) -> web_sys::Element {

View file

@ -47,7 +47,7 @@ This example demonstrates the desired future functionality which will possibly b
// Render once with the size info we currently have
render_circle(&canvas, window.inner_size());
event_loop.run(move |event, _, control_flow| {
let _ = event_loop.run(move |event, _, control_flow| {
*control_flow = ControlFlow::Wait;
match event {

View file

@ -10,7 +10,7 @@ use winit::{
#[path = "util/fill.rs"]
mod fill;
fn main() {
fn main() -> Result<(), impl std::error::Error> {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
@ -37,5 +37,5 @@ fn main() {
}
_ => (),
}
});
})
}

View file

@ -14,7 +14,7 @@ use winit::{
#[path = "util/fill.rs"]
mod fill;
fn main() {
fn main() -> Result<(), impl std::error::Error> {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
@ -71,5 +71,5 @@ fn main() {
}
_ => (),
}
});
})
}

View file

@ -14,7 +14,7 @@ use winit::{
#[path = "util/fill.rs"]
mod fill;
fn main() {
fn main() -> Result<(), impl std::error::Error> {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
@ -139,5 +139,5 @@ fn main() {
}
_ => (),
}
});
})
}

View file

@ -13,7 +13,7 @@ const BORDER: f64 = 8.0;
#[path = "util/fill.rs"]
mod fill;
fn main() {
fn main() -> Result<(), impl std::error::Error> {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
@ -74,7 +74,7 @@ fn main() {
fill::fill_window(&window);
}
_ => (),
});
})
}
fn cursor_direction_icon(resize_direction: Option<ResizeDirection>) -> CursorIcon {

View file

@ -12,7 +12,7 @@ use winit::{
#[path = "util/fill.rs"]
mod fill;
fn main() {
fn main() -> Result<(), impl std::error::Error> {
SimpleLogger::new().init().unwrap();
// You'll have to choose an icon size at your own discretion. On X11, the desired size varies
@ -48,7 +48,7 @@ fn main() {
} else if let Event::RedrawRequested(_) = event {
fill::fill_window(&window);
}
});
})
}
fn load_icon(path: &Path) -> Icon {

View file

@ -18,7 +18,7 @@ mod fill;
/// Prints the keyboard events characters received when option_is_alt is true versus false.
/// A left mouse click will toggle option_is_alt.
#[cfg(target_os = "macos")]
fn main() {
fn main() -> Result<(), impl std::error::Error> {
let event_loop = EventLoop::new();
let window = WindowBuilder::new()
@ -66,7 +66,7 @@ fn main() {
}
_ => (),
}
});
})
}
#[cfg(not(target_os = "macos"))]

View file

@ -11,7 +11,7 @@ use winit::{
#[path = "util/fill.rs"]
mod fill;
fn main() {
fn main() -> Result<(), impl std::error::Error> {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
@ -60,5 +60,5 @@ fn main() {
}
_ => (),
}
});
})
}

View file

@ -19,7 +19,7 @@ use winit::{
mod fill;
#[cfg(target_os = "macos")]
fn main() {
fn main() -> Result<(), impl std::error::Error> {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();

View file

@ -18,6 +18,7 @@ use std::time::{Duration, Instant};
#[cfg(wasm_platform)]
use web_time::{Duration, Instant};
use crate::error::RunLoopError;
use crate::{event::Event, monitor::MonitorHandle, platform_impl};
/// Provides a way to retrieve events from the system and from the windows that were registered to
@ -285,23 +286,33 @@ impl<T> EventLoop<T> {
EventLoopBuilder::<T>::with_user_event().build()
}
/// Hijacks the calling thread and initializes the winit event loop with the provided
/// closure. Since the closure is `'static`, it must be a `move` closure if it needs to
/// Runs the event loop in the calling thread and calls the given `event_handler` closure
/// to dispatch any pending events.
///
/// Since the closure is `'static`, it must be a `move` closure if it needs to
/// access any data from the calling context.
///
/// See the [`ControlFlow`] docs for information on how changes to `&mut ControlFlow` impact the
/// event loop's behavior.
///
/// 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.
/// - **iOS:** Will never return to the caller and so values not passed to this function will
/// *not* be dropped before the process exits.
/// - **Web:** Will _act_ as if it never returns to the caller by throwing a Javascript exception
/// (that Rust doesn't see) that will also mean that the rest of the function is never executed
/// and any values not passed to this function will *not* be dropped.
///
/// Web applications are recommended to use `spawn()` instead of `run()` to avoid the need
/// for the Javascript exception trick, and to make it clearer that the event loop runs
/// asynchronously (via the browser's own, internal, event loop) and doesn't block the
/// current thread of execution like it does on other platforms.
///
/// [`ControlFlow`]: crate::event_loop::ControlFlow
#[inline]
pub fn run<F>(self, event_handler: F) -> !
pub fn run<F>(self, event_handler: F) -> Result<(), RunLoopError>
where
F: 'static + FnMut(Event<'_, T>, &EventLoopWindowTarget<T>, &mut ControlFlow),
{

View file

@ -526,17 +526,12 @@ impl<T: 'static> EventLoop<T> {
self.pending_redraw = pending_redraw;
}
pub fn run<F>(mut self, event_handler: F) -> !
pub fn run<F>(mut self, event_handler: F) -> Result<(), RunLoopError>
where
F: 'static
+ FnMut(event::Event<'_, T>, &event_loop::EventLoopWindowTarget<T>, &mut ControlFlow),
{
let exit_code = match self.run_ondemand(event_handler) {
Err(RunLoopError::ExitFailure(code)) => code,
Err(_err) => 1,
Ok(_) => 0,
};
::std::process::exit(exit_code);
self.run_ondemand(event_handler)
}
pub fn run_ondemand<F>(&mut self, mut event_handler: F) -> Result<(), RunLoopError>

View file

@ -830,11 +830,11 @@ impl<T: 'static> EventLoop<T> {
x11_or_wayland!(match self; EventLoop(evlp) => evlp.create_proxy(); as EventLoopProxy)
}
pub fn run<F>(self, callback: F) -> !
pub fn run<F>(mut self, callback: F) -> Result<(), RunLoopError>
where
F: 'static + FnMut(crate::event::Event<'_, T>, &RootELW<T>, &mut ControlFlow),
F: FnMut(crate::event::Event<'_, T>, &RootELW<T>, &mut ControlFlow),
{
x11_or_wayland!(match self; EventLoop(evlp) => evlp.run(callback))
self.run_ondemand(callback)
}
pub fn run_ondemand<F>(&mut self, callback: F) -> Result<(), RunLoopError>

View file

@ -145,18 +145,6 @@ impl<T: 'static> EventLoop<T> {
Ok(event_loop)
}
pub fn run<F>(mut self, callback: F) -> !
where
F: FnMut(Event<'_, T>, &RootEventLoopWindowTarget<T>, &mut ControlFlow) + 'static,
{
let exit_code = match self.run_ondemand(callback) {
Err(RunLoopError::ExitFailure(code)) => code,
Err(_err) => 1,
Ok(_) => 0,
};
::std::process::exit(exit_code)
}
pub fn run_ondemand<F>(&mut self, mut event_handler: F) -> Result<(), RunLoopError>
where
F: FnMut(Event<'_, T>, &RootEventLoopWindowTarget<T>, &mut ControlFlow),

View file

@ -432,18 +432,6 @@ impl<T: 'static> EventLoop<T> {
&self.target
}
pub fn run<F>(mut self, callback: F) -> !
where
F: FnMut(Event<'_, T>, &RootELW<T>, &mut ControlFlow) + 'static,
{
let exit_code = match self.run_ondemand(callback) {
Err(RunLoopError::ExitFailure(code)) => code,
Err(_err) => 1,
Ok(_) => 0,
};
::std::process::exit(exit_code)
}
pub fn run_ondemand<F>(&mut self, mut event_handler: F) -> Result<(), RunLoopError>
where
F: FnMut(Event<'_, T>, &RootELW<T>, &mut ControlFlow),

View file

@ -6,7 +6,7 @@ use std::{
mem,
os::raw::c_void,
panic::{catch_unwind, resume_unwind, AssertUnwindSafe, RefUnwindSafe, UnwindSafe},
process, ptr,
ptr,
rc::{Rc, Weak},
sync::mpsc,
};
@ -192,16 +192,11 @@ impl<T> EventLoop<T> {
&self.window_target
}
pub fn run<F>(mut self, callback: F) -> !
pub fn run<F>(mut self, callback: F) -> Result<(), RunLoopError>
where
F: 'static + FnMut(Event<'_, T>, &RootWindowTarget<T>, &mut ControlFlow),
{
let exit_code = match self.run_ondemand(callback) {
Err(RunLoopError::ExitFailure(code)) => code,
Err(_err) => 1,
Ok(_) => 0,
};
process::exit(exit_code);
self.run_ondemand(callback)
}
// NB: we don't base this on `pump_events` because for `MacOs` we can't support

View file

@ -12,6 +12,7 @@ use orbclient::{
use raw_window_handle::{OrbitalDisplayHandle, RawDisplayHandle};
use crate::{
error::RunLoopError,
event::{self, Ime, Modifiers, StartCause},
event_loop::{self, ControlFlow},
keyboard::{
@ -442,7 +443,7 @@ impl<T: 'static> EventLoop<T> {
}
}
pub fn run<F>(mut self, event_handler: F) -> !
pub fn run<F>(mut self, mut event_handler_inner: F) -> Result<(), RunLoopError>
where
F: 'static
+ FnMut(event::Event<'_, T>, &event_loop::EventLoopWindowTarget<T>, &mut ControlFlow),
@ -688,7 +689,11 @@ impl<T: 'static> EventLoop<T> {
&mut control_flow,
);
::std::process::exit(code);
if code == 0 {
Ok(())
} else {
Err(RunLoopError::ExitFailure(code))
}
}
pub fn window_target(&self) -> &event_loop::EventLoopWindowTarget<T> {

View file

@ -243,16 +243,11 @@ impl<T: 'static> EventLoop<T> {
&self.window_target
}
pub fn run<F>(mut self, event_handler: F) -> !
pub fn run<F>(mut self, event_handler: F) -> Result<(), RunLoopError>
where
F: 'static + FnMut(Event<'_, T>, &RootELW<T>, &mut ControlFlow),
{
let exit_code = match self.run_ondemand(event_handler) {
Err(RunLoopError::ExitFailure(code)) => code,
Err(_err) => 1,
Ok(_) => 0,
};
::std::process::exit(exit_code);
self.run_ondemand(event_handler)
}
pub fn run_ondemand<F>(&mut self, mut event_handler: F) -> Result<(), RunLoopError>