1
0
mirror of https://github.com/rwengine/openrw.git synced 2024-11-22 10:22:52 +01:00

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
This commit is contained in:
Daniel Evans 2016-10-16 23:56:51 +01:00
parent b4aa01e4bb
commit 6a7802de87
15 changed files with 135 additions and 98 deletions

View File

@ -15,6 +15,8 @@ set(RWGAME_SOURCES
GameConfig.cpp GameConfig.cpp
GameWindow.cpp GameWindow.cpp
StateManager.hpp
StateManager.cpp
State.cpp State.cpp
states/LoadingState.hpp states/LoadingState.hpp

View File

@ -35,7 +35,6 @@ RWGame::RWGame(Logger& log, int argc, char* argv[])
: GameBase(log, argc, argv) : GameBase(log, argc, argv)
, data(&log, &work, config.getGameDataPath()) , data(&log, &work, config.getGameDataPath())
, renderer(&log, &data) { , renderer(&log, &data) {
bool newgame = options.count("newgame"); bool newgame = options.count("newgame");
bool test = options.count("test"); bool test = options.count("test");
std::string startSave( std::string startSave(
@ -81,20 +80,19 @@ RWGame::RWGame(Logger& log, int argc, char* argv[])
data.loadTXD(name + ".txd"); data.loadTXD(name + ".txd");
} }
auto loading = new LoadingState(this); StateManager::get().enter<LoadingState>(this, [=]() {
if (!benchFile.empty()) { if (!benchFile.empty()) {
loading->setNextState(new BenchmarkState(this, benchFile)); StateManager::get().enter<BenchmarkState>(this, benchFile);
} else if (test) { } else if (test) {
loading->setNextState(new IngameState(this, true, "test")); StateManager::get().enter<IngameState>(this, true, "test");
} else if (newgame) { } else if (newgame) {
loading->setNextState(new IngameState(this, true)); StateManager::get().enter<IngameState>(this, true);
} else if (!startSave.empty()) { } else if (!startSave.empty()) {
loading->setNextState(new IngameState(this, true, startSave)); StateManager::get().enter<IngameState>(this, true, startSave);
} else { } else {
loading->setNextState(new MenuState(this)); StateManager::get().enter<MenuState>(this);
} }
});
StateManager::get().enter(loading);
log.info("Game", "Started"); log.info("Game", "Started");
} }
@ -384,8 +382,9 @@ int RWGame::run() {
last_clock_time = clock.now(); last_clock_time = clock.now();
// Loop until we run out of states. // Loop until we run out of states.
while (StateManager::get().states.size()) { bool running = true;
State* state = StateManager::get().states.back(); while (!StateManager::get().states.empty() && running) {
State* state = StateManager::get().states.back().get();
RW_PROFILE_FRAME_BOUNDARY(); RW_PROFILE_FRAME_BOUNDARY();
@ -394,7 +393,7 @@ int RWGame::run() {
while (SDL_PollEvent(&event)) { while (SDL_PollEvent(&event)) {
switch (event.type) { switch (event.type) {
case SDL_QUIT: case SDL_QUIT:
StateManager::get().clear(); running = false;
break; break;
case SDL_WINDOWEVENT: case SDL_WINDOWEVENT:
@ -474,8 +473,13 @@ int RWGame::run() {
renderProfile(); renderProfile();
getWindow().swap(); getWindow().swap();
// Make sure the topmost state is the correct state
StateManager::get().updateStack();
} }
StateManager::get().clear();
return 0; return 0;
} }
@ -483,7 +487,7 @@ void RWGame::tick(float dt) {
// Process the Engine's background work. // Process the Engine's background work.
world->_work->update(); world->_work->update();
State* currState = StateManager::get().states.back(); State* currState = StateManager::get().states.back().get();
world->chase.update(dt); world->chase.update(dt);

View File

@ -1,8 +1,5 @@
#ifndef _GAME_STATE_HPP_ #ifndef RWGAME_STATE_HPP
#define _GAME_STATE_HPP_ #define RWGAME_STATE_HPP
#include <functional>
#include <glm/gtc/quaternion.hpp>
#include <queue>
#include <render/ViewCamera.hpp> #include <render/ViewCamera.hpp>
#include "GameWindow.hpp" #include "GameWindow.hpp"
#include "MenuSystem.hpp" #include "MenuSystem.hpp"
@ -11,8 +8,10 @@
class RWGame; class RWGame;
class GameWorld; class GameWorld;
class StateManager;
struct State { class State {
public:
// Helper for global menu behaviour // Helper for global menu behaviour
Menu* currentMenu; Menu* currentMenu;
Menu* nextMenu; Menu* nextMenu;
@ -66,46 +65,15 @@ struct State {
GameWorld* getWorld(); GameWorld* getWorld();
GameWindow& getWindow(); GameWindow& getWindow();
};
struct StateManager { bool hasExited() const {
static StateManager& get() { return hasexited_;
static StateManager m;
return m;
} }
std::deque<State*> states; private:
bool hasexited_ = false;
void clear() { protected:
states.clear(); void done() { hasexited_ = true; }
}
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();
}
}
}; };
#endif #endif

0
rwgame/StateManager.cpp Normal file
View File

70
rwgame/StateManager.hpp Normal file
View File

@ -0,0 +1,70 @@
#ifndef RWGAME_STATEMANAGER_HPP
#define RWGAME_STATEMANAGER_HPP
#include "State.hpp"
#include <memory>
#include <queue>
/**
* @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<std::unique_ptr<State>> states;
void clear() {
cleared = true;
}
template <class T, class... Targs>
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<T>(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

View File

@ -77,7 +77,7 @@ void BenchmarkState::tick(float dt) {
a = p; a = p;
} }
if (benchmarkTime > duration) { if (benchmarkTime > duration) {
StateManager::get().exit(); done();
} }
if (b.time != a.time) { if (b.time != a.time) {
float alpha = (benchmarkTime - a.time) / (b.time - a.time); float alpha = (benchmarkTime - a.time) / (b.time - a.time);

View File

@ -363,7 +363,7 @@ void DebugState::handleEvent(const SDL_Event& event) {
case SDL_KEYDOWN: case SDL_KEYDOWN:
switch (event.key.keysym.sym) { switch (event.key.keysym.sym) {
case SDLK_ESCAPE: case SDLK_ESCAPE:
StateManager::get().exit(); done();
break; break;
case SDLK_w: case SDLK_w:
_movement.x = 1.f; _movement.x = 1.f;

View File

@ -412,11 +412,11 @@ void IngameState::handleEvent(const SDL_Event& event) {
case SDL_KEYDOWN: case SDL_KEYDOWN:
switch (event.key.keysym.sym) { switch (event.key.keysym.sym) {
case SDLK_ESCAPE: case SDLK_ESCAPE:
StateManager::get().enter(new PauseState(game)); StateManager::get().enter<PauseState>(game);
break; break;
case SDLK_m: case SDLK_m:
StateManager::get().enter( StateManager::get().enter<DebugState>(game, _look.position,
new DebugState(game, _look.position, _look.rotation)); _look.rotation);
break; break;
case SDLK_SPACE: case SDLK_SPACE:
if (getWorld()->state->currentCutscene) { if (getWorld()->state->currentCutscene) {

View File

@ -1,7 +1,7 @@
#ifndef INGAMESTATE_HPP #ifndef INGAMESTATE_HPP
#define INGAMESTATE_HPP #define INGAMESTATE_HPP
#include "State.hpp" #include "StateManager.hpp"
class PlayerController; class PlayerController;

View File

@ -1,8 +1,8 @@
#include "LoadingState.hpp" #include "LoadingState.hpp"
#include <render/OpenGLRenderer.hpp>
#include "RWGame.hpp" #include "RWGame.hpp"
LoadingState::LoadingState(RWGame* game) : State(game), next(nullptr) { LoadingState::LoadingState(RWGame* game, std::function<void(void)> callback)
: State(game), complete(callback) {
} }
void LoadingState::enter() { void LoadingState::enter() {
@ -15,9 +15,11 @@ void LoadingState::exit() {
void LoadingState::tick(float dt) { void LoadingState::tick(float dt) {
RW_UNUSED(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()) { 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; return false;
} }
void LoadingState::setNextState(State* nextState) {
next = nextState;
}
void LoadingState::handleEvent(const SDL_Event& e) { void LoadingState::handleEvent(const SDL_Event& e) {
State::handleEvent(e); State::handleEvent(e);
} }

View File

@ -1,13 +1,14 @@
#ifndef LOADINGSTATE_HPP #ifndef LOADINGSTATE_HPP
#define LOADINGSTATE_HPP #define LOADINGSTATE_HPP
#include "StateManager.hpp"
#include "State.hpp" #include <functional>
class LoadingState : public State { class LoadingState : public State {
State* next; std::function<void(void)> complete;
public: public:
LoadingState(RWGame* game); LoadingState(RWGame* game, std::function<void(void)> callback);
virtual void enter(); virtual void enter();
virtual void exit(); virtual void exit();
@ -16,8 +17,6 @@ public:
virtual void draw(GameRenderer* r); virtual void draw(GameRenderer* r);
void setNextState(State* nextState);
virtual bool shouldWorldUpdate(); virtual bool shouldWorldUpdate();
virtual void handleEvent(const SDL_Event& event); virtual void handleEvent(const SDL_Event& event);

View File

@ -16,12 +16,12 @@ void MenuState::enterMainMenu() {
Menu* m = new Menu; Menu* m = new Menu;
m->offset = glm::vec2(200.f, 200.f); m->offset = glm::vec2(200.f, 200.f);
m->addEntry(Menu::lambda(t.text(MenuDefaults::kStartGameId), [=] { m->addEntry(Menu::lambda(t.text(MenuDefaults::kStartGameId), [=] {
StateManager::get().enter(new IngameState(game)); StateManager::get().enter<IngameState>(game);
})); }));
m->addEntry(Menu::lambda(t.text(MenuDefaults::kLoadGameId), m->addEntry(Menu::lambda(t.text(MenuDefaults::kLoadGameId),
[=] { enterLoadMenu(); })); [=] { enterLoadMenu(); }));
m->addEntry(Menu::lambda(t.text(MenuDefaults::kDebugId), [=] { m->addEntry(Menu::lambda(t.text(MenuDefaults::kDebugId), [=] {
StateManager::get().enter(new IngameState(game, true, "test")); StateManager::get().enter<IngameState>(game, true, "test");
})); }));
m->addEntry(Menu::lambda(t.text(MenuDefaults::kOptionsId), m->addEntry(Menu::lambda(t.text(MenuDefaults::kOptionsId),
[] { RW_UNIMPLEMENTED("Options Menu"); })); [] { RW_UNIMPLEMENTED("Options Menu"); }));
@ -45,13 +45,11 @@ void MenuState::enterLoadMenu() {
<< save.basicState.saveTime.minute << " "; << save.basicState.saveTime.minute << " ";
auto name = GameStringUtil::fromString(ss.str()); auto name = GameStringUtil::fromString(ss.str());
name += save.basicState.saveName; name += save.basicState.saveName;
m->addEntry(Menu::lambda(name, auto loadsave = [=] {
[=] { StateManager::get().enter<IngameState>(game, false);
StateManager::get().enter(
new IngameState(game, false));
game->loadGame(save.savePath); game->loadGame(save.savePath);
}, };
20.f)); m->addEntry(Menu::lambda(name, loadsave, 20.f));
} else { } else {
m->addEntry(Menu::lambda("CORRUPT", [=] {})); m->addEntry(Menu::lambda("CORRUPT", [=] {}));
} }
@ -75,7 +73,7 @@ void MenuState::handleEvent(const SDL_Event& e) {
case SDL_KEYUP: case SDL_KEYUP:
switch (e.key.keysym.sym) { switch (e.key.keysym.sym) {
case SDLK_ESCAPE: case SDLK_ESCAPE:
StateManager::get().exit(); done();
default: default:
break; break;
} }

View File

@ -1,7 +1,7 @@
#ifndef MENUSTATE_HPP #ifndef MENUSTATE_HPP
#define MENUSTATE_HPP #define MENUSTATE_HPP
#include "State.hpp" #include "StateManager.hpp"
class MenuState : public State { class MenuState : public State {
public: public:

View File

@ -1,6 +1,4 @@
#include "PauseState.hpp" #include "PauseState.hpp"
#include <ai/PlayerController.hpp>
#include <objects/CharacterObject.hpp>
#include "RWGame.hpp" #include "RWGame.hpp"
PauseState::PauseState(RWGame* game) : State(game) { PauseState::PauseState(RWGame* game) : State(game) {
@ -9,7 +7,7 @@ PauseState::PauseState(RWGame* game) : State(game) {
Menu* m = new Menu; Menu* m = new Menu;
m->offset = glm::vec2(200.f, 200.f); m->offset = glm::vec2(200.f, 200.f);
m->addEntry(Menu::lambda(t.text(MenuDefaults::kResumeGameId), m->addEntry(Menu::lambda(t.text(MenuDefaults::kResumeGameId),
[] { StateManager::get().exit(); })); [&] { done(); }));
m->addEntry(Menu::lambda(t.text(MenuDefaults::kOptionsId), m->addEntry(Menu::lambda(t.text(MenuDefaults::kOptionsId),
[] { std::cout << "Options" << std::endl; })); [] { std::cout << "Options" << std::endl; }));
m->addEntry(Menu::lambda(t.text(MenuDefaults::kQuitGameId), m->addEntry(Menu::lambda(t.text(MenuDefaults::kQuitGameId),
@ -51,7 +49,7 @@ void PauseState::handleEvent(const SDL_Event& e) {
case SDL_KEYDOWN: case SDL_KEYDOWN:
switch (e.key.keysym.sym) { switch (e.key.keysym.sym) {
case SDLK_ESCAPE: case SDLK_ESCAPE:
StateManager::get().exit(); done();
break; break;
default: default:
break; break;

View File

@ -1,7 +1,7 @@
#ifndef PAUSESTATE_HPP #ifndef PAUSESTATE_HPP
#define PAUSESTATE_HPP #define PAUSESTATE_HPP
#include "State.hpp" #include "StateManager.hpp"
class PauseState : public State { class PauseState : public State {
public: public: