Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

source: code/branches/resource/src/core/Game.cc @ 3359

Last change on this file since 3359 was 3359, checked in by rgrieder, 15 years ago
  • Stupid MSVC extension: map::erase delivers an iterator but it shouldn't.
  • And fixed another bug in orxonox_cast.
  • Added two missing includes.
  • Property svn:eol-style set to native
File size: 22.8 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
40#include "util/Debug.h"
41#include "util/Exception.h"
42#include "util/Sleep.h"
43#include "util/SubString.h"
44#include "Clock.h"
45#include "CommandLine.h"
46#include "ConsoleCommand.h"
47#include "Core.h"
48#include "CoreIncludes.h"
49#include "ConfigValueIncludes.h"
50#include "GameMode.h"
51#include "GameState.h"
52
53namespace orxonox
54{
55    using boost::shared_ptr;
56    using boost::weak_ptr;
57
58    static void stop_game()
59        { Game::getInstance().stop(); }
60    SetConsoleCommandShortcutExternAlias(stop_game, "exit");
61
62    std::map<std::string, GameStateInfo> Game::gameStateDeclarations_s;
63    Game* Game::singletonRef_s = 0;
64
65
66    /**
67    @brief
68        Represents one node of the game state tree.
69    */
70    struct GameStateTreeNode
71    {
72        std::string name_;
73        weak_ptr<GameStateTreeNode> parent_;
74        std::vector<shared_ptr<GameStateTreeNode> > children_;
75    };
76
77
78    /**
79    @brief
80        Another helper class for the Game singleton: we cannot derive
81        Game from OrxonoxClass because we need to handle the Identifier
82        destruction in the Core destructor.
83    */
84    class GameConfiguration : public OrxonoxClass
85    {
86    public:
87        GameConfiguration()
88        {
89            RegisterRootObject(GameConfiguration);
90            this->setConfigValues();
91        }
92
93        void setConfigValues()
94        {
95            SetConfigValue(statisticsRefreshCycle_, 250000)
96                .description("Sets the time in microseconds interval at which average fps, etc. get updated.");
97            SetConfigValue(statisticsAvgLength_, 1000000)
98                .description("Sets the time in microseconds interval at which average fps, etc. gets calculated.");
99            SetConfigValue(fpsLimit_, 50)
100                .description("Sets the desired framerate (0 for no limit).");
101        }
102
103        unsigned int statisticsRefreshCycle_;
104        unsigned int statisticsAvgLength_;
105        unsigned int fpsLimit_;
106    };
107
108
109    /**
110    @brief
111        Non-initialising constructor.
112    */
113    Game::Game(const std::string& cmdLine)
114    {
115        if (singletonRef_s != 0)
116        {
117            COUT(0) << "Error: The Game singleton cannot be recreated! Shutting down." << std::endl;
118            abort();
119        }
120        singletonRef_s = this;
121
122        this->bAbort_ = false;
123        bChangingState_ = false;
124
125#ifdef ORXONOX_PLATFORM_WINDOWS
126        minimumSleepTime_ = 1000/*us*/;
127#else
128        minimumSleepTime_ = 0/*us*/;
129#endif
130
131        // Create an empty root state
132        this->declareGameState<GameState>("GameState", "emptyRootGameState", true, false);
133
134        // Set up a basic clock to keep time
135        this->gameClock_ = new Clock();
136
137        // Create the Core
138        this->core_ = new Core(cmdLine);
139
140        // After the core has been created, we can safely instantiate the GameStates that don't require graphics
141        for (std::map<std::string, GameStateInfo>::const_iterator it = gameStateDeclarations_s.begin();
142            it != gameStateDeclarations_s.end(); ++it)
143        {
144            if (!it->second.bGraphicsMode)
145                constructedStates_[it->second.stateName] = GameStateFactory::fabricate(it->second);
146        }
147
148        // The empty root state is ALWAYS loaded!
149        this->rootStateNode_ = shared_ptr<GameStateTreeNode>(new GameStateTreeNode());
150        this->rootStateNode_->name_ = "emptyRootGameState";
151        this->loadedTopStateNode_ = this->rootStateNode_;
152        this->loadedStates_.push_back(this->getState(rootStateNode_->name_));
153
154        // Do this after the Core creation!
155        this->configuration_ = new GameConfiguration();
156    }
157
158    /**
159    @brief
160    */
161    Game::~Game()
162    {
163        // Destroy the configuration helper class instance
164        delete this->configuration_;
165
166        // Destroy the GameStates (note that the nodes still point to them, but doesn't matter)
167        for (std::map<std::string, GameState*>::const_iterator it = constructedStates_.begin();
168            it != constructedStates_.end(); ++it)
169            delete it->second;
170
171        // Destroy the Core and with it almost everything
172        delete this->core_;
173        delete this->gameClock_;
174
175        // Take care of the GameStateFactories
176        GameStateFactory::destroyFactories();
177
178        // Don't assign singletonRef_s with NULL! Recreation is not supported
179    }
180
181    /**
182    @brief
183        Main loop of the orxonox game.
184    @note
185        We use the Ogre::Timer to measure time since it uses the most precise
186        method an any platform (however the windows timer lacks time when under
187        heavy kernel load!).
188    */
189    void Game::run()
190    {
191        if (this->requestedStateNodes_.empty())
192            COUT(0) << "Warning: Starting game without requesting GameState. This automatically terminates the program." << std::endl;
193
194        // reset statistics
195        this->statisticsStartTime_ = 0;
196        this->statisticsTickTimes_.clear();
197        this->periodTickTime_ = 0;
198        this->periodTime_ = 0;
199        this->avgFPS_ = 0.0f;
200        this->avgTickTime_ = 0.0f;
201        this->excessSleepTime_ = 0;
202
203        // START GAME
204        // first delta time should be about 0 seconds
205        this->gameClock_->capture();
206        // A first item is required for the fps limiter
207        StatisticsTickInfo tickInfo = {0, 0};
208        statisticsTickTimes_.push_back(tickInfo);
209        while (!this->bAbort_ && (!this->loadedStates_.empty() || this->requestedStateNodes_.size() > 0))
210        {
211            // Generate the dt
212            this->gameClock_->capture();
213
214            // Statistics init
215            StatisticsTickInfo tickInfo = {gameClock_->getMicroseconds(), 0};
216            statisticsTickTimes_.push_back(tickInfo);
217            this->periodTime_ += this->gameClock_->getDeltaTimeMicroseconds();
218
219            // Update the GameState stack if required
220            this->updateGameStateStack();
221
222            // Core preUpdate (doesn't throw)
223            if (!this->core_->preUpdate(*this->gameClock_))
224            {
225                this->stop();
226                break;
227            }
228
229            // Update the GameStates bottom up in the stack
230            this->updateGameStates();
231
232            // Core postUpdate (doesn't throw)
233            if (!this->core_->postUpdate(*this->gameClock_))
234            {
235                this->stop();
236                break;
237            }
238
239            // Evaluate statistics
240            this->updateStatistics();
241
242            // Limit framerate
243            this->updateFPSLimiter();
244        }
245
246        // UNLOAD all remaining states
247        while (this->loadedStates_.size() > 1)
248            this->unloadState(this->loadedStates_.back()->getName());
249        this->loadedTopStateNode_ = this->rootStateNode_;
250        this->requestedStateNodes_.clear();
251    }
252
253    void Game::updateGameStateStack()
254    {
255        while (this->requestedStateNodes_.size() > 0)
256        {
257            shared_ptr<GameStateTreeNode> requestedStateNode = this->requestedStateNodes_.front();
258            assert(this->loadedTopStateNode_);
259            if (!this->loadedTopStateNode_->parent_.expired() && requestedStateNode == this->loadedTopStateNode_->parent_.lock())
260                this->unloadState(loadedTopStateNode_->name_);
261            else // has to be child
262            {
263                try
264                {
265                    this->loadState(requestedStateNode->name_);
266                }
267                catch (const std::exception& ex)
268                {
269                    COUT(1) << "Error: Loading GameState '" << requestedStateNode->name_ << "' failed: " << ex.what() << std::endl;
270                    // All scheduled operations have now been rendered inert --> flush them and issue a warning
271                    if (this->requestedStateNodes_.size() > 1)
272                        COUT(1) << "All " << this->requestedStateNodes_.size() - 1 << " scheduled transitions have been ignored." << std::endl;
273                    this->requestedStateNodes_.clear();
274                    break;
275                }
276            }
277            this->loadedTopStateNode_ = requestedStateNode;
278            this->requestedStateNodes_.erase(this->requestedStateNodes_.begin());
279        }
280    }
281
282    void Game::updateGameStates()
283    {
284        // Note: The first element is the empty root state, which doesn't need ticking
285        for (std::vector<GameState*>::const_iterator it = this->loadedStates_.begin() + 1;
286            it != this->loadedStates_.end(); ++it)
287        {
288            std::string exceptionMessage;
289            try
290            {
291                // Add tick time for most of the states
292                uint64_t timeBeforeTick;
293                if ((*it)->getInfo().bIgnoreTickTime)
294                    timeBeforeTick = this->gameClock_->getRealMicroseconds();
295                (*it)->update(*this->gameClock_);
296                if ((*it)->getInfo().bIgnoreTickTime)
297                    this->subtractTickTime(static_cast<int32_t>(this->gameClock_->getRealMicroseconds() - timeBeforeTick));
298            }
299            catch (const std::exception& ex)
300            { exceptionMessage = ex.what(); }
301            catch (...)
302            { exceptionMessage = "Unknown exception"; }
303            if (!exceptionMessage.empty())
304            {
305                COUT(1) << "An exception occurred while updating '" << (*it)->getName() << "': " << exceptionMessage << std::endl;
306                COUT(1) << "This should really never happen!" << std::endl;
307                COUT(1) << "Unloading all GameStates depending on the one that crashed." << std::endl;
308                shared_ptr<GameStateTreeNode> current = this->loadedTopStateNode_;
309                while (current->name_ != (*it)->getName() && current)
310                    current = current->parent_.lock();
311                if (current && current->parent_.lock())
312                    this->requestState(current->parent_.lock()->name_);
313                else
314                    this->stop();
315                break;
316            }
317        }
318    }
319
320    void Game::updateStatistics()
321    {
322        // Add the tick time of this frame (rendering time has already been subtracted)
323        uint64_t currentTime = gameClock_->getMicroseconds();
324        uint64_t currentRealTime = gameClock_->getRealMicroseconds();
325        this->statisticsTickTimes_.back().tickLength += currentRealTime - currentTime;
326        this->periodTickTime_ += currentRealTime - currentTime;
327        if (this->periodTime_ > this->configuration_->statisticsRefreshCycle_)
328        {
329            std::list<StatisticsTickInfo>::iterator it = this->statisticsTickTimes_.begin();
330            assert(it != this->statisticsTickTimes_.end());
331            int64_t lastTime = currentTime - this->configuration_->statisticsAvgLength_;
332            if (static_cast<int64_t>(it->tickTime) < lastTime)
333            {
334                do
335                {
336                    assert(this->periodTickTime_ >= it->tickLength);
337                    this->periodTickTime_ -= it->tickLength;
338                    ++it;
339                    assert(it != this->statisticsTickTimes_.end());
340                } while (static_cast<int64_t>(it->tickTime) < lastTime);
341                this->statisticsTickTimes_.erase(this->statisticsTickTimes_.begin(), it);
342            }
343
344            uint32_t framesPerPeriod = this->statisticsTickTimes_.size();
345            this->avgFPS_ = static_cast<float>(framesPerPeriod) / (currentTime - this->statisticsTickTimes_.front().tickTime) * 1000000.0f;
346            this->avgTickTime_ = static_cast<float>(this->periodTickTime_) / framesPerPeriod / 1000.0f;
347
348            this->periodTime_ -= this->configuration_->statisticsRefreshCycle_;
349        }
350    }
351
352    void Game::updateFPSLimiter()
353    {
354        // Why configuration_->fpsLimit_ - 1? No idea, but otherwise the fps rate is always (from 10 to 200!) one frame too high
355        uint32_t nextTime = gameClock_->getMicroseconds() - excessSleepTime_ + static_cast<uint32_t>(1000000.0f / (configuration_->fpsLimit_ - 1));
356        uint64_t currentRealTime = gameClock_->getRealMicroseconds();
357        while (currentRealTime < nextTime - minimumSleepTime_)
358        {
359            usleep(nextTime - currentRealTime);
360            currentRealTime = gameClock_->getRealMicroseconds();
361        }
362        // Integrate excess to avoid steady state error
363        excessSleepTime_ = currentRealTime - nextTime;
364        // Anti windup
365        if (excessSleepTime_ > 50000) // 20ms is about the maximum time Windows would sleep for too long
366            excessSleepTime_ = 50000;
367    }
368
369    void Game::stop()
370    {
371        this->bAbort_ = true;
372    }
373
374    void Game::subtractTickTime(int32_t length)
375    {
376        assert(!this->statisticsTickTimes_.empty());
377        this->statisticsTickTimes_.back().tickLength -= length;
378        this->periodTickTime_ -= length;
379    }
380
381
382    /***** GameState related *****/
383
384    void Game::requestState(const std::string& name)
385    {
386        if (!this->checkState(name))
387        {
388            COUT(2) << "Warning: GameState named '" << name << "' doesn't exist!" << std::endl;
389            return;
390        }
391
392        if (this->bChangingState_)
393        {
394            COUT(2) << "Warning: Requesting GameStates while loading/unloading a GameState is illegal! Ignoring." << std::endl;
395            return;
396        }
397
398        shared_ptr<GameStateTreeNode> lastRequestedNode;
399        if (this->requestedStateNodes_.empty())
400            lastRequestedNode = this->loadedTopStateNode_;
401        else
402            lastRequestedNode = this->requestedStateNodes_.back();
403        if (name == lastRequestedNode->name_)
404        {
405            COUT(2) << "Warning: Requesting the currently active state! Ignoring." << std::endl;
406            return;
407        }
408
409        // Check children first
410        std::vector<shared_ptr<GameStateTreeNode> > requestedNodes;
411        for (unsigned int i = 0; i < lastRequestedNode->children_.size(); ++i)
412        {
413            if (lastRequestedNode->children_[i]->name_ == name)
414            {
415                requestedNodes.push_back(lastRequestedNode->children_[i]);
416                break;
417            }
418        }
419
420        if (requestedNodes.empty())
421        {
422            // Check parent and all its grand parents
423            shared_ptr<GameStateTreeNode> currentNode = lastRequestedNode;
424            while (currentNode != NULL)
425            {
426                if (currentNode->name_ == name)
427                    break;
428                currentNode = currentNode->parent_.lock();
429                requestedNodes.push_back(currentNode);
430            }
431        }
432
433        if (requestedNodes.empty())
434            COUT(1) << "Error: Requested GameState transition is not allowed. Ignoring." << std::endl;
435        else
436            this->requestedStateNodes_.insert(requestedStateNodes_.end(), requestedNodes.begin(), requestedNodes.end());
437    }
438
439    void Game::requestStates(const std::string& names)
440    {
441        SubString tokens(names, ",;", " ");
442        for (unsigned int i = 0; i < tokens.size(); ++i)
443            this->requestState(tokens[i]);
444    }
445
446    void Game::popState()
447    {
448        shared_ptr<GameStateTreeNode> lastRequestedNode;
449        if (this->requestedStateNodes_.empty())
450            lastRequestedNode = this->loadedTopStateNode_;
451        else
452            lastRequestedNode = this->requestedStateNodes_.back();
453        if (lastRequestedNode != this->rootStateNode_)
454            this->requestState(lastRequestedNode->parent_.lock()->name_);
455        else
456            COUT(2) << "Warning: Can't pop the internal dummy root GameState" << std::endl;
457    }
458
459    GameState* Game::getState(const std::string& name)
460    {
461        std::map<std::string, GameState*>::const_iterator it = constructedStates_.find(name);
462        if (it != constructedStates_.end())
463            return it->second;
464        else
465        {
466            std::map<std::string, GameStateInfo>::const_iterator it = gameStateDeclarations_s.find(name);
467            if (it != gameStateDeclarations_s.end())
468                COUT(1) << "Error: GameState '" << name << "' has not yet been loaded." << std::endl;
469            else
470                COUT(1) << "Error: Could not find GameState '" << name << "'." << std::endl;
471            return 0;
472        }
473    }
474
475    void Game::setStateHierarchy(const std::string& str)
476    {
477        // Split string into pieces of the form whitespacesText
478        std::vector<std::pair<std::string, unsigned> > stateStrings;
479        size_t pos = 0;
480        size_t startPos = 0;
481        while (pos < str.size())
482        {
483            unsigned indentation = 0;
484            while(pos < str.size() && str[pos] == ' ')
485                ++indentation, ++pos;
486            startPos = pos;
487            while(pos < str.size() && str[pos] != ' ')
488                ++pos;
489            stateStrings.push_back(std::make_pair(str.substr(startPos, pos - startPos), indentation));
490        }
491        unsigned int currentLevel = 0;
492        shared_ptr<GameStateTreeNode> currentNode = this->rootStateNode_;
493        for (std::vector<std::pair<std::string, unsigned> >::const_iterator it = stateStrings.begin(); it != stateStrings.end(); ++it)
494        {
495            std::string newStateName = it->first;
496            unsigned newLevel = it->second + 1; // empty root is 0
497            if (!this->checkState(newStateName))
498                ThrowException(GameState, "GameState with name '" << newStateName << "' not found!");
499            if (newStateName == this->rootStateNode_->name_)
500                ThrowException(GameState, "You shouldn't use 'emptyRootGameState' in the hierarchy...");
501            shared_ptr<GameStateTreeNode> newNode(new GameStateTreeNode);
502            newNode->name_ = newStateName;
503
504            if (newLevel <= currentLevel)
505            {
506                do
507                    currentNode = currentNode->parent_.lock();
508                while (newLevel <= --currentLevel);
509            }
510            if (newLevel == currentLevel + 1)
511            {
512                // Add the child
513                newNode->parent_ = currentNode;
514                currentNode->children_.push_back(newNode);
515            }
516            else
517                ThrowException(GameState, "Indentation error while parsing the hierarchy.");
518            currentNode = newNode;
519            currentLevel = newLevel;
520        }
521    }
522
523    /*** Internal ***/
524
525    void Game::loadGraphics()
526    {
527        if (!GameMode::bShowsGraphics_s)
528        {
529            core_->loadGraphics();
530            GameMode::bShowsGraphics_s = true;
531
532            // Construct all the GameStates that require graphics
533            for (std::map<std::string, GameStateInfo>::const_iterator it = gameStateDeclarations_s.begin();
534                it != gameStateDeclarations_s.end(); ++it)
535            {
536                if (it->second.bGraphicsMode)
537                {
538                    if (!constructedStates_.insert(std::make_pair(
539                        it->second.stateName, GameStateFactory::fabricate(it->second))).second)
540                        assert(false); // GameState was already created!
541                }
542            }
543        }
544    }
545
546    void Game::unloadGraphics()
547    {
548        if (GameMode::bShowsGraphics_s)
549        {
550            // Destroy all the GameStates that require graphics
551            for (std::map<std::string, GameState*>::iterator it = constructedStates_.begin(); it != constructedStates_.end();)
552            {
553                if (it->second->getInfo().bGraphicsMode)
554                {
555                    delete it->second;
556                    constructedStates_.erase(it++);
557                }
558                else
559                    ++it;
560            }
561
562            core_->unloadGraphics();
563            GameMode::bShowsGraphics_s = false;
564        }
565    }
566
567    bool Game::checkState(const std::string& name) const
568    {
569        std::map<std::string, GameStateInfo>::const_iterator it = gameStateDeclarations_s.find(name);
570        if (it == gameStateDeclarations_s.end())
571            return false;
572        else
573            return true;
574    }
575
576    void Game::loadState(const std::string& name)
577    {
578        this->bChangingState_ = true;
579        // If state requires graphics, load it
580        if (gameStateDeclarations_s[name].bGraphicsMode)
581            this->loadGraphics();
582        GameState* state = this->getState(name);
583        state->activate();
584        if (!this->loadedStates_.empty())
585            this->loadedStates_.back()->activity_.topState = false;
586        this->loadedStates_.push_back(state);
587        state->activity_.topState = true;
588        this->bChangingState_ = false;
589    }
590
591    void Game::unloadState(const std::string& name)
592    {
593        GameState* state = this->getState(name);
594        this->bChangingState_ = true;
595        state->activity_.topState = false;
596        this->loadedStates_.pop_back();
597        if (!this->loadedStates_.empty())
598            this->loadedStates_.back()->activity_.topState = true;
599        try
600        {
601            state->deactivate();
602            // Check if graphis is still required
603            bool graphicsRequired = false;
604            for (unsigned i = 0; i < loadedStates_.size(); ++i)
605                graphicsRequired |= loadedStates_[i]->getInfo().bGraphicsMode;
606            if (!graphicsRequired)
607                this->unloadGraphics();
608        }
609        catch (const std::exception& ex)
610        {
611            COUT(2) << "Warning: Unloading GameState '" << name << "' threw an exception: " << ex.what() << std::endl;
612            COUT(2) << "         There might be potential resource leaks involved! To avoid this, improve exception-safety." << std::endl;
613        }
614        this->bChangingState_ = false;
615    }
616
617    std::map<std::string, Game::GameStateFactory*> Game::GameStateFactory::factories_s;
618
619    /*static*/ GameState* Game::GameStateFactory::fabricate(const GameStateInfo& info)
620    {
621        std::map<std::string, GameStateFactory*>::const_iterator it = factories_s.find(info.className);
622        assert(it != factories_s.end());
623        return it->second->fabricateInternal(info);
624    }
625
626    /*static*/ void Game::GameStateFactory::destroyFactories()
627    {
628        for (std::map<std::string, GameStateFactory*>::const_iterator it = factories_s.begin(); it != factories_s.end(); ++it)
629            delete it->second;
630        factories_s.clear();
631    }
632}
Note: See TracBrowser for help on using the repository browser.