Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Continue execution after closing native eframe window #1889

Merged
merged 8 commits into from Aug 5, 2022

Conversation

emilk
Copy link
Owner

@emilk emilk commented Aug 3, 2022

Closes #1223

This adds NativeOptions::run_and_return with default value true.

If true, execution will continue after the eframe window is closed. This is a new behavior introduced in this PR.
If false, the app will close once the eframe window is closed. The is the old behavior.

This is true by default, and the false option is only there so we can revert if we find any bugs.

When true, winit::platform::run_return::EventLoopExtRunReturn::run_return is used. The winit docs warns of its usage, recommending EventLoop::run, but 🤷
When false, winit::event_loop::EventLoop::run is used.

This is a really useful feature. You can now use eframe to quickly open up a window and show some data or options, and then continue your program after the eframe window is closed

My previous attempt at this caused some problems, but my new attempt seems to be working much better, at least on my Mac.

This will allow us to try out another event loop,
one that continues execution after the window closes.
`true` by default. Only implemented for glow so far.
@emilk emilk force-pushed the refactor-winit-event-loop branch from f45cfd7 to 2512aae Compare August 3, 2022 19:27
@emilk emilk marked this pull request as ready for review August 3, 2022 19:27
@emilk emilk changed the title Refactor winit event loop Continue execution after closing native eframe window Aug 3, 2022
@emilk
Copy link
Owner Author

emilk commented Aug 4, 2022

UnityGaming says on the egui Discord that they've tested this PR on Windows 11, and it seems to work fine.

@crumblingstatue
Copy link
Contributor

Does this allow recreating the window?
I have a media player with a closeable window that can be reopened by clicking tray icon. I had to use another integration to make it work.
Alternatively, does eframe support hiding/showing the window?

@emilk
Copy link
Owner Author

emilk commented Aug 5, 2022

Unfortunately, on my Mac EventLoopExtRunReturn::run_return crashes the second time it is called:

