Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

source: code/trunk/src/libraries/core/Game.cc @ 8185

Last change on this file since 8185 was 8079, checked in by landauf, 14 years ago

merged usability branch back to trunk

incomplete summary of the changes in this branch:

  • enhanced keyboard navigation in GUIs
  • implemented new graphics menu and changeable window size at runtime
  • added developer mode
  • HUD shows if game is paused, game pauses if ingame menu is opened
  • removed a few obsolete commands and hid some that are more for internal use
  • numpad works in console and gui
  • faster loading of level info
  • enhanced usage of compositors (Shader class)
  • improved camera handling, configurable FOV and aspect ratio
  • Property svn:eol-style set to native
File size: 23.3 KB
Line 
1/*
2 *   ORXONOX - the hottest 3D action shooter ever to exist
3 *                    > www.orxonox.net <
4 *
5 *
6 *   License notice:
7 *
8 *   This program is free software; you can redistribute it and/or
9 *   modify it under the terms of the GNU General Public License
10 *   as published by the Free Software Foundation; either version 2
11 *   of the License, or (at your option) any later version.
12 *
13 *   This program is distributed in the hope that it will be useful,
14 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
15 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 *   GNU General Public License for more details.
17 *
18 *   You should have received a copy of the GNU General Public License
19 *   along with this program; if not, write to the Free Software
20 *   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
21 *
22 *   Author:
23 *      Reto Grieder
24 *   Co-authors:
25 *      ...
26 *
27 */
28
29/**
30@file
31@brief
32    Implementation of the Game class.
33*/
34
35#include "Game.h"
36
37#include <exception>
38#include <boost/weak_ptr.hpp>
39#include <loki/ScopeGuard.h>
40
41#include "util/Clock.h"
42#include "util/Debug.h"
43#include "util/Exception.h"
44#include "util/Sleep.h"
45#include "util/SubString.h"
46#include "CommandLineParser.h"
47#include "Core.h"
48#include "CoreIncludes.h"
49#include "ConfigValueIncludes.h"
50#include "GameMode.h"
51#include "GameState.h"
52#include "GraphicsManager.h"
53#include "GUIManager.h"
54#include "command/ConsoleCommand.h"
55
56namespace orxonox
57{
58    static void stop_game()
59        { Game::getInstance().stop(); }
60    SetConsoleCommand("exit", &stop_game);
61    static void printFPS()
62        { COUT(0) << Game::getInstance().getAvgFPS() << std::endl; }
63    SetConsoleCommand("Stats", "printFPS", &printFPS);
64    static void printTickTime()
65        { COUT(0) << Game::getInstance().getAvgTickTime() << std::endl; }
66    SetConsoleCommand("Stats", "printTickTime", &printTickTime);
67
68    std::map<std::string, GameStateInfo> Game::gameStateDeclarations_s;
69    Game* Game::singletonPtr_s = 0;
70
71    //! Represents one node of the game state tree.
72    struct GameStateTreeNode
73    {
74        std::string name_;
75        weak_ptr<GameStateTreeNode> parent_;
76        std::vector<shared_ptr<GameStateTreeNode> > children_;
77    };
78
79    Game::Game(const std::string& cmdLine)
80        // Destroy factories before the Core!
81        : gsFactoryDestroyer_(Game::GameStateFactory::getFactories(), &std::map<std::string, shared_ptr<GameStateFactory> >::clear)
82    {
83        this->bAbort_ = false;
84        bChangingState_ = false;
85
86#ifdef ORXONOX_PLATFORM_WINDOWS
87        minimumSleepTime_ = 1000/*us*/;
88#else
89        minimumSleepTime_ = 0/*us*/;
90#endif
91
92        // reset statistics
93        this->statisticsStartTime_ = 0;
94        this->statisticsTickTimes_.clear();
95        this->periodTickTime_ = 0;
96        this->periodTime_ = 0;
97        this->avgFPS_ = 0.0f;
98        this->avgTickTime_ = 0.0f;
99        this->excessSleepTime_ = 0;
100
101        // Create an empty root state
102        this->declareGameState<GameState>("GameState", "emptyRootGameState", true, false);
103
104        // Set up a basic clock to keep time
105        this->gameClock_.reset(new Clock());
106
107        // Create the Core
108        this->core_.reset(new Core(cmdLine));
109
110        // Do this after the Core creation!
111        ClassIdentifier<Game>::getIdentifier("Game")->initialiseObject(this, "Game", true);
112        this->setConfigValues();
113
114        // After the core has been created, we can safely instantiate the GameStates that don't require graphics
115        for (std::map<std::string, GameStateInfo>::const_iterator it = gameStateDeclarations_s.begin();
116            it != gameStateDeclarations_s.end(); ++it)
117        {
118            if (!it->second.bGraphicsMode)
119                constructedStates_[it->second.stateName] = GameStateFactory::fabricate(it->second);
120        }
121
122        // The empty root state is ALWAYS loaded!
123        this->rootStateNode_ = shared_ptr<GameStateTreeNode>(new GameStateTreeNode());
124        this->rootStateNode_->name_ = "emptyRootGameState";
125        this->loadedTopStateNode_ = this->rootStateNode_;
126        this->loadedStates_.push_back(this->getState(rootStateNode_->name_));
127    }
128
129    /**
130    @brief
131        All destruction code is handled by scoped_ptrs and SimpleScopeGuards.
132    */
133    Game::~Game()
134    {
135        // Remove us from the object lists again to avoid problems when destroying them
136        this->unregisterObject();
137    }
138
139    void Game::setConfigValues()
140    {
141        SetConfigValue(statisticsRefreshCycle_, 250000)
142            .description("Sets the time in microseconds interval at which average fps, etc. get updated.");
143        SetConfigValue(statisticsAvgLength_, 1000000)
144            .description("Sets the time in microseconds interval at which average fps, etc. gets calculated.");
145
146        SetConfigValueExternal(fpsLimit_, "GraphicsSettings", "fpsLimit", 50)
147            .description("Sets the desired frame rate (0 for no limit).");
148    }
149
150    /**
151    @brief
152        Main loop of the orxonox game.
153    @note
154        We use the Ogre::Timer to measure time since it uses the most precise
155        method an any platform (however the windows timer lacks time when under
156        heavy kernel load!).
157    */
158    void Game::run()
159    {
160        if (this->requestedStateNodes_.empty())
161            COUT(0) << "Warning: Starting game without requesting GameState. This automatically terminates the program." << std::endl;
162
163        // START GAME
164        // first delta time should be about 0 seconds
165        this->gameClock_->capture();
166        // A first item is required for the fps limiter
167        StatisticsTickInfo tickInfo = {0, 0};
168        statisticsTickTimes_.push_back(tickInfo);
169        while (!this->bAbort_ && (!this->loadedStates_.empty() || this->requestedStateNodes_.size() > 0))
170        {
171            // Generate the dt
172            this->gameClock_->capture();
173
174            // Statistics init
175            StatisticsTickInfo tickInfo = {gameClock_->getMicroseconds(), 0};
176            statisticsTickTimes_.push_back(tickInfo);
177            this->periodTime_ += this->gameClock_->getDeltaTimeMicroseconds();
178
179            // Update the GameState stack if required
180            this->updateGameStateStack();
181
182            // Core preUpdate
183            try
184                { this->core_->preUpdate(*this->gameClock_); }
185            catch (...)
186            {
187                COUT(0) << "An exception occurred in the Core preUpdate: " << Exception::handleMessage() << std::endl;
188                COUT(0) << "This should really never happen! Closing the program." << std::endl;
189                this->stop();
190                break;
191            }
192
193            // Update the GameStates bottom up in the stack
194            this->updateGameStates();
195
196            // Core postUpdate
197            try
198                { this->core_->postUpdate(*this->gameClock_); }
199            catch (...)
200            {
201                COUT(0) << "An exception occurred in the Core postUpdate: " << Exception::handleMessage() << std::endl;
202                COUT(0) << "This should really never happen! Closing the program." << std::endl;
203                this->stop();
204                break;
205            }
206
207            // Evaluate statistics
208            this->updateStatistics();
209
210            // Limit frame rate
211            static bool hasVSync = GraphicsManager::getInstance().hasVSyncEnabled(); // can be static since changes of VSync currently require a restart
212            if (this->fpsLimit_ > 0 && !hasVSync)
213                this->updateFPSLimiter();
214        }
215
216        // UNLOAD all remaining states
217        while (this->loadedStates_.size() > 1)
218            this->unloadState(this->loadedStates_.back()->getName());
219        this->loadedTopStateNode_ = this->rootStateNode_;
220        this->requestedStateNodes_.clear();
221    }
222
223    void Game::updateGameStateStack()
224    {
225        while (this->requestedStateNodes_.size() > 0)
226        {
227            shared_ptr<GameStateTreeNode> requestedStateNode = this->requestedStateNodes_.front();
228            assert(this->loadedTopStateNode_);
229            if (!this->loadedTopStateNode_->parent_.expired() && requestedStateNode == this->loadedTopStateNode_->parent_.lock())
230                this->unloadState(loadedTopStateNode_->name_);
231            else // has to be child
232            {
233                try
234                {
235                    this->loadState(requestedStateNode->name_);
236                }
237                catch (...)
238                {
239                    COUT(1) << "Error: Loading GameState '" << requestedStateNode->name_ << "' failed: " << Exception::handleMessage() << std::endl;
240                    // All scheduled operations have now been rendered inert --> flush them and issue a warning
241                    if (this->requestedStateNodes_.size() > 1)
242                        COUT(4) << "All " << this->requestedStateNodes_.size() - 1 << " scheduled transitions have been ignored." << std::endl;
243                    this->requestedStateNodes_.clear();
244                    break;
245                }
246            }
247            this->loadedTopStateNode_ = requestedStateNode;
248            this->requestedStateNodes_.erase(this->requestedStateNodes_.begin());
249        }
250    }
251
252    void Game::updateGameStates()
253    {
254        // Note: The first element is the empty root state, which doesn't need ticking
255        for (GameStateVector::const_iterator it = this->loadedStates_.begin() + 1;
256            it != this->loadedStates_.end(); ++it)
257        {
258            try
259            {
260                // Add tick time for most of the states
261                uint64_t timeBeforeTick = 0;
262                if ((*it)->getInfo().bIgnoreTickTime)
263                    timeBeforeTick = this->gameClock_->getRealMicroseconds();
264                (*it)->update(*this->gameClock_);
265                if ((*it)->getInfo().bIgnoreTickTime)
266                    this->subtractTickTime(static_cast<int32_t>(this->gameClock_->getRealMicroseconds() - timeBeforeTick));
267            }
268            catch (...)
269            {
270                COUT(1) << "An exception occurred while updating '" << (*it)->getName() << "': " << Exception::handleMessage() << std::endl;
271                COUT(1) << "This should really never happen!" << std::endl;
272                COUT(1) << "Unloading all GameStates depending on the one that crashed." << std::endl;
273                shared_ptr<GameStateTreeNode> current = this->loadedTopStateNode_;
274                while (current->name_ != (*it)->getName() && current)
275                    current = current->parent_.lock();
276                if (current && current->parent_.lock())
277                    this->requestState(current->parent_.lock()->name_);
278                else
279                    this->stop();
280                break;
281            }
282        }
283    }
284
285    void Game::updateStatistics()
286    {
287        // Add the tick time of this frame (rendering time has already been subtracted)
288        uint64_t currentTime = gameClock_->getMicroseconds();
289        uint64_t currentRealTime = gameClock_->getRealMicroseconds();
290        this->statisticsTickTimes_.back().tickLength += (uint32_t)(currentRealTime - currentTime);
291        this->periodTickTime_ += (uint32_t)(currentRealTime - currentTime);
292        if (this->periodTime_ > this->statisticsRefreshCycle_)
293        {
294            std::list<StatisticsTickInfo>::iterator it = this->statisticsTickTimes_.begin();
295            assert(it != this->statisticsTickTimes_.end());
296            int64_t lastTime = currentTime - this->statisticsAvgLength_;
297            if (static_cast<int64_t>(it->tickTime) < lastTime)
298            {
299                do
300                {
301                    assert(this->periodTickTime_ >= it->tickLength);
302                    this->periodTickTime_ -= it->tickLength;
303                    ++it;
304                    assert(it != this->statisticsTickTimes_.end());
305                } while (static_cast<int64_t>(it->tickTime) < lastTime);
306                this->statisticsTickTimes_.erase(this->statisticsTickTimes_.begin(), it);
307            }
308
309            uint32_t framesPerPeriod = this->statisticsTickTimes_.size();
310            // Why minus 1? No idea, but otherwise the fps rate is always (from 10 to 200!) one frame too low
311            this->avgFPS_ = -1 + static_cast<float>(framesPerPeriod) / (currentTime - this->statisticsTickTimes_.front().tickTime) * 1000000.0f;
312            this->avgTickTime_ = static_cast<float>(this->periodTickTime_) / framesPerPeriod / 1000.0f;
313
314            this->periodTime_ -= this->statisticsRefreshCycle_;
315        }
316    }
317
318    void Game::updateFPSLimiter()
319    {
320        uint64_t nextTime = gameClock_->getMicroseconds() - excessSleepTime_ + static_cast<uint32_t>(1000000.0f / fpsLimit_);
321        uint64_t currentRealTime = gameClock_->getRealMicroseconds();
322        while (currentRealTime < nextTime - minimumSleepTime_)
323        {
324            usleep((unsigned long)(nextTime - currentRealTime));
325            currentRealTime = gameClock_->getRealMicroseconds();
326        }
327        // Integrate excess to avoid steady state error
328        excessSleepTime_ = (int)(currentRealTime - nextTime);
329        // Anti windup
330        if (excessSleepTime_ > 50000) // 20ms is about the maximum time Windows would sleep for too long
331            excessSleepTime_ = 50000;
332    }
333
334    void Game::stop()
335    {
336        this->bAbort_ = true;
337    }
338
339    void Game::subtractTickTime(int32_t length)
340    {
341        assert(!this->statisticsTickTimes_.empty());
342        this->statisticsTickTimes_.back().tickLength -= length;
343        this->periodTickTime_ -= length;
344    }
345
346
347    /***** GameState related *****/
348
349    void Game::requestState(const std::string& name)
350    {
351        if (!this->checkState(name))
352        {
353            COUT(2) << "Warning: GameState named '" << name << "' doesn't exist!" << std::endl;
354            return;
355        }
356
357        if (this->bChangingState_)
358        {
359            COUT(2) << "Warning: Requesting GameStates while loading/unloading a GameState is illegal! Ignoring." << std::endl;
360            return;
361        }
362
363        shared_ptr<GameStateTreeNode> lastRequestedNode;
364        if (this->requestedStateNodes_.empty())
365            lastRequestedNode = this->loadedTopStateNode_;
366        else
367            lastRequestedNode = this->requestedStateNodes_.back();
368        if (name == lastRequestedNode->name_)
369        {
370            COUT(2) << "Warning: Requesting the currently active state! Ignoring." << std::endl;
371            return;
372        }
373
374        // Check children first
375        std::vector<shared_ptr<GameStateTreeNode> > requestedNodes;
376        for (unsigned int i = 0; i < lastRequestedNode->children_.size(); ++i)
377        {
378            if (lastRequestedNode->children_[i]->name_ == name)
379            {
380                requestedNodes.push_back(lastRequestedNode->children_[i]);
381                break;
382            }
383        }
384
385        if (requestedNodes.empty())
386        {
387            // Check parent and all its grand parents
388            shared_ptr<GameStateTreeNode> currentNode = lastRequestedNode;
389            while (currentNode != NULL)
390            {
391                if (currentNode->name_ == name)
392                    break;
393                currentNode = currentNode->parent_.lock();
394                requestedNodes.push_back(currentNode);
395            }
396            if (currentNode == NULL)
397                requestedNodes.clear();
398        }
399
400        if (requestedNodes.empty())
401            COUT(1) << "Error: Requested GameState transition is not allowed. Ignoring." << std::endl;
402        else
403            this->requestedStateNodes_.insert(requestedStateNodes_.end(), requestedNodes.begin(), requestedNodes.end());
404    }
405
406    void Game::requestStates(const std::string& names)
407    {
408        SubString tokens(names, ",;", " ");
409        for (unsigned int i = 0; i < tokens.size(); ++i)
410            this->requestState(tokens[i]);
411    }
412
413    void Game::popState()
414    {
415        shared_ptr<GameStateTreeNode> lastRequestedNode;
416        if (this->requestedStateNodes_.empty())
417            lastRequestedNode = this->loadedTopStateNode_;
418        else
419            lastRequestedNode = this->requestedStateNodes_.back();
420        if (lastRequestedNode != this->rootStateNode_)
421            this->requestState(lastRequestedNode->parent_.lock()->name_);
422        else
423            COUT(2) << "Warning: Can't pop the internal dummy root GameState" << std::endl;
424    }
425
426    shared_ptr<GameState> Game::getState(const std::string& name)
427    {
428        GameStateMap::const_iterator it = constructedStates_.find(name);
429        if (it != constructedStates_.end())
430            return it->second;
431        else
432        {
433            std::map<std::string, GameStateInfo>::const_iterator it = gameStateDeclarations_s.find(name);
434            if (it != gameStateDeclarations_s.end())
435                COUT(1) << "Error: GameState '" << name << "' has not yet been loaded." << std::endl;
436            else
437                COUT(1) << "Error: Could not find GameState '" << name << "'." << std::endl;
438            return shared_ptr<GameState>();
439        }
440    }
441
442    void Game::setStateHierarchy(const std::string& str)
443    {
444        // Split string into pieces of the form whitespacesText
445        std::vector<std::pair<std::string, int> > stateStrings;
446        size_t pos = 0;
447        size_t startPos = 0;
448        while (pos < str.size())
449        {
450            int indentation = 0;
451            while (pos < str.size() && str[pos] == ' ')
452                ++indentation, ++pos;
453            startPos = pos;
454            while (pos < str.size() && str[pos] != ' ')
455                ++pos;
456            stateStrings.push_back(std::make_pair(str.substr(startPos, pos - startPos), indentation));
457        }
458        if (stateStrings.empty())
459            ThrowException(GameState, "Emtpy GameState hierarchy provided, terminating.");
460        // Add element with large identation to detect the last with just an iterator
461        stateStrings.push_back(std::make_pair("", -1));
462
463        // Parse elements recursively
464        std::vector<std::pair<std::string, int> >::const_iterator begin = stateStrings.begin();
465        parseStates(begin, this->rootStateNode_);
466    }
467
468    /*** Internal ***/
469
470    void Game::parseStates(std::vector<std::pair<std::string, int> >::const_iterator& it, shared_ptr<GameStateTreeNode> currentNode)
471    {
472        SubString tokens(it->first, ",");
473        std::vector<std::pair<std::string, int> >::const_iterator startIt = it;
474
475        for (unsigned int i = 0; i < tokens.size(); ++i)
476        {
477            it = startIt; // Reset iterator to the beginning of the sub tree
478            if (!this->checkState(tokens[i]))
479                ThrowException(GameState, "GameState with name '" << tokens[i] << "' not found!");
480            if (tokens[i] == this->rootStateNode_->name_)
481                ThrowException(GameState, "You shouldn't use 'emptyRootGameState' in the hierarchy...");
482            shared_ptr<GameStateTreeNode> node(new GameStateTreeNode());
483            node->name_ = tokens[i];
484            node->parent_ = currentNode;
485            currentNode->children_.push_back(node);
486
487            int currentLevel = it->second;
488            ++it;
489            while (it->second != -1)
490            {
491                if (it->second <= currentLevel)
492                    break;
493                else if (it->second == currentLevel + 1)
494                    parseStates(it, node);
495                else
496                    ThrowException(GameState, "Indentation error while parsing the hierarchy.");
497            }
498        }
499    }
500
501    void Game::loadGraphics()
502    {
503        if (!GameMode::showsGraphics())
504        {
505            core_->loadGraphics();
506            Loki::ScopeGuard graphicsUnloader = Loki::MakeObjGuard(*this, &Game::unloadGraphics);
507
508            // Construct all the GameStates that require graphics
509            for (std::map<std::string, GameStateInfo>::const_iterator it = gameStateDeclarations_s.begin();
510                it != gameStateDeclarations_s.end(); ++it)
511            {
512                if (it->second.bGraphicsMode)
513                {
514                    // Game state loading failure is serious --> don't catch
515                    shared_ptr<GameState> gameState = GameStateFactory::fabricate(it->second);
516                    if (!constructedStates_.insert(std::make_pair(
517                        it->second.stateName, gameState)).second)
518                        assert(false); // GameState was already created!
519                }
520            }
521            graphicsUnloader.Dismiss();
522        }
523    }
524
525    void Game::unloadGraphics()
526    {
527        if (GameMode::showsGraphics())
528        {
529            // Destroy all the GameStates that require graphics
530            for (GameStateMap::iterator it = constructedStates_.begin(); it != constructedStates_.end();)
531            {
532                if (it->second->getInfo().bGraphicsMode)
533                    constructedStates_.erase(it++);
534                else
535                    ++it;
536            }
537
538            core_->unloadGraphics();
539        }
540    }
541
542    bool Game::checkState(const std::string& name) const
543    {
544        std::map<std::string, GameStateInfo>::const_iterator it = gameStateDeclarations_s.find(name);
545        if (it == gameStateDeclarations_s.end())
546            return false;
547        else
548            return true;
549    }
550
551    void Game::loadState(const std::string& name)
552    {
553        this->bChangingState_ = true;
554        LOKI_ON_BLOCK_EXIT_OBJ(*this, &Game::resetChangingState);
555
556        // If state requires graphics, load it
557        Loki::ScopeGuard graphicsUnloader = Loki::MakeObjGuard(*this, &Game::unloadGraphics);
558        if (gameStateDeclarations_s[name].bGraphicsMode && !GameMode::showsGraphics())
559            this->loadGraphics();
560        else
561            graphicsUnloader.Dismiss();
562
563        shared_ptr<GameState> state = this->getState(name);
564        state->activateInternal();
565        if (!this->loadedStates_.empty())
566            this->loadedStates_.back()->activity_.topState = false;
567        this->loadedStates_.push_back(state);
568        state->activity_.topState = true;
569
570        graphicsUnloader.Dismiss();
571    }
572
573    void Game::unloadState(const std::string& name)
574    {
575        this->bChangingState_ = true;
576        try
577        {
578            shared_ptr<GameState> state = this->getState(name);
579            state->activity_.topState = false;
580            this->loadedStates_.pop_back();
581            if (!this->loadedStates_.empty())
582                this->loadedStates_.back()->activity_.topState = true;
583            state->deactivateInternal();
584        }
585        catch (...)
586        {
587            COUT(2) << "Warning: Unloading GameState '" << name << "' threw an exception: " << Exception::handleMessage() << std::endl;
588            COUT(2) << "         There might be potential resource leaks involved! To avoid this, improve exception-safety." << std::endl;
589        }
590        // Check if graphics is still required
591        if (!bAbort_)
592        {
593            bool graphicsRequired = false;
594            for (unsigned i = 0; i < loadedStates_.size(); ++i)
595                graphicsRequired |= loadedStates_[i]->getInfo().bGraphicsMode;
596            if (!graphicsRequired)
597                this->unloadGraphics();
598        }
599        this->bChangingState_ = false;
600    }
601
602    /*static*/ std::map<std::string, shared_ptr<Game::GameStateFactory> >& Game::GameStateFactory::getFactories()
603    {
604        static std::map<std::string, shared_ptr<GameStateFactory> > factories;
605        return factories;
606    }
607
608    /*static*/ shared_ptr<GameState> Game::GameStateFactory::fabricate(const GameStateInfo& info)
609    {
610        std::map<std::string, shared_ptr<Game::GameStateFactory> >::const_iterator it = getFactories().find(info.className);
611        assert(it != getFactories().end());
612        return it->second->fabricateInternal(info);
613    }
614}
Note: See TracBrowser for help on using the repository browser.