taisei/src/stage.h
Andrei Alexeyev 4a50c85574
stage: fix pause menu related crashes
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.
2024-04-20 22:03:32 +02:00

88 lines
2.4 KiB
C

/*
* This software is licensed under the terms of the MIT License.
* See COPYING for further information.
* ---
* Copyright (c) 2011-2019, Lukas Weber <laochailan@web.de>.
* Copyright (c) 2012-2019, Andrei Alexeyev <akari@taisei-project.org>.
*/
#pragma once
#include "taisei.h"
#include "projectile.h"
#include "boss.h"
#include "difficulty.h"
#include "util/graphics.h"
#include "dialog.h"
#include "coroutine.h"
#include "dynarray.h"
#include "stageinfo.h"
#include "resource/resource.h"
typedef struct StageClearBonus {
uint64_t base;
uint64_t lives;
uint64_t voltage;
uint64_t graze;
uint64_t total;
struct {
uint64_t base;
real diff_multiplier;
uint64_t diff_bonus;
} all_clear;
} StageClearBonus;
void stage_enter(StageInfo *stage, ResourceGroup *rg, CallChain next);
void stage_finish(int gameover);
void stage_gameover(void);
void stage_start_bgm(const char *bgm);
typedef enum ClearHazardsFlags {
CLEAR_HAZARDS_BULLETS = (1 << 0),
CLEAR_HAZARDS_LASERS = (1 << 1),
CLEAR_HAZARDS_FORCE = (1 << 2),
CLEAR_HAZARDS_NOW = (1 << 3),
CLEAR_HAZARDS_SPAWN_VOLTAGE = (1 << 4),
CLEAR_HAZARDS_ALL = CLEAR_HAZARDS_BULLETS | CLEAR_HAZARDS_LASERS,
} ClearHazardsFlags;
void stage_clear_hazards(ClearHazardsFlags flags);
void stage_clear_hazards_at(cmplx origin, double radius, ClearHazardsFlags flags);
void stage_clear_hazards_in_ellipse(Ellipse e, ClearHazardsFlags flags);
void stage_clear_hazards_predicate(bool (*predicate)(EntityInterface *ent, void *arg), void *arg, ClearHazardsFlags flags);
void stage_set_voltage_thresholds(uint easy, uint normal, uint hard, uint lunatic);
bool stage_is_cleared(void);
void stage_unlock_bgm(const char *bgm);
void stage_begin_dialog(Dialog *d) attr_nonnull_all;
void stage_shake_view(float strength);
float stage_get_view_shake_strength(void);
void stage_load_quicksave(void);
CoSched *stage_get_sched(void);
bool stage_is_demo_mode(void);
#ifdef DEBUG
#define HAVE_SKIP_MODE
#endif
#ifdef HAVE_SKIP_MODE
void _stage_bookmark(const char *name);
#define STAGE_BOOKMARK(name) _stage_bookmark(#name)
DECLARE_EXTERN_TASK(stage_bookmark, { const char *name; });
#define STAGE_BOOKMARK_DELAYED(delay, name) INVOKE_TASK_DELAYED(delay, stage_bookmark, #name)
bool stage_is_skip_mode(void);
#else
#define STAGE_BOOKMARK(name) ((void)0)
#define STAGE_BOOKMARK_DELAYED(delay, name) ((void)0)
INLINE bool stage_is_skip_mode(void) { return false; }
#endif