From d3084ad721bfc840c3902bd041d5b9a09b01bfbb Mon Sep 17 00:00:00 2001 From: darkf Date: Sun, 31 Jul 2016 06:57:20 -0700 Subject: [PATCH] Rework how exiting and cleanup is handled. This involves a few changes. The first changes involve allocating GameWindow and WorkContext on the heap, so that RWGame still owns them but chooses when they're freed. The work queue is given a method to stop the worker thread without destroying the work context, so that subsystems relying on the work context may still function to shut down. Then RWGame is rearranged to cleanup separate subsystems in an order that does not conflict (i.e., stop the work queue, shut down other subsystems, then the renderer, *then* the window.) The window needs to be cleaned up *after* the renderer because it owns the OpenGL context. --- rwgame/RWGame.cpp | 50 +++++++++++++++++++++++--------- rwgame/RWGame.hpp | 6 ++-- rwgame/State.hpp | 5 ++++ rwgame/menustate.cpp | 2 +- rwgame/pausestate.cpp | 2 +- rwlib/source/job/WorkContext.cpp | 17 ++++++----- rwlib/source/job/WorkContext.hpp | 21 ++++++++++---- 7 files changed, 71 insertions(+), 32 deletions(-) diff --git a/rwgame/RWGame.cpp b/rwgame/RWGame.cpp index d1a9a5d8..94094082 100644 --- a/rwgame/RWGame.cpp +++ b/rwgame/RWGame.cpp @@ -35,6 +35,7 @@ StdOutReciever logPrinter; RWGame::RWGame(int argc, char* argv[]) : config("openrw.ini") , state(nullptr), world(nullptr), renderer(nullptr), script(nullptr), + window(nullptr), work(nullptr), debugScript(false), inFocus(true), showDebugStats(false), showDebugPaths(false), showDebugPhysics(false), accum(0.f), timescale(1.f) @@ -90,8 +91,11 @@ RWGame::RWGame(int argc, char* argv[]) if (SDL_Init(SDL_INIT_VIDEO) < 0) throw std::runtime_error("Failed to initialize SDL2!"); - window.create(w, h, fullscreen); - window.hideCursor(); + window = new GameWindow(); + window->create(w, h, fullscreen); + window->hideCursor(); + + work = new WorkContext(); log.addReciever(&logPrinter); log.info("Game", "Game directory: " + config.getGameDataPath()); @@ -101,7 +105,7 @@ RWGame::RWGame(int argc, char* argv[]) throw std::runtime_error("Invalid game directory path: " + config.getGameDataPath()); } - data = new GameData(&log, &work, config.getGameDataPath()); + data = new GameData(&log, work, config.getGameDataPath()); // Initalize all the archives. data->loadIMG("/models/gta3"); @@ -168,10 +172,30 @@ RWGame::RWGame(int argc, char* argv[]) RWGame::~RWGame() { + log.info("Game", "Beginning cleanup"); + + log.info("Game", "Stopping work queue"); + work->stop(); + + log.info("Game", "Cleaning up scripts"); delete script; + + log.info("Game", "Cleaning up renderer"); delete renderer; + + log.info("Game", "Cleaning up world"); delete world; + + log.info("Game", "Cleaning up state"); delete state; + + log.info("Game", "Cleaning up window"); + delete window; + + log.info("Game", "Cleaning up work queue"); + delete work; + + log.info("Game", "Done cleaning up"); } void RWGame::newGame() @@ -183,7 +207,7 @@ void RWGame::newGame() } state = new GameState(); - world = new GameWorld(&log, &work, data); + world = new GameWorld(&log, work, data); world->dynamicsWorld->setDebugDrawer(debug); // Associate the new world with the new state and vice versa @@ -294,8 +318,8 @@ int RWGame::run() { last_clock_time = clock.now(); - // Loop until the window is closed or we run out of state. - while (window.isOpen() && StateManager::get().states.size()) { + // Loop until we run out of states. + while (StateManager::get().states.size()) { State* state = StateManager::get().states.back(); RW_PROFILE_FRAME_BOUNDARY(); @@ -305,7 +329,7 @@ int RWGame::run() while (SDL_PollEvent(&event)) { switch (event.type) { case SDL_QUIT: - window.close(); + StateManager::get().clear(); break; case SDL_WINDOWEVENT: @@ -343,14 +367,14 @@ int RWGame::run() RW_PROFILE_BEGIN("Update"); if ( accum >= GAME_TIMESTEP ) { - RW_PROFILE_BEGIN("state"); - StateManager::get().tick(GAME_TIMESTEP); - RW_PROFILE_END(); - if (StateManager::get().states.size() == 0) { break; } + RW_PROFILE_BEGIN("state"); + StateManager::get().tick(GAME_TIMESTEP); + RW_PROFILE_END(); + RW_PROFILE_BEGIN("engine"); tick(GAME_TIMESTEP); RW_PROFILE_END(); @@ -385,7 +409,7 @@ int RWGame::run() renderProfile(); - window.swap(); + window->swap(); } if( httpserver_thread ) @@ -483,7 +507,7 @@ void RWGame::render(float alpha, float time) getRenderer()->getRenderer()->swap(); - glm::ivec2 windowSize = window.getSize(); + glm::ivec2 windowSize = window->getSize(); renderer->setViewport(windowSize.x, windowSize.y); ViewCamera viewCam; diff --git a/rwgame/RWGame.hpp b/rwgame/RWGame.hpp index 42c353b7..fc71ce77 100644 --- a/rwgame/RWGame.hpp +++ b/rwgame/RWGame.hpp @@ -28,11 +28,11 @@ class RWGame GameRenderer* renderer; ScriptMachine* script; // Background worker - WorkContext work; + WorkContext *work; bool debugScript; HttpServer* httpserver = nullptr; std::thread* httpserver_thread = nullptr; - GameWindow window; + GameWindow *window; std::chrono::steady_clock clock; std::chrono::steady_clock::time_point last_clock_time; @@ -79,7 +79,7 @@ public: GameWindow& getWindow() { - return window; + return *window; } ScriptMachine* getScript() const diff --git a/rwgame/State.hpp b/rwgame/State.hpp index 03618497..d0f089a0 100644 --- a/rwgame/State.hpp +++ b/rwgame/State.hpp @@ -80,6 +80,11 @@ struct StateManager } std::deque states; + + void clear() + { + states.clear(); + } void enter(State* state) { diff --git a/rwgame/menustate.cpp b/rwgame/menustate.cpp index 98254e7b..9ca737dd 100644 --- a/rwgame/menustate.cpp +++ b/rwgame/menustate.cpp @@ -20,7 +20,7 @@ void MenuState::enterMainMenu() m->addEntry(Menu::lambda("Load Game", [=] { enterLoadMenu(); })); m->addEntry(Menu::lambda("Test", [=] { StateManager::get().enter(new IngameState(game, true, "test")); })); m->addEntry(Menu::lambda("Options", [] { RW_UNIMPLEMENTED("Options Menu"); })); - m->addEntry(Menu::lambda("Exit", [=] { getWindow().close(); })); + m->addEntry(Menu::lambda("Exit", [] { StateManager::get().clear(); })); this->enterMenu(m); } diff --git a/rwgame/pausestate.cpp b/rwgame/pausestate.cpp index f44b111b..69bd7169 100644 --- a/rwgame/pausestate.cpp +++ b/rwgame/pausestate.cpp @@ -10,7 +10,7 @@ PauseState::PauseState(RWGame* game) m->offset = glm::vec2( 200.f, 200.f ); m->addEntry(Menu::lambda("Continue", [] { StateManager::get().exit(); })); m->addEntry(Menu::lambda("Options", [] { std::cout << "Options" << std::endl; })); - m->addEntry(Menu::lambda("Exit", [&] { getWindow().close(); })); + m->addEntry(Menu::lambda("Exit", [] { StateManager::get().clear(); })); this->enterMenu(m); } diff --git a/rwlib/source/job/WorkContext.cpp b/rwlib/source/job/WorkContext.cpp index 76b55c58..73784048 100644 --- a/rwlib/source/job/WorkContext.cpp +++ b/rwlib/source/job/WorkContext.cpp @@ -12,20 +12,21 @@ void WorkContext::workNext() { WorkJob* j = nullptr; - _inMutex.lock(); - if( ! _workQueue.empty() ) { - j = _workQueue.front(); - _workQueue.pop(); + { + std::lock_guard guard( _inMutex ); + + if( ! _workQueue.empty() ) { + j = _workQueue.front(); + _workQueue.pop(); + } } - _inMutex.unlock(); if( j == nullptr ) return; j->work(); - - _outMutex.lock(); + + std::lock_guard guard( _outMutex ); _completeQueue.push(j); - _outMutex.unlock(); } void WorkContext::update() diff --git a/rwlib/source/job/WorkContext.hpp b/rwlib/source/job/WorkContext.hpp index 0d074b0d..c5f0756a 100644 --- a/rwlib/source/job/WorkContext.hpp +++ b/rwlib/source/job/WorkContext.hpp @@ -5,8 +5,11 @@ #include #include #include +#include +#include #include #include +#include class WorkContext; @@ -16,7 +19,7 @@ class LoadWorker public: - bool _running; + std::atomic _running; std::thread _thread; void start(); @@ -66,26 +69,32 @@ class GameWorld; */ class WorkContext { + std::unique_ptr _worker; + std::queue _workQueue; std::queue _completeQueue; - LoadWorker _worker; - std::mutex _inMutex; std::mutex _outMutex; public: WorkContext() - : _worker(this) { } + : _worker(new LoadWorker(this)) { } void queueJob( WorkJob* job ) { - std::lock_guard guard(_inMutex); + std::lock_guard guard( _inMutex ); _workQueue.push( job ); } - // Called by the worker thread - don't touch; + void stop() + { + // Stop serving the queue. + _worker.reset(nullptr); + } + + // Called by the worker thread - don't touch void workNext(); const std::queue getWorkQueue() const { return _workQueue; }