Open ericoporto opened 3 years ago
~I would not call this a refactor, but rather a rewrite...~
Which branch this task is proposed for?
I have a feeling we will live with ags3 and ags4 together for a while, so my guess is ags3.
There are many ways to go for this implementation wise. I created the issue to figure the way to go.
LÖVE engine games are interesting to get ideas since each of them solve this in different ways - the engine only has one game state so the game developers always have to figure how to deal with managing states and there are multiple approaches.
The game states have a looping part and a clean up stage that needs to be run before switching state.
I have a feeling we will live with ags3 and ags4 together for a while, so my guess is ags3.
I hope not in 3.6.0 though?
For a full picture I must note that the list above is incomplete. There are also blocking actions that run game loop until certain condition is met. Look up for GameLoopUntil*
functions.
Each of these blocking actions will have to return back to the script where it was started.
In terms of a system, states may have Begin/Update/End functions, and if we have substates (like a blocking function called from within a dialog), then it may also need a stack of states, where states are pushed and removed to return to their parent state.
I hope not in 3.6.0 though?
No, no need for. This doesn't block anything now, but I think will make things easier in the future.
Begin/Update/End functions, and if we have substates (like a blocking function called from within a dialog), then it may also need a stack of states, where states are pushed and removed to return to their parent state
That's literally what I imagined to do! I like this approach.
There may be at least two approaches to the stack of states too.
I think 1 is thinking on game states and it was what I had in mind.
2 is something that I don't know how to name (state-tag?) but I do understand what it means, and it could reduce code duplication. For me going to 2 looks harder (probably just because I had not given thought to it before) but I see advantages with this approach too.
Perhaps, for ags3 this may be done in a simplest possible way: define states as enum, and have a stack with push/pop mechanic to start the state and get back out to the "main" one. Popping main state would exit the game.
I may propose following approach to the refactor:
EDIT: will make sense to have Render along with Update, because there are situations when we want to draw, but not update, or vice-versa.
Since this ticket was mentioned from another PR, a small amendment to the above discussion about state classes. Previously mentioned that the state class should have Begin/Update/End and perhaps Render function. It's clear now they should have virtual input handling methods, like OnKey, OnMouseButton, and so forth, and it will be useful to have a input processing loop in a base state class that runs the general algorithm of reading the input events queue and calling virtual child methods per each event. There's a question of whether we still need to have our own input events queue, or may have the SDL events processing right in this base state class. We may begin with keeping current way where we gather our internal queue first and process later when the state needs to. Then this may be revisited after separating sdl events and game update into 2 or more threads.
For the record, in regards to this:
For a full picture I must note that the list above is incomplete. There are also blocking actions that run game loop until certain condition is met. Look up for GameLoopUntil* functions. Each of these blocking actions will have to return back to the script where it was started.
This must be elaborated, because it's one of the major issues in this task. I actually suspect it's the biggest one.
Each blocking script function runs a nested game loop while waiting. In order to unwind these nested loops, the blocking function should only start "waiting" state, and return back to the script interpreter. But script interpreter cannot continue current code, instead it will have to suspend this instance at the point of calling the function, and return from Run(). And this instance will have to be able to resume from the same point afterwards. But that's part of a deal, another part is the blocking function's return value, which cannot be ready until the waiting state ends. So, when resuming an Instance, engine has to somehow pass this return value, which Instance will finally write into the register.
But this is still not all... because that script may be run as a callback in a sequence of callbacks. For example, engine runs event in each script module in order. Next modules cannot be run until previous is done. So, not only we should suspend the run Instance, but we should suspend the event processing, and anything else that wraps it.
There is a proposal for browsers for the JavaScript Promise Integration API that has possibilities to enable me to drop Asyncify in the Emscripten port, even if this issue isn't implemented, but this will take at least two years (super optimistically) to be enabled by default in major browsers (Chrome, Safari, Firefox, Edge), assuming the proposal is accepted.
This issue being solved would still be nice for future Apple platforms with SDL3 - I guess something to look into in two years too.
The irony here is that the problem with pausing a script (mentioned in my previous comment), which is a major blocking issue, and recently I've been thinking that it would be easier to solve if script VM was run on a separate thread.
In simple words, the multithreaded solution makes script running a sort of a "coroutine", which halts execution before giving control back to the game update, and can continue from the same point later, when control is returned to it. I think that this could have been a much more elegant solution, than figuring out how to break out from the nested script calls.
But I obviously cannot plan this if there's a port that cannot use threads, as it will require a special separate handling anyway...
AGS has many little internal loops, I proppose we refactor to having one loop and a way to manage game states. This should clarify in which game state it's and make it easier to work with things like updating the screen and SDL events.
There are weird things going on, like game responsabilities in graphics drivers to support fading!
Additionally, having only one game loop would greatly improve performance of the Emscripten port (#1346). In this way, #1338 would only be a different state and it would be more obvious.
Syncing sound would be easier and we would not need things like https://github.com/adventuregamestudio/ags/commit/9634b6d3613f2b066998eaaf853945017f59fa01 .
List of all loops:
additional context