Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

source: code/branches/resource/src/core/Core.cc @ 3343

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

Expanded Core class by loadGraphics and unloadGraphics which don't do anything at the moment.

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