thread 'main' panicked at 'The panic info must exist here. This failure indicates a developer error.', /Users/emilk/.cargo/registry/src/github.com-1ecc6299db9ec823/winit-0.26.1/src/platform_impl/macos/app_state.rs:375:14
stack backtrace:
   0: rust_begin_unwind
             at /rustc/fe5b13d681f25ee6474be29d748c65adcd91f69e/library/std/src/panicking.rs:584:5
   1: core::panicking::panic_fmt
             at /rustc/fe5b13d681f25ee6474be29d748c65adcd91f69e/library/core/src/panicking.rs:143:14
   2: core::panicking::panic_display
             at /rustc/fe5b13d681f25ee6474be29d748c65adcd91f69e/library/core/src/panicking.rs:72:5
   3: core::panicking::panic_str
             at /rustc/fe5b13d681f25ee6474be29d748c65adcd91f69e/library/core/src/panicking.rs:56:5
   4: core::option::expect_failed
             at /rustc/fe5b13d681f25ee6474be29d748c65adcd91f69e/library/core/src/option.rs:1874:5
   5: core::option::Option<T>::expect
             at /rustc/fe5b13d681f25ee6474be29d748c65adcd91f69e/library/core/src/option.rs:718:21
   6: winit::platform_impl::platform::app_state::AppState::cleared
             at /Users/emilk/.cargo/registry/src/github.com-1ecc6299db9ec823/winit-0.26.1/src/platform_impl/macos/app_state.rs:373:26
   7: winit::platform_impl::platform::observer::control_flow_end_handler::{{closure}}
             at /Users/emilk/.cargo/registry/src/github.com-1ecc6299db9ec823/winit-0.26.1/src/platform_impl/macos/observer.rs:180:21
   8: winit::platform_impl::platform::observer::control_flow_handler::{{closure}}
             at /Users/emilk/.cargo/registry/src/github.com-1ecc6299db9ec823/winit-0.26.1/src/platform_impl/macos/observer.rs:142:57
   9: std::panicking::try::do_call
             at /rustc/fe5b13d681f25ee6474be29d748c65adcd91f69e/library/std/src/panicking.rs:492:40
  10: ___rust_try
  11: std::panicking::try
             at /rustc/fe5b13d681f25ee6474be29d748c65adcd91f69e/library/std/src/panicking.rs:456:19
  12: std::panic::catch_unwind
             at /rustc/fe5b13d681f25ee6474be29d748c65adcd91f69e/library/std/src/panic.rs:137:14
  13: winit::platform_impl::platform::event_loop::stop_app_on_panic
             at /Users/emilk/.cargo/registry/src/github.com-1ecc6299db9ec823/winit-0.26.1/src/platform_impl/macos/event_loop.rs:229:11
  14: winit::platform_impl::platform::observer::control_flow_handler
             at /Users/emilk/.cargo/registry/src/github.com-1ecc6299db9ec823/winit-0.26.1/src/platform_impl/macos/observer.rs:142:5
  15: winit::platform_impl::platform::observer::control_flow_end_handler
             at /Users/emilk/.cargo/registry/src/github.com-1ecc6299db9ec823/winit-0.26.1/src/platform_impl/macos/observer.rs:175:9
  16: <unknown>
  17: <unknown>
  18: <unknown>
  19: <unknown>
  20: <unknown>
  21: <unknown>
  22: <unknown>
  23: <unknown>
  24: <unknown>
  25: <unknown>
  26: <() as objc::message::MessageArguments>::invoke
             at /Users/emilk/.cargo/registry/src/github.com-1ecc6299db9ec823/objc-0.2.7/src/message/mod.rs:128:17
  27: objc::message::platform::send_unverified
             at /Users/emilk/.cargo/registry/src/github.com-1ecc6299db9ec823/objc-0.2.7/src/message/apple/mod.rs:27:9
  28: objc::message::send_message
             at /Users/emilk/.cargo/registry/src/github.com-1ecc6299db9ec823/objc-0.2.7/src/message/mod.rs:178:5
  29: winit::platform_impl::platform::event_loop::EventLoop<T>::run_return::{{closure}}
             at /Users/emilk/.cargo/registry/src/github.com-1ecc6299db9ec823/winit-0.26.1/src/platform_impl/macos/event_loop.rs:188:22
  30: objc::rc::autorelease::autoreleasepool
             at /Users/emilk/.cargo/registry/src/github.com-1ecc6299db9ec823/objc-0.2.7/src/rc/autorelease.rs:29:5
  31: winit::platform_impl::platform::event_loop::EventLoop<T>::run_return
             at /Users/emilk/.cargo/registry/src/github.com-1ecc6299db9ec823/winit-0.26.1/src/platform_impl/macos/event_loop.rs:178:9
  32: <winit::event_loop::EventLoop<T> as winit::platform::run_return::EventLoopExtRunReturn>::run_return
             at /Users/emilk/.cargo/registry/src/github.com-1ecc6299db9ec823/winit-0.26.1/src/platform/run_return.rs:56:9
  33: eframe::native::run::run_and_return
             at ./eframe/src/native/run.rs:82:5
  34: eframe::native::run::glow_integration::run_glow
             at ./eframe/src/native/run.rs:429:13
  35: eframe::run_native
             at ./eframe/src/lib.rs:171:13
  36: egui_demo_app::main
             at ./egui_demo_app/src/main.rs:26:5
  37: core::ops::function::FnOnce::call_once
             at /rustc/fe5b13d681f25ee6474be29d748c65adcd91f69e/library/core/src/ops/function.rs:227:5
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.
thread 'main' panicked at 'called `Option::unwrap()` on a `None` value', /Users/emilk/.cargo/registry/src/github.com-1ecc6299db9ec823/winit-0.26.1/src/platform_impl/macos/event_loop.rs:237:55
stack backtrace:
   0: rust_begin_unwind
             at /rustc/fe5b13d681f25ee6474be29d748c65adcd91f69e/library/std/src/panicking.rs:584:5
   1: core::panicking::panic_fmt
             at /rustc/fe5b13d681f25ee6474be29d748c65adcd91f69e/library/core/src/panicking.rs:143:14
   2: core::panicking::panic
             at /rustc/fe5b13d681f25ee6474be29d748c65adcd91f69e/library/core/src/panicking.rs:48:5
   3: core::option::Option<T>::unwrap
             at /rustc/fe5b13d681f25ee6474be29d748c65adcd91f69e/library/core/src/option.rs:755:21
   4: winit::platform_impl::platform::event_loop::stop_app_on_panic
             at /Users/emilk/.cargo/registry/src/github.com-1ecc6299db9ec823/winit-0.26.1/src/platform_impl/macos/event_loop.rs:237:34
   5: winit::platform_impl::platform::observer::control_flow_handler
             at /Users/emilk/.cargo/registry/src/github.com-1ecc6299db9ec823/winit-0.26.1/src/platform_impl/macos/observer.rs:142:5
   6: winit::platform_impl::platform::observer::control_flow_end_handler
             at /Users/emilk/.cargo/registry/src/github.com-1ecc6299db9ec823/winit-0.26.1/src/platform_impl/macos/observer.rs:175:9
   7: <unknown>
   8: <unknown>
   9: <unknown>
  10: <unknown>
  11: <unknown>
  12: <unknown>
  13: <unknown>
  14: <unknown>
  15: <unknown>
  16: <unknown>
  17: <() as objc::message::MessageArguments>::invoke
             at /Users/emilk/.cargo/registry/src/github.com-1ecc6299db9ec823/objc-0.2.7/src/message/mod.rs:128:17
  18: objc::message::platform::send_unverified
             at /Users/emilk/.cargo/registry/src/github.com-1ecc6299db9ec823/objc-0.2.7/src/message/apple/mod.rs:27:9
  19: objc::message::send_message
             at /Users/emilk/.cargo/registry/src/github.com-1ecc6299db9ec823/objc-0.2.7/src/message/mod.rs:178:5
  20: winit::platform_impl::platform::event_loop::EventLoop<T>::run_return::{{closure}}
             at /Users/emilk/.cargo/registry/src/github.com-1ecc6299db9ec823/winit-0.26.1/src/platform_impl/macos/event_loop.rs:188:22
  21: objc::rc::autorelease::autoreleasepool
             at /Users/emilk/.cargo/registry/src/github.com-1ecc6299db9ec823/objc-0.2.7/src/rc/autorelease.rs:29:5
  22: winit::platform_impl::platform::event_loop::EventLoop<T>::run_return
             at /Users/emilk/.cargo/registry/src/github.com-1ecc6299db9ec823/winit-0.26.1/src/platform_impl/macos/event_loop.rs:178:9
  23: <winit::event_loop::EventLoop<T> as winit::platform::run_return::EventLoopExtRunReturn>::run_return
             at /Users/emilk/.cargo/registry/src/github.com-1ecc6299db9ec823/winit-0.26.1/src/platform/run_return.rs:56:9
  24: eframe::native::run::run_and_return
             at ./eframe/src/native/run.rs:82:5
  25: eframe::native::run::glow_integration::run_glow
             at ./eframe/src/native/run.rs:429:13
  26: eframe::run_native
             at ./eframe/src/lib.rs:171:13
  27: egui_demo_app::main
             at ./egui_demo_app/src/main.rs:26:5
  28: core::ops::function::FnOnce::call_once
             at /rustc/fe5b13d681f25ee6474be29d748c65adcd91f69e/library/core/src/ops/function.rs:227:5
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.

