Create an asynchronous action that waits for a button click to happen.
e.g. I am creating a dialog extension, which shows a question, lets the user input a value. When the “ok” button is pressed, it stores all of that in a variable and closes the popup.
I want this to by async so that the action after it can process the user input.
“Asynchronous events are special actions that will not execute when called. Instead, they’ll do a bit of work between each frame, and once it is done, will allow the actions and subevents following it to run. Just after being called, the actions and subevents following it will be skipped, as they’ll only run when the asynchronous action has finished its work, and the event sheet will continue executing the rest normally.”
The actions at the button click do not justify it. Changing the text and hiding some objects
are done in an instant, and if you want events to action after the button has been clicked then why can’t you add them as subevents?
If you explain the use case more clearly and including screen snips of the events affected by the closing of the popup someone may be able to help.
Yes, this description in the manual perfectly describes the effect of an async await syntax in javascript.
i.e. you want to read a file or do an API call. The response may take a couple of milliseconds, and you don’t want to freeze the application while this happens. So, you would call the function with an await … like
let data = await readFile('file.txt');
what actually happens in this case …
let data;
function cb(response) {
data = response
}
readFile('file.txt' , cb);
Simply put, everything that happens after the blocking function is handled by a separate function as a callback when the file reading is done.
But you can do the same for user interaction.
let userName = await showPopup('enter username please');
The thing is, the application needs to wait for something to happen, and it doesn’t want to block the application.
So, what I did in GDevelop.
I created an extension “Bram”
In that extension I created an object “BramPopup”
The last screenshot (which I posted earlier) shows the graphical components that I used in this object.
I made all of them hidden by default
Then I created an Action for my BramPopup, which is called Ask.
I marked it as “Asynchronous”, which unlocks the “Mark asynchronous function as ended”.
The popup is immediately shown when the Ask action is executed.
The action only completes when the popup is closed again.
The way I see it working is that you call the ASK function, it processes the first event. However, the second event (PopupOkButton is clicked) doesn’t process because the button isn’t clicked. At that point GDevelop exits the function, and the end async doesn’t get actioned. If this is how it’s working, then I wouldn’t consider it a bug.
You could test this by adding a “while PopupOkButton is not clicked” in the middle of the two events and see if that makes it work.
I think/fear that you’re spot on. That does appear to be what happens.
But I think it is a bug.
I mean, that behavior should change, the moment you mark the “Asynchronous” checkbox. From that point on, it should just keep the function alive until the "Mark asynchronous function as ended" action is called.
But if that doesn’t happen, then why is it allowed to exit?
And even more so, why does that Mark asynchronous function as ended even exist then.
I think it explicitly is intended for the scenario where I am using it.
e.g. like in javascript when you create a Promise.
The promise stays active until the resolve function is called.
new Promise((resolve, reject) => {
// stays active until the resolve function is called
});
And after all, GDevelop shows a hint the moment you enable the “Asynchronous” checkbox.
This is an asynchronous action, meaning that the actions and sub-events following it will wait for it to end. Don’t forget to use the action “End asynchronous function” to mark the end of the action.
If it allows the actions in the same event as the Ask action to get processed (or subevents after the Ask action event), then it looks like it may be a bug. In which case I’d suggest you raise it as a GDevelop bug.
It seems to be to be working as intended. If it only takes a fraction of a second to process the events then it’s not going to need to be asynchronous. The function would most likely need some from of looping like while, for each or repeat otherwise there’s no benefit or need for it.
What was the issue with using the doStep? That seems like the best option.
Certain things only get updated in between frames. There doesn’t seem to be any reason to check for certain status changes multiple times with a function between frames. Checking once per frame seems like enough. If you were to use a loop within a function it seems like it would just keep asking the same question even though there’s no opportunity to update the response within the loop or in the same frame.
I dug a little deeper and learned a couple of things.
When the Action is executed, it only checks/executes each event just once. Which I honestly didn’t expect, but makes sense at some level. (PS: do correct me if I’m wrong, it wasn’t that easy to test and rule it out)
A single execution is fine for non-async actions,
since they are short-lived anyway.
And after all, (as also @Keith_1357 suggested), if you need it to be constantly re-evaluated, you can just put it in the doStepPostEvents.
But for async-actions that’s not so pretty.
Because those actions are long-lived.
So you want to be able to re-evaluate conditions in the course of that action.
Also you cannot put the “Mark asynchronous function as ended” in the doStepPostEvents. Because it is linked directly to an asynchronous action.
So, what if there is a condition, that needs to be re-evaluated, before the Mark asynchronous function as ended can be called?
→ you can’t do that. You can’t conditionally delay the end of your async action. ( which is exactly what I wanted to do)
And I think that reduces the use of it, to …
→ i.e. if you don’t have conditions, so e.g. just a wait x seconds without any logic attached to it.
Yeah, that works. But it’s not very useful.
I wonder if javascript gives you more control. e.g. if I could actually put that “mark …” function in a variable, which could be accessible from the doStepPostEvents loop, then I could execute it from there, and that would solve everything.
Actually, the function is only called once because it is supposed to use other async actions like “wait”. For instance, you can use it to play a few tweens.
Well, I’m not here to nitpick about what has been documented or not. I am also not here to criticize GDevelop. I think it’s amazing how efficiently you can code some things.
What gave me even higher expectations is.
the fact that there already are async actions (i.e. “Wait x seconds”)
the fact that you can mark custom actions as async.
That gave me the (wrong) impression that we could create our own async actions.
However, it is more like, if you want to use existing async actions in your custom actions, and you want your action to wait for those to be completed, then you need to check the “asynchronous …” checkbox.
That two-choices-dialog-boxes extension is extremely basic in that regards. It’s not async at all. Not much magic there. If you use that extension, you still need to do half of the work yourself.
you need at least 2 events to consume those dialog boxes
one to open it
another one to check if it is closed
but what if it gets reopened later?
you need to reset variables/conditions
And that’s not the fault of the extension. It does what it can. You just can’t make it more reusable than that - without real async actions.
What I wanted to accomplish
handle all of it in 1 event
when the popup opens, the flow pauses, as it waits until it is closed again.
within that same event handling, you get the user’s input as a return value of the action
so that the consumer of the popup no longer needs state management, no variables to be reset. Because the action cleans up after itself.
But well, if it’s not supported, it’s not supported I guess.
Thank you for your time though.
And your comments about the hiding of the entire object is useful. Thank you for that.
The thing in javascript, is that you rarely ever need a new Promise(...) explicitly.
e.g. in Node.js if you want to read a file, the async readFile(...) already returns is an async function, if you call an API with axios, those are all async functions as well.
So, once the framework offers a bunch of async functions/actions that simplifies stuff a lot, as you don’t need new Promise(...) anymore.
e.g. if there would be a "Wait for button <Button> to be clicked" action out of the box, then I would just be able to use that in my extension.
From a JS event, you can get the ManuallyResolvableTask of the function call with eventsFunctionContext.task. You could store it into your custom object to be able to call resolve from the doStepPostEvents function.