C++ coroutines instead of modal loops and async callbacks for UI?

Let’s take the usage of a PopupMenu as an example.

The simplest way to use it to interact with the user is:

PopupMenu menu;
// add items here

const auto result = menu.show();

if (result == /*whatever*/)
    doStuff();

This is easily readable and maintainable, but it’s bad because menu.show() is a synchronous call, which blocks execution at that line by spinning its own modal loop.

The next best thing is to use an asynchronous call, which does not block but delegates dealing with the result to a callback:

PopupMenu menu;
// add items here

auto action = [] (int result)
{
    if (result == /*whatever*/)
        doStuff();
};

const auto result = menu.showMenuAsync({}, action);

This is slightly less readable, because it puts the handling code before the line that displays the menu.

Reading tutorials about the coroutines introduced in C++20, it seems to me that there should be a way to use them to restore the possibility of writing code in the “original” order, using coroutines instead of synchronous calls, but I haven’t grasped them well enought to wrap my head around a way to do it.

Is there some coroutines wizard here that could shed some light on this subject?

const auto result = menu.showMenuAsync({}, [] (int result)
{
    if (result == /*whatever*/)
        doStuff();
} );

Less readable?

1 Like

I myself prefer constructing the menu items directly with the callback lambdas.

juce::Popupmenu menu;
menu.addItem("Action 1", [this](){ doThing1(); });
menu.addItem("Action 2", [this](){ doThing2(); });
menu.showAsync({});
4 Likes

This. Much cleaner than having magic numbers all over the place.
Niggle: You don’t need the empty call brackets so you can just use:
menu.addItem ("Action 1", [this] { doThing1(); });

4 Likes

Yes, my example was obviously very simple on purpose. In more complex cases, for example if you have to show an AlertWindow in response to the choice made, then you have to have another nested lambda inside the one that handles the menu. And so on if you need more user input e.g. a file name, etc.

Yes, this is my favorite style as well nowadays, but as I was saying in the other comment, it has the potential to become a mess if the code handling each choice has then to display a dialog window to ask for a filename, then an AlertWindow if that file already exists, that sort of things.

I was ready to the possibility that this topic would turn into “you don’t need to use coroutines, you can do it these other ways instead”.
I appreciate your input and I’m aware of these other ways that you have mentioned.
I am already using them in my code for the good reasons that you already have pointed out.

The specific goal of this topic is: setting aside all the arguments about why I shouldn’t need to use coroutines for this use case, is there a way to do that, and, if so, how?

Sure, coroutines would be a good tool for this kind of async programming. I just don’t think there’s good library support for implementing them with a PopupMenu at the moment.

Maybe take a look at
GitHub - andreasbuhr/cppcoro: A library of C++ coroutine abstractions for the coroutines TS?

I’ve not looked in to it too much but I think you’d build it on a cppcoro::task<int>