From 6a7802de87465608f849a5f9d92433c9f88dd243 Mon Sep 17 00:00:00 2001 From: Daniel Evans Date: Sun, 16 Oct 2016 23:56:51 +0100 Subject: [PATCH] Overhaul State and StateManager to remove pointers Removed raw State pointers in favour of unique_ptrs Avoid allowing control flow to re-enter States that have exited Defer releasing states until the end of the frame --- rwgame/CMakeLists.txt | 2 + rwgame/RWGame.cpp | 44 +++++++++++--------- rwgame/State.hpp | 54 +++++------------------- rwgame/StateManager.cpp | 0 rwgame/StateManager.hpp | 70 ++++++++++++++++++++++++++++++++ rwgame/states/BenchmarkState.cpp | 2 +- rwgame/states/DebugState.cpp | 2 +- rwgame/states/IngameState.cpp | 6 +-- rwgame/states/IngameState.hpp | 2 +- rwgame/states/LoadingState.cpp | 14 +++---- rwgame/states/LoadingState.hpp | 9 ++-- rwgame/states/MenuState.cpp | 18 ++++---- rwgame/states/MenuState.hpp | 2 +- rwgame/states/PauseState.cpp | 6 +-- rwgame/states/PauseState.hpp | 2 +- 15 files changed, 135 insertions(+), 98 deletions(-) create mode 100644 rwgame/StateManager.cpp create mode 100644 rwgame/StateManager.hpp diff --git a/rwgame/CMakeLists.txt b/rwgame/CMakeLists.txt index 57dda9bd..3691edbe 100644 --- a/rwgame/CMakeLists.txt +++ b/rwgame/CMakeLists.txt @@ -15,6 +15,8 @@ set(RWGAME_SOURCES GameConfig.cpp GameWindow.cpp + StateManager.hpp + StateManager.cpp State.cpp states/LoadingState.hpp diff --git a/rwgame/RWGame.cpp b/rwgame/RWGame.cpp index 4df030d3..8da7de98 100644 --- a/rwgame/RWGame.cpp +++ b/rwgame/RWGame.cpp @@ -35,7 +35,6 @@ RWGame::RWGame(Logger& log, int argc, char* argv[]) : GameBase(log, argc, argv) , data(&log, &work, config.getGameDataPath()) , renderer(&log, &data) { - bool newgame = options.count("newgame"); bool test = options.count("test"); std::string startSave( @@ -73,7 +72,7 @@ RWGame::RWGame(Logger& log, int argc, char* argv[]) data.loadGXT("text/" + config.getGameLanguage() + ".gxt"); getRenderer().water.setWaterTable(data.waterHeights, 48, data.realWater, - 128 * 128); + 128 * 128); for (int m = 0; m < MAP_BLOCK_SIZE; ++m) { std::string num = (m < 10 ? "0" : ""); @@ -81,20 +80,19 @@ RWGame::RWGame(Logger& log, int argc, char* argv[]) data.loadTXD(name + ".txd"); } - auto loading = new LoadingState(this); - if (!benchFile.empty()) { - loading->setNextState(new BenchmarkState(this, benchFile)); - } else if (test) { - loading->setNextState(new IngameState(this, true, "test")); - } else if (newgame) { - loading->setNextState(new IngameState(this, true)); - } else if (!startSave.empty()) { - loading->setNextState(new IngameState(this, true, startSave)); - } else { - loading->setNextState(new MenuState(this)); - } - - StateManager::get().enter(loading); + StateManager::get().enter(this, [=]() { + if (!benchFile.empty()) { + StateManager::get().enter(this, benchFile); + } else if (test) { + StateManager::get().enter(this, true, "test"); + } else if (newgame) { + StateManager::get().enter(this, true); + } else if (!startSave.empty()) { + StateManager::get().enter(this, true, startSave); + } else { + StateManager::get().enter(this); + } + }); log.info("Game", "Started"); } @@ -384,8 +382,9 @@ int RWGame::run() { last_clock_time = clock.now(); // Loop until we run out of states. - while (StateManager::get().states.size()) { - State* state = StateManager::get().states.back(); + bool running = true; + while (!StateManager::get().states.empty() && running) { + State* state = StateManager::get().states.back().get(); RW_PROFILE_FRAME_BOUNDARY(); @@ -394,7 +393,7 @@ int RWGame::run() { while (SDL_PollEvent(&event)) { switch (event.type) { case SDL_QUIT: - StateManager::get().clear(); + running = false; break; case SDL_WINDOWEVENT: @@ -474,8 +473,13 @@ int RWGame::run() { renderProfile(); getWindow().swap(); + + // Make sure the topmost state is the correct state + StateManager::get().updateStack(); } + StateManager::get().clear(); + return 0; } @@ -483,7 +487,7 @@ void RWGame::tick(float dt) { // Process the Engine's background work. world->_work->update(); - State* currState = StateManager::get().states.back(); + State* currState = StateManager::get().states.back().get(); world->chase.update(dt); diff --git a/rwgame/State.hpp b/rwgame/State.hpp index 193cf000..7a918e7d 100644 --- a/rwgame/State.hpp +++ b/rwgame/State.hpp @@ -1,8 +1,5 @@ -#ifndef _GAME_STATE_HPP_ -#define _GAME_STATE_HPP_ -#include -#include -#include +#ifndef RWGAME_STATE_HPP +#define RWGAME_STATE_HPP #include #include "GameWindow.hpp" #include "MenuSystem.hpp" @@ -11,8 +8,10 @@ class RWGame; class GameWorld; +class StateManager; -struct State { +class State { +public: // Helper for global menu behaviour Menu* currentMenu; Menu* nextMenu; @@ -66,46 +65,15 @@ struct State { GameWorld* getWorld(); GameWindow& getWindow(); -}; -struct StateManager { - static StateManager& get() { - static StateManager m; - return m; + bool hasExited() const { + return hasexited_; } - std::deque states; - - void clear() { - states.clear(); - } - - void enter(State* state) { - states.push_back(state); - state->enter(); - } - - void exec(State* state) { - exit(); - enter(state); - } - - void tick(float dt) { - states.back()->tick(dt); - } - - void draw(GameRenderer* r) { - states.back()->draw(r); - } - - void exit() { - // TODO: Resole states being leaked. - states.back()->exit(); - states.pop_back(); - if (states.size() > 0) { - states.back()->enter(); - } - } +private: + bool hasexited_ = false; +protected: + void done() { hasexited_ = true; } }; #endif diff --git a/rwgame/StateManager.cpp b/rwgame/StateManager.cpp new file mode 100644 index 00000000..e69de29b diff --git a/rwgame/StateManager.hpp b/rwgame/StateManager.hpp new file mode 100644 index 00000000..96f282ba --- /dev/null +++ b/rwgame/StateManager.hpp @@ -0,0 +1,70 @@ +#ifndef RWGAME_STATEMANAGER_HPP +#define RWGAME_STATEMANAGER_HPP +#include "State.hpp" + +#include +#include + +/** + * @brief Handles current state focus and transitions + * + * Possible states: + * Foreground (topmost state) + * Background (any other position) + * + * Transitions: + * New State (at the top) + * Suspended (a state was created above us) + * Resumed (blocking state was removed) + */ +class StateManager { +public: + static StateManager& get() { + static StateManager m; + return m; + } + + std::deque> states; + + void clear() { + cleared = true; + } + + template + void enter(Targs&&... args) { + // Notify the previous state it has been suspended + if (!states.empty()) { + states.back()->exit(); + } + states.emplace_back(std::move(std::make_unique(args...))); + states.back()->enter(); + } + + void updateStack() { + if (cleared) { + states.clear(); + cleared = false; + } + + while (!states.empty() && states.back()->hasExited()) { + states.back()->exit(); + states.pop_back(); + if (!states.empty()) { + states.back()->enter(); + } + } + } + + void tick(float dt) { + states.back()->tick(dt); + } + + void draw(GameRenderer* r) { + states.back()->draw(r); + } + +private: + bool cleared = false; +}; + +#endif diff --git a/rwgame/states/BenchmarkState.cpp b/rwgame/states/BenchmarkState.cpp index 6277373f..8e603fcd 100644 --- a/rwgame/states/BenchmarkState.cpp +++ b/rwgame/states/BenchmarkState.cpp @@ -77,7 +77,7 @@ void BenchmarkState::tick(float dt) { a = p; } if (benchmarkTime > duration) { - StateManager::get().exit(); + done(); } if (b.time != a.time) { float alpha = (benchmarkTime - a.time) / (b.time - a.time); diff --git a/rwgame/states/DebugState.cpp b/rwgame/states/DebugState.cpp index e4fb3a1e..5a01d662 100644 --- a/rwgame/states/DebugState.cpp +++ b/rwgame/states/DebugState.cpp @@ -363,7 +363,7 @@ void DebugState::handleEvent(const SDL_Event& event) { case SDL_KEYDOWN: switch (event.key.keysym.sym) { case SDLK_ESCAPE: - StateManager::get().exit(); + done(); break; case SDLK_w: _movement.x = 1.f; diff --git a/rwgame/states/IngameState.cpp b/rwgame/states/IngameState.cpp index 8588caa6..b9e6c433 100644 --- a/rwgame/states/IngameState.cpp +++ b/rwgame/states/IngameState.cpp @@ -412,11 +412,11 @@ void IngameState::handleEvent(const SDL_Event& event) { case SDL_KEYDOWN: switch (event.key.keysym.sym) { case SDLK_ESCAPE: - StateManager::get().enter(new PauseState(game)); + StateManager::get().enter(game); break; case SDLK_m: - StateManager::get().enter( - new DebugState(game, _look.position, _look.rotation)); + StateManager::get().enter(game, _look.position, + _look.rotation); break; case SDLK_SPACE: if (getWorld()->state->currentCutscene) { diff --git a/rwgame/states/IngameState.hpp b/rwgame/states/IngameState.hpp index 3e5b8437..871c432e 100644 --- a/rwgame/states/IngameState.hpp +++ b/rwgame/states/IngameState.hpp @@ -1,7 +1,7 @@ #ifndef INGAMESTATE_HPP #define INGAMESTATE_HPP -#include "State.hpp" +#include "StateManager.hpp" class PlayerController; diff --git a/rwgame/states/LoadingState.cpp b/rwgame/states/LoadingState.cpp index ff41e55c..45244901 100644 --- a/rwgame/states/LoadingState.cpp +++ b/rwgame/states/LoadingState.cpp @@ -1,8 +1,8 @@ #include "LoadingState.hpp" -#include #include "RWGame.hpp" -LoadingState::LoadingState(RWGame* game) : State(game), next(nullptr) { +LoadingState::LoadingState(RWGame* game, std::function callback) + : State(game), complete(callback) { } void LoadingState::enter() { @@ -15,9 +15,11 @@ void LoadingState::exit() { void LoadingState::tick(float dt) { RW_UNUSED(dt); - // If background work is completed, switch to the next state + // If background work is completed, do callback if (getWorld()->_work->isEmpty()) { - StateManager::get().exec(next); + // If we ever get back to this state it should be completed + done(); + complete(); } } @@ -25,10 +27,6 @@ bool LoadingState::shouldWorldUpdate() { return false; } -void LoadingState::setNextState(State* nextState) { - next = nextState; -} - void LoadingState::handleEvent(const SDL_Event& e) { State::handleEvent(e); } diff --git a/rwgame/states/LoadingState.hpp b/rwgame/states/LoadingState.hpp index 712edfc7..9bcddcb9 100644 --- a/rwgame/states/LoadingState.hpp +++ b/rwgame/states/LoadingState.hpp @@ -1,13 +1,14 @@ #ifndef LOADINGSTATE_HPP #define LOADINGSTATE_HPP +#include "StateManager.hpp" -#include "State.hpp" +#include class LoadingState : public State { - State* next; + std::function complete; public: - LoadingState(RWGame* game); + LoadingState(RWGame* game, std::function callback); virtual void enter(); virtual void exit(); @@ -16,8 +17,6 @@ public: virtual void draw(GameRenderer* r); - void setNextState(State* nextState); - virtual bool shouldWorldUpdate(); virtual void handleEvent(const SDL_Event& event); diff --git a/rwgame/states/MenuState.cpp b/rwgame/states/MenuState.cpp index adf9f15f..ac31770d 100644 --- a/rwgame/states/MenuState.cpp +++ b/rwgame/states/MenuState.cpp @@ -16,12 +16,12 @@ void MenuState::enterMainMenu() { Menu* m = new Menu; m->offset = glm::vec2(200.f, 200.f); m->addEntry(Menu::lambda(t.text(MenuDefaults::kStartGameId), [=] { - StateManager::get().enter(new IngameState(game)); + StateManager::get().enter(game); })); m->addEntry(Menu::lambda(t.text(MenuDefaults::kLoadGameId), [=] { enterLoadMenu(); })); m->addEntry(Menu::lambda(t.text(MenuDefaults::kDebugId), [=] { - StateManager::get().enter(new IngameState(game, true, "test")); + StateManager::get().enter(game, true, "test"); })); m->addEntry(Menu::lambda(t.text(MenuDefaults::kOptionsId), [] { RW_UNIMPLEMENTED("Options Menu"); })); @@ -45,13 +45,11 @@ void MenuState::enterLoadMenu() { << save.basicState.saveTime.minute << " "; auto name = GameStringUtil::fromString(ss.str()); name += save.basicState.saveName; - m->addEntry(Menu::lambda(name, - [=] { - StateManager::get().enter( - new IngameState(game, false)); - game->loadGame(save.savePath); - }, - 20.f)); + auto loadsave = [=] { + StateManager::get().enter(game, false); + game->loadGame(save.savePath); + }; + m->addEntry(Menu::lambda(name, loadsave, 20.f)); } else { m->addEntry(Menu::lambda("CORRUPT", [=] {})); } @@ -75,7 +73,7 @@ void MenuState::handleEvent(const SDL_Event& e) { case SDL_KEYUP: switch (e.key.keysym.sym) { case SDLK_ESCAPE: - StateManager::get().exit(); + done(); default: break; } diff --git a/rwgame/states/MenuState.hpp b/rwgame/states/MenuState.hpp index 82269867..691c25db 100644 --- a/rwgame/states/MenuState.hpp +++ b/rwgame/states/MenuState.hpp @@ -1,7 +1,7 @@ #ifndef MENUSTATE_HPP #define MENUSTATE_HPP -#include "State.hpp" +#include "StateManager.hpp" class MenuState : public State { public: diff --git a/rwgame/states/PauseState.cpp b/rwgame/states/PauseState.cpp index 82352549..37fd5edb 100644 --- a/rwgame/states/PauseState.cpp +++ b/rwgame/states/PauseState.cpp @@ -1,6 +1,4 @@ #include "PauseState.hpp" -#include -#include #include "RWGame.hpp" PauseState::PauseState(RWGame* game) : State(game) { @@ -9,7 +7,7 @@ PauseState::PauseState(RWGame* game) : State(game) { Menu* m = new Menu; m->offset = glm::vec2(200.f, 200.f); m->addEntry(Menu::lambda(t.text(MenuDefaults::kResumeGameId), - [] { StateManager::get().exit(); })); + [&] { done(); })); m->addEntry(Menu::lambda(t.text(MenuDefaults::kOptionsId), [] { std::cout << "Options" << std::endl; })); m->addEntry(Menu::lambda(t.text(MenuDefaults::kQuitGameId), @@ -51,7 +49,7 @@ void PauseState::handleEvent(const SDL_Event& e) { case SDL_KEYDOWN: switch (e.key.keysym.sym) { case SDLK_ESCAPE: - StateManager::get().exit(); + done(); break; default: break; diff --git a/rwgame/states/PauseState.hpp b/rwgame/states/PauseState.hpp index 893f7013..32eddcc8 100644 --- a/rwgame/states/PauseState.hpp +++ b/rwgame/states/PauseState.hpp @@ -1,7 +1,7 @@ #ifndef PAUSESTATE_HPP #define PAUSESTATE_HPP -#include "State.hpp" +#include "StateManager.hpp" class PauseState : public State { public: