# HG changeset patch # User Diggory Hardy # Date 1205862712 0 # Node ID 56a42ec950243fa3fc9f7fc0763c770c0472ce67 # Parent 5f90774ea1ef71b7b6997296d97b395eb8f69f7f Changes to Init and logging. Moved an Init function; hence events now depends on Init rather than vice-versa. Paths and logging set up by Init's static this(). Logging location set at compile time. committer: Diggory Hardy diff -r 5f90774ea1ef -r 56a42ec95024 codeDoc/imports.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/codeDoc/imports.txt Tue Mar 18 17:51:52 2008 +0000 @@ -0,0 +1,170 @@ +Import relationships: +(only imports from mde, to show inter-code relationships) +As of commit 2aacda05d942e74cdeefe952e701be7b5d3342d6, Sat Mar 15 15:14:25 2008 +0000 +As of 18th March, updated but index/letters no longer current. + +Index (n) is such that it is greater than that of any module importing the module. +Additionally, each module importing nothing is given a unique letter. Then each module depending (directly or indirectly) on this is given this letter. + +Lines: +-> x +<- y +means this imports x, and y imports this. + +mde.events (): +-> mde.exception +-> mde.global +-> mde.Init +-> mde.input.input +-> mde.scheduler +<- mde.mde + +mde.exception (12e): +<- mde.events +<- mde.i18n.I18nTranslation +<- mde.Init +<- mde.input.exception +<- mde.mde +<- mde.mergetag.exception + +mde.global (4):cde +-> mde.input.input +<- mde.events +<- mde.mde + +mde.i18n.I18nTranslation (1):cde +-> mde.exception +-> mde.mergetag.DataSet +-> mde.mergetag.exception +-> mde.mergetag.Reader +-> mde.options +-> mde.resource.paths +<- mde.mde + +mde.Init (): +-> mde.exception +-> mde.options +-> mde.resource.paths +<- mde.events +<- mde.mde +<- mde.SDL + +mde.input.config (6):cde +-> mde.input.exception +-> mde.mergetag.Reader +-> mde.resource.paths +<- mde.input.input + +mde.input.exception (7):e +-> mde.exception +<- mde.input.config +<- mde.input.input + +mde.input.input (5):cde +-> mde.input.config +-> mde.input.exception +<- mde.events +<- mde.global +<- mde.mde + +mde.input.joystick (2a): +<- mde.SDL + +mde.mde (0):abcde +-> mde.events +-> mde.exception +-> mde.global +-> mde.i18n.I18nTranslation +-> mde.Init +-> mde.input.input +-> mde.scheduler +-> mde.SDL (not a dependancy) + +mde.mergetag.DataSet (9):de +-> mde.mergetag.DefaultData +-> mde.mergetag.exception +<- mde.i18n.I18nTranslation +<- mde.mergetag.iface.IReader +<- mde.mergetag.iface.IWriter +<- mde.mergetag.Reader +<- mde.mergetag.Writer +<- mde.options +<- mde.resource.paths + +mde.mergetag.DefaultData (10):de +-> mde.mergetag.exception +-> mde.mergetag.iface.IDataSection +<- mde.mergetag.DataSet +<- mde.mergetag.Reader + +mde.mergetag.exception (11):e +-> mde.exception +<- mde.i18n.I18nTranslation +<- mde.mergetag.DataSet +<- mde.mergetag.DefaultData +<- mde.mergetag.Reader +<- mde.mergetag.Writer +<- mde.options +<- mde.resource.paths + +mde.mergetag.iface.IDataSection (11d): +<- mde.mergetag.DefaultData + +mde.mergetag.iface.IReader (9):de +-> mde.mergetag.DataSet +<- mde.mergetag.Reader + +mde.mergetag.iface.IWriter (9):de +-> mde.mergetag.DataSet +<- mde.mergetag.Writer + +mde.mergetag.internal (9c): +<- mde.mergetag.Reader +<- mde.mergetag.Writer + +mde.mergetag.Reader (8):cde +-> mde.mergetag.DataSet +-> mde.mergetag.DefaultData +-> mde.mergetag.exception +-> mde.mergetag.iface.IReader +-> mde.mergetag.internal +<- mde.i18n.I18nTranslation +<- mde.input.config +<- mde.options +<- mde.resource.paths + +mde.mergetag.Writer (8):cde +-> mde.mergetag.DataSet +-> mde.mergetag.exception +-> mde.mergetag.iface.IWriter +-> mde.mergetag.internal +<- mde.options +<- mde.resource.paths + +mde.options (3):cde +-> mde.mergetag.DataSet +-> mde.mergetag.exception +-> mde.mergetag.Reader +-> mde.mergetag.Writer +-> mde.resource.paths +<- mde.i18n.I18nTranslation +<- mde.Init + +mde.resource.paths (7):cde +-> mde.mergetag.DataSet +-> mde.mergetag.exception +-> mde.mergetag.Reader +-> mde.mergetag.Writer +<- mde.i18n.I18nTranslation +<- mde.Init +<- mde.input.config +<- mde.options + +mde.scheduler (4b): +<- mde.events +<- mde.mde + +mde.SDL (1):abcde +-> mde.Init +-> mde.input.joystick +<- mde.mde diff -r 5f90774ea1ef -r 56a42ec95024 codeDoc/jobs.txt --- a/codeDoc/jobs.txt Sat Mar 15 15:14:25 2008 +0000 +++ b/codeDoc/jobs.txt Tue Mar 18 17:51:52 2008 +0000 @@ -42,3 +42,5 @@ Done (for git log message): +Moved an Init function; hence events now depends on Init rather than vice-versa. +Paths and logging set up by Init's static this(). Logging location set at compile time. diff -r 5f90774ea1ef -r 56a42ec95024 codeDoc/other_projects.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/codeDoc/other_projects.txt Tue Mar 18 17:51:52 2008 +0000 @@ -0,0 +1,32 @@ +What this is: a list of projects (mostly from dsource.org) which may interest me, and some I've looked at and realised don't. +What I'm interested in: +1: a platform independant windowing & event library, like SDL or GLFW +2: a graphics engine (or part thereof) + + +Possible projects to use/cooperate with: + +GLFW http://glfw.sourceforge.net/ replacement for SDL; details on website look good +Schooner http://www.dsource.org/projects/schooner GLFW in D + freetype font library (definitely take a better look; but is phobos based and needs a new build method) + +Odin's eye http://www.dsource.org/projects/odinseye 3d engine; ~16000 lines. Lots of classes, many of which don't do a lot. Not sure, possibly just take any code I want. + + +Projects I'm less interested in: + +http://www.dsource.org/projects/universal some kind of programming framework, only 448 lines of code, long untouched + +gefuege http://www.dsource.org/projects/gefuege game engine; ~7500 lines, don't like the coding +Torus Trooper (tt) (URL?) simple game; not very generic coding + +http://www.dsource.org/projects/l8night win32-based GUI toolkit +http://www.dsource.org/projects/dwt win32/GTK (?) based GUI toolkit +http://www.dsource.org/projects/cursestui win32-based GUI toolkit +http://www.dsource.org/projects/gtkd GTK wrapper +http://www.dsource.org/projects/dxul abandoned; no source + +Other: + +Sinbad http://www.dsource.org/projects/sinbad reimplementation of Ogre in D (untouched for a long time; only appears to contain the function/class prototypes and very little actual code) + +Element http://www.dsource.org/projects/element replacement for SDL + windowing toolkit (alpha); cool demo but no recent activity & source deleted from repo 25/1/08 (before more recent changes): now dead? diff -r 5f90774ea1ef -r 56a42ec95024 doc/Readme.txt --- a/doc/Readme.txt Sat Mar 15 15:14:25 2008 +0000 +++ b/doc/Readme.txt Tue Mar 18 17:51:52 2008 +0000 @@ -1,12 +1,11 @@ Copyright © 2007-2008 Diggory Hardy License: GNU General Public License version 2 (see doc/License.txt) - -There is no proper readme for mde yet. +Some brief info: Platforms: I work on mde using linux and perform most testing on this platform. -I have done a little testing on Windows, but currently is highly likely there may be problems on this platform. +I have done a little testing on Windows, but currently it's highly likely there may be problems on this platform. Build instructions: dsss build @@ -23,3 +22,38 @@ Walter Bright and Digital Mars for D and DMD. The tango team for Tango. + + +--- Goal --- + +Mde is an attempt to build a game engine. + +It is my second serious attempt at a game engine, following on from ge118 (http://diggory.hardy.googlepages.com/ge118). GE118 was written in C++, starting from some basic OpenGL and SDL tests and some of my early ideas about implementing an engine, and was heavily revised over time as I learned more about programming. + +Mde − a Modular D (game-oriented) Engine − was started after I discovered D, and decided I wanted to make a clean start. The acronym starts "modular" because, from experience working on ge118, I had discovered that tightly-related module dependancies (and worse, circular dependancies) caused many difficulties while programming and prevented good designs of components; thus I wanted to separate each part of the project in order to enable better design and facilitate developing a replacement for a part of the program (in case someone would wish to do so). + +It is not a library, since: +a) IMO, different games using the same engine should, where possible (which should be the case with an open source engine) share the same executable and only use config/command line arguments to select the different game. +b) The source is tied together so tightly and is only designed to be used in one way; thus there seems little point organising it as a library and example app. +Anyway, anyone wanting to use it as a library need only remove mde/mde.d and compile it. + + +--- FAQ (or just a few questions) --- + ++ How many lines are there? +# wc -l $(find . -name "*.d") +As of last count, that was 3908. + ++ Why is the code so DENSE? +That's just my coding style (I still mostly stick to the D style guide). I like code to look neat, but I don't like spacing it out a lot because then it takes more room (more scrolling) and IMO it becomes harder to visualise what goes on. + ++ What toolkits/external libraries are used? + tango + tango.scrapple + SDL (possibly will be replaced) + ++ What libraries are planned to be used? + OpenGL (of course...) + OpenAL (most likely; however audio support is a LONG way off) + Schooner: GLD & fonts (or conversions to tango) ? + GLFW ? diff -r 5f90774ea1ef -r 56a42ec95024 mde/Init.d --- a/mde/Init.d Sat Mar 15 15:14:25 2008 +0000 +++ b/mde/Init.d Tue Mar 18 17:51:52 2008 +0000 @@ -16,22 +16,22 @@ /************************************************************************************************** * Initialisation setup and exit cleanup module. * - * This module controls most of the initialisation and deinitialisation of the program. + * This module provides an infrastructure for handling much of the initialisation and + * deinitialisation of the program. It does not, however, provide much of the (de)initialisation + * code; with the exception of that for the logger. *************************************************************************************************/ module mde.Init; import mde.exception; - import mde.options; -import mde.events; -import global = mde.global; -import mde.input.input; +import paths = mde.resource.paths; // tango imports import tango.core.Thread; import tango.core.Exception; import tango.util.log.Log : Log, Logger; import tango.util.log.ConsoleAppender : ConsoleAppender; +import tango.util.log.SwitchingFileAppender : SwitchingFileAppender; import tango.stdc.stringz : fromStringz; /** @@ -42,18 +42,31 @@ */ static this() { - version (mdeTest) {} // test.mdeTest sets up its own root logger + // Find/create paths: + try { + paths.resolvePaths(); + } catch (Exception e) { + throw new InitException ("Resolving paths failed: " ~ e.msg); + } + + // Set up the logger: + version (mdeTest) {} // test.mdeTest sets up its own root logger else { - // For now, just log to the console: + // Where logging is done to is determined at compile-time, currently just via static ifs. Logger root = Log.getRootLogger(); - root.addAppender(new ConsoleAppender); + + static if (true ) { // Log to the console + root.addAppender(new ConsoleAppender); + } + static if (true ) { // Log to files + // Use 2 log files with a maximum size of 1 MB: + root.addAppender (new SwitchingFileAppender (paths.logDir~"/log-.txt", 5)); + } // Set the level here, but set it again once options have been loaded: debug root.setLevel(root.Level.Trace); else root.setLevel(root.Level.Info); } - - Init.addFunc (&miscInit); } static ~this() { @@ -89,11 +102,13 @@ */ this() { + logger.info ("Init: starting"); - logger.info ("Init: starting"); //BEGIN Pre-init (loading options) - // Load options FIRST. Should be fast, most work is probably disk access, - // and it's a really good idea to let the options apply to all other loading. + /* Load options now. Don't load in a thread since: + * Loading should be fast + * Most work is probably disk access + * It's a really good idea to let the options apply to all other loading. */ try { Options.load(); } catch (optionsLoadException e) { @@ -101,6 +116,7 @@ } addCleanupFct (&Options.save); // not strictly cleanup, but needs to be called somewhere + // Now re-set the logging level: Log.getRootLogger.setLevel (cast(Log.Level) Options.misc.logLevel, true); // set the stored log level //END Pre-init @@ -240,18 +256,3 @@ logger.info ("Unittest complete."); } } - -void miscInit () { - /* Some miscellaneous short calls. - * - * Was executed in the main loop, but for the sake of simplicity use another thread. - */ - try { - global.input = new Input(); - global.input.loadConfig (); // (may also create instance) - - addEventsSchedule (); - } catch (Exception e) { - init.setFailure (); // must clean up properly - } -} diff -r 5f90774ea1ef -r 56a42ec95024 mde/events.d --- a/mde/events.d Sat Mar 15 15:14:25 2008 +0000 +++ b/mde/events.d Tue Mar 18 17:51:52 2008 +0000 @@ -16,6 +16,8 @@ /// Handles all events from SDL_PollEvent. module mde.events; +import mde.Init; + import mde.scheduler; import global = mde.global; @@ -29,10 +31,20 @@ private Logger logger; static this() { logger = Log.getLogger ("mde.events"); + + Init.addFunc (&initInput); } -void addEventsSchedule () { - Scheduler.perFrame (&pollEvents); +// An Init function +void initInput () { + try { + global.input = new Input(); + global.input.loadConfig (); // (may also create instance) + + Scheduler.perFrame (&pollEvents); + } catch (Exception e) { + init.setFailure (); // must clean up properly + } } void pollEvents (double) { diff -r 5f90774ea1ef -r 56a42ec95024 mde/global.d --- a/mde/global.d Sat Mar 15 15:14:25 2008 +0000 +++ b/mde/global.d Tue Mar 18 17:51:52 2008 +0000 @@ -25,6 +25,6 @@ import mde.input.input; -bool run = true; // mail loop continues if this is true +bool run = true; // main loop continues if this is true Input input; // Input instance. When multiple users are allowed instances will be per-user. diff -r 5f90774ea1ef -r 56a42ec95024 mde/mde.d --- a/mde/mde.d Sat Mar 15 15:14:25 2008 +0000 +++ b/mde/mde.d Tue Mar 18 17:51:52 2008 +0000 @@ -44,7 +44,8 @@ { //BEGIN Initialisation Logger logger = Log.getLogger ("mde.mde"); - + logger.info ("Starting mde..."); + try { init = new Init(); // initialisation } catch (InitException e) { diff -r 5f90774ea1ef -r 56a42ec95024 mde/options.d --- a/mde/options.d Sat Mar 15 15:14:25 2008 +0000 +++ b/mde/options.d Tue Mar 18 17:51:52 2008 +0000 @@ -137,6 +137,7 @@ class OptionsMisc : Options { bool useThreads; // set 0 to disable threading char[] L10n; // locale, e.g. en-GB + int logLevel; // tango logger level this () { diff -r 5f90774ea1ef -r 56a42ec95024 mde/resource/paths.d --- a/mde/resource/paths.d Sat Mar 15 15:14:25 2008 +0000 +++ b/mde/resource/paths.d Tue Mar 18 17:51:52 2008 +0000 @@ -102,13 +102,23 @@ paths[pathsLen++] = path~'/'; } - // Test a path and add if is a folder - void tryPath (char[] path) { - PathView pv = FilePath (path); - if (pv.exists && pv.isFolder) { + // Test a path and add if is a folder. + bool tryPath (char[] path, bool create = false) { + FilePath fp = FilePath (path); + if (fp.exists && fp.isFolder) { paths[pathsLen++] = path~'/'; - logger.info ("Path "~path~" is writable: " ~ (pv.isWritable ? "yes" : "no")); + return true; + } else if (create) { + try { + fp.create; + paths[pathsLen++] = fp.toString~'/'; + return true; + } catch (Exception e) { + logger.warn ("Creating path "~path~" failed:"); + logger.warn (e.msg); + } } + return false; } // Use a static array to store all possible paths with separate length counters. @@ -119,34 +129,11 @@ /** These are the actual instances, one for each of the data and conf "directories". */ mdeDirectory dataDir, confDir; +char[] logDir; //BEGIN Path resolution static this() { logger = Log.getLogger ("mde.resource.paths"); - - // NOTE: May fail. Currently I think I'll just let the exception halt execution. - resolvePaths(); - if (!dataDir.pathsLen) throw new mdeException ("Fatal: no data path found!"); - if (!confDir.pathsLen) throw new mdeException ("Fatal: no conf path found!"); -} - -private: -// The maximum number of paths for any one "directory". -// There are NO CHECKS that this is not exceeded. -const MAX_PATHS = 3; - -Logger logger; - -/* Try each path in succession, returning the first two exist and be a folder. Throws if none are. */ -char[] findPath (char[][] paths ...) { - foreach (path; paths) { - PathView pv = new FilePath (path); - if (pv.exists && pv.isFolder) return pv.toString; // got a valid path - } - // no valid path... - logger.fatal ("Unable to resolve a required path! The following were tried:"); - foreach (path; paths) logger.fatal ('\t' ~ path); - throw new mdeException ("Unable to resolve a required path (see log for details)."); } // These are used several times: @@ -155,21 +142,33 @@ version (linux) { void resolvePaths () { + logger.trace ("1"); // Home directory: char[] HOME = fromStringz (getenv (toStringz ("HOME"))); + logger.trace ("3"); // Base paths: - char[] staticPath = findPath ("/usr/share/games/mde", "/usr/local/share/games/mde", "data"); - char[] userPath = findPath (HOME~"/.config/mde", HOME~"/.mde"); + // Static data (must exist): + PathView staticPath = findPath (false, "/usr/share/games/mde", "/usr/local/share/games/mde", "data"); + // Config (can just use defaults if necessary, so long as we can save afterwards): + PathView userPath = findPath (true, HOME~"/.config/mde", HOME~"/.mde"); + logger.trace ("5"); // Static data paths: - dataDir.addPath (staticPath); // we know this is valid anyway - dataDir.tryPath (userPath ~ DATA); + dataDir.addPath (staticPath.toString); // we know this is valid anyway + dataDir.tryPath (userPath.toString ~ DATA); + if (!dataDir.pathsLen) throw new mdeException ("Fatal: no data path found!"); + logger.trace ("7"); // Configuration paths: - confDir.tryPath (staticPath ~ CONF); - confDir.tryPath ("/etc/mde"); - confDir.tryPath (userPath ~ CONF); + confDir.tryPath (staticPath.toString ~ CONF); + bool sysConf = confDir.tryPath ("/etc/mde"); + confDir.tryPath (userPath.toString ~ CONF, !sysConf); + if (!confDir.pathsLen) throw new mdeException ("Fatal: no conf path found!"); + + logger.trace ("9"); + // Logging path: + logDir = userPath.toString; } } else version (Windows) { void resolvePaths () { @@ -181,17 +180,52 @@ char[] staticPath = findPath (installPath ~ DATA); // Static data paths: - dataDir.addPath[dataLength++] = staticPath; // we know this is valid anyway - dataDir.tryPath (userPath ~ DATA); - + dataDir.addPath (staticPath.toString); // we know this is valid anyway + dataDir.tryPath (userPath.toString ~ DATA); + if (!dataDir.pathsLen) throw new mdeException ("Fatal: no data path found!"); + // Configuration paths: confDir.tryPath (staticPath.toString ~ CONF); - confDir.tryPath (installPath ~ CONF); - confDir.tryPath (userPath.toString ~ CONF); + bool sysConf = confDir.tryPath (installPath ~ CONF); + confDir.tryPath (userPath.toString ~ CONF, !sysConf); // create if no system conf dir + if (!confDir.pathsLen) throw new mdeException ("Fatal: no conf path found!"); + + // Logging path: + logDir = userPath; } } else { static assert (false, "Platform is not linux or Windows: no support for paths on this platform yet!"); } + +private: +// The maximum number of paths for any one "directory". +// There are NO CHECKS that this is not exceeded. +const MAX_PATHS = 3; + +Logger logger; + +/* Try each path in succession, returning the first to exist and be a folder. +* If none are valid and create is true, will try creating each in turn. +* If still none are valid, throws. */ +PathView findPath (bool create, char[][] paths ...) { + foreach (path; paths) { + PathView pv = new FilePath (path); + if (pv.exists && pv.isFolder) return pv; // got a valid path + } + if (create) { // try to create a folder, using each path in turn until succesful + foreach (path; paths) { + PathView pv = new FilePath (path); + try { + return pv; + } + catch (Exception e) {} + } + } + // no valid path... + logger.fatal ("Unable to find"~(create ? " or create" : "")~" a required path! The following were tried:"); + foreach (path; paths) logger.fatal ('\t' ~ path); + throw new mdeException ("Unable to resolve a required path (see log for details)."); +} //END Path resolution /** A special adapter for reading from multiple mergetag files with the same relative path to an