leopard
leopard copied to clipboard
Match Scratch's script sequencing and execution semantics
Right now, Leopard's execution semantics seem to be rather simple--we step each script once per frame. This is close enough to what Scratch does in many cases, but in actuality it's a bit more complex. Scratch's logic is roughly:
- Once per frame, we step all the scripts.
- Then, we check if any redraws have been requested. Most blocks which cause a visual change on-stage (e.g. motion blocks, looks blocks) will request a redraw, but notably the "hide" block does not.
- If no redraws have been requested, we keep stepping all the scripts until either a redraw is requested, all the scripts are done, or we've run out of time budget (75% of the frametime).
Scratch also supports "turbo mode" semantics, which fit neatly into the above logic: if turbo mode is enabled, then we don't stop when a redraw is requested, only caring about the other two conditions (scripts are done, or time budget).
The main pitfall to watch out for when implementing this is busy-waiting. Right now, there's a lot of code that yield
s in a loop while waiting for a promise to resolve. This is fine if we only step threads once per frame, but in the case where no redraws have been requested or we're running in turbo mode, those loops will gladly spin until we've used our time budget, eating up a bunch of CPU time.
To avoid busy-waiting, I believe we need runtime support for yielding to promises--a script waiting for a promise is placed into a "sleeping" state where it will not be stepped until the promise resolves.