@emilk
Copy link
Owner Author

emilk commented Aug 5, 2022

Alternatively, does eframe support hiding/showing the window?

Yes: https://github.com/emilk/egui/blob/master/eframe/src/epi.rs#L593-L597

@emilk
Copy link
Owner Author

emilk commented Aug 5, 2022

Ok, I'm going to merge this and see how it goes.

@emilk emilk merged commit 66c601f into master Aug 5, 2022
@emilk emilk deleted the refactor-winit-event-loop branch August 5, 2022 06:20
@zu1k
Copy link
Contributor

zu1k commented Aug 5, 2022

I just test it on my Windows 10 PC, looks like there is still a problem.

I build a minimized app, it is expected to exit gracefully after clicking close and reopen the window after waiting two seconds.

use std::{thread::sleep, time::Duration};
use eframe::egui;

fn main() {
    for _ in 0..10 {
        eframe::run_native(
            "Exit",
            eframe::NativeOptions::default(),
            Box::new(|_cc| Box::new(MyApp::default())),
        );

        sleep(Duration::from_secs(2));
    }
}

#[derive(Default)]
struct MyApp {}

impl eframe::App for MyApp {
    fn update(&mut self, _ctx: &egui::Context, _frame: &mut eframe::Frame) {
    }
}

I still encountered the same problem before. After clicking close, the window cannot be closed normally, and the task manager shows a timeout. After waiting for two seconds, the window closes and a new window opens. Same as #1223 (comment)

