Changeset 7421 for sandbox_qt/src/libraries/core/Game.cc
- Timestamp:
- Sep 12, 2010, 12:47:30 AM (14 years ago)
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
sandbox_qt/src/libraries/core/Game.cc
r7284 r7421 36 36 37 37 #include <exception> 38 #include <boost/weak_ptr.hpp>39 #include <loki/ScopeGuard.h>40 38 41 #include "util/Clock.h"42 39 #include "util/Debug.h" 43 40 #include "util/Exception.h" 44 41 #include "util/Sleep.h" 45 #include "util/SubString.h"46 #include "CommandLineParser.h"47 42 #include "Core.h" 48 #include "CoreIncludes.h"49 #include "ConfigValueIncludes.h"50 #include "GameMode.h"51 #include "GameState.h"52 #include "GUIManager.h"53 #include "command/ConsoleCommand.h"54 43 55 44 namespace orxonox 56 45 { 57 static void stop_game()58 { Game::getInstance().stop(); }59 SetConsoleCommand("exit", &stop_game);60 static void printFPS()61 { COUT(0) << Game::getInstance().getAvgFPS() << std::endl; }62 SetConsoleCommand("printFPS", &printFPS);63 static void printTickTime()64 { COUT(0) << Game::getInstance().getAvgTickTime() << std::endl; }65 SetConsoleCommand("printTickTime", &printTickTime);66 67 std::map<std::string, GameStateInfo> Game::gameStateDeclarations_s;68 46 Game* Game::singletonPtr_s = 0; 69 47 70 //! Represents one node of the game state tree.71 struct GameStateTreeNode72 {73 std::string name_;74 weak_ptr<GameStateTreeNode> parent_;75 std::vector<shared_ptr<GameStateTreeNode> > children_;76 };77 78 48 Game::Game(const std::string& cmdLine) 79 // Destroy factories before the Core!80 : gsFactoryDestroyer_(Game::GameStateFactory::getFactories(), &std::map<std::string, shared_ptr<GameStateFactory> >::clear)81 49 { 82 50 this->bAbort_ = false; 83 bChangingState_ = false;84 85 #ifdef ORXONOX_PLATFORM_WINDOWS86 minimumSleepTime_ = 1000/*us*/;87 #else88 minimumSleepTime_ = 0/*us*/;89 #endif90 91 // reset statistics92 this->statisticsStartTime_ = 0;93 this->statisticsTickTimes_.clear();94 this->periodTickTime_ = 0;95 this->periodTime_ = 0;96 this->avgFPS_ = 0.0f;97 this->avgTickTime_ = 0.0f;98 this->excessSleepTime_ = 0;99 100 // Create an empty root state101 this->declareGameState<GameState>("GameState", "emptyRootGameState", true, false);102 103 // Set up a basic clock to keep time104 this->gameClock_.reset(new Clock());105 51 106 52 // Create the Core 107 53 this->core_.reset(new Core(cmdLine)); 108 109 // Do this after the Core creation!110 ClassIdentifier<Game>::getIdentifier("Game")->initialiseObject(this, "Game", true);111 this->setConfigValues();112 113 // After the core has been created, we can safely instantiate the GameStates that don't require graphics114 for (std::map<std::string, GameStateInfo>::const_iterator it = gameStateDeclarations_s.begin();115 it != gameStateDeclarations_s.end(); ++it)116 {117 if (!it->second.bGraphicsMode)118 constructedStates_[it->second.stateName] = GameStateFactory::fabricate(it->second);119 }120 121 // The empty root state is ALWAYS loaded!122 this->rootStateNode_ = shared_ptr<GameStateTreeNode>(new GameStateTreeNode());123 this->rootStateNode_->name_ = "emptyRootGameState";124 this->loadedTopStateNode_ = this->rootStateNode_;125 this->loadedStates_.push_back(this->getState(rootStateNode_->name_));126 54 } 127 55 128 56 /** 129 57 @brief 130 All destruction code is handled by scoped_ptrs and SimpleScopeGuards.58 All destruction code is handled by QScopedPointers. 131 59 */ 132 60 Game::~Game() 133 61 { 134 // Remove us from the object lists again to avoid problems when destroying them135 this->unregisterObject();136 62 } 137 63 138 64 void Game::setConfigValues() 139 65 { 66 /* 140 67 SetConfigValue(statisticsRefreshCycle_, 250000) 141 68 .description("Sets the time in microseconds interval at which average fps, etc. get updated."); … … 144 71 SetConfigValue(fpsLimit_, 50) 145 72 .description("Sets the desired frame rate (0 for no limit)."); 73 */ 146 74 } 147 75 … … 149 77 @brief 150 78 Main loop of the orxonox game. 151 @note152 We use the Ogre::Timer to measure time since it uses the most precise153 method an any platform (however the windows timer lacks time when under154 heavy kernel load!).155 79 */ 156 80 void Game::run() 157 81 { 158 if (this->requestedStateNodes_.empty()) 159 COUT(0) << "Warning: Starting game without requesting GameState. This automatically terminates the program." << std::endl; 160 161 // START GAME 162 // first delta time should be about 0 seconds 163 this->gameClock_->capture(); 164 // A first item is required for the fps limiter 165 StatisticsTickInfo tickInfo = {0, 0}; 166 statisticsTickTimes_.push_back(tickInfo); 167 while (!this->bAbort_ && (!this->loadedStates_.empty() || this->requestedStateNodes_.size() > 0)) 82 while (!this->bAbort_) 168 83 { 169 // Generate the dt170 this->gameClock_->capture();171 172 // Statistics init173 StatisticsTickInfo tickInfo = {gameClock_->getMicroseconds(), 0};174 statisticsTickTimes_.push_back(tickInfo);175 this->periodTime_ += this->gameClock_->getDeltaTimeMicroseconds();176 177 // Update the GameState stack if required178 this->updateGameStateStack();179 180 // Core preUpdate181 84 try 182 { this->core_->preUpdate(*this->gameClock_); } 183 catch (...) 184 { 185 COUT(0) << "An exception occurred in the Core preUpdate: " << Exception::handleMessage() << std::endl; 186 COUT(0) << "This should really never happen! Closing the program." << std::endl; 187 this->stop(); 188 break; 189 } 190 191 // Update the GameStates bottom up in the stack 192 this->updateGameStates(); 193 194 // Core postUpdate 195 try 196 { this->core_->postUpdate(*this->gameClock_); } 85 { this->core_->update(); } 197 86 catch (...) 198 87 { … … 202 91 break; 203 92 } 204 205 // Evaluate statistics206 this->updateStatistics();207 208 // Limit frame rate209 this->updateFPSLimiter();210 93 } 211 212 // UNLOAD all remaining states213 while (this->loadedStates_.size() > 1)214 this->unloadState(this->loadedStates_.back()->getName());215 this->loadedTopStateNode_ = this->rootStateNode_;216 this->requestedStateNodes_.clear();217 }218 219 void Game::updateGameStateStack()220 {221 while (this->requestedStateNodes_.size() > 0)222 {223 shared_ptr<GameStateTreeNode> requestedStateNode = this->requestedStateNodes_.front();224 assert(this->loadedTopStateNode_);225 if (!this->loadedTopStateNode_->parent_.expired() && requestedStateNode == this->loadedTopStateNode_->parent_.lock())226 this->unloadState(loadedTopStateNode_->name_);227 else // has to be child228 {229 try230 {231 this->loadState(requestedStateNode->name_);232 }233 catch (...)234 {235 COUT(1) << "Error: Loading GameState '" << requestedStateNode->name_ << "' failed: " << Exception::handleMessage() << std::endl;236 // All scheduled operations have now been rendered inert --> flush them and issue a warning237 if (this->requestedStateNodes_.size() > 1)238 COUT(4) << "All " << this->requestedStateNodes_.size() - 1 << " scheduled transitions have been ignored." << std::endl;239 this->requestedStateNodes_.clear();240 break;241 }242 }243 this->loadedTopStateNode_ = requestedStateNode;244 this->requestedStateNodes_.erase(this->requestedStateNodes_.begin());245 }246 }247 248 void Game::updateGameStates()249 {250 // Note: The first element is the empty root state, which doesn't need ticking251 for (GameStateVector::const_iterator it = this->loadedStates_.begin() + 1;252 it != this->loadedStates_.end(); ++it)253 {254 try255 {256 // Add tick time for most of the states257 uint64_t timeBeforeTick = 0;258 if ((*it)->getInfo().bIgnoreTickTime)259 timeBeforeTick = this->gameClock_->getRealMicroseconds();260 (*it)->update(*this->gameClock_);261 if ((*it)->getInfo().bIgnoreTickTime)262 this->subtractTickTime(static_cast<int32_t>(this->gameClock_->getRealMicroseconds() - timeBeforeTick));263 }264 catch (...)265 {266 COUT(1) << "An exception occurred while updating '" << (*it)->getName() << "': " << Exception::handleMessage() << std::endl;267 COUT(1) << "This should really never happen!" << std::endl;268 COUT(1) << "Unloading all GameStates depending on the one that crashed." << std::endl;269 shared_ptr<GameStateTreeNode> current = this->loadedTopStateNode_;270 while (current->name_ != (*it)->getName() && current)271 current = current->parent_.lock();272 if (current && current->parent_.lock())273 this->requestState(current->parent_.lock()->name_);274 else275 this->stop();276 break;277 }278 }279 }280 281 void Game::updateStatistics()282 {283 // Add the tick time of this frame (rendering time has already been subtracted)284 uint64_t currentTime = gameClock_->getMicroseconds();285 uint64_t currentRealTime = gameClock_->getRealMicroseconds();286 this->statisticsTickTimes_.back().tickLength += (uint32_t)(currentRealTime - currentTime);287 this->periodTickTime_ += (uint32_t)(currentRealTime - currentTime);288 if (this->periodTime_ > this->statisticsRefreshCycle_)289 {290 std::list<StatisticsTickInfo>::iterator it = this->statisticsTickTimes_.begin();291 assert(it != this->statisticsTickTimes_.end());292 int64_t lastTime = currentTime - this->statisticsAvgLength_;293 if (static_cast<int64_t>(it->tickTime) < lastTime)294 {295 do296 {297 assert(this->periodTickTime_ >= it->tickLength);298 this->periodTickTime_ -= it->tickLength;299 ++it;300 assert(it != this->statisticsTickTimes_.end());301 } while (static_cast<int64_t>(it->tickTime) < lastTime);302 this->statisticsTickTimes_.erase(this->statisticsTickTimes_.begin(), it);303 }304 305 uint32_t framesPerPeriod = this->statisticsTickTimes_.size();306 // Why minus 1? No idea, but otherwise the fps rate is always (from 10 to 200!) one frame too low307 this->avgFPS_ = -1 + static_cast<float>(framesPerPeriod) / (currentTime - this->statisticsTickTimes_.front().tickTime) * 1000000.0f;308 this->avgTickTime_ = static_cast<float>(this->periodTickTime_) / framesPerPeriod / 1000.0f;309 310 this->periodTime_ -= this->statisticsRefreshCycle_;311 }312 }313 314 void Game::updateFPSLimiter()315 {316 uint64_t nextTime = gameClock_->getMicroseconds() - excessSleepTime_ + static_cast<uint32_t>(1000000.0f / fpsLimit_);317 uint64_t currentRealTime = gameClock_->getRealMicroseconds();318 while (currentRealTime < nextTime - minimumSleepTime_)319 {320 usleep((unsigned long)(nextTime - currentRealTime));321 currentRealTime = gameClock_->getRealMicroseconds();322 }323 // Integrate excess to avoid steady state error324 excessSleepTime_ = (int)(currentRealTime - nextTime);325 // Anti windup326 if (excessSleepTime_ > 50000) // 20ms is about the maximum time Windows would sleep for too long327 excessSleepTime_ = 50000;328 94 } 329 95 … … 332 98 this->bAbort_ = true; 333 99 } 334 335 void Game::subtractTickTime(int32_t length)336 {337 assert(!this->statisticsTickTimes_.empty());338 this->statisticsTickTimes_.back().tickLength -= length;339 this->periodTickTime_ -= length;340 }341 342 343 /***** GameState related *****/344 345 void Game::requestState(const std::string& name)346 {347 if (!this->checkState(name))348 {349 COUT(2) << "Warning: GameState named '" << name << "' doesn't exist!" << std::endl;350 return;351 }352 353 if (this->bChangingState_)354 {355 COUT(2) << "Warning: Requesting GameStates while loading/unloading a GameState is illegal! Ignoring." << std::endl;356 return;357 }358 359 shared_ptr<GameStateTreeNode> lastRequestedNode;360 if (this->requestedStateNodes_.empty())361 lastRequestedNode = this->loadedTopStateNode_;362 else363 lastRequestedNode = this->requestedStateNodes_.back();364 if (name == lastRequestedNode->name_)365 {366 COUT(2) << "Warning: Requesting the currently active state! Ignoring." << std::endl;367 return;368 }369 370 // Check children first371 std::vector<shared_ptr<GameStateTreeNode> > requestedNodes;372 for (unsigned int i = 0; i < lastRequestedNode->children_.size(); ++i)373 {374 if (lastRequestedNode->children_[i]->name_ == name)375 {376 requestedNodes.push_back(lastRequestedNode->children_[i]);377 break;378 }379 }380 381 if (requestedNodes.empty())382 {383 // Check parent and all its grand parents384 shared_ptr<GameStateTreeNode> currentNode = lastRequestedNode;385 while (currentNode != NULL)386 {387 if (currentNode->name_ == name)388 break;389 currentNode = currentNode->parent_.lock();390 requestedNodes.push_back(currentNode);391 }392 if (currentNode == NULL)393 requestedNodes.clear();394 }395 396 if (requestedNodes.empty())397 COUT(1) << "Error: Requested GameState transition is not allowed. Ignoring." << std::endl;398 else399 this->requestedStateNodes_.insert(requestedStateNodes_.end(), requestedNodes.begin(), requestedNodes.end());400 }401 402 void Game::requestStates(const std::string& names)403 {404 SubString tokens(names, ",;", " ");405 for (unsigned int i = 0; i < tokens.size(); ++i)406 this->requestState(tokens[i]);407 }408 409 void Game::popState()410 {411 shared_ptr<GameStateTreeNode> lastRequestedNode;412 if (this->requestedStateNodes_.empty())413 lastRequestedNode = this->loadedTopStateNode_;414 else415 lastRequestedNode = this->requestedStateNodes_.back();416 if (lastRequestedNode != this->rootStateNode_)417 this->requestState(lastRequestedNode->parent_.lock()->name_);418 else419 COUT(2) << "Warning: Can't pop the internal dummy root GameState" << std::endl;420 }421 422 shared_ptr<GameState> Game::getState(const std::string& name)423 {424 GameStateMap::const_iterator it = constructedStates_.find(name);425 if (it != constructedStates_.end())426 return it->second;427 else428 {429 std::map<std::string, GameStateInfo>::const_iterator it = gameStateDeclarations_s.find(name);430 if (it != gameStateDeclarations_s.end())431 COUT(1) << "Error: GameState '" << name << "' has not yet been loaded." << std::endl;432 else433 COUT(1) << "Error: Could not find GameState '" << name << "'." << std::endl;434 return shared_ptr<GameState>();435 }436 }437 438 void Game::setStateHierarchy(const std::string& str)439 {440 // Split string into pieces of the form whitespacesText441 std::vector<std::pair<std::string, int> > stateStrings;442 size_t pos = 0;443 size_t startPos = 0;444 while (pos < str.size())445 {446 int indentation = 0;447 while (pos < str.size() && str[pos] == ' ')448 ++indentation, ++pos;449 startPos = pos;450 while (pos < str.size() && str[pos] != ' ')451 ++pos;452 stateStrings.push_back(std::make_pair(str.substr(startPos, pos - startPos), indentation));453 }454 if (stateStrings.empty())455 ThrowException(GameState, "Emtpy GameState hierarchy provided, terminating.");456 // Add element with large identation to detect the last with just an iterator457 stateStrings.push_back(std::make_pair("", -1));458 459 // Parse elements recursively460 std::vector<std::pair<std::string, int> >::const_iterator begin = stateStrings.begin();461 parseStates(begin, this->rootStateNode_);462 }463 464 /*** Internal ***/465 466 void Game::parseStates(std::vector<std::pair<std::string, int> >::const_iterator& it, shared_ptr<GameStateTreeNode> currentNode)467 {468 SubString tokens(it->first, ",");469 std::vector<std::pair<std::string, int> >::const_iterator startIt = it;470 471 for (unsigned int i = 0; i < tokens.size(); ++i)472 {473 it = startIt; // Reset iterator to the beginning of the sub tree474 if (!this->checkState(tokens[i]))475 ThrowException(GameState, "GameState with name '" << tokens[i] << "' not found!");476 if (tokens[i] == this->rootStateNode_->name_)477 ThrowException(GameState, "You shouldn't use 'emptyRootGameState' in the hierarchy...");478 shared_ptr<GameStateTreeNode> node(new GameStateTreeNode());479 node->name_ = tokens[i];480 node->parent_ = currentNode;481 currentNode->children_.push_back(node);482 483 int currentLevel = it->second;484 ++it;485 while (it->second != -1)486 {487 if (it->second <= currentLevel)488 break;489 else if (it->second == currentLevel + 1)490 parseStates(it, node);491 else492 ThrowException(GameState, "Indentation error while parsing the hierarchy.");493 }494 }495 }496 497 void Game::loadGraphics()498 {499 if (!GameMode::showsGraphics())500 {501 core_->loadGraphics();502 Loki::ScopeGuard graphicsUnloader = Loki::MakeObjGuard(*this, &Game::unloadGraphics);503 504 // Construct all the GameStates that require graphics505 for (std::map<std::string, GameStateInfo>::const_iterator it = gameStateDeclarations_s.begin();506 it != gameStateDeclarations_s.end(); ++it)507 {508 if (it->second.bGraphicsMode)509 {510 // Game state loading failure is serious --> don't catch511 shared_ptr<GameState> gameState = GameStateFactory::fabricate(it->second);512 if (!constructedStates_.insert(std::make_pair(513 it->second.stateName, gameState)).second)514 assert(false); // GameState was already created!515 }516 }517 graphicsUnloader.Dismiss();518 }519 }520 521 void Game::unloadGraphics()522 {523 if (GameMode::showsGraphics())524 {525 // Destroy all the GameStates that require graphics526 for (GameStateMap::iterator it = constructedStates_.begin(); it != constructedStates_.end();)527 {528 if (it->second->getInfo().bGraphicsMode)529 constructedStates_.erase(it++);530 else531 ++it;532 }533 534 core_->unloadGraphics();535 }536 }537 538 bool Game::checkState(const std::string& name) const539 {540 std::map<std::string, GameStateInfo>::const_iterator it = gameStateDeclarations_s.find(name);541 if (it == gameStateDeclarations_s.end())542 return false;543 else544 return true;545 }546 547 void Game::loadState(const std::string& name)548 {549 this->bChangingState_ = true;550 LOKI_ON_BLOCK_EXIT_OBJ(*this, &Game::resetChangingState);551 552 // If state requires graphics, load it553 Loki::ScopeGuard graphicsUnloader = Loki::MakeObjGuard(*this, &Game::unloadGraphics);554 if (gameStateDeclarations_s[name].bGraphicsMode && !GameMode::showsGraphics())555 this->loadGraphics();556 else557 graphicsUnloader.Dismiss();558 559 shared_ptr<GameState> state = this->getState(name);560 state->activateInternal();561 if (!this->loadedStates_.empty())562 this->loadedStates_.back()->activity_.topState = false;563 this->loadedStates_.push_back(state);564 state->activity_.topState = true;565 566 graphicsUnloader.Dismiss();567 }568 569 void Game::unloadState(const std::string& name)570 {571 this->bChangingState_ = true;572 try573 {574 shared_ptr<GameState> state = this->getState(name);575 state->activity_.topState = false;576 this->loadedStates_.pop_back();577 if (!this->loadedStates_.empty())578 this->loadedStates_.back()->activity_.topState = true;579 state->deactivateInternal();580 }581 catch (...)582 {583 COUT(2) << "Warning: Unloading GameState '" << name << "' threw an exception: " << Exception::handleMessage() << std::endl;584 COUT(2) << " There might be potential resource leaks involved! To avoid this, improve exception-safety." << std::endl;585 }586 // Check if graphics is still required587 if (!bAbort_)588 {589 bool graphicsRequired = false;590 for (unsigned i = 0; i < loadedStates_.size(); ++i)591 graphicsRequired |= loadedStates_[i]->getInfo().bGraphicsMode;592 if (!graphicsRequired)593 this->unloadGraphics();594 }595 this->bChangingState_ = false;596 }597 598 /*static*/ std::map<std::string, shared_ptr<Game::GameStateFactory> >& Game::GameStateFactory::getFactories()599 {600 static std::map<std::string, shared_ptr<GameStateFactory> > factories;601 return factories;602 }603 604 /*static*/ shared_ptr<GameState> Game::GameStateFactory::fabricate(const GameStateInfo& info)605 {606 std::map<std::string, shared_ptr<Game::GameStateFactory> >::const_iterator it = getFactories().find(info.className);607 assert(it != getFactories().end());608 return it->second->fabricateInternal(info);609 }610 100 }
Note: See TracChangeset
for help on using the changeset viewer.