Menu-based event function never returns


#1

So, I’ve got my menu class. In case someone who’s out of the loop reads this topic, my menu class does the following:

  • Generates a GUI containing a list box with menu items;
  • Adds event handlers (as C++ lambdas) for when the focus is changed (between all controls), when an element in the list box is toggled, and when a button is clicked;
  • Adds two buttons (select and back) for submitting your selection to the system and going back a menu; and
  • Adds sounds and nav groups to make interacting with controls easier.

It also adds accessibility to the menu. In case anyone wants it, I’ve attached my menu class (both header and source) as well as my nice bsAll.hpp header, which I use in my own project (saves me a lot of work including a bunch of headers). My difficulty is this: the function never returns. I’ll upload it as a little something for people to use in their projects once its finished and I can document it.
I’ve tried to have a return in the lambda event functions, but they don’t do anything. I have a statement in my main source code of my app that runs the menu, then speaks (through a screen reader) what choice you’ve selected. But when you hit ‘select’ in the menu, it plays its selection sound but doesn’t actually return anything, instead just sitting there quietly as if nothing actually happened. I know I messed up somewhere – but where? If anyone could help that would be great :)!
Edit: apparently I can’t upload files :(. Instead I’ll link to each file (I’d paste them, but the menu class is well over 100 lines long):


#2

Your lambdas will trigger many frames after your function is already done executing (when the user interacts with them), meaning the selected variable will never get modified. You’ll need to create an event and a callback of your own to trigger, or poll the current value every frame.


#3

So, if I understand you correctly, the variable isn’t actually being modified, but remaining at 0? Just confirming so I don’t misunderstand. :slight_smile:


#4

That is correct.

Additionally, the variable gets destroyed as soon as that function exits, so the lambdas end up referencing a deleted memory location, meaning when the variable does get modified its going to corrupt memory if something else got put in that memory location since.


#5

Would there be any way I could directly get the lambda to modify the external wider-scoped variable rather than making a capture copy of it? I’d bring in the C++ 20 feature, P0409R2 (Allow lambda-capture [=, this]), but its only supported in GCC 8 and Clang 6. Plus I get the feeling that that wouldn’t solve anything. I’ve tried moving the function (event handler) to a class-level function, declared in the header and defined in the source, but MSVC apparently disagreed with that idea. I could do a custom class event, but that’s not what I’m trying to accomplish; what I want to happen is for you to be able to select a GUI list box element, click ‘select’, and for the entire GUI to close (that is, stop interpreting events). As modifying a variable directly won’t work, I can only come up with two possibilities: create a function to modify it and widen its scope to the class level, as private, and have the function return it (though, again, I suspect I’d run into this very same issue even if I did that, as event handling wouldn’t terminate); or I throw an exception that’s caught in main, thrown by the lambda, to force the function to terminate and return. I’d rather not throw an exception if I can help it; that method seems a bit… brutal, and I’d rather not have an application with a ton of interwoven and layered exception handlers on top of each other. That’s a disaster just waiting to happen. I ran MSVC over the file with /W4 parameter and /WX and it didn’t complain at all, which is odd, since I’d think the compiler would be able to tell you about something like this. I’m going to see if (once this build finishes) I can run clang over it and see if it’ll provide a bit more information. (If I run MSVC with the /WALL option, it spouts out thousands of warnings about internal, system header files, which makes finding the root cause impractical, if not downright impossible.)
Perhaps I’ve missed something. I know, I’m asking a lot of questions, and I do apologize if its annoying. I’m just learning the framework though, and once I get used to it and passed this hurtle I hopefully should be fine.


#6

There’s no way to do what you want. bsf runs the GUI update on the same thread as your application, so you cannot ‘wait’ for user input in your function because the GUI rendering/input code didn’t even have a chance to run at that point. You need to either use callbacks to report user interaction from the lambdas, or record the value in some global/member variable that won’t go out of scope - and then check that variable for changes every frame and react (i.e. trigger a close method in your case).


#7

Ah. I’ve done that. My private portion of the menu class looks like:

private:
inline void setSelected(int selection) { selected=selection; }
inline int getSelected() { return selected; }
//...
int selected;

And my lambdas (at least the important ones) now look like:

options->onSelectionToggled.connect([&](const UINT32 idx, const bool enabled) {
if (enabled) {
Say(choices.at(idx), true);
setSelected(idx);
MoveAudioSource->play();
}
});

Do you think adding a bool for when the click event is called would do the trick, setting that bool with a private function in the click callback, and then, at the end of the Run function:

if (submitted) {
return getSelected();
}

Or would that need to be a while loop? I was considering running menus on their own thread, but thought against it, and especially now since I know that that really wouldn’t solve much.


#8

It cannot be done in the Run function, the first frame the GUI is rendered and its input is handled happens after that function returns.

You need to check if user submitted input every frame, which is easiest by creating your own Component and overloading the update function.


#9

OK… so overload the list box then. Or would I overload the button too? I’m sorry, this is just a bit confusing for me. I’ll get used to it over time. :slight_smile:


#10

You need to create a class that derived from Component and implements the update method. If that’s unclear I suggest you check the manuals, it’s mentioned fairly early on.


#11

And creating an event, called something like onSelectionSet, and then destroying the menu scene after calling that event wouldn’t be an easier way of doing things? I was checking the developer manuals (wrong manuals, I don’t know) and couldn’t find out how to write new components, though I did check out the part on extending the GUI system.


#12

Developer manuals are for advanced users. Check out user manuals.

Creating an event would be easier but you said earlier that’s not what you wanted.


#13

I might do that anyway. I’m just curious how I’d get the menu to close when the button is clicked… I take it that calling ->destroy() on all the scenes wouldn’t be a good idea?


#14

Depending on your need you can hide an element, remove it from the layout, destroy the element or destroy the parent GUI widget.


#15

I was talking about destroying the scenes that the GUI needs entirely to destroy all elements, all event processors, etc. so the camera scene, audio scenes… you get the idea.


#16

I’m assuming you mean scene objects. If that’s the case sure you can destroy those and any child objects will be destroyed with them.


#17

Yep… tried the destroy() call in the event for onClick with the buttons and the game crashed. Horribly. Spectacularly, in fact. So I can’t destroy the scenes – I’ll just have to let the compilers automatically inserted destructor handle that. But I do now have an event (onMenuDestroyed). Not an apt name, I know. The problem now is the problem of, “What if I want to instantiate another menu?” This arose when I wanted to create an options menu (which would have submenus/dialogs for configuration). The problem is that when a new menu appears, the two overlap (that is, the sounds do, not sure if the GUI does too, but it probably does). I know I’m missing something; what is it? (And I hope that didn’t come across as demanding; if so, I did not intend it to be so.)
Also, I hope this issue isn’t too much of a hassle for you and your not tired of my questions :). Are the problems I’m having common? Or is my case unique?


#18

GUIPanel allows you to specify depth to determine which GUI element is on top, for overlapping elements. You can also offset the panel to a different position. Sounds overlapping is something you need to take care of in your own code so playback doesn’t trigger twice when unnecessary.


#19

I can do that. I think I’ve got this. I’ll give it a go and tell you how it goes. Thanks for your help! :slight_smile: