Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

source: code/branches/output/src/libraries/core/Game.cc @ 9228

Last change on this file since 9228 was 8830, checked in by landauf, 13 years ago

added some output (user and internal) throughout the initialization of the game, graphics, and game states

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