[1] And generally dynamic stuff like drag-n-drop, which is infinitely times simpler in any other architecture than in React.
[2] That is also true for business apps, but their animations are usually so simple you can simply use CSS.
[1] And generally dynamic stuff like drag-n-drop, which is infinitely times simpler in any other architecture than in React.
[2] That is also true for business apps, but their animations are usually so simple you can simply use CSS.
If you implement the deterministic update pattern to handle state synchronisation you can add "event" inside the logic that handles updates that pause the processing allowing your animations to be played. In JS, for example:
async function handleUpdate(update) {
if (update.type == "sell-items") {
this.player.inventory[update.itemId] -= 1;
await emitEvent("itemSold");
this.player.money += 10;
await emitEvent("moneyGain");
}
}
Server-side, "emitEvents" would be a no-op. Everything would resolve synchronously.Client-side, the UI can listen to those events to pause the updating of the game state to see the intermediary state of the game and play animations. When the animation is done, it can resolve the promise, resuming the game updating logic.
If an update arrives while an update is being handled, it can be queued so it can be played after the current update finishes.
Every player action could cause a cascade of updates, which would all be resolved “instantly” to the point no more cascaded updates were left to be processed.
While this is happening, any update that includes an animation pushes that to an “animation stack”, then the animations are played back one by one to show the player what happened. In this animation state most input in disabled and the game is effectively on hold until the animations complete (or are skipped by the player).
The “animations” were basically commands that the Model used to update the View, just with the option to apply them one by one over time. So the model is always up to date as fast as possible as possible, and the view just lags behind a bit and catches up.
https://github.com/sjrd/barrage/blob/main/src/main/scala/be/...
Here is a public gist with the `Result` data structure, as well a good portion of the file handling all the game mechanics, which should show it gets used. https://gist.github.com/sjrd/34fe234d1b6232cf42ffda5d23292d3...
Then you can 'execute' the stack within a command VM of some sorts, where instructions can move sprites around, play sounds, etc. You can have 'high level' instructions ("display the enemy's death") implemented as a combination of low level instructions ('reduce that health bar count until it goes to zero' -> 'change entity X's sprite to dead sprite' -> 'give player 200 gold as reward' -> 'play sound' -> 'change text in the text bar to something' -> ...)
It ended up working waaay better than what I was expecting, felt very easy to reason about, wasn't hard to maintain, and scratched that itch of implementing an interesting solution. :)
I ended up using ref's heavily to avoid stale closure and async re-render issues. Which basically amounts to circumventing React and interacting with the DOM directly.