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:
parent
b4aa01e4bb
commit
6a7802de87
@ -15,6 +15,8 @@ set(RWGAME_SOURCES
|
||||
GameConfig.cpp
|
||||
GameWindow.cpp
|
||||
|
||||
StateManager.hpp
|
||||
StateManager.cpp
|
||||
State.cpp
|
||||
|
||||
states/LoadingState.hpp
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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
0
rwgame/StateManager.cpp
Normal file
70
rwgame/StateManager.hpp
Normal file
70
rwgame/StateManager.hpp
Normal 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
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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) {
|
||||
|
@ -1,7 +1,7 @@
|
||||
#ifndef INGAMESTATE_HPP
|
||||
#define INGAMESTATE_HPP
|
||||
|
||||
#include "State.hpp"
|
||||
#include "StateManager.hpp"
|
||||
|
||||
class PlayerController;
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
#ifndef MENUSTATE_HPP
|
||||
#define MENUSTATE_HPP
|
||||
|
||||
#include "State.hpp"
|
||||
#include "StateManager.hpp"
|
||||
|
||||
class MenuState : public State {
|
||||
public:
|
||||
|
@ -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;
|
||||
|
@ -1,7 +1,7 @@
|
||||
#ifndef PAUSESTATE_HPP
|
||||
#define PAUSESTATE_HPP
|
||||
|
||||
#include "State.hpp"
|
||||
#include "StateManager.hpp"
|
||||
|
||||
class PauseState : public State {
|
||||
public:
|
||||
|
Loading…
Reference in New Issue
Block a user