1
0
mirror of https://github.com/rwengine/openrw.git synced 2024-11-21 18:02:43 +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
GameWindow.cpp
StateManager.hpp
StateManager.cpp
State.cpp
states/LoadingState.hpp

View File

@ -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<LoadingState>(this, [=]() {
if (!benchFile.empty()) {
StateManager::get().enter<BenchmarkState>(this, benchFile);
} else if (test) {
StateManager::get().enter<IngameState>(this, true, "test");
} else if (newgame) {
StateManager::get().enter<IngameState>(this, true);
} else if (!startSave.empty()) {
StateManager::get().enter<IngameState>(this, true, startSave);
} else {
StateManager::get().enter<MenuState>(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);

View File

@ -1,8 +1,5 @@
#ifndef _GAME_STATE_HPP_
#define _GAME_STATE_HPP_
#include <functional>
#include <glm/gtc/quaternion.hpp>
#include <queue>
#ifndef RWGAME_STATE_HPP
#define RWGAME_STATE_HPP
#include <render/ViewCamera.hpp>
#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<State*> 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

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;
}
if (benchmarkTime > duration) {
StateManager::get().exit();
done();
}
if (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:
switch (event.key.keysym.sym) {
case SDLK_ESCAPE:
StateManager::get().exit();
done();
break;
case SDLK_w:
_movement.x = 1.f;

View File

@ -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<PauseState>(game);
break;
case SDLK_m:
StateManager::get().enter(
new DebugState(game, _look.position, _look.rotation));
StateManager::get().enter<DebugState>(game, _look.position,
_look.rotation);
break;
case SDLK_SPACE:
if (getWorld()->state->currentCutscene) {

View File

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

View File

@ -1,8 +1,8 @@
#include "LoadingState.hpp"
#include <render/OpenGLRenderer.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() {
@ -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);
}

View File

@ -1,13 +1,14 @@
#ifndef LOADINGSTATE_HPP
#define LOADINGSTATE_HPP
#include "StateManager.hpp"
#include "State.hpp"
#include <functional>
class LoadingState : public State {
State* next;
std::function<void(void)> complete;
public:
LoadingState(RWGame* game);
LoadingState(RWGame* game, std::function<void(void)> 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);

View File

@ -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<IngameState>(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<IngameState>(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<IngameState>(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;
}

View File

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

View File

@ -1,6 +1,4 @@
#include "PauseState.hpp"
#include <ai/PlayerController.hpp>
#include <objects/CharacterObject.hpp>
#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;

View File

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