Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

source: code/branches/gui/src/core/Core.cc @ 2800

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

Renaming "tick" to "update" for all those classes not inheriting from Tickable to avoid confusions.
GameState::ticked still exists, but that's going to change anyway.

  • Property svn:eol-style set to native
File size: 20.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 *      Fabian 'x3n' Landau
24 *      Reto Grieder
25 *   Co-authors:
26 *      ...
27 *
28 */
29
30/**
31    @file
32    @brief Implementation of the Core class.
33*/
34
35#include "Core.h"
36
37#include <cassert>
38#include <fstream>
39#include <cstdlib>
40#include <cstdio>
41#include <boost/filesystem.hpp>
42
43#ifdef ORXONOX_PLATFORM_WINDOWS
44#  ifndef WIN32_LEAN_AND_MEAN
45#    define WIN32_LEAN_AND_MEAN
46#  endif
47#  include <windows.h>
48#elif defined(ORXONOX_PLATFORM_APPLE)
49#  include <sys/param.h>
50#  include <mach-o/dyld.h>
51#else /* Linux */
52#  include <sys/types.h>
53#  include <unistd.h>
54#endif
55
56#include "SpecialConfig.h"
57#include "util/Debug.h"
58#include "util/Exception.h"
59#include "util/SignalHandler.h"
60#include "Clock.h"
61#include "CommandExecutor.h"
62#include "CommandLine.h"
63#include "ConfigFileManager.h"
64#include "ConfigValueIncludes.h"
65#include "CoreIncludes.h"
66#include "Factory.h"
67#include "Identifier.h"
68#include "Language.h"
69#include "LuaBind.h"
70#include "Shell.h"
71#include "TclBind.h"
72#include "TclThreadManager.h"
73
74namespace orxonox
75{
76    //! Path to the parent directory of the ones above if program was installed with relativ pahts
77    static boost::filesystem::path rootPath_g;
78    static boost::filesystem::path executablePath_g;            //!< Path to the executable
79    static boost::filesystem::path mediaPath_g;                 //!< Path to the media file folder
80    static boost::filesystem::path configPath_g;                //!< Path to the config file folder
81    static boost::filesystem::path logPath_g;                   //!< Path to the log file folder
82
83    bool Core::bShowsGraphics_s = false;
84    bool Core::bHasServer_s     = false;
85    bool Core::bIsClient_s      = false;
86    bool Core::bIsStandalone_s  = false;
87    bool Core::bIsMaster_s      = false;
88
89    Core* Core::singletonRef_s  = 0;
90
91    SetCommandLineArgument(mediaPath, "").information("PATH");
92    SetCommandLineArgument(writingPathSuffix, "").information("DIR");
93    SetCommandLineArgument(settingsFile, "orxonox.ini");
94    SetCommandLineArgument(limitToCPU, 0).information("0: off | #cpu");
95
96    /**
97        @brief Constructor: Registers the object and sets the config-values.
98        @param A reference to a global variable, used to avoid an infinite recursion in getSoftDebugLevel()
99    */
100    Core::Core(int argc, char** argv)
101    {
102        RegisterRootObject(Core);
103
104        assert(Core::singletonRef_s == 0);
105        Core::singletonRef_s = this;
106
107        // Parse command line arguments fist
108        try
109        {
110            CommandLine::parseAll(argc, argv);
111        }
112        catch (ArgumentException& ex)
113        {
114            COUT(1) << ex.what() << std::endl;
115            COUT(0) << "Usage:" << std::endl << "orxonox " << CommandLine::getUsageInformation() << std::endl;
116        }
117
118        // limit the main thread to the first core so that QueryPerformanceCounter doesn't jump
119        // do this after ogre has initialised. Somehow Ogre changes the settings again (not through
120        // the timer though).
121        int limitToCPU = CommandLine::getValue("limitToCPU");
122        if (limitToCPU > 0)
123            setThreadAffinity((unsigned int)limitToCPU);
124
125        // Determine and set the location of the executable
126        setExecutablePath();
127
128        // Determine whether we have an installed or a binary dir run
129        // The latter occurs when simply running from the build directory
130        checkDevBuild();
131
132        // Make sure the directories we write in exist or else make them
133        createDirectories();
134
135        // create a signal handler (only active for linux)
136        // This call is placed as soon as possible, but after the directories are set
137        this->signalHandler_ = new SignalHandler();
138        this->signalHandler_->doCatch(executablePath_g.string(), Core::getLogPathString() + "orxonox_crash.log");
139
140        // Set the correct log path. Before this call, /tmp (Unix) or %TEMP% was used
141        OutputHandler::getOutStream().setLogPath(Core::getLogPathString());
142
143        // Manage ini files and set the default settings file (usually orxonox.ini)
144        this->configFileManager_ = new ConfigFileManager();
145        this->configFileManager_->setFilename(ConfigFileType::Settings,
146            CommandLine::getValue("settingsFile").getString());
147
148        this->languageInstance_ = new Language();
149
150        // Do this soon after the ConfigFileManager has been created to open up the
151        // possibility to configure everything below here
152        this->setConfigValues();
153
154        // Possible media path override by the command line
155        if (!CommandLine::getArgument("mediaPath")->hasDefaultValue())
156        {
157            //std::string mediaPath = CommandLine::getValue("mediaPath");
158            Core::tsetMediaPath(CommandLine::getValue("mediaPath"));
159        }
160
161        // Create the lua interface
162        this->luaBind_ = new LuaBind();
163
164        // initialise Tcl
165        this->tclBind_ = new TclBind(Core::getMediaPathString());
166        this->tclThreadManager_ = new TclThreadManager(tclBind_->getTclInterpreter());
167
168        // create a shell
169        this->shell_ = new Shell();
170
171        // creates the class hierarchy for all classes with factories
172        Factory::createClassHierarchy();
173       
174        this->loaded_ = true;
175    }
176
177    /**
178        @brief Sets the bool to true to avoid static functions accessing a deleted object.
179    */
180    Core::~Core()
181    {
182        this->loaded_ = false;
183
184        delete this->shell_;
185        delete this->tclThreadManager_;
186        delete this->tclBind_;
187        delete this->luaBind_;
188        delete this->languageInstance_;
189        delete this->configFileManager_;
190        delete this->signalHandler_;
191
192        // Destroy command line arguments
193        CommandLine::destroyAllArguments();
194        // Also delete external console command that don't belong to an Identifier
195        CommandExecutor::destroyExternalCommands();
196
197        assert(Core::singletonRef_s);
198        Core::singletonRef_s = 0;
199    }
200
201    /**
202        @brief Function to collect the SetConfigValue-macro calls.
203    */
204    void Core::setConfigValues()
205    {
206#ifdef NDEBUG
207        const unsigned int defaultLevelConsole = 1;
208        const unsigned int defaultLevelLogfile = 3;
209        const unsigned int defaultLevelShell   = 1;
210#else
211        const unsigned int defaultLevelConsole = 3;
212        const unsigned int defaultLevelLogfile = 4;
213        const unsigned int defaultLevelShell   = 3;
214#endif
215        SetConfigValue(softDebugLevelConsole_, defaultLevelConsole)
216            .description("The maximal level of debug output shown in the console").callback(this, &Core::debugLevelChanged);
217        SetConfigValue(softDebugLevelLogfile_, defaultLevelLogfile)
218            .description("The maximal level of debug output shown in the logfile").callback(this, &Core::debugLevelChanged);
219        SetConfigValue(softDebugLevelShell_, defaultLevelShell)
220            .description("The maximal level of debug output shown in the ingame shell").callback(this, &Core::debugLevelChanged);
221
222        SetConfigValue(language_, Language::getLanguage().defaultLanguage_).description("The language of the ingame text").callback(this, &Core::languageChanged);
223        SetConfigValue(bInitializeRandomNumberGenerator_, true).description("If true, all random actions are different each time you start the game").callback(this, &Core::initializeRandomNumberGenerator);
224
225        SetConfigValue(mediaPathString_, mediaPath_g.string())
226            .description("Relative path to the game data.").callback(this, &Core::mediaPathChanged);
227    }
228
229    /**
230        @brief Callback function if the debug level has changed.
231    */
232    void Core::debugLevelChanged()
233    {
234        // softDebugLevel_ is the maximum of the 3 variables
235        this->softDebugLevel_ = this->softDebugLevelConsole_;
236        if (this->softDebugLevelLogfile_ > this->softDebugLevel_)
237            this->softDebugLevel_ = this->softDebugLevelLogfile_;
238        if (this->softDebugLevelShell_ > this->softDebugLevel_)
239            this->softDebugLevel_ = this->softDebugLevelShell_;
240
241        OutputHandler::setSoftDebugLevel(OutputHandler::LD_All,     this->softDebugLevel_);
242        OutputHandler::setSoftDebugLevel(OutputHandler::LD_Console, this->softDebugLevelConsole_);
243        OutputHandler::setSoftDebugLevel(OutputHandler::LD_Logfile, this->softDebugLevelLogfile_);
244        OutputHandler::setSoftDebugLevel(OutputHandler::LD_Shell,   this->softDebugLevelShell_);
245    }
246
247    /**
248        @brief Callback function if the language has changed.
249    */
250    void Core::languageChanged()
251    {
252        // Read the translation file after the language was configured
253        Language::getLanguage().readTranslatedLanguageFile();
254    }
255
256    /**
257    @brief
258        Callback function if the media path has changed.
259    */
260    void Core::mediaPathChanged()
261    {
262        mediaPath_g = boost::filesystem::path(this->mediaPathString_);
263    }
264
265    /**
266        @brief Returns the softDebugLevel for the given device (returns a default-value if the class ist right about to be created).
267        @param device The device
268        @return The softDebugLevel
269    */
270    int Core::getSoftDebugLevel(OutputHandler::OutputDevice device)
271    {
272        switch (device)
273        {
274        case OutputHandler::LD_All:
275            return Core::getInstance().softDebugLevel_;
276        case OutputHandler::LD_Console:
277            return Core::getInstance().softDebugLevelConsole_;
278        case OutputHandler::LD_Logfile:
279            return Core::getInstance().softDebugLevelLogfile_;
280        case OutputHandler::LD_Shell:
281            return Core::getInstance().softDebugLevelShell_;
282        default:
283            assert(0);
284            return 2;
285        }
286    }
287
288     /**
289        @brief Sets the softDebugLevel for the given device. Please use this only temporary and restore the value afterwards, as it overrides the configured value.
290        @param device The device
291        @param level The level
292    */
293     void Core::setSoftDebugLevel(OutputHandler::OutputDevice device, int level)
294     {
295        if (device == OutputHandler::LD_All)
296            Core::getInstance().softDebugLevel_ = level;
297        else if (device == OutputHandler::LD_Console)
298            Core::getInstance().softDebugLevelConsole_ = level;
299        else if (device == OutputHandler::LD_Logfile)
300            Core::getInstance().softDebugLevelLogfile_ = level;
301        else if (device == OutputHandler::LD_Shell)
302            Core::getInstance().softDebugLevelShell_ = level;
303
304        OutputHandler::setSoftDebugLevel(device, level);
305     }
306
307    /**
308        @brief Returns the configured language.
309    */
310    const std::string& Core::getLanguage()
311    {
312        return Core::getInstance().language_;
313    }
314
315    /**
316        @brief Sets the language in the config-file back to the default.
317    */
318    void Core::resetLanguage()
319    {
320        Core::getInstance().resetLanguageIntern();
321    }
322
323    /**
324        @brief Sets the language in the config-file back to the default.
325    */
326    void Core::resetLanguageIntern()
327    {
328        ResetConfigValue(language_);
329    }
330
331    /**
332    @brief
333        Temporary sets the media path
334    @param path
335        The new media path
336    */
337    void Core::_tsetMediaPath(const std::string& path)
338    {
339        ModifyConfigValue(mediaPathString_, tset, path);
340    }
341
342    /*static*/ const boost::filesystem::path& Core::getMediaPath()
343    {
344        return mediaPath_g;
345    }
346    /*static*/ std::string Core::getMediaPathString()
347    {
348        return mediaPath_g.string() + '/';
349    }
350
351    /*static*/ const boost::filesystem::path& Core::getConfigPath()
352    {
353        return configPath_g;
354    }
355    /*static*/ std::string Core::getConfigPathString()
356    {
357        return configPath_g.string() + '/';
358    }
359
360    /*static*/ const boost::filesystem::path& Core::getLogPath()
361    {
362        return logPath_g;
363    }
364    /*static*/ std::string Core::getLogPathString()
365    {
366        return logPath_g.string() + '/';
367    }
368
369    void Core::initializeRandomNumberGenerator()
370    {
371        static bool bInitialized = false;
372        if (!bInitialized && this->bInitializeRandomNumberGenerator_)
373        {
374            srand(time(0));
375            rand();
376            bInitialized = true;
377        }
378    }
379
380
381    /**
382    @note
383        The code of this function has been copied and adjusted from OGRE, an open source graphics engine.
384            (Object-oriented Graphics Rendering Engine)
385        For the latest info, see http://www.ogre3d.org/
386
387        Copyright (c) 2000-2008 Torus Knot Software Ltd
388
389        OGRE is licensed under the LGPL. For more info, see OGRE license.
390    */
391    void Core::setThreadAffinity(int limitToCPU)
392    {
393        if (limitToCPU <= 0)
394            return;
395
396        unsigned int coreNr = limitToCPU - 1;
397#ifdef ORXONOX_PLATFORM_WINDOWS
398        // Get the current process core mask
399        DWORD procMask;
400        DWORD sysMask;
401#  if _MSC_VER >= 1400 && defined (_M_X64)
402        GetProcessAffinityMask(GetCurrentProcess(), (PDWORD_PTR)&procMask, (PDWORD_PTR)&sysMask);
403#  else
404        GetProcessAffinityMask(GetCurrentProcess(), &procMask, &sysMask);
405#  endif
406
407        // If procMask is 0, consider there is only one core available
408        // (using 0 as procMask will cause an infinite loop below)
409        if (procMask == 0)
410            procMask = 1;
411
412        // if the core specified with coreNr is not available, take the lowest one
413        if (!(procMask & (1 << coreNr)))
414            coreNr = 0;
415
416        // Find the lowest core that this process uses and coreNr suggests
417        DWORD threadMask = 1;
418        while ((threadMask & procMask) == 0 || (threadMask < (1u << coreNr)))
419            threadMask <<= 1;
420
421        // Set affinity to the first core
422        SetThreadAffinityMask(GetCurrentThread(), threadMask);
423#endif
424    }
425
426    /**
427    @brief
428        Compares the executable path with the working directory
429    */
430    void Core::setExecutablePath()
431    {
432#ifdef ORXONOX_PLATFORM_WINDOWS
433        // get executable module
434        TCHAR buffer[1024];
435        if (GetModuleFileName(NULL, buffer, 1024) == 0)
436            ThrowException(General, "Could not retrieve executable path.");
437
438#elif defined(ORXONOX_PLATFORM_APPLE)
439        char buffer[1024];
440        unsigned long path_len = 1023;
441        if (_NSGetExecutablePath(buffer, &path_len))
442            ThrowException(General, "Could not retrieve executable path.");
443
444#else /* Linux */
445        /* written by Nicolai Haehnle <prefect_@gmx.net> */
446
447        /* Get our PID and build the name of the link in /proc */
448        char linkname[64]; /* /proc/<pid>/exe */
449        if (snprintf(linkname, sizeof(linkname), "/proc/%i/exe", getpid()) < 0)
450        {
451            /* This should only happen on large word systems. I'm not sure
452               what the proper response is here.
453               Since it really is an assert-like condition, aborting the
454               program seems to be in order. */
455            assert(false);
456        }
457
458        /* Now read the symbolic link */
459        char buffer[1024];
460        int ret;
461        ret = readlink(linkname, buffer, 1024);
462        /* In case of an error, leave the handling up to the caller */
463        if (ret == -1)
464            ThrowException(General, "Could not retrieve executable path.");
465
466        /* Ensure proper NUL termination */
467        buffer[ret] = 0;
468#endif
469
470        executablePath_g = boost::filesystem::path(buffer);
471#ifndef ORXONOX_PLATFORM_APPLE
472        executablePath_g = executablePath_g.branch_path(); // remove executable name
473#endif
474    }
475
476    /**
477    @brief
478        Checks for "orxonox_dev_build.keep_me" in the executable diretory.
479        If found it means that this is not an installed run, hence we
480        don't write the logs and config files to ~/.orxonox
481    */
482    void Core::checkDevBuild()
483    {
484        if (boost::filesystem::exists(executablePath_g / "orxonox_dev_build.keep_me"))
485        {
486            COUT(1) << "Running from the build tree." << std::endl;
487            Core::isDevBuild_ = true;
488            mediaPath_g  = ORXONOX_MEDIA_DEV_PATH;
489            configPath_g = ORXONOX_CONFIG_DEV_PATH;
490            logPath_g    = ORXONOX_LOG_DEV_PATH;
491        }
492        else
493        {
494#ifdef INSTALL_COPYABLE // --> relative paths
495            // Also set the root path
496            boost::filesystem::path relativeExecutablePath(ORXONOX_RUNTIME_INSTALL_PATH);
497            rootPath_g = executablePath_g;
498            while (!boost::filesystem::equivalent(rootPath_g / relativeExecutablePath, executablePath_g) || rootPath_g.empty())
499                rootPath_g = rootPath_g.branch_path();
500            if (rootPath_g.empty())
501                ThrowException(General, "Could not derive a root directory. Might the binary installation directory contain '..' when taken relative to the installation prefix path?");
502
503            // Using paths relative to the install prefix, complete them
504            mediaPath_g  = rootPath_g / ORXONOX_MEDIA_INSTALL_PATH;
505            configPath_g = rootPath_g / ORXONOX_CONFIG_INSTALL_PATH;
506            logPath_g    = rootPath_g / ORXONOX_LOG_INSTALL_PATH;
507#else
508            // There is no root path, so don't set it at all
509
510            mediaPath_g  = ORXONOX_MEDIA_INSTALL_PATH;
511
512            // Get user directory
513#  ifdef ORXONOX_PLATFORM_UNIX /* Apple? */
514            char* userDataPathPtr(getenv("HOME"));
515#  else
516            char* userDataPathPtr(getenv("APPDATA"));
517#  endif
518            if (userDataPathPtr == NULL)
519                ThrowException(General, "Could not retrieve user data path.");
520            boost::filesystem::path userDataPath(userDataPathPtr);
521            userDataPath /= ".orxonox";
522
523            configPath_g = userDataPath / ORXONOX_CONFIG_INSTALL_PATH;
524            logPath_g    = userDataPath / ORXONOX_LOG_INSTALL_PATH;
525#endif
526        }
527
528        // Option to put all the config and log files in a separate folder
529        if (!CommandLine::getArgument("writingPathSuffix")->hasDefaultValue())
530        {
531            std::string directory(CommandLine::getValue("writingPathSuffix").getString());
532            configPath_g = configPath_g / directory;
533            logPath_g    = logPath_g    / directory;
534        }
535    }
536
537    /*
538    @brief
539        Checks for the log and the config directory and creates them
540        if necessary. Otherwise me might have problems opening those files.
541    */
542    void Core::createDirectories()
543    {
544        std::vector<std::pair<boost::filesystem::path, std::string> > directories;
545        directories.push_back(std::pair<boost::filesystem::path, std::string>
546            (boost::filesystem::path(configPath_g), "config"));
547        directories.push_back(std::pair<boost::filesystem::path, std::string>
548            (boost::filesystem::path(logPath_g),    "log"));
549
550        for (std::vector<std::pair<boost::filesystem::path, std::string> >::iterator it = directories.begin();
551            it != directories.end(); ++it)
552        {
553            if (boost::filesystem::exists(it->first) && !boost::filesystem::is_directory(it->first))
554            {
555                ThrowException(General, std::string("The ") + it->second + " directory has been preoccupied by a file! \
556                                         Please remove " + it->first.string());
557            }
558            if (boost::filesystem::create_directories(it->first)) // function may not return true at all (bug?)
559            {
560                COUT(4) << "Created " << it->second << " directory" << std::endl;
561            }
562        }
563    }
564
565    void Core::update(const Clock& time)
566    {
567        this->tclThreadManager_->update(time);
568    }
569}
Note: See TracBrowser for help on using the repository browser.