20220805_153041.mp4

https://github.com/bayswaterpc/visual_studio_cpp_egui_demo/blob/481bcb597ea474c86d8e234ff7dea583f16f90c5/egui_glium_pure/src/lib.rs#L115

@emilk
Copy link
Owner Author

emilk commented Aug 5, 2022

@zu1k interesting!

Could you please open an issue in the winit repo, and perhaps open a PR to add a call to winuser::DestroyWindow(h_wnd); in eframe? 🙏

@zu1k
Copy link
Contributor

zu1k commented Aug 6, 2022

It looks like winit already fix this in this PR rust-windowing/winit#2057.
https://github.com/rust-windowing/winit/blob/6b7ceedc9178b552935f289649e0a5abb316050e/src/platform_impl/windows/event_loop.rs#L503

So this maybe works #1877, I will have a try.

@zu1k
Copy link
Contributor

zu1k commented Aug 6, 2022

It looks like a EventLoop should only be created once, so the drop func for EventLoop which calls DestroyWindow would not be executed.

@zu1k
Copy link
Contributor

zu1k commented Aug 6, 2022

I found a way, this works on my win10.

use instant::Duration;
use simple_logger::SimpleLogger;
use std::thread::sleep;
use winit::{
    event::{Event, WindowEvent},
    event_loop::EventLoop,
    platform::run_return::EventLoopExtRunReturn,
    window::Window,
};

fn main() {
    SimpleLogger::new().init().unwrap();
    let mut event_loop = EventLoop::new();

    for _ in 0..5 {
        {
            let _window = Window::new(&event_loop).unwrap();

            event_loop.run_return(move |event, _event_loop, control_flow| {
                control_flow.set_wait();

                match event {
                    Event::WindowEvent { event, window_id } => match event {
                        WindowEvent::CloseRequested => {
                            println!("Window {:?} has received the signal to close", window_id);
                            control_flow.set_exit();
                        }
                        _ => {}
                    },
                    _ => (),
                }
            });
        }

        // we need to call `run_return` again
        event_loop.run_return(|_, _, cf| cf.set_exit());
        sleep(Duration::from_secs(2));
    }
}
20220806_105521.mp4

@d10sfan
Copy link
Contributor

d10sfan commented Aug 6, 2022

trying something similar with glutin and running the event_loop again does not seem to work on Linux, when i close the first window, the second window immediately closes. this is with egui 0.18.1

@emilk
Copy link
Owner Author

emilk commented Aug 6, 2022

@zu1k thanks for investigating! So we need to reuse the same event_loop – is that true for both winit 0.28 and winit 0.29?

@zu1k
Copy link
Contributor

zu1k commented Aug 6, 2022

@emilk I test it on winit master branch. Now I need to call run_return again which is not expected. Maybe someone could fix it later.

@emilk
Copy link
Owner Author

emilk commented Aug 15, 2022

I created an issue for this in #1918

@emilk
Copy link
Owner Author

emilk commented Aug 15, 2022

@zu1k I tested adding an extra run_return but it didn't do the trick for me on Mac. I have started some work in #1919 if you want to give it a go.

Getting something that works on at least windows would at least be a start.

@vincev
Copy link

vincev commented Sep 5, 2022

@emilk I have just noticed that App::on_exit doesn't get called on Mac when I press Cmd-Q if I create the app with the default NativeOptions, it gets called though if I set run_and_return: false:

fn main() {
    let native_options = eframe::NativeOptions {
        // This is false to trigger App::on_exit when we closing the app with Cmd-Q.
        run_and_return: false,
        ..Default::default()
    };
    eframe::run_native("My App", native_options, Box::new(|cc| Box::new(MyEguiApp::new(cc))));
}

impl eframe::App for MyEguiApp {
    fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
       egui::CentralPanel::default().show(ctx, |ui| ui.heading("Hello World!"));
    }

    fn on_exit(&mut self, _gl: Option<&eframe::glow::Context>) {
        println!("On exit");
    }
}

I am using App::on_exit to save some pending state in the app, I am running 0.19. Maybe would be good to add it to the docs for the option, I don't mind creating a PR if you think is appropriate.

@emilk
Copy link
Owner Author

emilk commented Sep 6, 2022

@vincev nice find - it will be fixed in #2013

@Boscop
Copy link

Boscop commented Dec 5, 2022

Is it also possible with eframe to run_return each frame individually so that control flow returns to the caller after each GUI frame is done?
It would be useful for those applications where the GUI must be run in the same thread.

pan93412 added a commit to pan93412/egui that referenced this pull request Jun 4, 2023
The approach of emilk#1889 may remove the observer of a view
twice, which produces the Obj-C Exception:

    Cannot remove an observer <...> for the key path
    "nextResponder" from <WinitView ...> because
    it is not registered as an observer.

The above message can only be seen when attaching the
application to debugger. Users normally see:

    [1]    *** trace trap  cargo run

This commit fixes it by only running event_loop twice
on Windows. Besides:

* We have set ControlFlow::Exit on 'Event::LoopDestoryed',
  'EventResult::Exit' and on error; therefore, it is safe
  to not calling `set_exit()`.
* This commit also fix the persistence function in macOS.
  It can't store the content in Memory due to this exception.

Fixed: emilk#2768 (eframe: "App quit unexpectedly" on macOS)
Signed-off-by: pan93412 <pan93412@gmail.com>
pan93412 added a commit to pan93412/egui that referenced this pull request Jun 4, 2023
The approach of emilk#1889 may remove observers in a view
twice, which produces the Obj-C Exception:

    Cannot remove an observer <...> for the key path
    "nextResponder" from <WinitView ...> because
    it is not registered as an observer.

The above message can only be seen when attaching the
application to debugger. Users normally see:

    [1]    *** trace trap  cargo run

This commit fixes it by only running `event_loop.run_return()`
twice on Windows. Besides:

* We have set `ControlFlow::Exit` on `Event::LoopDestroyed`,
  `EventResult::Exit` and on error; therefore, it is safe
  to not calling `set_exit()`.
* This commit also fix the persistence function in macOS.
  It can't store the content in Memory due to this exception.

Fixed: emilk#2768 (eframe: "App quit unexpectedly" on macOS)
Signed-off-by: pan93412 <pan93412@gmail.com>
emilk pushed a commit that referenced this pull request Jun 5, 2023
The approach of #1889 may remove observers in a view
twice, which produces the Obj-C Exception:

    Cannot remove an observer <...> for the key path
    "nextResponder" from <WinitView ...> because
    it is not registered as an observer.

The above message can only be seen when attaching the
application to debugger. Users normally see:

    [1]    *** trace trap  cargo run

This commit fixes it by only running `event_loop.run_return()`
twice on Windows. Besides:

* We have set `ControlFlow::Exit` on `Event::LoopDestroyed`,
  `EventResult::Exit` and on error; therefore, it is safe
  to not calling `set_exit()`.
* This commit also fix the persistence function in macOS.
  It can't store the content in Memory due to this exception.

Fixed: #2768 (eframe: "App quit unexpectedly" on macOS)

Signed-off-by: pan93412 <pan93412@gmail.com>
@wlwatkins
Copy link

Is this a stable feature? because i seem to get an issue where the last frame sticks around and the main window becomes unresponsive. this is still true on windows in 0.27.2.

for a mve https://stackoverflow.com/questions/78294791/egui-run-and-return-not-destroying-main-window

@couleurm
Copy link

couleurm commented May 8, 2024

I second this, circumvented this for now by grabbing it's hwnd via a receiver and using win32 DestroyWindow to close it

https://github.com/couleur-tweak-tips/smoothie-rs/blob/main/src/main.rs#L124

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Support exiting event loop (closing native window) without quitting program
8 participants