Closed jwcarlsonsw closed 3 years ago
You don't need eframe
to use egui
or egui_demo_lib
. Truth be told, eframe
is a convenience crate that targets a specific use case. Namely providing a window platform and event loop. You don't need either of these features to integrate with an existing app. Use the lower-level layers instead.
Realistically you will only need an egui
backend that integrates with your graphics stack. egui_glium
is a backend, but also a window platform provider. You can use it as an example to integrate with your existing graphics stack. E.g. it contains the shaders for the renderer and provides an allocator for user textures.
Most of the existing backends that I am aware of target pure Rust apps and graphics stacks. There are a few for wgpu
, at least one for bevy
, and another for vulkano
.
Thanks for that overview. I'm exploring these options in this repo visual_studio_cpp_egui_demo where I'm trying to statically link a c++ console project to an egui popup.
I'm really looking to have the egui
windows operate as a separate dialog, so I guess I'm looking for an easy all in one graphics stack solution. Checking out the pure example it's using a glutin::event_loop run() function which also returns !
.
In checking out a bevy
option there are many c++ linker errors with glslang for bevy shaders. Getting that linked is not going to win in the convenience department.
The eframe
and egui_glium
projects are linking fine. So it seems like the thing to check out next is wgpu
. Is there any known way to hack around a !
? Place it in a tokio thread which kills itself?
Maybe you can spawn eframe::run_native
in a background thread and work around it like that? 🤷♂️
As for alternative backends: you can also check out https://crates.io/crates/egui-macroquad, though the downsides with (all?) other backends is that they do not support reactive mode (only repainting when there is input).
@emilk thanks for the tips. I gave putting it in a thread a shot with the following
#[no_mangle]
pub extern fn start_egui_demo() {
let child = std::thread::spawn( move || {
let app = egui_demo_lib::WrapApp::default();
let options = eframe::NativeOptions {
// Let's show off that we support transparent windows
transparent: true,
decorated: true,
..Default::default()
};
eframe::run_native(Box::new(app), options);
});
child.join();
}
Which compiles and links fine, but gives a run time error
thread '<unnamed>' panicked at 'Initializing the event loop outside of the main thread is a significant cross-platform compatibility hazard. If you really, absolutely need to create an EventLoop on a different thread, please use the `EventLoopExtWindows::new_any_thread` function.', C:\Users\jmw99\.cargo\registry\src\github.com-1ecc6299db9ec823\winit-0.24.0\src\platform_impl\windows\event_loop.rs:136:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
winit does not like it being a background thread apparently.
I followed the calls to run on the event_loop all the way down to the source so\
eframe -> egui_glium -> winit\src\platform_impl\windows\event_loop.rs
And it all rests on run
which is actually a wrapper on run_return
, which allows for a graceful return. I don't know if this has been investigated as an option in eframe, it may lack implementations for linux and mac, but I'm happy to look investigate and make a pr trying to bring that option in.\
Please let me know if this has already been explored and I'm going after a dead-end here.
Please forgive the long-winded response, but this should contain all the info you need for this.
TL;DR: You probably don't need to go to the trouble of patching egui_glium
or eframe
. You can run a custom event loop in its own thread and it probably doesn't need to use run_return()
at all. But if it does, you can always choose to use it instead of run()
. Beware that running an event loop off the main thread is only possible on Windows and Unix-flavors. Most platforms require the main thread to handle all UI stuff.
Using winit::platform::windows::EventLoopExtWindows::new_any_thread()
in egui_glium::run()
as suggested in the error message will require patching egui_glium
: https://github.com/emilk/egui/blob/e320ef6c64fe85df9c4d793cc73bfabbf7358186/egui_glium/src/backend.rs#L175
The simplest possible patch is:
let event_loop = if cfg!(target_os = "windows") {
glutin::platform::windows::EventLoopExtWindows::new_any_thread()
} else {
glutin::event_loop::EventLoop::with_user_event()
};
It depends on the implementation of EventLoop::new()
on Windows just being a wrapper around EventLoopExtWindows::new_any_thread()
: https://github.com/rust-windowing/winit/blob/v0.24.0/src/platform_impl/windows/event_loop.rs#L135-L139 I.e. the patch is currently identical on Windows other than removing the main thread check.
That will at least unblock you on the main thread check if you just want to use egui_glium::run()
. I don't know how well it will work in practice.
You may also want to change the code you are using to spawn the thread. Using child.join()
will block the caller thread, which you probably don't want. Just return the JoinHandle
from your start_egui_demo()
function. You can discard it in C++ land if you want.
That's enough to get a window running in a new thread. The only thing left to discuss is the !
return type...
winit
event handling has evolved to be very unusual. This is mostly caused by the crate trying to adapt to the behaviors of multiple platforms. There is a lot of discussion on this particular topic in https://github.com/rust-windowing/winit/issues/459 Ultimately, EventLoop::run()
will call process::exit(0)
when the window is closed on all platforms. EventLoopExtRunReturn::run_return()
will simply return when the window is closed, but is not supported on iOS or web platforms.
The question is when do you need run_return()
? Since the only difference is whether or not the function returns when the window is closed, the most likely answer is that it depends on whether your application should continue running without the window. And if your use case does fall into this category, then you will need further patches to both eframe
and egui_glium
to remove the !
return types. This has the immediate benefit that the JoinHandle::join()
call will be able to return when the window closes. In other words, your console app can wait for the window to close by calling join()
. And you can also create a new window at some point later if you need to.
However, there is another caveat to beware of; winit
does not work well with multiple event loops running simultaneously. So you will need to exercise caution both when creating your egui thread (don't create more than one at a time) and when using run_return()
and recreating the window later.
You can still handle complex window lifecycle events even with run() -> !
. For instance, this winit
example prevents the window from closing until the user explicitly confirms they don't want to save. Of course, this assume you are in control of the event loop, not egui_glium::run()
.
... And this gets back to what I originally suggested about using the lower layers. For instance, this egui_glium
example implements its own event loop. Which of course you can tailor to fit your needs. E.g. combining the CloseRequested
logic from the winit
example linked above. Since you have no existing graphics stack, this example is probably the right place to start.
Thank you for all of the quality information. It's greatly appreciated. I've usually focused on app development on frameworks, vs the nitty-gritty of the frameworks themselves.
The target use case is having a single egui modal popup in a Windows C++ application. With data passed back and forth on modal open and close. Once I can get that working I'll see about multiple windows and asynchronous handling.
I think the cleanest solution then is going manging the event loop myself. It should make the example I'm trying to develop be much more reusable. I hadn't realized that it was so easy to just the run_return() was so easy to do. So I modified the example as you suggested here. It's essentially the same except for
use glium::glutin::platform::run_return::EventLoopExtRunReturn;
...
event_loop.run_return(move |event, _, control_flow| {}
It's able to return but the window does not close after returning from the function. It does close after a 2nd GUI window does pop up or the C++ program terminates. You can see it at my example repo
I'm trying to explicitly drop the window here but that doesn't kill it either. Based on the discussion in winit/issues/434 it just dropping it seems to be the right approach for killing the window.
...
glutin::event::Event::WindowEvent { event, .. } => {
egui.on_event(event, control_flow);
match control_flow {
glutin::event_loop::ControlFlow::Exit => {
println!("exiting");
std::mem::drop(display.gl_window().window());
println!("Dropped Winit Window");
std::mem::drop(display.gl_window());
println!("Dropped Gl Window")
//std::mem::drop(display);
}
_ => {
display.gl_window().window().request_redraw(); // TODO: ask egui if the events warrants a repaint instead
}
}
}
...
I'm seeing this was a past bug that was supposed to be fixed /rust-windowing/winit/issues/7, winit/pull/416.
Does Egui change the behavior of winit / glutin or is this an issue I should take to glutin?
Well, I'm chasing that issue down with this issue in the glium repo, in summary, it seems to be an error on glium's part but I've found a workaround by calling DestroyWindow
explicitly. See this minimal repo
I'm going to close this as it seems like the original question of how to find the workaround for the ! never return type has been answered.
meant to close
Since https://github.com/emilk/egui/pull/631 run_native
no longer returns !
, but returns when the app has finished running (window is closed).
@emilk It looks like the window will not be closed after run_native
returned.
On windows, I use this method to destroy window, however on linux, I don't know how to destroy the window.
@emilk Thanks for the update on this, I'll update my minimal examples and test it out later this week. Appreciate all the work on the Egui framework!
I updated my mini example here and can confirm that the window is lingering unless that method is used. Will move discussion to #677.
I was hoping to try and use egui for a pop dialog in a c++ program. I was able to link the demo library statically to a test c++ Hello World style program, but when it runs, when I quit the program due to the eframe::run_native() function returning never, it causes exceptions
lib.rs
c_wrapper.h
main.cpp
I understand that the never return type is useful when working with web processes to indicate a process should keep running, but on Native, windows in my case, where I want to close a process is there a way I can gracefully exit, and resume another c++ process?