There were two distinct things going on here:
1. If we receive multiple buffered TE_GAME_PAUSE events in the same
frame, we'd process all of them and create a pause menu for each.
This could theoretically overflow the evloop stack and crash the
game.
2. The `stage_comain` task starts before scheduling the stage main
loop via `eventloop_enter`. It initializes systems that depend on
tasks, and then immediatelly enters its per-frame async loop,
finishing its first iteration before yielding back to `_stage_enter`
and thus allowing `eventloop_enter` to be finally called. If there is
a TE_GAME_PAUSE event in the queue at this point, it would be handled
right there, and a pause menu would be created before the stage main
loop is scheduled. This messes things up quite a bit, leaking a
"zombie" pause menu into the evloop stack. After the stage is
destroyed, the evloop would try to switch to the frame created for
this menu. The menu's draw function would then attempt to reference
free'd resources of the destroyed stage, crashing the game. This
crash has actually been observed and reported (thanks @0kalekale)
To fix #1, the stage now tracks its paused state and refuses to open a
pause menu if one already exists.
To fix #2, `stage_comain` now yields before starting its async loop, to
let the stage set up its main loop early.
Note that because the stage main loop runs all coroutine tasks before
incrementing the frame counter, `stage_comain`'s per-frame logic would
execute twice on frame 0. This is obviously wrong, but this behavior
must be preserved to maintain compatibility with v1.4 replays. For that
reason, the `stage_comain` loop now skips its first YIELD. This hack can
be removed once v1.4 compat is no longer a concern.
This allows having multiple task schedulers without having to switch the
global "target" scheduler for INVOKE_ macros.
An important side effect of this change is that it's not possible to use
the regular INVOKE_ macros from a non-coroutine context anymore. A
series of complimentary SCHED_INVOKE_ macros was added that behave
identically, but allow explicitly specifying a scheduler as the first
argument. The stage loop has been slightly refactored to accomodate this
behavior.
This replaces SDL_mixer with an internal streaming and mixing system,
relying only on basic SDL audio support. It's also a partial refactor of
the audio API, most notably BGM-related. The BGM metadata resource type
is gone, as well as the `.bgm` files. The metadata is now stored inside
the `.opus` files directly, using standard Opus tags.
This also introduces `float32`, `float64`, and `real` typedefs to be
used in place of `float` and `double` later. `real` is for game code and
other places where we don't particularly care about the precision and
format of the underlying type, and is currently defined to `double`.
`float32` and `float64` should replace `float` and `double` respectively
* implement player spellcard declarations on bomb
* stagedraw: move "bottom text" to a separate (higher) layer
* update boss spell declaration effect
* import afensorm's character portrait art
* improve dialog visuals
* acknowledge afensorm in credits and COPYING
* 'alphamap' functionality; effects for wriggle and youmu portraits
* update afens art
* add cirno alphamap
* dialog: draw active speaker above other other
* charselect: use r_draw_sprite
* Major refactoring of the main loop(s) and control flow (WIP)
run_at_fps() is gone 🦀
Instead of nested blocking event loops, there is now an eventloop API
that manages an explicit stack of scenes. This makes Taisei a lot more
portable to async environments where spinning a loop forever without
yielding control simply is not an option, and that is the entire point
of this change.
A prime example of such an environment is the Web (via emscripten).
Taisei was able to run there through a terrible hack: inserting
emscripten_sleep calls into the loop, which would yield to the browser.
This has several major drawbacks: first of all, every function that
could possibly call emscripten_sleep must be compiled into a special
kind of bytecode, which then has to be interpreted at runtime, *much*
slower than JITed WebAssembly. And that includes *everything* down the
call stack, too! For more information, see
https://emscripten.org/docs/porting/emterpreter.html
Even though that method worked well enough for experimenting, despite
suboptimal performance, there is another obvious drawback:
emscripten_sleep is implemented via setTimeout(), which can be very
imprecise and is generally not reliable for fluid animation. Browsers
actually have an API specifically for that use case:
window.requestAnimationFrame(), but Taisei's original blocking control
flow style is simply not compatible with it. Emscripten exposes this API
with its emscripten_set_main_loop(), which the eventloop backend now
uses on that platform.
Unfortunately, C is still C, with no fancy closures or coroutines.
With blocking calls into menu/scene loops gone, the control flow is
reimplemented via so-called (pun intended) "call chains". That is
basically an euphemism for callback hell. With manual memory management
and zero type-safety. Not that the menu system wasn't shitty enough
already. I'll just keep telling myself that this is all temporary and
will be replaced with scripts in v1.4.
* improve build system for emscripten + various fixes
* squish menu bugs
* improve emscripten event loop; disable EMULATE_FUNCTION_POINTER_CASTS
Note that stock freetype does not work without
EMULATE_FUNCTION_POINTER_CASTS; use a patched version from the
"emscripten" branch here:
https://github.com/taisei-project/freetype2/tree/emscripten
* Enable -Wcast-function-type
Calling functions through incompatible pointers is nasal demons and
doesn't work in WASM.
* webgl: workaround a crash on some browsers
* emscripten improvements:
* Persist state (config, progress, replays, ...) in local IndexDB
* Simpler HTML shell (temporary)
* Enable more optimizations
* fix build if validate_glsl=false
* emscripten: improve asset packaging, with local cache
Note that even though there are rules to build audio bundles, audio
does *not* work yet. It looks like SDL2_mixer can not work without
threads, which is a problem. Yet another reason to write an OpenAL
backend - emscripten supports that natively.
* emscripten: customize the html shell
* emscripten: force "show log" checkbox unchecked initially
* emscripten: remove quit shortcut from main menu (since there's no quit)
* emscripten: log area fixes
* emscripten/webgl: workaround for fullscreen viewport issue
* emscripten: implement frameskip
* emscripter: improve framerate limiter
* align List to at least 8 bytes (shut up warnings)
* fix non-emscripten builds
* improve fullscreen handling, mainly for emscripten
* Workaround to make audio work in chromium
emscripten-core/emscripten#6511
* emscripten: better vsync handling; enable vsync & disable fxaa by default
* Renderer: rename render targets to framebuffers
* Refactor framebuffer pair helper and some of the video API
* Remove hardcoded dimensions from draw_framebuffer_tex
* Make viewport a per-framebuffer property rather than a global one
* Handle config updates via the events system. React to viewport fg/bg quality change requests.
* items bounce off the viewport "walls"
* autocollect is interrupted on death
* all projectiles are continuously cleared during the death and
respawn process