Drop application handler on run loop exit (#4149)
Calling the `Drop` impl of the user's `ApplicationHandler` is important on iOS and Web, since they don't return from `EventLoop::run_app`. And now that we reliably call `Drop`, the `ApplicationHandler::exited` event/callback is unnecessary; using `Drop` composes much better (open files etc. stored in the app state will be automatically flushed), and prevents weirdness like attempting to create a new window while exiting.
This commit is contained in:
parent
ef37b1d5dd
commit
afb731bb52
19 changed files with 170 additions and 137 deletions
|
|
@ -592,18 +592,18 @@ impl ApplicationHandler for Application {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(android_platform))]
|
|
||||||
fn exiting(&mut self, _event_loop: &dyn ActiveEventLoop) {
|
|
||||||
// We must drop the context here.
|
|
||||||
self.context = None;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
fn macos_handler(&mut self) -> Option<&mut dyn ApplicationHandlerExtMacOS> {
|
fn macos_handler(&mut self) -> Option<&mut dyn ApplicationHandlerExtMacOS> {
|
||||||
Some(self)
|
Some(self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Drop for Application {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
info!("Application exited");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
impl ApplicationHandlerExtMacOS for Application {
|
impl ApplicationHandlerExtMacOS for Application {
|
||||||
fn standard_key_binding(
|
fn standard_key_binding(
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,8 @@ use winit::window::{Window, WindowAttributes, WindowId};
|
||||||
|
|
||||||
#[path = "util/fill.rs"]
|
#[path = "util/fill.rs"]
|
||||||
mod fill;
|
mod fill;
|
||||||
|
#[path = "util/tracing.rs"]
|
||||||
|
mod tracing;
|
||||||
|
|
||||||
#[derive(Default, Debug)]
|
#[derive(Default, Debug)]
|
||||||
struct App {
|
struct App {
|
||||||
|
|
@ -70,9 +72,12 @@ fn main() -> Result<(), Box<dyn Error>> {
|
||||||
#[cfg(web_platform)]
|
#[cfg(web_platform)]
|
||||||
console_error_panic_hook::set_once();
|
console_error_panic_hook::set_once();
|
||||||
|
|
||||||
|
tracing::init();
|
||||||
|
|
||||||
let event_loop = EventLoop::new()?;
|
let event_loop = EventLoop::new()?;
|
||||||
let mut app = App::default();
|
|
||||||
|
|
||||||
// For alternative loop run options see `pump_events` and `run_on_demand` examples.
|
// For alternative loop run options see `pump_events` and `run_on_demand` examples.
|
||||||
event_loop.run_app(&mut app).map_err(Into::into)
|
event_loop.run_app(App::default())?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,17 @@ use crate::event_loop::ActiveEventLoop;
|
||||||
use crate::platform::macos::ApplicationHandlerExtMacOS;
|
use crate::platform::macos::ApplicationHandlerExtMacOS;
|
||||||
use crate::window::WindowId;
|
use crate::window::WindowId;
|
||||||
|
|
||||||
/// The handler of the application events.
|
/// The handler of application-level events.
|
||||||
|
///
|
||||||
|
/// See [the top-level docs] for example usage, and [`EventLoop::run_app`] for an overview of when
|
||||||
|
/// events are delivered.
|
||||||
|
///
|
||||||
|
/// This is [dropped] when the event loop is shut down. Note that this only works if you're passing
|
||||||
|
/// the entire state to [`EventLoop::run_app`] (passing `&mut app` won't work).
|
||||||
|
///
|
||||||
|
/// [the top-level docs]: crate
|
||||||
|
/// [`EventLoop::run_app`]: crate::event_loop::EventLoop::run_app
|
||||||
|
/// [dropped]: std::ops::Drop
|
||||||
pub trait ApplicationHandler {
|
pub trait ApplicationHandler {
|
||||||
/// Emitted when new events arrive from the OS to be processed.
|
/// Emitted when new events arrive from the OS to be processed.
|
||||||
///
|
///
|
||||||
|
|
@ -57,7 +67,6 @@ pub trait ApplicationHandler {
|
||||||
///
|
///
|
||||||
/// [`resumed()`]: Self::resumed()
|
/// [`resumed()`]: Self::resumed()
|
||||||
/// [`suspended()`]: Self::suspended()
|
/// [`suspended()`]: Self::suspended()
|
||||||
/// [`exiting()`]: Self::exiting()
|
|
||||||
fn resumed(&mut self, event_loop: &dyn ActiveEventLoop) {
|
fn resumed(&mut self, event_loop: &dyn ActiveEventLoop) {
|
||||||
let _ = event_loop;
|
let _ = event_loop;
|
||||||
}
|
}
|
||||||
|
|
@ -162,8 +171,6 @@ pub trait ApplicationHandler {
|
||||||
///
|
///
|
||||||
/// let (sender, receiver) = mpsc::channel();
|
/// let (sender, receiver) = mpsc::channel();
|
||||||
///
|
///
|
||||||
/// let mut app = MyApp { receiver };
|
|
||||||
///
|
|
||||||
/// // Send an event in a loop
|
/// // Send an event in a loop
|
||||||
/// let proxy = event_loop.create_proxy();
|
/// let proxy = event_loop.create_proxy();
|
||||||
/// let background_thread = thread::spawn(move || {
|
/// let background_thread = thread::spawn(move || {
|
||||||
|
|
@ -171,7 +178,7 @@ pub trait ApplicationHandler {
|
||||||
/// loop {
|
/// loop {
|
||||||
/// println!("sending: {i}");
|
/// println!("sending: {i}");
|
||||||
/// if sender.send(i).is_err() {
|
/// if sender.send(i).is_err() {
|
||||||
/// // Stop sending once `MyApp` is dropped
|
/// // Stop sending once the receiver is dropped
|
||||||
/// break;
|
/// break;
|
||||||
/// }
|
/// }
|
||||||
/// // Trigger the wake-up _after_ we placed the event in the channel.
|
/// // Trigger the wake-up _after_ we placed the event in the channel.
|
||||||
|
|
@ -182,9 +189,8 @@ pub trait ApplicationHandler {
|
||||||
/// }
|
/// }
|
||||||
/// });
|
/// });
|
||||||
///
|
///
|
||||||
/// event_loop.run_app(&mut app)?;
|
/// event_loop.run_app(MyApp { receiver })?;
|
||||||
///
|
///
|
||||||
/// drop(app);
|
|
||||||
/// background_thread.join().unwrap();
|
/// background_thread.join().unwrap();
|
||||||
///
|
///
|
||||||
/// Ok(())
|
/// Ok(())
|
||||||
|
|
@ -203,6 +209,10 @@ pub trait ApplicationHandler {
|
||||||
);
|
);
|
||||||
|
|
||||||
/// Emitted when the OS sends an event to a device.
|
/// Emitted when the OS sends an event to a device.
|
||||||
|
///
|
||||||
|
/// For this to be called, it must be enabled with [`EventLoop::listen_device_events`].
|
||||||
|
///
|
||||||
|
/// [`EventLoop::listen_device_events`]: crate::event_loop::EventLoop::listen_device_events
|
||||||
fn device_event(
|
fn device_event(
|
||||||
&mut self,
|
&mut self,
|
||||||
event_loop: &dyn ActiveEventLoop,
|
event_loop: &dyn ActiveEventLoop,
|
||||||
|
|
@ -258,7 +268,7 @@ pub trait ApplicationHandler {
|
||||||
/// to the user. This is a good place to stop refreshing UI, running animations and other visual
|
/// to the user. This is a good place to stop refreshing UI, running animations and other visual
|
||||||
/// things. It is driven by Android's [`onStop()`] method.
|
/// things. It is driven by Android's [`onStop()`] method.
|
||||||
///
|
///
|
||||||
/// After this event the application either receives [`resumed()`] again, or [`exiting()`].
|
/// After this event the application either receives [`resumed()`] again, or will exit.
|
||||||
///
|
///
|
||||||
/// [`onStop()`]: https://developer.android.com/reference/android/app/Activity#onStop()
|
/// [`onStop()`]: https://developer.android.com/reference/android/app/Activity#onStop()
|
||||||
///
|
///
|
||||||
|
|
@ -268,7 +278,6 @@ pub trait ApplicationHandler {
|
||||||
///
|
///
|
||||||
/// [`resumed()`]: Self::resumed()
|
/// [`resumed()`]: Self::resumed()
|
||||||
/// [`suspended()`]: Self::suspended()
|
/// [`suspended()`]: Self::suspended()
|
||||||
/// [`exiting()`]: Self::exiting()
|
|
||||||
fn suspended(&mut self, event_loop: &dyn ActiveEventLoop) {
|
fn suspended(&mut self, event_loop: &dyn ActiveEventLoop) {
|
||||||
let _ = event_loop;
|
let _ = event_loop;
|
||||||
}
|
}
|
||||||
|
|
@ -310,14 +319,6 @@ pub trait ApplicationHandler {
|
||||||
let _ = event_loop;
|
let _ = event_loop;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Emitted when the event loop is being shut down.
|
|
||||||
///
|
|
||||||
/// This is irreversible - if this method is called, it is guaranteed that the event loop
|
|
||||||
/// will exit right after.
|
|
||||||
fn exiting(&mut self, event_loop: &dyn ActiveEventLoop) {
|
|
||||||
let _ = event_loop;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Emitted when the application has received a memory warning.
|
/// Emitted when the application has received a memory warning.
|
||||||
///
|
///
|
||||||
/// ## Platform-specific
|
/// ## Platform-specific
|
||||||
|
|
@ -413,11 +414,6 @@ impl<A: ?Sized + ApplicationHandler> ApplicationHandler for &mut A {
|
||||||
(**self).destroy_surfaces(event_loop);
|
(**self).destroy_surfaces(event_loop);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn exiting(&mut self, event_loop: &dyn ActiveEventLoop) {
|
|
||||||
(**self).exiting(event_loop);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn memory_warning(&mut self, event_loop: &dyn ActiveEventLoop) {
|
fn memory_warning(&mut self, event_loop: &dyn ActiveEventLoop) {
|
||||||
(**self).memory_warning(event_loop);
|
(**self).memory_warning(event_loop);
|
||||||
|
|
@ -487,11 +483,6 @@ impl<A: ?Sized + ApplicationHandler> ApplicationHandler for Box<A> {
|
||||||
(**self).destroy_surfaces(event_loop);
|
(**self).destroy_surfaces(event_loop);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn exiting(&mut self, event_loop: &dyn ActiveEventLoop) {
|
|
||||||
(**self).exiting(event_loop);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn memory_warning(&mut self, event_loop: &dyn ActiveEventLoop) {
|
fn memory_warning(&mut self, event_loop: &dyn ActiveEventLoop) {
|
||||||
(**self).memory_warning(event_loop);
|
(**self).memory_warning(event_loop);
|
||||||
|
|
|
||||||
|
|
@ -228,6 +228,8 @@ changelog entry.
|
||||||
- Remove `Window::inner_position`, use the new `Window::surface_position` instead.
|
- Remove `Window::inner_position`, use the new `Window::surface_position` instead.
|
||||||
- Remove `CustomCursorExtWeb`, use the `CustomCursorSource`.
|
- Remove `CustomCursorExtWeb`, use the `CustomCursorSource`.
|
||||||
- Remove `CustomCursor::from_rgba`, use `CustomCursorSource` instead.
|
- Remove `CustomCursor::from_rgba`, use `CustomCursorSource` instead.
|
||||||
|
- Removed `ApplicationHandler::exited`, the event loop being shut down can now be listened to in
|
||||||
|
the `Drop` impl on the application handler.
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
|
|
|
||||||
39
src/event.rs
39
src/event.rs
|
|
@ -1,39 +1,4 @@
|
||||||
//! The event enums and assorted supporting types.
|
//! The event enums and assorted supporting types.
|
||||||
//!
|
|
||||||
//! These are sent to the closure given to [`EventLoop::run_app(...)`], where they get
|
|
||||||
//! processed and used to modify the program state. For more details, see the root-level
|
|
||||||
//! documentation.
|
|
||||||
//!
|
|
||||||
//! Some of these events represent different "parts" of a traditional event-handling loop. You could
|
|
||||||
//! approximate the basic ordering loop of [`EventLoop::run_app(...)`] like this:
|
|
||||||
//!
|
|
||||||
//! ```rust,ignore
|
|
||||||
//! let mut start_cause = StartCause::Init;
|
|
||||||
//!
|
|
||||||
//! while !elwt.exiting() {
|
|
||||||
//! app.new_events(event_loop, start_cause);
|
|
||||||
//!
|
|
||||||
//! for event in (window events, user events, device events) {
|
|
||||||
//! // This will pick the right method on the application based on the event.
|
|
||||||
//! app.handle_event(event_loop, event);
|
|
||||||
//! }
|
|
||||||
//!
|
|
||||||
//! for window_id in (redraw windows) {
|
|
||||||
//! app.window_event(event_loop, window_id, RedrawRequested);
|
|
||||||
//! }
|
|
||||||
//!
|
|
||||||
//! app.about_to_wait(event_loop);
|
|
||||||
//! start_cause = wait_if_necessary();
|
|
||||||
//! }
|
|
||||||
//!
|
|
||||||
//! app.exiting(event_loop);
|
|
||||||
//! ```
|
|
||||||
//!
|
|
||||||
//! This leaves out timing details like [`ControlFlow::WaitUntil`] but hopefully
|
|
||||||
//! describes what happens in what order.
|
|
||||||
//!
|
|
||||||
//! [`EventLoop::run_app(...)`]: crate::event_loop::EventLoop::run_app
|
|
||||||
//! [`ControlFlow::WaitUntil`]: crate::event_loop::ControlFlow::WaitUntil
|
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::sync::{Mutex, Weak};
|
use std::sync::{Mutex, Weak};
|
||||||
#[cfg(not(web_platform))]
|
#[cfg(not(web_platform))]
|
||||||
|
|
@ -641,10 +606,12 @@ impl FingerId {
|
||||||
///
|
///
|
||||||
/// Useful for interactions that diverge significantly from a conventional 2D GUI, such as 3D camera
|
/// Useful for interactions that diverge significantly from a conventional 2D GUI, such as 3D camera
|
||||||
/// or first-person game controls. Many physical actions, such as mouse movement, can produce both
|
/// or first-person game controls. Many physical actions, such as mouse movement, can produce both
|
||||||
/// device and window events. Because window events typically arise from virtual devices
|
/// device and [window events]. Because window events typically arise from virtual devices
|
||||||
/// (corresponding to GUI pointers and keyboard focus) the device IDs may not match.
|
/// (corresponding to GUI pointers and keyboard focus) the device IDs may not match.
|
||||||
///
|
///
|
||||||
/// Note that these events are delivered regardless of input focus.
|
/// Note that these events are delivered regardless of input focus.
|
||||||
|
///
|
||||||
|
/// [window events]: WindowEvent
|
||||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||||
pub enum DeviceEvent {
|
pub enum DeviceEvent {
|
||||||
/// Change in physical position of a pointing device.
|
/// Change in physical position of a pointing device.
|
||||||
|
|
|
||||||
|
|
@ -186,7 +186,56 @@ impl EventLoop {
|
||||||
impl EventLoop {
|
impl EventLoop {
|
||||||
/// Run the application with the event loop on the calling thread.
|
/// Run the application with the event loop on the calling thread.
|
||||||
///
|
///
|
||||||
/// See the [`set_control_flow()`] docs on how to change the event loop's behavior.
|
/// ## Event loop flow
|
||||||
|
///
|
||||||
|
/// This function internally handles the different parts of a traditional event-handling loop.
|
||||||
|
/// You can imagine this method as being implemented like this:
|
||||||
|
///
|
||||||
|
/// ```rust,ignore
|
||||||
|
/// let mut start_cause = StartCause::Init;
|
||||||
|
///
|
||||||
|
/// // Run the event loop.
|
||||||
|
/// while !event_loop.exiting() {
|
||||||
|
/// // Wake up.
|
||||||
|
/// app.new_events(event_loop, start_cause);
|
||||||
|
///
|
||||||
|
/// // Indicate that surfaces can now safely be created.
|
||||||
|
/// if start_cause == StartCause::Init {
|
||||||
|
/// app.can_create_surfaces(event_loop);
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// // Handle proxy wake-up event.
|
||||||
|
/// if event_loop.proxy_wake_up_set() {
|
||||||
|
/// event_loop.proxy_wake_up_clear();
|
||||||
|
/// app.proxy_wake_up(event_loop);
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// // Handle actions done by the user / system such as moving the cursor, resizing the
|
||||||
|
/// // window, changing the window theme, etc.
|
||||||
|
/// for event in event_loop.events() {
|
||||||
|
/// match event {
|
||||||
|
/// window event => app.window_event(event_loop, window_id, event),
|
||||||
|
/// device event => app.device_event(event_loop, device_id, event),
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// // Handle redraws.
|
||||||
|
/// for window_id in event_loop.pending_redraws() {
|
||||||
|
/// app.window_event(event_loop, window_id, WindowEvent::RedrawRequested);
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// // Done handling events, wait until we're woken up again.
|
||||||
|
/// app.about_to_wait(event_loop);
|
||||||
|
/// start_cause = event_loop.wait_if_necessary();
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// // Finished running, drop application state.
|
||||||
|
/// drop(app);
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// This is of course a very coarse-grained overview, and leaves out timing details like
|
||||||
|
/// [`ControlFlow::WaitUntil`] and life-cycle methods like [`ApplicationHandler::resumed`], but
|
||||||
|
/// it should give you an idea of how things fit together.
|
||||||
///
|
///
|
||||||
/// ## Platform-specific
|
/// ## Platform-specific
|
||||||
///
|
///
|
||||||
|
|
@ -381,14 +430,21 @@ pub trait ActiveEventLoop: AsAny + fmt::Debug {
|
||||||
/// Gets the current [`ControlFlow`].
|
/// Gets the current [`ControlFlow`].
|
||||||
fn control_flow(&self) -> ControlFlow;
|
fn control_flow(&self) -> ControlFlow;
|
||||||
|
|
||||||
/// This exits the event loop.
|
/// Stop the event loop.
|
||||||
///
|
///
|
||||||
/// See [`exiting`][crate::application::ApplicationHandler::exiting].
|
/// ## Platform-specific
|
||||||
|
///
|
||||||
|
/// ### iOS
|
||||||
|
///
|
||||||
|
/// It is not possible to programmatically exit/quit an application on iOS, so this function is
|
||||||
|
/// a no-op there. See also [this technical Q&A][qa1561].
|
||||||
|
///
|
||||||
|
/// [qa1561]: https://developer.apple.com/library/archive/qa/qa1561/_index.html
|
||||||
fn exit(&self);
|
fn exit(&self);
|
||||||
|
|
||||||
/// Returns if the [`EventLoop`] is about to stop.
|
/// Returns whether the [`EventLoop`] is about to stop.
|
||||||
///
|
///
|
||||||
/// See [`exit()`][Self::exit].
|
/// Set by [`exit()`][Self::exit].
|
||||||
fn exiting(&self) -> bool;
|
fn exiting(&self) -> bool;
|
||||||
|
|
||||||
/// Gets a persistent reference to the underlying platform display.
|
/// Gets a persistent reference to the underlying platform display.
|
||||||
|
|
|
||||||
32
src/lib.rs
32
src/lib.rs
|
|
@ -26,9 +26,8 @@
|
||||||
//! Some user activity, like mouse movement, can generate both a [`WindowEvent`] *and* a
|
//! Some user activity, like mouse movement, can generate both a [`WindowEvent`] *and* a
|
||||||
//! [`DeviceEvent`].
|
//! [`DeviceEvent`].
|
||||||
//!
|
//!
|
||||||
//! You can retrieve events by calling [`EventLoop::run_app()`]. This function will
|
//! You can retrieve events by calling [`EventLoop::run_app()`]. This function will dispatch events
|
||||||
//! dispatch events for every [`Window`] that was created with that particular [`EventLoop`], and
|
//! for every [`Window`] that was created with that particular [`EventLoop`].
|
||||||
//! will run until [`exit()`] is used, at which point [`exiting()`] is called.
|
|
||||||
//!
|
//!
|
||||||
//! 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
|
//! model, since that can't be implemented properly on some platforms (e.g Web, iOS) and works
|
||||||
|
|
@ -58,10 +57,19 @@
|
||||||
//!
|
//!
|
||||||
//! impl ApplicationHandler for App {
|
//! impl ApplicationHandler for App {
|
||||||
//! fn can_create_surfaces(&mut self, event_loop: &dyn ActiveEventLoop) {
|
//! fn can_create_surfaces(&mut self, event_loop: &dyn ActiveEventLoop) {
|
||||||
|
//! // The event loop has launched, and we can initialize our UI state.
|
||||||
|
//!
|
||||||
|
//! // Create a simple window with default attributes.
|
||||||
//! self.window = Some(event_loop.create_window(WindowAttributes::default()).unwrap());
|
//! self.window = Some(event_loop.create_window(WindowAttributes::default()).unwrap());
|
||||||
//! }
|
//! }
|
||||||
//!
|
//!
|
||||||
//! fn window_event(&mut self, event_loop: &dyn ActiveEventLoop, id: WindowId, event: WindowEvent) {
|
//! fn window_event(
|
||||||
|
//! &mut self,
|
||||||
|
//! event_loop: &dyn ActiveEventLoop,
|
||||||
|
//! id: WindowId,
|
||||||
|
//! event: WindowEvent,
|
||||||
|
//! ) {
|
||||||
|
//! // Called by `EventLoop::run_app` when a new event happens on the window.
|
||||||
//! match event {
|
//! match event {
|
||||||
//! WindowEvent::CloseRequested => {
|
//! WindowEvent::CloseRequested => {
|
||||||
//! println!("The close button was pressed; stopping");
|
//! println!("The close button was pressed; stopping");
|
||||||
|
|
@ -82,15 +90,18 @@
|
||||||
//! // applications which do not always need to. Applications that redraw continuously
|
//! // applications which do not always need to. Applications that redraw continuously
|
||||||
//! // can render here instead.
|
//! // can render here instead.
|
||||||
//! self.window.as_ref().unwrap().request_redraw();
|
//! self.window.as_ref().unwrap().request_redraw();
|
||||||
//! }
|
//! },
|
||||||
//! _ => (),
|
//! _ => (),
|
||||||
//! }
|
//! }
|
||||||
//! }
|
//! }
|
||||||
//! }
|
//! }
|
||||||
//!
|
//!
|
||||||
//! # // Intentionally use `fn main` for clarity
|
//! # // Intentionally use `fn main` for clarity
|
||||||
//! fn main() {
|
//! fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
//! let event_loop = EventLoop::new().unwrap();
|
//! // Create a new event loop.
|
||||||
|
//! let event_loop = EventLoop::new()?;
|
||||||
|
//!
|
||||||
|
//! // Configure settings before launching.
|
||||||
//!
|
//!
|
||||||
//! // ControlFlow::Poll continuously runs the event loop, even if the OS hasn't
|
//! // ControlFlow::Poll continuously runs the event loop, even if the OS hasn't
|
||||||
//! // dispatched any events. This is ideal for games and similar applications.
|
//! // dispatched any events. This is ideal for games and similar applications.
|
||||||
|
|
@ -101,8 +112,10 @@
|
||||||
//! // input, and uses significantly less power/CPU time than ControlFlow::Poll.
|
//! // input, and uses significantly less power/CPU time than ControlFlow::Poll.
|
||||||
//! event_loop.set_control_flow(ControlFlow::Wait);
|
//! event_loop.set_control_flow(ControlFlow::Wait);
|
||||||
//!
|
//!
|
||||||
//! let mut app = App::default();
|
//! // Launch and begin running the event loop.
|
||||||
//! event_loop.run_app(&mut app);
|
//! event_loop.run_app(App::default())?;
|
||||||
|
//!
|
||||||
|
//! Ok(())
|
||||||
//! }
|
//! }
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
|
|
@ -261,7 +274,6 @@
|
||||||
//! [`Window::id()`]: window::Window::id
|
//! [`Window::id()`]: window::Window::id
|
||||||
//! [`WindowEvent`]: event::WindowEvent
|
//! [`WindowEvent`]: event::WindowEvent
|
||||||
//! [`DeviceEvent`]: event::DeviceEvent
|
//! [`DeviceEvent`]: event::DeviceEvent
|
||||||
//! [`exiting()`]: crate::application::ApplicationHandler::exiting
|
|
||||||
//! [`raw_window_handle`]: ./window/struct.Window.html#method.raw_window_handle
|
//! [`raw_window_handle`]: ./window/struct.Window.html#method.raw_window_handle
|
||||||
//! [`raw_display_handle`]: ./window/struct.Window.html#method.raw_display_handle
|
//! [`raw_display_handle`]: ./window/struct.Window.html#method.raw_display_handle
|
||||||
//! [^1]: `EventLoopExtPumpEvents::pump_app_events()` is only available on Windows, macOS, Android, X11 and Wayland.
|
//! [^1]: `EventLoopExtPumpEvents::pump_app_events()` is only available on Windows, macOS, Android, X11 and Wayland.
|
||||||
|
|
|
||||||
|
|
@ -85,12 +85,9 @@
|
||||||
//!
|
//!
|
||||||
//! - applicationDidBecomeActive is Resumed
|
//! - applicationDidBecomeActive is Resumed
|
||||||
//! - applicationWillResignActive is Suspended
|
//! - applicationWillResignActive is Suspended
|
||||||
//! - applicationWillTerminate is LoopExiting
|
//! - applicationWillTerminate corresponds to `Drop`ping the application handler.
|
||||||
//!
|
//!
|
||||||
//! Keep in mind that after LoopExiting event is received every attempt to draw with
|
//! Note that an app may not receive the `Drop` event if suspended; it might be SIGKILL'ed.
|
||||||
//! opengl will result in segfault.
|
|
||||||
//!
|
|
||||||
//! Also note that app may not receive the LoopExiting event if suspended; it might be SIGKILL'ed.
|
|
||||||
//!
|
//!
|
||||||
//! ## Custom `UIApplicationDelegate`
|
//! ## Custom `UIApplicationDelegate`
|
||||||
//!
|
//!
|
||||||
|
|
|
||||||
|
|
@ -546,8 +546,6 @@ impl EventLoop {
|
||||||
if self.exiting() {
|
if self.exiting() {
|
||||||
self.loop_running = false;
|
self.loop_running = false;
|
||||||
|
|
||||||
app.exiting(&self.window_target);
|
|
||||||
|
|
||||||
PumpStatus::Exit(0)
|
PumpStatus::Exit(0)
|
||||||
} else {
|
} else {
|
||||||
PumpStatus::Continue
|
PumpStatus::Continue
|
||||||
|
|
|
||||||
|
|
@ -157,6 +157,7 @@ impl AppState {
|
||||||
pub fn will_terminate(self: &Rc<Self>, _notification: &NSNotification) {
|
pub fn will_terminate(self: &Rc<Self>, _notification: &NSNotification) {
|
||||||
trace_scope!("NSApplicationWillTerminateNotification");
|
trace_scope!("NSApplicationWillTerminateNotification");
|
||||||
// TODO: Notify every window that it will be destroyed, like done in iOS?
|
// TODO: Notify every window that it will be destroyed, like done in iOS?
|
||||||
|
self.event_handler.terminate();
|
||||||
self.internal_exit();
|
self.internal_exit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -164,10 +165,10 @@ impl AppState {
|
||||||
/// of the given closure.
|
/// of the given closure.
|
||||||
pub fn set_event_handler<R>(
|
pub fn set_event_handler<R>(
|
||||||
&self,
|
&self,
|
||||||
handler: &mut dyn ApplicationHandler,
|
handler: impl ApplicationHandler,
|
||||||
closure: impl FnOnce() -> R,
|
closure: impl FnOnce() -> R,
|
||||||
) -> R {
|
) -> R {
|
||||||
self.event_handler.set(handler, closure)
|
self.event_handler.set(Box::new(handler), closure)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn event_loop_proxy(&self) -> &Arc<EventLoopProxy> {
|
pub fn event_loop_proxy(&self) -> &Arc<EventLoopProxy> {
|
||||||
|
|
@ -202,10 +203,6 @@ impl AppState {
|
||||||
/// NOTE: that if the `NSApplication` has been launched then that state is preserved,
|
/// NOTE: that if the `NSApplication` has been launched then that state is preserved,
|
||||||
/// and we won't need to re-launch the app if subsequent EventLoops are run.
|
/// and we won't need to re-launch the app if subsequent EventLoops are run.
|
||||||
pub fn internal_exit(self: &Rc<Self>) {
|
pub fn internal_exit(self: &Rc<Self>) {
|
||||||
self.with_handler(|app, event_loop| {
|
|
||||||
app.exiting(event_loop);
|
|
||||||
});
|
|
||||||
|
|
||||||
self.set_is_running(false);
|
self.set_is_running(false);
|
||||||
self.set_stop_on_redraw(false);
|
self.set_stop_on_redraw(false);
|
||||||
self.set_stop_before_wait(false);
|
self.set_stop_before_wait(false);
|
||||||
|
|
|
||||||
|
|
@ -285,10 +285,10 @@ impl EventLoop {
|
||||||
// redundant wake ups.
|
// redundant wake ups.
|
||||||
pub fn run_app_on_demand<A: ApplicationHandler>(
|
pub fn run_app_on_demand<A: ApplicationHandler>(
|
||||||
&mut self,
|
&mut self,
|
||||||
mut app: A,
|
app: A,
|
||||||
) -> Result<(), EventLoopError> {
|
) -> Result<(), EventLoopError> {
|
||||||
self.app_state.clear_exit();
|
self.app_state.clear_exit();
|
||||||
self.app_state.set_event_handler(&mut app, || {
|
self.app_state.set_event_handler(app, || {
|
||||||
autoreleasepool(|_| {
|
autoreleasepool(|_| {
|
||||||
// clear / normalize pump_events state
|
// clear / normalize pump_events state
|
||||||
self.app_state.set_wait_timeout(None);
|
self.app_state.set_wait_timeout(None);
|
||||||
|
|
@ -324,9 +324,9 @@ impl EventLoop {
|
||||||
pub fn pump_app_events<A: ApplicationHandler>(
|
pub fn pump_app_events<A: ApplicationHandler>(
|
||||||
&mut self,
|
&mut self,
|
||||||
timeout: Option<Duration>,
|
timeout: Option<Duration>,
|
||||||
mut app: A,
|
app: A,
|
||||||
) -> PumpStatus {
|
) -> PumpStatus {
|
||||||
self.app_state.set_event_handler(&mut app, || {
|
self.app_state.set_event_handler(app, || {
|
||||||
autoreleasepool(|_| {
|
autoreleasepool(|_| {
|
||||||
// As a special case, if the application hasn't been launched yet then we at least
|
// As a special case, if the application hasn't been launched yet then we at least
|
||||||
// run the loop until it has fully launched.
|
// run the loop until it has fully launched.
|
||||||
|
|
|
||||||
|
|
@ -8,10 +8,10 @@ use crate::application::ApplicationHandler;
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub(crate) struct EventHandler {
|
pub(crate) struct EventHandler {
|
||||||
/// This can be in the following states:
|
/// This can be in the following states:
|
||||||
/// - Not registered by the event loop (None).
|
/// - Not registered by the event loop, or terminated (None).
|
||||||
/// - Present (Some(handler)).
|
/// - Present (Some(handler)).
|
||||||
/// - Currently executing the handler / in use (RefCell borrowed).
|
/// - Currently executing the handler / in use (RefCell borrowed).
|
||||||
inner: RefCell<Option<&'static mut dyn ApplicationHandler>>,
|
inner: RefCell<Option<Box<dyn ApplicationHandler + 'static>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Debug for EventHandler {
|
impl fmt::Debug for EventHandler {
|
||||||
|
|
@ -37,7 +37,7 @@ impl EventHandler {
|
||||||
/// from within the closure.
|
/// from within the closure.
|
||||||
pub(crate) fn set<'handler, R>(
|
pub(crate) fn set<'handler, R>(
|
||||||
&self,
|
&self,
|
||||||
app: &'handler mut dyn ApplicationHandler,
|
app: Box<dyn ApplicationHandler + 'handler>,
|
||||||
closure: impl FnOnce() -> R,
|
closure: impl FnOnce() -> R,
|
||||||
) -> R {
|
) -> R {
|
||||||
// SAFETY: We extend the lifetime of the handler here so that we can
|
// SAFETY: We extend the lifetime of the handler here so that we can
|
||||||
|
|
@ -48,8 +48,8 @@ impl EventHandler {
|
||||||
// extended beyond `'handler`.
|
// extended beyond `'handler`.
|
||||||
let handler = unsafe {
|
let handler = unsafe {
|
||||||
mem::transmute::<
|
mem::transmute::<
|
||||||
&'handler mut dyn ApplicationHandler,
|
Box<dyn ApplicationHandler + 'handler>,
|
||||||
&'static mut dyn ApplicationHandler,
|
Box<dyn ApplicationHandler + 'static>,
|
||||||
>(app)
|
>(app)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -71,10 +71,13 @@ impl EventHandler {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
match self.0.inner.try_borrow_mut().as_deref_mut() {
|
match self.0.inner.try_borrow_mut().as_deref_mut() {
|
||||||
Ok(data @ Some(_)) => {
|
Ok(data @ Some(_)) => {
|
||||||
*data = None;
|
let handler = data.take();
|
||||||
|
// Explicitly `Drop` the application handler.
|
||||||
|
drop(handler);
|
||||||
},
|
},
|
||||||
Ok(None) => {
|
Ok(None) => {
|
||||||
tracing::error!("tried to clear handler, but no handler was set");
|
// Allowed, happens if the handler was cleared manually
|
||||||
|
// elsewhere (such as in `applicationWillTerminate:`).
|
||||||
},
|
},
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
// Note: This is not expected to ever happen, this
|
// Note: This is not expected to ever happen, this
|
||||||
|
|
@ -110,16 +113,16 @@ impl EventHandler {
|
||||||
matches!(self.inner.try_borrow().as_deref(), Ok(Some(_)))
|
matches!(self.inner.try_borrow().as_deref(), Ok(Some(_)))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn handle(&self, callback: impl FnOnce(&mut dyn ApplicationHandler)) {
|
pub(crate) fn handle(&self, callback: impl FnOnce(&mut (dyn ApplicationHandler + '_))) {
|
||||||
match self.inner.try_borrow_mut().as_deref_mut() {
|
match self.inner.try_borrow_mut().as_deref_mut() {
|
||||||
Ok(Some(user_app)) => {
|
Ok(Some(ref mut user_app)) => {
|
||||||
// It is important that we keep the reference borrowed here,
|
// It is important that we keep the reference borrowed here,
|
||||||
// so that `in_use` can properly detect that the handler is
|
// so that `in_use` can properly detect that the handler is
|
||||||
// still in use.
|
// still in use.
|
||||||
//
|
//
|
||||||
// If the handler unwinds, the `RefMut` will ensure that the
|
// If the handler unwinds, the `RefMut` will ensure that the
|
||||||
// handler is no longer borrowed.
|
// handler is no longer borrowed.
|
||||||
callback(*user_app);
|
callback(&mut **user_app);
|
||||||
},
|
},
|
||||||
Ok(None) => {
|
Ok(None) => {
|
||||||
// `NSApplication`, our app state and this handler are all
|
// `NSApplication`, our app state and this handler are all
|
||||||
|
|
@ -133,4 +136,21 @@ impl EventHandler {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn terminate(&self) {
|
||||||
|
match self.inner.try_borrow_mut().as_deref_mut() {
|
||||||
|
Ok(data @ Some(_)) => {
|
||||||
|
let handler = data.take();
|
||||||
|
// Explicitly `Drop` the application handler.
|
||||||
|
drop(handler);
|
||||||
|
},
|
||||||
|
Ok(None) => {
|
||||||
|
// When terminating, we expect the application handler to still be registered.
|
||||||
|
tracing::error!("tried to clear handler, but no handler was set");
|
||||||
|
},
|
||||||
|
Err(_) => {
|
||||||
|
panic!("tried to clear handler while an event is currently being handled");
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -305,8 +305,8 @@ pub(crate) fn queue_gl_or_metal_redraw(mtm: MainThreadMarker, window: Retained<W
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn launch(mtm: MainThreadMarker, app: &mut dyn ApplicationHandler, run: impl FnOnce()) {
|
pub(crate) fn launch(mtm: MainThreadMarker, app: impl ApplicationHandler, run: impl FnOnce()) {
|
||||||
get_handler(mtm).set(app, run)
|
get_handler(mtm).set(Box::new(app), run)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn did_finish_launching(mtm: MainThreadMarker) {
|
pub fn did_finish_launching(mtm: MainThreadMarker) {
|
||||||
|
|
@ -495,13 +495,11 @@ pub(crate) fn terminated(application: &UIApplication) {
|
||||||
|
|
||||||
let mut this = AppState::get_mut(mtm);
|
let mut this = AppState::get_mut(mtm);
|
||||||
this.terminated_transition();
|
this.terminated_transition();
|
||||||
drop(this);
|
|
||||||
|
|
||||||
get_handler(mtm).handle(|app| app.exiting(&ActiveEventLoop { mtm }));
|
|
||||||
|
|
||||||
let this = AppState::get_mut(mtm);
|
|
||||||
// Prevent EventLoopProxy from firing again.
|
// Prevent EventLoopProxy from firing again.
|
||||||
this.event_loop_proxy.invalidate();
|
this.event_loop_proxy.invalidate();
|
||||||
|
drop(this);
|
||||||
|
|
||||||
|
get_handler(mtm).terminate();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_wrapped_event(mtm: MainThreadMarker, event: EventWrapper) {
|
fn handle_wrapped_event(mtm: MainThreadMarker, event: EventWrapper) {
|
||||||
|
|
|
||||||
|
|
@ -243,7 +243,7 @@ impl EventLoop {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run_app<A: ApplicationHandler>(self, mut app: A) -> ! {
|
pub fn run_app<A: ApplicationHandler>(self, app: A) -> ! {
|
||||||
let application: Option<Retained<UIApplication>> =
|
let application: Option<Retained<UIApplication>> =
|
||||||
unsafe { msg_send![UIApplication::class(), sharedApplication] };
|
unsafe { msg_send![UIApplication::class(), sharedApplication] };
|
||||||
assert!(
|
assert!(
|
||||||
|
|
@ -259,7 +259,7 @@ impl EventLoop {
|
||||||
fn _NSGetArgv() -> *mut *mut *mut c_char;
|
fn _NSGetArgv() -> *mut *mut *mut c_char;
|
||||||
}
|
}
|
||||||
|
|
||||||
app_state::launch(self.mtm, &mut app, || unsafe {
|
app_state::launch(self.mtm, app, || unsafe {
|
||||||
UIApplicationMain(
|
UIApplicationMain(
|
||||||
*_NSGetArgc(),
|
*_NSGetArgc(),
|
||||||
NonNull::new(*_NSGetArgv()).unwrap(),
|
NonNull::new(*_NSGetArgv()).unwrap(),
|
||||||
|
|
|
||||||
|
|
@ -203,11 +203,10 @@ impl EventLoop {
|
||||||
if !self.exiting() {
|
if !self.exiting() {
|
||||||
self.poll_events_with_timeout(timeout, &mut app);
|
self.poll_events_with_timeout(timeout, &mut app);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(code) = self.exit_code() {
|
if let Some(code) = self.exit_code() {
|
||||||
self.loop_running = false;
|
self.loop_running = false;
|
||||||
|
|
||||||
app.exiting(&self.active_event_loop);
|
|
||||||
|
|
||||||
PumpStatus::Exit(code)
|
PumpStatus::Exit(code)
|
||||||
} else {
|
} else {
|
||||||
PumpStatus::Continue
|
PumpStatus::Continue
|
||||||
|
|
|
||||||
|
|
@ -498,8 +498,6 @@ impl EventLoop {
|
||||||
if let Some(code) = self.exit_code() {
|
if let Some(code) = self.exit_code() {
|
||||||
self.loop_running = false;
|
self.loop_running = false;
|
||||||
|
|
||||||
app.exiting(self.window_target());
|
|
||||||
|
|
||||||
PumpStatus::Exit(code)
|
PumpStatus::Exit(code)
|
||||||
} else {
|
} else {
|
||||||
PumpStatus::Continue
|
PumpStatus::Continue
|
||||||
|
|
|
||||||
|
|
@ -650,8 +650,6 @@ impl EventLoop {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
app.exiting(&self.window_target);
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -158,7 +158,6 @@ impl Runner {
|
||||||
Event::Resumed => self.app.resumed(&self.event_loop),
|
Event::Resumed => self.app.resumed(&self.event_loop),
|
||||||
Event::CreateSurfaces => self.app.can_create_surfaces(&self.event_loop),
|
Event::CreateSurfaces => self.app.can_create_surfaces(&self.event_loop),
|
||||||
Event::AboutToWait => self.app.about_to_wait(&self.event_loop),
|
Event::AboutToWait => self.app.about_to_wait(&self.event_loop),
|
||||||
Event::LoopExiting => self.app.exiting(&self.event_loop),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -639,7 +638,9 @@ impl Shared {
|
||||||
self.apply_control_flow();
|
self.apply_control_flow();
|
||||||
// We don't call `handle_loop_destroyed` here because we don't need to
|
// We don't call `handle_loop_destroyed` here because we don't need to
|
||||||
// perform cleanup when the Web browser is going to destroy the page.
|
// perform cleanup when the Web browser is going to destroy the page.
|
||||||
self.handle_event(Event::LoopExiting);
|
//
|
||||||
|
// We do want to run the application handler's `Drop` impl.
|
||||||
|
*self.0.runner.borrow_mut() = RunnerEnum::Destroyed;
|
||||||
}
|
}
|
||||||
|
|
||||||
// handle_event takes in events and either queues them or applies a callback
|
// handle_event takes in events and either queues them or applies a callback
|
||||||
|
|
@ -737,7 +738,6 @@ impl Shared {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_loop_destroyed(&self) {
|
fn handle_loop_destroyed(&self) {
|
||||||
self.handle_event(Event::LoopExiting);
|
|
||||||
let all_canvases = std::mem::take(&mut *self.0.all_canvases.borrow_mut());
|
let all_canvases = std::mem::take(&mut *self.0.all_canvases.borrow_mut());
|
||||||
*self.0.page_transition_event_handle.borrow_mut() = None;
|
*self.0.page_transition_event_handle.borrow_mut() = None;
|
||||||
*self.0.on_mouse_move.borrow_mut() = None;
|
*self.0.on_mouse_move.borrow_mut() = None;
|
||||||
|
|
@ -879,6 +879,5 @@ pub(crate) enum Event {
|
||||||
CreateSurfaces,
|
CreateSurfaces,
|
||||||
Resumed,
|
Resumed,
|
||||||
AboutToWait,
|
AboutToWait,
|
||||||
LoopExiting,
|
|
||||||
UserWakeUp,
|
UserWakeUp,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -333,7 +333,6 @@ impl EventLoopRunner {
|
||||||
self.call_new_events(true);
|
self.call_new_events(true);
|
||||||
self.call_event_handler(|app, event_loop| app.about_to_wait(event_loop));
|
self.call_event_handler(|app, event_loop| app.about_to_wait(event_loop));
|
||||||
self.last_events_cleared.set(Instant::now());
|
self.last_events_cleared.set(Instant::now());
|
||||||
self.call_event_handler(|app, event_loop| app.exiting(event_loop));
|
|
||||||
},
|
},
|
||||||
(_, Uninitialized) => panic!("cannot move state to Uninitialized"),
|
(_, Uninitialized) => panic!("cannot move state to Uninitialized"),
|
||||||
|
|
||||||
|
|
@ -341,9 +340,7 @@ impl EventLoopRunner {
|
||||||
(Idle, HandlingMainEvents) => {
|
(Idle, HandlingMainEvents) => {
|
||||||
self.call_new_events(false);
|
self.call_new_events(false);
|
||||||
},
|
},
|
||||||
(Idle, Destroyed) => {
|
(Idle, Destroyed) => {},
|
||||||
self.call_event_handler(|app, event_loop| app.exiting(event_loop));
|
|
||||||
},
|
|
||||||
|
|
||||||
(HandlingMainEvents, Idle) => {
|
(HandlingMainEvents, Idle) => {
|
||||||
// This is always the last event we dispatch before waiting for new events
|
// This is always the last event we dispatch before waiting for new events
|
||||||
|
|
@ -353,7 +350,6 @@ impl EventLoopRunner {
|
||||||
(HandlingMainEvents, Destroyed) => {
|
(HandlingMainEvents, Destroyed) => {
|
||||||
self.call_event_handler(|app, event_loop| app.about_to_wait(event_loop));
|
self.call_event_handler(|app, event_loop| app.about_to_wait(event_loop));
|
||||||
self.last_events_cleared.set(Instant::now());
|
self.last_events_cleared.set(Instant::now());
|
||||||
self.call_event_handler(|app, event_loop| app.exiting(event_loop));
|
|
||||||
},
|
},
|
||||||
|
|
||||||
(Destroyed, _) => panic!("cannot move state from Destroyed"),
|
(Destroyed, _) => panic!("cannot move state from Destroyed"),
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue