refactor main.cpp
This commit is contained in:
718
src/main.cpp
718
src/main.cpp
@ -117,23 +117,121 @@ static bool isNewHighScore = false;
|
|||||||
static std::string playerName = "";
|
static std::string playerName = "";
|
||||||
static bool helpOverlayPausedGame = false;
|
static bool helpOverlayPausedGame = false;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
int main(int, char **)
|
struct AppRuntime {
|
||||||
|
SDL_Window* window = nullptr;
|
||||||
|
SDL_Renderer* renderer = nullptr;
|
||||||
|
|
||||||
|
AssetLoader assetLoader;
|
||||||
|
std::unique_ptr<LoadingManager> loadingManager;
|
||||||
|
std::unique_ptr<TextureLoader> textureLoader;
|
||||||
|
|
||||||
|
FontAtlas pixelFont;
|
||||||
|
FontAtlas font;
|
||||||
|
|
||||||
|
ScoreManager scores;
|
||||||
|
std::atomic<bool> scoresLoadComplete{false};
|
||||||
|
std::jthread scoreLoader;
|
||||||
|
std::jthread menuTrackLoader;
|
||||||
|
|
||||||
|
Starfield starfield;
|
||||||
|
Starfield3D starfield3D;
|
||||||
|
SpaceWarp spaceWarp;
|
||||||
|
SpaceWarpFlightMode warpFlightMode = SpaceWarpFlightMode::Forward;
|
||||||
|
bool warpAutoPilotEnabled = true;
|
||||||
|
|
||||||
|
LineEffect lineEffect;
|
||||||
|
|
||||||
|
SDL_Texture* logoTex = nullptr;
|
||||||
|
SDL_Texture* logoSmallTex = nullptr;
|
||||||
|
int logoSmallW = 0;
|
||||||
|
int logoSmallH = 0;
|
||||||
|
SDL_Texture* backgroundTex = nullptr;
|
||||||
|
SDL_Texture* mainScreenTex = nullptr;
|
||||||
|
int mainScreenW = 0;
|
||||||
|
int mainScreenH = 0;
|
||||||
|
|
||||||
|
SDL_Texture* blocksTex = nullptr;
|
||||||
|
SDL_Texture* scorePanelTex = nullptr;
|
||||||
|
SDL_Texture* statisticsPanelTex = nullptr;
|
||||||
|
SDL_Texture* nextPanelTex = nullptr;
|
||||||
|
|
||||||
|
BackgroundManager levelBackgrounds;
|
||||||
|
int startLevelSelection = 0;
|
||||||
|
|
||||||
|
// Music loading tracking
|
||||||
|
int totalTracks = 0;
|
||||||
|
int currentTrackLoading = 0;
|
||||||
|
bool musicLoaded = false;
|
||||||
|
bool musicStarted = false;
|
||||||
|
bool musicLoadingStarted = false;
|
||||||
|
|
||||||
|
// Loader control: execute incrementally on main thread to avoid SDL threading issues
|
||||||
|
std::atomic_bool loadingStarted{false};
|
||||||
|
std::atomic_bool loadingComplete{false};
|
||||||
|
std::atomic<size_t> loadingStep{0};
|
||||||
|
|
||||||
|
std::unique_ptr<Game> game;
|
||||||
|
std::vector<std::string> singleSounds;
|
||||||
|
std::vector<std::string> doubleSounds;
|
||||||
|
std::vector<std::string> tripleSounds;
|
||||||
|
std::vector<std::string> tetrisSounds;
|
||||||
|
bool suppressLineVoiceForLevelUp = false;
|
||||||
|
|
||||||
|
AppState state = AppState::Loading;
|
||||||
|
double loadingProgress = 0.0;
|
||||||
|
Uint64 loadStart = 0;
|
||||||
|
bool running = true;
|
||||||
|
bool isFullscreen = false;
|
||||||
|
bool leftHeld = false;
|
||||||
|
bool rightHeld = false;
|
||||||
|
double moveTimerMs = 0.0;
|
||||||
|
double DAS = 170.0;
|
||||||
|
double ARR = 40.0;
|
||||||
|
SDL_Rect logicalVP{0, 0, LOGICAL_W, LOGICAL_H};
|
||||||
|
float logicalScale = 1.f;
|
||||||
|
Uint64 lastMs = 0;
|
||||||
|
|
||||||
|
enum class MenuFadePhase { None, FadeOut, FadeIn };
|
||||||
|
MenuFadePhase menuFadePhase = MenuFadePhase::None;
|
||||||
|
double menuFadeClockMs = 0.0;
|
||||||
|
float menuFadeAlpha = 0.0f;
|
||||||
|
double MENU_PLAY_FADE_DURATION_MS = 450.0;
|
||||||
|
AppState menuFadeTarget = AppState::Menu;
|
||||||
|
bool menuPlayCountdownArmed = false;
|
||||||
|
bool gameplayCountdownActive = false;
|
||||||
|
double gameplayCountdownElapsed = 0.0;
|
||||||
|
int gameplayCountdownIndex = 0;
|
||||||
|
double GAMEPLAY_COUNTDOWN_STEP_MS = 400.0;
|
||||||
|
std::array<const char*, 4> GAMEPLAY_COUNTDOWN_LABELS = { "3", "2", "1", "START" };
|
||||||
|
double gameplayBackgroundClockMs = 0.0;
|
||||||
|
|
||||||
|
std::unique_ptr<StateManager> stateMgr;
|
||||||
|
StateContext ctx{};
|
||||||
|
std::unique_ptr<LoadingState> loadingState;
|
||||||
|
std::unique_ptr<MenuState> menuState;
|
||||||
|
std::unique_ptr<OptionsState> optionsState;
|
||||||
|
std::unique_ptr<LevelSelectorState> levelSelectorState;
|
||||||
|
std::unique_ptr<PlayingState> playingState;
|
||||||
|
};
|
||||||
|
|
||||||
|
static int initApp(AppRuntime& app)
|
||||||
{
|
{
|
||||||
// Initialize random seed for procedural effects
|
// Initialize random seed for procedural effects
|
||||||
srand(static_cast<unsigned int>(SDL_GetTicks()));
|
srand(static_cast<unsigned int>(SDL_GetTicks()));
|
||||||
|
|
||||||
// Load settings
|
// Load settings
|
||||||
Settings::instance().load();
|
Settings::instance().load();
|
||||||
|
|
||||||
// Sync static variables with settings
|
// Sync static variables with settings
|
||||||
musicEnabled = Settings::instance().isMusicEnabled();
|
musicEnabled = Settings::instance().isMusicEnabled();
|
||||||
playerName = Settings::instance().getPlayerName();
|
playerName = Settings::instance().getPlayerName();
|
||||||
if (playerName.empty()) playerName = "Player";
|
if (playerName.empty()) playerName = "Player";
|
||||||
|
|
||||||
// Apply sound settings to manager
|
// Apply sound settings to manager
|
||||||
SoundEffectManager::instance().setEnabled(Settings::instance().isSoundEnabled());
|
SoundEffectManager::instance().setEnabled(Settings::instance().isSoundEnabled());
|
||||||
|
|
||||||
int sdlInitRes = SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO);
|
int sdlInitRes = SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO);
|
||||||
if (sdlInitRes < 0)
|
if (sdlInitRes < 0)
|
||||||
{
|
{
|
||||||
@ -147,30 +245,31 @@ int main(int, char **)
|
|||||||
SDL_Quit();
|
SDL_Quit();
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
SDL_WindowFlags windowFlags = SDL_WINDOW_RESIZABLE;
|
SDL_WindowFlags windowFlags = SDL_WINDOW_RESIZABLE;
|
||||||
if (Settings::instance().isFullscreen()) {
|
if (Settings::instance().isFullscreen()) {
|
||||||
windowFlags |= SDL_WINDOW_FULLSCREEN;
|
windowFlags |= SDL_WINDOW_FULLSCREEN;
|
||||||
}
|
}
|
||||||
|
|
||||||
SDL_Window *window = SDL_CreateWindow("Tetris (SDL3)", LOGICAL_W, LOGICAL_H, windowFlags);
|
app.window = SDL_CreateWindow("Tetris (SDL3)", LOGICAL_W, LOGICAL_H, windowFlags);
|
||||||
if (!window)
|
if (!app.window)
|
||||||
{
|
{
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL_CreateWindow failed: %s", SDL_GetError());
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL_CreateWindow failed: %s", SDL_GetError());
|
||||||
TTF_Quit();
|
TTF_Quit();
|
||||||
SDL_Quit();
|
SDL_Quit();
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
SDL_Renderer *renderer = SDL_CreateRenderer(window, nullptr);
|
app.renderer = SDL_CreateRenderer(app.window, nullptr);
|
||||||
if (!renderer)
|
if (!app.renderer)
|
||||||
{
|
{
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL_CreateRenderer failed: %s", SDL_GetError());
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL_CreateRenderer failed: %s", SDL_GetError());
|
||||||
SDL_DestroyWindow(window);
|
SDL_DestroyWindow(app.window);
|
||||||
|
app.window = nullptr;
|
||||||
TTF_Quit();
|
TTF_Quit();
|
||||||
SDL_Quit();
|
SDL_Quit();
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
SDL_SetRenderVSync(renderer, 1);
|
SDL_SetRenderVSync(app.renderer, 1);
|
||||||
|
|
||||||
if (const char* basePathRaw = SDL_GetBasePath()) {
|
if (const char* basePathRaw = SDL_GetBasePath()) {
|
||||||
std::filesystem::path exeDir(basePathRaw);
|
std::filesystem::path exeDir(basePathRaw);
|
||||||
@ -192,100 +291,54 @@ int main(int, char **)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Asset loader (creates SDL_Textures on the main thread)
|
// Asset loader (creates SDL_Textures on the main thread)
|
||||||
AssetLoader assetLoader;
|
app.assetLoader.init(app.renderer);
|
||||||
assetLoader.init(renderer);
|
app.loadingManager = std::make_unique<LoadingManager>(&app.assetLoader);
|
||||||
LoadingManager loadingManager(&assetLoader);
|
|
||||||
|
|
||||||
// Legacy image loader (used only as a fallback when AssetLoader misses)
|
// Legacy image loader (used only as a fallback when AssetLoader misses)
|
||||||
TextureLoader textureLoader(g_loadedTasks, g_currentLoadingFile, g_currentLoadingMutex, g_assetLoadErrors, g_assetLoadErrorsMutex);
|
app.textureLoader = std::make_unique<TextureLoader>(
|
||||||
|
g_loadedTasks,
|
||||||
|
g_currentLoadingFile,
|
||||||
|
g_currentLoadingMutex,
|
||||||
|
g_assetLoadErrors,
|
||||||
|
g_assetLoadErrorsMutex);
|
||||||
|
|
||||||
// Font and UI asset handles (actual loading deferred until Loading state)
|
|
||||||
FontAtlas pixelFont;
|
|
||||||
FontAtlas font;
|
|
||||||
|
|
||||||
ScoreManager scores;
|
|
||||||
std::atomic<bool> scoresLoadComplete{false};
|
|
||||||
// Load scores asynchronously but keep the worker alive until shutdown to avoid lifetime issues
|
// Load scores asynchronously but keep the worker alive until shutdown to avoid lifetime issues
|
||||||
std::jthread scoreLoader([&scores, &scoresLoadComplete]() {
|
app.scoreLoader = std::jthread([&app]() {
|
||||||
scores.load();
|
app.scores.load();
|
||||||
scoresLoadComplete.store(true, std::memory_order_release);
|
app.scoresLoadComplete.store(true, std::memory_order_release);
|
||||||
});
|
});
|
||||||
std::jthread menuTrackLoader;
|
|
||||||
Starfield starfield;
|
app.starfield.init(200, LOGICAL_W, LOGICAL_H);
|
||||||
starfield.init(200, LOGICAL_W, LOGICAL_H);
|
app.starfield3D.init(LOGICAL_W, LOGICAL_H, 200);
|
||||||
Starfield3D starfield3D;
|
app.spaceWarp.init(LOGICAL_W, LOGICAL_H, 420);
|
||||||
starfield3D.init(LOGICAL_W, LOGICAL_H, 200);
|
app.spaceWarp.setFlightMode(app.warpFlightMode);
|
||||||
SpaceWarp spaceWarp;
|
app.warpAutoPilotEnabled = true;
|
||||||
spaceWarp.init(LOGICAL_W, LOGICAL_H, 420);
|
app.spaceWarp.setAutoPilotEnabled(true);
|
||||||
SpaceWarpFlightMode warpFlightMode = SpaceWarpFlightMode::Forward;
|
|
||||||
spaceWarp.setFlightMode(warpFlightMode);
|
|
||||||
bool warpAutoPilotEnabled = true;
|
|
||||||
spaceWarp.setAutoPilotEnabled(true);
|
|
||||||
|
|
||||||
// Initialize line clearing effects
|
// Initialize line clearing effects
|
||||||
LineEffect lineEffect;
|
app.lineEffect.init(app.renderer);
|
||||||
lineEffect.init(renderer);
|
|
||||||
|
|
||||||
// Asset handles (textures initialized by loader thread when Loading state starts)
|
|
||||||
SDL_Texture* logoTex = nullptr;
|
|
||||||
int logoSmallW = 0, logoSmallH = 0;
|
|
||||||
SDL_Texture* logoSmallTex = nullptr;
|
|
||||||
SDL_Texture* backgroundTex = nullptr; // No static background texture is used
|
|
||||||
int mainScreenW = 0, mainScreenH = 0;
|
|
||||||
SDL_Texture* mainScreenTex = nullptr;
|
|
||||||
|
|
||||||
// Level background manager (moved to BackgroundManager)
|
app.game = std::make_unique<Game>(app.startLevelSelection);
|
||||||
BackgroundManager levelBackgrounds;
|
|
||||||
|
|
||||||
// Default start level selection: 0 (declare here so it's in scope for all handlers)
|
|
||||||
int startLevelSelection = 0;
|
|
||||||
|
|
||||||
SDL_Texture* blocksTex = nullptr;
|
|
||||||
SDL_Texture* scorePanelTex = nullptr;
|
|
||||||
SDL_Texture* statisticsPanelTex = nullptr;
|
|
||||||
SDL_Texture* nextPanelTex = nullptr;
|
|
||||||
|
|
||||||
// Music loading tracking
|
|
||||||
int totalTracks = 0;
|
|
||||||
int currentTrackLoading = 0;
|
|
||||||
bool musicLoaded = false;
|
|
||||||
bool musicStarted = false;
|
|
||||||
bool musicLoadingStarted = false;
|
|
||||||
|
|
||||||
// Loader control: execute incrementally on main thread to avoid SDL threading issues
|
|
||||||
std::atomic_bool g_loadingStarted{false};
|
|
||||||
std::atomic_bool g_loadingComplete{false};
|
|
||||||
std::atomic<size_t> g_loadingStep{0};
|
|
||||||
|
|
||||||
// Loading is now handled by AssetLoader + LoadingManager.
|
|
||||||
// Old incremental lambda removed; use LoadingManager to queue texture loads and
|
|
||||||
// perform a single step per frame. Non-texture initialization (fonts, SFX)
|
|
||||||
// is performed on the first loading frame below when the loader is started.
|
|
||||||
|
|
||||||
Game game(startLevelSelection);
|
|
||||||
// Apply global gravity speed multiplier from config
|
// Apply global gravity speed multiplier from config
|
||||||
game.setGravityGlobalMultiplier(Config::Gameplay::GRAVITY_SPEED_MULTIPLIER);
|
app.game->setGravityGlobalMultiplier(Config::Gameplay::GRAVITY_SPEED_MULTIPLIER);
|
||||||
game.reset(startLevelSelection);
|
app.game->reset(app.startLevelSelection);
|
||||||
|
|
||||||
// Sound effects system already initialized; audio loads are handled by loader thread
|
|
||||||
|
|
||||||
// Define voice line banks for gameplay callbacks
|
|
||||||
std::vector<std::string> singleSounds = {"well_played", "smooth_clear", "great_move"};
|
|
||||||
std::vector<std::string> doubleSounds = {"nice_combo", "you_fire", "keep_that_ryhtm"};
|
|
||||||
std::vector<std::string> tripleSounds = {"impressive", "triple_strike"};
|
|
||||||
std::vector<std::string> tetrisSounds = {"amazing", "you_re_unstoppable", "boom_tetris", "wonderful"};
|
|
||||||
|
|
||||||
bool suppressLineVoiceForLevelUp = false;
|
|
||||||
|
|
||||||
auto playVoiceCue = [&](int linesCleared) {
|
// Define voice line banks for gameplay callbacks
|
||||||
|
app.singleSounds = {"well_played", "smooth_clear", "great_move"};
|
||||||
|
app.doubleSounds = {"nice_combo", "you_fire", "keep_that_ryhtm"};
|
||||||
|
app.tripleSounds = {"impressive", "triple_strike"};
|
||||||
|
app.tetrisSounds = {"amazing", "you_re_unstoppable", "boom_tetris", "wonderful"};
|
||||||
|
app.suppressLineVoiceForLevelUp = false;
|
||||||
|
|
||||||
|
auto playVoiceCue = [&app](int linesCleared) {
|
||||||
const std::vector<std::string>* bank = nullptr;
|
const std::vector<std::string>* bank = nullptr;
|
||||||
switch (linesCleared) {
|
switch (linesCleared) {
|
||||||
case 1: bank = &singleSounds; break;
|
case 1: bank = &app.singleSounds; break;
|
||||||
case 2: bank = &doubleSounds; break;
|
case 2: bank = &app.doubleSounds; break;
|
||||||
case 3: bank = &tripleSounds; break;
|
case 3: bank = &app.tripleSounds; break;
|
||||||
default:
|
default:
|
||||||
if (linesCleared >= 4) {
|
if (linesCleared >= 4) {
|
||||||
bank = &tetrisSounds;
|
bank = &app.tetrisSounds;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -295,7 +348,7 @@ int main(int, char **)
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Set up sound effect callbacks
|
// Set up sound effect callbacks
|
||||||
game.setSoundCallback([&, playVoiceCue](int linesCleared) {
|
app.game->setSoundCallback([&app, playVoiceCue](int linesCleared) {
|
||||||
if (linesCleared <= 0) {
|
if (linesCleared <= 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -304,92 +357,257 @@ int main(int, char **)
|
|||||||
SoundEffectManager::instance().playSound("clear_line", 1.0f);
|
SoundEffectManager::instance().playSound("clear_line", 1.0f);
|
||||||
|
|
||||||
// Layer a voiced callout based on the number of cleared lines
|
// Layer a voiced callout based on the number of cleared lines
|
||||||
if (!suppressLineVoiceForLevelUp) {
|
if (!app.suppressLineVoiceForLevelUp) {
|
||||||
playVoiceCue(linesCleared);
|
playVoiceCue(linesCleared);
|
||||||
}
|
}
|
||||||
suppressLineVoiceForLevelUp = false;
|
app.suppressLineVoiceForLevelUp = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
game.setLevelUpCallback([&](int newLevel) {
|
app.game->setLevelUpCallback([&app](int newLevel) {
|
||||||
SoundEffectManager::instance().playSound("new_level", 1.0f);
|
SoundEffectManager::instance().playSound("new_level", 1.0f);
|
||||||
SoundEffectManager::instance().playSound("lets_go", 1.0f); // Existing voice line
|
SoundEffectManager::instance().playSound("lets_go", 1.0f); // Existing voice line
|
||||||
suppressLineVoiceForLevelUp = true;
|
app.suppressLineVoiceForLevelUp = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
AppState state = AppState::Loading;
|
|
||||||
double loadingProgress = 0.0;
|
|
||||||
Uint64 loadStart = SDL_GetTicks();
|
|
||||||
bool running = true;
|
|
||||||
bool isFullscreen = Settings::instance().isFullscreen();
|
|
||||||
bool leftHeld = false, rightHeld = false;
|
|
||||||
double moveTimerMs = 0;
|
|
||||||
const double DAS = 170.0, ARR = 40.0;
|
|
||||||
SDL_Rect logicalVP{0, 0, LOGICAL_W, LOGICAL_H};
|
|
||||||
float logicalScale = 1.f;
|
|
||||||
Uint64 lastMs = SDL_GetPerformanceCounter();
|
|
||||||
|
|
||||||
enum class MenuFadePhase { None, FadeOut, FadeIn };
|
app.state = AppState::Loading;
|
||||||
MenuFadePhase menuFadePhase = MenuFadePhase::None;
|
app.loadingProgress = 0.0;
|
||||||
double menuFadeClockMs = 0.0;
|
app.loadStart = SDL_GetTicks();
|
||||||
float menuFadeAlpha = 0.0f;
|
app.running = true;
|
||||||
const double MENU_PLAY_FADE_DURATION_MS = 450.0;
|
app.isFullscreen = Settings::instance().isFullscreen();
|
||||||
AppState menuFadeTarget = AppState::Menu;
|
app.leftHeld = false;
|
||||||
bool menuPlayCountdownArmed = false;
|
app.rightHeld = false;
|
||||||
bool gameplayCountdownActive = false;
|
app.moveTimerMs = 0;
|
||||||
double gameplayCountdownElapsed = 0.0;
|
app.DAS = 170.0;
|
||||||
int gameplayCountdownIndex = 0;
|
app.ARR = 40.0;
|
||||||
const double GAMEPLAY_COUNTDOWN_STEP_MS = 400.0;
|
app.logicalVP = SDL_Rect{0, 0, LOGICAL_W, LOGICAL_H};
|
||||||
const std::array<const char*, 4> GAMEPLAY_COUNTDOWN_LABELS = { "3", "2", "1", "START" };
|
app.logicalScale = 1.f;
|
||||||
double gameplayBackgroundClockMs = 0.0;
|
app.lastMs = SDL_GetPerformanceCounter();
|
||||||
|
|
||||||
|
app.menuFadePhase = AppRuntime::MenuFadePhase::None;
|
||||||
|
app.menuFadeClockMs = 0.0;
|
||||||
|
app.menuFadeAlpha = 0.0f;
|
||||||
|
app.MENU_PLAY_FADE_DURATION_MS = 450.0;
|
||||||
|
app.menuFadeTarget = AppState::Menu;
|
||||||
|
app.menuPlayCountdownArmed = false;
|
||||||
|
app.gameplayCountdownActive = false;
|
||||||
|
app.gameplayCountdownElapsed = 0.0;
|
||||||
|
app.gameplayCountdownIndex = 0;
|
||||||
|
app.GAMEPLAY_COUNTDOWN_STEP_MS = 400.0;
|
||||||
|
app.GAMEPLAY_COUNTDOWN_LABELS = { "3", "2", "1", "START" };
|
||||||
|
app.gameplayBackgroundClockMs = 0.0;
|
||||||
|
|
||||||
// Instantiate state manager
|
// Instantiate state manager
|
||||||
StateManager stateMgr(state);
|
app.stateMgr = std::make_unique<StateManager>(app.state);
|
||||||
|
|
||||||
// Prepare shared context for states
|
// Prepare shared context for states
|
||||||
StateContext ctx{};
|
app.ctx = StateContext{};
|
||||||
// Allow states to access the state manager for transitions
|
app.ctx.stateManager = app.stateMgr.get();
|
||||||
ctx.stateManager = &stateMgr;
|
app.ctx.game = app.game.get();
|
||||||
ctx.game = &game;
|
app.ctx.scores = nullptr; // populated once async load finishes
|
||||||
ctx.scores = nullptr; // populated once async load finishes
|
app.ctx.starfield = &app.starfield;
|
||||||
ctx.starfield = &starfield;
|
app.ctx.starfield3D = &app.starfield3D;
|
||||||
ctx.starfield3D = &starfield3D;
|
app.ctx.font = &app.font;
|
||||||
ctx.font = &font;
|
app.ctx.pixelFont = &app.pixelFont;
|
||||||
ctx.pixelFont = &pixelFont;
|
app.ctx.lineEffect = &app.lineEffect;
|
||||||
ctx.lineEffect = &lineEffect;
|
app.ctx.logoTex = app.logoTex;
|
||||||
ctx.logoTex = logoTex;
|
app.ctx.logoSmallTex = app.logoSmallTex;
|
||||||
ctx.logoSmallTex = logoSmallTex;
|
app.ctx.logoSmallW = app.logoSmallW;
|
||||||
ctx.logoSmallW = logoSmallW;
|
app.ctx.logoSmallH = app.logoSmallH;
|
||||||
ctx.logoSmallH = logoSmallH;
|
app.ctx.backgroundTex = nullptr;
|
||||||
ctx.backgroundTex = nullptr;
|
app.ctx.blocksTex = app.blocksTex;
|
||||||
ctx.blocksTex = blocksTex;
|
app.ctx.scorePanelTex = app.scorePanelTex;
|
||||||
ctx.scorePanelTex = scorePanelTex;
|
app.ctx.statisticsPanelTex = app.statisticsPanelTex;
|
||||||
ctx.statisticsPanelTex = statisticsPanelTex;
|
app.ctx.nextPanelTex = app.nextPanelTex;
|
||||||
ctx.nextPanelTex = nextPanelTex;
|
app.ctx.mainScreenTex = app.mainScreenTex;
|
||||||
ctx.mainScreenTex = mainScreenTex;
|
app.ctx.mainScreenW = app.mainScreenW;
|
||||||
ctx.mainScreenW = mainScreenW;
|
app.ctx.mainScreenH = app.mainScreenH;
|
||||||
ctx.mainScreenH = mainScreenH;
|
app.ctx.musicEnabled = &musicEnabled;
|
||||||
ctx.musicEnabled = &musicEnabled;
|
app.ctx.startLevelSelection = &app.startLevelSelection;
|
||||||
ctx.startLevelSelection = &startLevelSelection;
|
app.ctx.hoveredButton = &hoveredButton;
|
||||||
ctx.hoveredButton = &hoveredButton;
|
app.ctx.showSettingsPopup = &showSettingsPopup;
|
||||||
ctx.showSettingsPopup = &showSettingsPopup;
|
app.ctx.showHelpOverlay = &showHelpOverlay;
|
||||||
ctx.showHelpOverlay = &showHelpOverlay;
|
app.ctx.showExitConfirmPopup = &showExitConfirmPopup;
|
||||||
ctx.showExitConfirmPopup = &showExitConfirmPopup;
|
app.ctx.exitPopupSelectedButton = &exitPopupSelectedButton;
|
||||||
ctx.exitPopupSelectedButton = &exitPopupSelectedButton;
|
app.ctx.gameplayCountdownActive = &app.gameplayCountdownActive;
|
||||||
ctx.gameplayCountdownActive = &gameplayCountdownActive;
|
app.ctx.menuPlayCountdownArmed = &app.menuPlayCountdownArmed;
|
||||||
ctx.menuPlayCountdownArmed = &menuPlayCountdownArmed;
|
app.ctx.playerName = &playerName;
|
||||||
ctx.playerName = &playerName;
|
app.ctx.fullscreenFlag = &app.isFullscreen;
|
||||||
ctx.fullscreenFlag = &isFullscreen;
|
app.ctx.applyFullscreen = [&app](bool enable) {
|
||||||
ctx.applyFullscreen = [window, &isFullscreen](bool enable) {
|
SDL_SetWindowFullscreen(app.window, enable ? SDL_WINDOW_FULLSCREEN : 0);
|
||||||
SDL_SetWindowFullscreen(window, enable ? SDL_WINDOW_FULLSCREEN : 0);
|
app.isFullscreen = enable;
|
||||||
isFullscreen = enable;
|
|
||||||
};
|
};
|
||||||
ctx.queryFullscreen = [window]() -> bool {
|
app.ctx.queryFullscreen = [&app]() -> bool {
|
||||||
return (SDL_GetWindowFlags(window) & SDL_WINDOW_FULLSCREEN) != 0;
|
return (SDL_GetWindowFlags(app.window) & SDL_WINDOW_FULLSCREEN) != 0;
|
||||||
};
|
};
|
||||||
ctx.requestQuit = [&running]() {
|
app.ctx.requestQuit = [&app]() {
|
||||||
running = false;
|
app.running = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
auto beginStateFade = [&app](AppState targetState, bool armGameplayCountdown) {
|
||||||
|
if (!app.ctx.stateManager) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (app.state == targetState) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (app.menuFadePhase != AppRuntime::MenuFadePhase::None) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
app.menuFadePhase = AppRuntime::MenuFadePhase::FadeOut;
|
||||||
|
app.menuFadeClockMs = 0.0;
|
||||||
|
app.menuFadeAlpha = 0.0f;
|
||||||
|
app.menuFadeTarget = targetState;
|
||||||
|
app.menuPlayCountdownArmed = armGameplayCountdown;
|
||||||
|
app.gameplayCountdownActive = false;
|
||||||
|
app.gameplayCountdownIndex = 0;
|
||||||
|
app.gameplayCountdownElapsed = 0.0;
|
||||||
|
|
||||||
|
if (!armGameplayCountdown) {
|
||||||
|
if (app.game) {
|
||||||
|
app.game->setPaused(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
auto startMenuPlayTransition = [&app, beginStateFade]() {
|
||||||
|
if (!app.ctx.stateManager) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (app.state != AppState::Menu) {
|
||||||
|
app.state = AppState::Playing;
|
||||||
|
app.ctx.stateManager->setState(app.state);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
beginStateFade(AppState::Playing, true);
|
||||||
|
};
|
||||||
|
app.ctx.startPlayTransition = startMenuPlayTransition;
|
||||||
|
|
||||||
|
auto requestStateFade = [&app, startMenuPlayTransition, beginStateFade](AppState targetState) {
|
||||||
|
if (!app.ctx.stateManager) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (targetState == AppState::Playing) {
|
||||||
|
startMenuPlayTransition();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
beginStateFade(targetState, false);
|
||||||
|
};
|
||||||
|
app.ctx.requestFadeTransition = requestStateFade;
|
||||||
|
|
||||||
|
// Instantiate state objects
|
||||||
|
app.loadingState = std::make_unique<LoadingState>(app.ctx);
|
||||||
|
app.menuState = std::make_unique<MenuState>(app.ctx);
|
||||||
|
app.optionsState = std::make_unique<OptionsState>(app.ctx);
|
||||||
|
app.levelSelectorState = std::make_unique<LevelSelectorState>(app.ctx);
|
||||||
|
app.playingState = std::make_unique<PlayingState>(app.ctx);
|
||||||
|
|
||||||
|
// Register handlers and lifecycle hooks
|
||||||
|
app.stateMgr->registerHandler(AppState::Loading, [&app](const SDL_Event& e){ app.loadingState->handleEvent(e); });
|
||||||
|
app.stateMgr->registerOnEnter(AppState::Loading, [&app](){ app.loadingState->onEnter(); app.loadingStarted.store(true); });
|
||||||
|
app.stateMgr->registerOnExit(AppState::Loading, [&app](){ app.loadingState->onExit(); });
|
||||||
|
|
||||||
|
app.stateMgr->registerHandler(AppState::Menu, [&app](const SDL_Event& e){ app.menuState->handleEvent(e); });
|
||||||
|
app.stateMgr->registerOnEnter(AppState::Menu, [&app](){ app.menuState->onEnter(); });
|
||||||
|
app.stateMgr->registerOnExit(AppState::Menu, [&app](){ app.menuState->onExit(); });
|
||||||
|
|
||||||
|
app.stateMgr->registerHandler(AppState::Options, [&app](const SDL_Event& e){ app.optionsState->handleEvent(e); });
|
||||||
|
app.stateMgr->registerOnEnter(AppState::Options, [&app](){ app.optionsState->onEnter(); });
|
||||||
|
app.stateMgr->registerOnExit(AppState::Options, [&app](){ app.optionsState->onExit(); });
|
||||||
|
|
||||||
|
app.stateMgr->registerHandler(AppState::LevelSelector, [&app](const SDL_Event& e){ app.levelSelectorState->handleEvent(e); });
|
||||||
|
app.stateMgr->registerOnEnter(AppState::LevelSelector, [&app](){ app.levelSelectorState->onEnter(); });
|
||||||
|
app.stateMgr->registerOnExit(AppState::LevelSelector, [&app](){ app.levelSelectorState->onExit(); });
|
||||||
|
|
||||||
|
app.stateMgr->registerHandler(AppState::Playing, [&app](const SDL_Event& e){ app.playingState->handleEvent(e); });
|
||||||
|
app.stateMgr->registerOnEnter(AppState::Playing, [&app](){ app.playingState->onEnter(); });
|
||||||
|
app.stateMgr->registerOnExit(AppState::Playing, [&app](){ app.playingState->onExit(); });
|
||||||
|
|
||||||
|
// Manually trigger the initial Loading state's onEnter
|
||||||
|
app.loadingState->onEnter();
|
||||||
|
app.loadingStarted.store(true);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void runGameLoop(AppRuntime& app)
|
||||||
|
{
|
||||||
|
using MenuFadePhase = AppRuntime::MenuFadePhase;
|
||||||
|
|
||||||
|
SDL_Window* window = app.window;
|
||||||
|
SDL_Renderer* renderer = app.renderer;
|
||||||
|
AssetLoader& assetLoader = app.assetLoader;
|
||||||
|
LoadingManager& loadingManager = *app.loadingManager;
|
||||||
|
TextureLoader& textureLoader = *app.textureLoader;
|
||||||
|
FontAtlas& pixelFont = app.pixelFont;
|
||||||
|
FontAtlas& font = app.font;
|
||||||
|
ScoreManager& scores = app.scores;
|
||||||
|
std::atomic<bool>& scoresLoadComplete = app.scoresLoadComplete;
|
||||||
|
std::jthread& scoreLoader = app.scoreLoader;
|
||||||
|
std::jthread& menuTrackLoader = app.menuTrackLoader;
|
||||||
|
Starfield& starfield = app.starfield;
|
||||||
|
Starfield3D& starfield3D = app.starfield3D;
|
||||||
|
SpaceWarp& spaceWarp = app.spaceWarp;
|
||||||
|
SpaceWarpFlightMode& warpFlightMode = app.warpFlightMode;
|
||||||
|
bool& warpAutoPilotEnabled = app.warpAutoPilotEnabled;
|
||||||
|
LineEffect& lineEffect = app.lineEffect;
|
||||||
|
SDL_Texture*& logoTex = app.logoTex;
|
||||||
|
int& logoSmallW = app.logoSmallW;
|
||||||
|
int& logoSmallH = app.logoSmallH;
|
||||||
|
SDL_Texture*& logoSmallTex = app.logoSmallTex;
|
||||||
|
SDL_Texture*& backgroundTex = app.backgroundTex;
|
||||||
|
int& mainScreenW = app.mainScreenW;
|
||||||
|
int& mainScreenH = app.mainScreenH;
|
||||||
|
SDL_Texture*& mainScreenTex = app.mainScreenTex;
|
||||||
|
BackgroundManager& levelBackgrounds = app.levelBackgrounds;
|
||||||
|
int& startLevelSelection = app.startLevelSelection;
|
||||||
|
SDL_Texture*& blocksTex = app.blocksTex;
|
||||||
|
SDL_Texture*& scorePanelTex = app.scorePanelTex;
|
||||||
|
SDL_Texture*& statisticsPanelTex = app.statisticsPanelTex;
|
||||||
|
SDL_Texture*& nextPanelTex = app.nextPanelTex;
|
||||||
|
int& totalTracks = app.totalTracks;
|
||||||
|
int& currentTrackLoading = app.currentTrackLoading;
|
||||||
|
bool& musicLoaded = app.musicLoaded;
|
||||||
|
bool& musicStarted = app.musicStarted;
|
||||||
|
bool& musicLoadingStarted = app.musicLoadingStarted;
|
||||||
|
std::atomic_bool& g_loadingStarted = app.loadingStarted;
|
||||||
|
std::atomic_bool& g_loadingComplete = app.loadingComplete;
|
||||||
|
std::atomic<size_t>& g_loadingStep = app.loadingStep;
|
||||||
|
Game& game = *app.game;
|
||||||
|
bool& suppressLineVoiceForLevelUp = app.suppressLineVoiceForLevelUp;
|
||||||
|
AppState& state = app.state;
|
||||||
|
double& loadingProgress = app.loadingProgress;
|
||||||
|
Uint64& loadStart = app.loadStart;
|
||||||
|
bool& running = app.running;
|
||||||
|
bool& isFullscreen = app.isFullscreen;
|
||||||
|
bool& leftHeld = app.leftHeld;
|
||||||
|
bool& rightHeld = app.rightHeld;
|
||||||
|
double& moveTimerMs = app.moveTimerMs;
|
||||||
|
const double DAS = app.DAS;
|
||||||
|
const double ARR = app.ARR;
|
||||||
|
SDL_Rect& logicalVP = app.logicalVP;
|
||||||
|
float& logicalScale = app.logicalScale;
|
||||||
|
Uint64& lastMs = app.lastMs;
|
||||||
|
AppRuntime::MenuFadePhase& menuFadePhase = app.menuFadePhase;
|
||||||
|
double& menuFadeClockMs = app.menuFadeClockMs;
|
||||||
|
float& menuFadeAlpha = app.menuFadeAlpha;
|
||||||
|
const double MENU_PLAY_FADE_DURATION_MS = app.MENU_PLAY_FADE_DURATION_MS;
|
||||||
|
AppState& menuFadeTarget = app.menuFadeTarget;
|
||||||
|
bool& menuPlayCountdownArmed = app.menuPlayCountdownArmed;
|
||||||
|
bool& gameplayCountdownActive = app.gameplayCountdownActive;
|
||||||
|
double& gameplayCountdownElapsed = app.gameplayCountdownElapsed;
|
||||||
|
int& gameplayCountdownIndex = app.gameplayCountdownIndex;
|
||||||
|
const double GAMEPLAY_COUNTDOWN_STEP_MS = app.GAMEPLAY_COUNTDOWN_STEP_MS;
|
||||||
|
const std::array<const char*, 4>& GAMEPLAY_COUNTDOWN_LABELS = app.GAMEPLAY_COUNTDOWN_LABELS;
|
||||||
|
double& gameplayBackgroundClockMs = app.gameplayBackgroundClockMs;
|
||||||
|
StateManager& stateMgr = *app.stateMgr;
|
||||||
|
StateContext& ctx = app.ctx;
|
||||||
|
auto& loadingState = app.loadingState;
|
||||||
|
auto& menuState = app.menuState;
|
||||||
|
auto& optionsState = app.optionsState;
|
||||||
|
auto& levelSelectorState = app.levelSelectorState;
|
||||||
|
auto& playingState = app.playingState;
|
||||||
|
|
||||||
auto ensureScoresLoaded = [&]() {
|
auto ensureScoresLoaded = [&]() {
|
||||||
if (scoreLoader.joinable()) {
|
if (scoreLoader.joinable()) {
|
||||||
scoreLoader.join();
|
scoreLoader.join();
|
||||||
@ -399,91 +617,17 @@ int main(int, char **)
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
auto beginStateFade = [&](AppState targetState, bool armGameplayCountdown) {
|
|
||||||
if (!ctx.stateManager) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (state == targetState) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (menuFadePhase != MenuFadePhase::None) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
menuFadePhase = MenuFadePhase::FadeOut;
|
|
||||||
menuFadeClockMs = 0.0;
|
|
||||||
menuFadeAlpha = 0.0f;
|
|
||||||
menuFadeTarget = targetState;
|
|
||||||
menuPlayCountdownArmed = armGameplayCountdown;
|
|
||||||
gameplayCountdownActive = false;
|
|
||||||
gameplayCountdownIndex = 0;
|
|
||||||
gameplayCountdownElapsed = 0.0;
|
|
||||||
|
|
||||||
if (!armGameplayCountdown) {
|
|
||||||
game.setPaused(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
auto startMenuPlayTransition = [&]() {
|
auto startMenuPlayTransition = [&]() {
|
||||||
if (!ctx.stateManager) {
|
if (ctx.startPlayTransition) {
|
||||||
return;
|
ctx.startPlayTransition();
|
||||||
}
|
}
|
||||||
if (state != AppState::Menu) {
|
|
||||||
state = AppState::Playing;
|
|
||||||
ctx.stateManager->setState(state);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
beginStateFade(AppState::Playing, true);
|
|
||||||
};
|
};
|
||||||
ctx.startPlayTransition = startMenuPlayTransition;
|
|
||||||
|
|
||||||
auto requestStateFade = [&](AppState targetState) {
|
auto requestStateFade = [&](AppState targetState) {
|
||||||
if (!ctx.stateManager) {
|
if (ctx.requestFadeTransition) {
|
||||||
return;
|
ctx.requestFadeTransition(targetState);
|
||||||
}
|
}
|
||||||
if (targetState == AppState::Playing) {
|
|
||||||
startMenuPlayTransition();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
beginStateFade(targetState, false);
|
|
||||||
};
|
};
|
||||||
ctx.requestFadeTransition = requestStateFade;
|
|
||||||
|
|
||||||
// Instantiate state objects
|
|
||||||
auto loadingState = std::make_unique<LoadingState>(ctx);
|
|
||||||
auto menuState = std::make_unique<MenuState>(ctx);
|
|
||||||
auto optionsState = std::make_unique<OptionsState>(ctx);
|
|
||||||
auto levelSelectorState = std::make_unique<LevelSelectorState>(ctx);
|
|
||||||
auto playingState = std::make_unique<PlayingState>(ctx);
|
|
||||||
|
|
||||||
// Register handlers and lifecycle hooks
|
|
||||||
stateMgr.registerHandler(AppState::Loading, [&](const SDL_Event& e){ loadingState->handleEvent(e); });
|
|
||||||
stateMgr.registerOnEnter(AppState::Loading, [&](){ loadingState->onEnter(); g_loadingStarted.store(true); });
|
|
||||||
stateMgr.registerOnExit(AppState::Loading, [&](){ loadingState->onExit(); });
|
|
||||||
|
|
||||||
stateMgr.registerHandler(AppState::Menu, [&](const SDL_Event& e){ menuState->handleEvent(e); });
|
|
||||||
stateMgr.registerOnEnter(AppState::Menu, [&](){ menuState->onEnter(); });
|
|
||||||
stateMgr.registerOnExit(AppState::Menu, [&](){ menuState->onExit(); });
|
|
||||||
|
|
||||||
stateMgr.registerHandler(AppState::Options, [&](const SDL_Event& e){ optionsState->handleEvent(e); });
|
|
||||||
stateMgr.registerOnEnter(AppState::Options, [&](){ optionsState->onEnter(); });
|
|
||||||
stateMgr.registerOnExit(AppState::Options, [&](){ optionsState->onExit(); });
|
|
||||||
|
|
||||||
stateMgr.registerHandler(AppState::LevelSelector, [&](const SDL_Event& e){ levelSelectorState->handleEvent(e); });
|
|
||||||
stateMgr.registerOnEnter(AppState::LevelSelector, [&](){ levelSelectorState->onEnter(); });
|
|
||||||
stateMgr.registerOnExit(AppState::LevelSelector, [&](){ levelSelectorState->onExit(); });
|
|
||||||
|
|
||||||
// Combined Playing state handler: run playingState handler
|
|
||||||
stateMgr.registerHandler(AppState::Playing, [&](const SDL_Event& e){
|
|
||||||
// First give the PlayingState a chance to handle the event
|
|
||||||
playingState->handleEvent(e);
|
|
||||||
});
|
|
||||||
stateMgr.registerOnEnter(AppState::Playing, [&](){ playingState->onEnter(); });
|
|
||||||
stateMgr.registerOnExit(AppState::Playing, [&](){ playingState->onExit(); });
|
|
||||||
|
|
||||||
// Manually trigger the initial Loading state's onEnter
|
|
||||||
loadingState->onEnter();
|
|
||||||
g_loadingStarted.store(true);
|
|
||||||
|
|
||||||
// Playing, LevelSelect and GameOver currently use inline logic in main; we'll migrate later
|
// Playing, LevelSelect and GameOver currently use inline logic in main; we'll migrate later
|
||||||
while (running)
|
while (running)
|
||||||
@ -1735,37 +1879,75 @@ int main(int, char **)
|
|||||||
SDL_RenderPresent(renderer);
|
SDL_RenderPresent(renderer);
|
||||||
SDL_SetRenderScale(renderer, 1.f, 1.f);
|
SDL_SetRenderScale(renderer, 1.f, 1.f);
|
||||||
}
|
}
|
||||||
if (logoTex)
|
}
|
||||||
SDL_DestroyTexture(logoTex);
|
|
||||||
if (mainScreenTex)
|
static void shutdownApp(AppRuntime& app)
|
||||||
SDL_DestroyTexture(mainScreenTex);
|
{
|
||||||
levelBackgrounds.reset();
|
|
||||||
if (blocksTex)
|
|
||||||
SDL_DestroyTexture(blocksTex);
|
|
||||||
if (scorePanelTex)
|
|
||||||
SDL_DestroyTexture(scorePanelTex);
|
|
||||||
if (logoSmallTex)
|
|
||||||
SDL_DestroyTexture(logoSmallTex);
|
|
||||||
|
|
||||||
// Save settings on exit
|
// Save settings on exit
|
||||||
Settings::instance().save();
|
Settings::instance().save();
|
||||||
|
|
||||||
if (scoreLoader.joinable()) {
|
if (app.logoTex) {
|
||||||
scoreLoader.join();
|
SDL_DestroyTexture(app.logoTex);
|
||||||
if (!ctx.scores) {
|
app.logoTex = nullptr;
|
||||||
ctx.scores = &scores;
|
}
|
||||||
|
if (app.mainScreenTex) {
|
||||||
|
SDL_DestroyTexture(app.mainScreenTex);
|
||||||
|
app.mainScreenTex = nullptr;
|
||||||
|
}
|
||||||
|
app.levelBackgrounds.reset();
|
||||||
|
if (app.blocksTex) {
|
||||||
|
SDL_DestroyTexture(app.blocksTex);
|
||||||
|
app.blocksTex = nullptr;
|
||||||
|
}
|
||||||
|
if (app.scorePanelTex) {
|
||||||
|
SDL_DestroyTexture(app.scorePanelTex);
|
||||||
|
app.scorePanelTex = nullptr;
|
||||||
|
}
|
||||||
|
if (app.logoSmallTex) {
|
||||||
|
SDL_DestroyTexture(app.logoSmallTex);
|
||||||
|
app.logoSmallTex = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (app.scoreLoader.joinable()) {
|
||||||
|
app.scoreLoader.join();
|
||||||
|
if (!app.ctx.scores) {
|
||||||
|
app.ctx.scores = &app.scores;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (menuTrackLoader.joinable()) {
|
if (app.menuTrackLoader.joinable()) {
|
||||||
menuTrackLoader.join();
|
app.menuTrackLoader.join();
|
||||||
}
|
}
|
||||||
lineEffect.shutdown();
|
|
||||||
|
app.lineEffect.shutdown();
|
||||||
Audio::instance().shutdown();
|
Audio::instance().shutdown();
|
||||||
SoundEffectManager::instance().shutdown();
|
SoundEffectManager::instance().shutdown();
|
||||||
font.shutdown();
|
app.font.shutdown();
|
||||||
|
|
||||||
TTF_Quit();
|
TTF_Quit();
|
||||||
SDL_DestroyRenderer(renderer);
|
|
||||||
SDL_DestroyWindow(window);
|
if (app.renderer) {
|
||||||
|
SDL_DestroyRenderer(app.renderer);
|
||||||
|
app.renderer = nullptr;
|
||||||
|
}
|
||||||
|
if (app.window) {
|
||||||
|
SDL_DestroyWindow(app.window);
|
||||||
|
app.window = nullptr;
|
||||||
|
}
|
||||||
SDL_Quit();
|
SDL_Quit();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
int main(int, char **)
|
||||||
|
{
|
||||||
|
AppRuntime app;
|
||||||
|
const int initRc = initApp(app);
|
||||||
|
if (initRc != 0) {
|
||||||
|
shutdownApp(app);
|
||||||
|
return initRc;
|
||||||
|
}
|
||||||
|
|
||||||
|
runGameLoop(app);
|
||||||
|
shutdownApp(app);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user