Fix UI hangs on dialog boxes on macOS#42
Conversation
…ing while dialog box is open. Adds initial support for macOS.
jbuehler23
left a comment
There was a problem hiding this comment.
Overall, I'm happy with these changes - it would be good to just have some test evidence that the UI is not affecting on Windows machines as well. I can try and test locally later on this branch
|
"Somewhat out of scope of the issue, but I'd like to go in and separate out all the platform specific code. There are a lot of #[cfg(target_os = foo)], but not applied equally. AsyncFileDialog should also allow it to work in the browser." I would be very happy with these changes. I had to a hack a lot of this stuff together, and I would love to see it removed |
- spawn_and_poll now returns a DialogStatus which can be used to determine if a dialog has been closed or is still in progress - Moved EguiAsyncPlugin and DialogBinds initialization into DialogBoxPlugin
|
In addition to resolving your commets, I also completed the first todo and got dialog cancellation working. Also, can confirm that the dialog boxes work as usual on Windows. In fact, now, the window doesnt freeze when you open a dialog box on windows! You can still interact with the editor and open other dialogs. This may lead to unintended bugs with weird interactions. For example, opening a dialog box in a menu, interacting with the UI such that the menu is no longer being rendered, and then submitting the dialog box. It may not propogate into the system until menu is opened again. Maybe we should consider moving this completely to a bevy observer and resource. Not sure it's a big enough issue fix, but we should be aware of it when building out any more UI with dialog boxes. |
|
Excellent work @chris-marrero! Let's get this merged in once CI is passing. I'll cut a new release likely tomorrow as well and push to crates.io |
|
Merging this in, once CI passes :) |
Problem
See #41 for a detailed explanation of the root cause. In brief, dialog boxes spin the thread which causes the ui to freeze and macOS functionality to be broken.
My Solution1
It starts with
DialogBindsandDialogBind, a thin wrapper aroundegui_async::Bind. It is a bevyResourceto allow access to it every update.Bindserves as a way to keep a future between updates. See here for more information on how I use it.When we want a new dialog box, we call
DialogBinds::spawn_and_pollwhich, among other things, does what is shown below.There are many dialog kinds, one per original call to
rfd::FileDialog::new. Each kind is mapped to both a function call torfd::AsyncFileDialogand a file filter. For example, if evaluatingkind.open(file_dialog)whenDialogKind::Open, this would be called:file_dialog.pick_file().Certain dialog boxes require data to be opened properly, such as setting the default name for a new file. This is handled by adding an
OptiontoDialogBindsand adding explicit cases for each piece of data.Finally, this method relies on polling the future every update in order to progress. The first poll of the future will essentially never return a value. Therefore, the poll must kept being called every frame that we should expect a result. Each dialog box call must handle polling, so each call has a bespoke method for keeping the call alive every frame.
There are two main methods. If the area of code the dialog box is in is being run every update, we can just check if the bind is
in_progressand keep polling if it is.However,
render_dialogswilltakethepending_actionfrom the state, meaning unless the action is replaced, that branch will not run the next update and the future will not be polled again. In this case, it is a simple matter of setting thepending_actionback.In the end, it leads to a call that looks something like this:
Todo and Thoughts
DialogBindmakes no distinction between a finished bind which returned nothing and an in progress bind.#[cfg(target_os = foo)], but not applied equally.AsyncFileDialogshould also allow it to work in the browser.DialogKindto not havefilterandopen. Instead, the logic is returned to the call site as it was before this commit. So you'd have a call something like below. The problem is that we don't know which bind to use. The bind must be stable across updates for dialog box's lifetime. It would be easier if the next list item was implemented.Footnotes
Sorry for the long explanation. It's a complex problem. Feel free to skip the code and come back to the explanation for specifics. ↩