Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

source: code/branches/gui/src/core/Game.cc @ 2844

Last change on this file since 2844 was 2844, checked in by rgrieder, 15 years ago

Implemented new GameState concept. It doesn't differ that much from the old one, but there's still lots of changes.
The most important change is that one GameState can occur multiple times in the hierarchy.

Short log:

  • No RootGameState anymore. Simply the highest is root.
  • Game::requestGameState(name) can refer to the parent, grandparent, great-grandparent, etc. or one of the children.
  • Requested states are saved. So if you select "level", the next request (even right after the call) will be relative to "level"
  • Game::popState() will simply unload the current one
  • Re added Main.cc again because Game as well as GameState have been moved to the core
  • Adapted all GameStates to the new system

Things should already work, except for network support because standalone only works with a little hack.
We can now start creating a better hierarchy.

  • Property svn:eol-style set to native
File size: 13.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 <cassert>
39
40#include "util/Debug.h"
41#include "util/Exception.h"
42#include "Clock.h"
43#include "CommandLine.h"
44#include "ConsoleCommand.h"
45#include "Core.h"
46#include "CoreIncludes.h"
47#include "ConfigValueIncludes.h"
48#include "GameState.h"
49
50namespace orxonox
51{
52    void stop_game()
53    {
54        Game::getInstance().stop();
55    }
56
57    struct _CoreExport GameStateTreeNode
58    {
59        GameState*                      state_;
60        GameStateTreeNode*              parent_;
61        std::vector<GameStateTreeNode*> children_;
62    };
63
64    SetCommandLineArgument(state, "gui").shortcut("s");
65    SetCommandLineSwitch(startWithConsole);
66    SetConsoleCommandShortcutExternAlias(stop_game, "exit");
67
68    std::map<std::string, GameState*> Game::allStates_s;
69    Game* Game::singletonRef_s = 0;
70
71    /**
72    @brief
73        Non-initialising constructor.
74    */
75    Game::Game(int argc, char** argv)
76    {
77        assert(singletonRef_s == 0);
78        singletonRef_s = this;
79
80        this->rootStateNode_ = 0;
81        this->activeStateNode_ = 0;
82
83        this->abort_ = false;
84
85        // reset statistics
86        this->statisticsStartTime_ = 0;
87        this->statisticsTickTimes_.clear();
88        this->periodTickTime_ = 0;
89        this->periodTime_ = 0;
90        this->avgFPS_ = 0.0f;
91        this->avgTickTime_ = 0.0f;
92
93        this->core_ = new orxonox::Core();
94        this->gameClock_ = this->core_->initialise(argc, argv);
95
96        RegisterRootObject(Game);
97        this->setConfigValues();
98    }
99
100    /**
101    @brief
102    */
103    Game::~Game()
104    {
105        // Destroy pretty much everyhting left
106        delete this->core_;
107
108        // Delete all GameStates created by the macros
109        for (std::map<std::string, GameState*>::const_iterator it = allStates_s.begin(); it != allStates_s.end(); ++it)
110            delete it->second;
111
112        assert(singletonRef_s);
113        singletonRef_s = 0;
114    }
115
116    void Game::setConfigValues()
117    {
118        SetConfigValue(statisticsRefreshCycle_, 250000)
119            .description("Sets the time in microseconds interval at which average fps, etc. get updated.");
120        SetConfigValue(statisticsAvgLength_, 1000000)
121            .description("Sets the time in microseconds interval at which average fps, etc. gets calculated.");
122    }
123
124    /**
125    @brief
126        Main loop of the orxonox game.
127    @note
128        We use the Ogre::Timer to measure time since it uses the most precise
129        method an any platform (however the windows timer lacks time when under
130        heavy kernel load!).
131    */
132    void Game::run()
133    {
134        // </EXPORT THIS>
135        this->setStateHierarchy(
136        "root"
137        " graphics"
138        "  gui"
139        "  standalone"
140        "   level"
141        "  server"
142        "   level"
143        "  client"
144        "   level"
145        " dedicated"
146        "  level"
147        " ioConsole"
148        );
149        // </EXPORT THIS>
150
151
152        // Always start with the root state
153        this->requestedStateNodes_.push_back(this->rootStateNode_);
154        this->activeStateNode_ = this->rootStateNode_;
155        this->loadState(this->rootStateNode_->state_);
156
157        // <EXPORT THIS>
158        if (CommandLine::getValue("startWithConsole").getBool())
159        {
160            // Start the game in the console
161            this->requestState("ioConsole");
162        }
163        else
164        {
165            // Start in GUI main menu
166            this->requestState("graphics");
167            this->requestState("gui");
168        }
169        // </EXPORT THIS>
170
171        this->gameClock_->capture(); // first delta time should be about 0 seconds
172        while (!this->abort_ && !this->activeStates_.empty())
173        {
174            this->gameClock_->capture();
175            uint64_t currentTime = this->gameClock_->getMicroseconds();
176
177            // STATISTICS
178            statisticsTickInfo tickInfo = {currentTime, 0};
179            statisticsTickTimes_.push_back(tickInfo);
180            this->periodTime_ += this->gameClock_->getDeltaTimeMicroseconds();
181
182            // UPDATE STATE STACK
183            while (this->requestedStateNodes_.size() > 1)
184            {
185                // Note: this->requestedStateNodes_.front() is the currently active state node
186                std::vector<GameStateTreeNode*>::iterator it = this->requestedStateNodes_.begin() + 1;
187                if (*it == this->activeStateNode_->parent_)
188                    this->unloadState(this->activeStateNode_->state_);
189                else // has to be child
190                    this->loadState((*it)->state_);
191                this->activeStateNode_ = *it;
192                this->requestedStateNodes_.erase(this->requestedStateNodes_.begin());
193            }
194
195            // UPDATE, bottom to top in the stack
196            for (std::vector<GameState*>::const_iterator it = this->activeStates_.begin();
197                it != this->activeStates_.end(); ++it)
198                (*it)->update(*this->gameClock_);
199
200            // STATISTICS
201            if (this->periodTime_ > statisticsRefreshCycle_)
202            {
203                std::list<statisticsTickInfo>::iterator it = this->statisticsTickTimes_.begin();
204                assert(it != this->statisticsTickTimes_.end());
205                int64_t lastTime = currentTime - this->statisticsAvgLength_;
206                if ((int64_t)it->tickTime < lastTime)
207                {
208                    do
209                    {
210                        assert(this->periodTickTime_ > it->tickLength);
211                        this->periodTickTime_ -= it->tickLength;
212                        ++it;
213                        assert(it != this->statisticsTickTimes_.end());
214                    } while ((int64_t)it->tickTime < lastTime);
215                    this->statisticsTickTimes_.erase(this->statisticsTickTimes_.begin(), it);
216                }
217
218                uint32_t framesPerPeriod = this->statisticsTickTimes_.size();
219                this->avgFPS_ = (float)framesPerPeriod / (currentTime - this->statisticsTickTimes_.front().tickTime) * 1000000.0;
220                this->avgTickTime_ = (float)this->periodTickTime_ / framesPerPeriod / 1000.0;
221
222                this->periodTime_ -= this->statisticsRefreshCycle_;
223            }
224        }
225
226        // Unload all remaining states
227        while (!this->activeStates_.empty())
228            this->unloadState(this->activeStates_.back());
229        this->activeStateNode_ = 0;
230        this->requestedStateNodes_.clear();
231    }
232
233    void Game::stop()
234    {
235        this->abort_ = true;
236    }
237
238    void Game::addTickTime(uint32_t length)
239    {
240        assert(!this->statisticsTickTimes_.empty());
241        this->statisticsTickTimes_.back().tickLength += length;
242        this->periodTickTime_+=length;
243    }
244
245
246    /***** GameState related *****/
247
248    void Game::requestState(const std::string& name)
249    {
250        GameState* state = this->getState(name);
251        if (state == NULL || this->activeStateNode_ == NULL)
252            return;
253
254        GameStateTreeNode* requestedNode = 0;
255
256        // this->requestedStateNodes_.back() is the currently active state
257        GameStateTreeNode* lastRequestedNode = this->requestedStateNodes_.back();
258
259        // Already the active node?
260        if (state == lastRequestedNode->state_)
261        {
262            COUT(2) << "Warning: Requesting the currently active state! Ignoring." << std::endl;
263            return;
264        }
265
266        // Check children first
267        for (unsigned int i = 0; i < lastRequestedNode->children_.size(); ++i)
268        {
269            if (lastRequestedNode->children_[i]->state_ == state)
270            {
271                requestedNode = lastRequestedNode->children_[i];
272                break;
273            }
274        }
275
276        // Check parent and all its grand parents
277        GameStateTreeNode* currentNode = lastRequestedNode;
278        while (requestedNode == NULL && currentNode->parent_ != NULL)
279        {
280            if (currentNode->state_ == state)
281                requestedNode = currentNode;
282            currentNode = currentNode->parent_;
283        }
284
285        if (requestedNode == NULL)
286            COUT(1) << "Error: Requested GameState transition is not allowed. Ignoring." << std::endl;
287        else
288            this->requestedStateNodes_.push_back(requestedNode);
289    }
290
291    void Game::popState()
292    {
293        if (this->activeStateNode_ != NULL && this->requestedStateNodes_.back()->parent_)
294            this->requestState(this->requestedStateNodes_.back()->parent_->state_->getName());
295        else
296            COUT(2) << "Warning: Could not pop GameState. Ignoring." << std::endl;
297    }
298
299    GameState* Game::getState(const std::string& name)
300    {
301        std::map<std::string, GameState*>::const_iterator it = allStates_s.find(name);
302        if (it != allStates_s.end())
303            return it->second;
304        else
305        {
306            COUT(1) << "Error: Could not find GameState '" << name << "'. Ignoring." << std::endl;
307            return 0;
308        }
309    }
310
311    void Game::setStateHierarchy(const std::string& str)
312    {
313        // Split string into pieces of the form whitespacesText
314        std::vector<std::pair<std::string, unsigned> > stateStrings;
315        size_t pos = 0;
316        size_t startPos = 0;
317        while (pos < str.size())
318        {
319            unsigned indentation = 0;
320            while(pos < str.size() && str[pos] == ' ')
321                ++indentation, ++pos;
322            startPos = pos;
323            while(pos < str.size() && str[pos] != ' ')
324                ++pos;
325            stateStrings.push_back(std::pair<std::string, unsigned>(
326                str.substr(startPos, pos - startPos), indentation));
327        }
328        unsigned int currentLevel = 0;
329        GameStateTreeNode* currentNode = 0;
330        for (std::vector<std::pair<std::string, unsigned> >::const_iterator it = stateStrings.begin(); it != stateStrings.end(); ++it)
331        {
332            std::string newStateName = it->first;
333            unsigned newLevel = it->second;
334            GameState* newState = this->getState(newStateName);
335            if (!newState)
336                ThrowException(GameState, std::string("GameState with name '") + newStateName + "' not found!");
337            if (newLevel == 0)
338            {
339                // root
340                if (this->rootStateNode_ != NULL)
341                    ThrowException(GameState, "No two root GameStates are allowed!");
342                GameStateTreeNode* newNode = new GameStateTreeNode;
343                newNode->state_ = newState;
344                newNode->parent_ = 0;
345                this->rootStateNode_ = newNode;
346                currentNode = this->rootStateNode_;
347            }
348            else if (currentNode)
349            {
350                GameStateTreeNode* newNode = new GameStateTreeNode;
351                newNode->state_ = newState;
352                if (newLevel < currentLevel)
353                {
354                    // Get down the hierarchy
355                    do
356                        currentNode = currentNode->parent_;
357                    while (newLevel < --currentLevel);
358                }
359                if (newLevel == currentLevel)
360                {
361                    // same level
362                    newNode->parent_ = currentNode->parent_;
363                    newNode->parent_->children_.push_back(newNode);
364                }
365                else if (newLevel == currentLevel + 1)
366                {
367                    // child
368                    newNode->parent_ = currentNode;
369                    currentNode->children_.push_back(newNode);
370                }
371                else
372                    ThrowException(GameState, "Indentation error while parsing the hierarchy.");
373                currentNode = newNode;
374                currentLevel = newLevel;
375            }
376            else
377            {
378                ThrowException(GameState, "No root GameState specified!");
379            }
380        }
381    }
382
383    /*** Internal ***/
384
385    void Game::loadState(GameState* state)
386    {
387        state->activate();
388        this->activeStates_.push_back(state);
389    }
390
391    void Game::unloadState(orxonox::GameState* state)
392    {
393        state->deactivate();
394        this->activeStates_.pop_back();
395    }
396
397    /*static*/ bool Game::addGameState(GameState* state)
398    {
399        std::map<std::string, GameState*>::const_iterator it = allStates_s.find(state->getName());
400        if (it == allStates_s.end())
401            allStates_s[state->getName()] = state;
402        else
403            ThrowException(GameState, "Cannot add two GameStates with the same name to 'Game'.");
404
405        // just a required dummy return value
406        return true;
407    }
408}
Note: See TracBrowser for help on using the repository browser.