changeset 20:838577503598

Reworked much of Init. Moved mde.Init to mde.scheduler.Init and largely cleaned up the code. Implemented mde.scheduler.InitStage to reduce dependancies of modules running Init functions. committer: Diggory Hardy <diggory.hardy@gmail.com>
author Diggory Hardy <diggory.hardy@gmail.com>
date Sat, 22 Mar 2008 16:22:59 +0000
parents db0b48f02b69
children a60cbb7359dd
files codeDoc/imports.txt codeDoc/jobs.txt codeDoc/other_projects.txt mde/Init.d mde/SDL.d mde/events.d mde/exception.d mde/gl.d mde/i18n/I18nTranslation.d mde/input/config.d mde/mde.d mde/scheduler.d mde/scheduler/Init.d mde/scheduler/InitStage.d mde/scheduler/exception.d mde/scheduler/runTime.d test/mdeTest.d
diffstat 17 files changed, 645 insertions(+), 501 deletions(-) [+]
line wrap: on
line diff
--- a/codeDoc/imports.txt	Fri Mar 21 12:31:21 2008 +0000
+++ b/codeDoc/imports.txt	Sat Mar 22 16:22:59 2008 +0000
@@ -1,7 +1,7 @@
 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.
+As of 22th 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.
@@ -16,7 +16,8 @@
 ->  mde.global
 ->  mde.Init
 ->  mde.input.input
-->  mde.scheduler
+->  mde.scheduler.Init
+->  mde.scheduler.runTime
 <-  mde.mde
 
 mde.exception (12e):
@@ -41,14 +42,6 @@
 ->  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
@@ -75,9 +68,10 @@
 ->  mde.exception
 ->  mde.global
 ->  mde.i18n.I18nTranslation
-->  mde.Init
 ->  mde.input.input
-->  mde.scheduler
+->  mde.scheduler.exception
+->  mde.scheduler.Init
+->  mde.scheduler.runTime
 ->  mde.SDL (not a dependancy)
 
 mde.mergetag.DataSet (9):de
@@ -160,11 +154,29 @@
 <-  mde.input.config
 <-  mde.options
 
-mde.scheduler (4b):
+mde.scheduler.exception:
+->  mde.exception
+<-  mde.scheduler.Init
+<-  mde.mde
+
+mde.scheduler.Init:
+->  mde.exception
+->  mde.options
+->  mde.resource.paths
+->  mde.scheduler.exception
+->  mde.scheduler.InitStage
+<-  mde.mde
+
+mde.scheduler.InitStage:
+<-  mde.events
+<-  mde.scheduler.Init
+<-  mde.SDL
+
+mde.scheduler.runTime:
 <-  mde.events
 <-  mde.mde
 
 mde.SDL (1):abcde
-->  mde.Init
+->  mde.scheduler.InitStage
 ->  mde.input.joystick
 <-  mde.mde
--- a/codeDoc/jobs.txt	Fri Mar 21 12:31:21 2008 +0000
+++ b/codeDoc/jobs.txt	Sat Mar 22 16:22:59 2008 +0000
@@ -6,11 +6,10 @@
 
 
 To do:
+*   SDL window settings from options. Design to be resetable.
 Also see todo.txt.
-*   Control logging output via options.
 *   Windows building/compatibility (currently partial)
 *   gdc building/compatibility (wait for tango 0.99.5 release?)
-*   Config loaded from correct places
 *	OutOfMemoryException is not currently checked for − it should be at least in critical places (use high-level catching of all errors?).
 *	Sensitivity adjustments. From es_a_out:
         /+ FIXME: revise.
@@ -42,5 +41,3 @@
 
 
 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.
--- a/codeDoc/other_projects.txt	Fri Mar 21 12:31:21 2008 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,32 +0,0 @@
-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?
--- a/mde/Init.d	Fri Mar 21 12:31:21 2008 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,265 +0,0 @@
-/* LICENSE BLOCK
-Part of mde: a Modular D game-oriented Engine
-Copyright © 2007-2008 Diggory Hardy
-
-This program is free software; you can redistribute it and/or modify it under the terms of
-the GNU General Public License, version 2, as published by the Free Software Foundation.
-
-This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
-without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
-See the GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License along
-with this program; if not, write to the Free Software Foundation, Inc.,
-51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */
-
-/**************************************************************************************************
- * Initialisation setup and exit cleanup module.
- *
- * 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 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;
-version (SwitchAppender) {  // My own variation, currently just a test
-    import tango.util.log.SwitchingFileAppender : SwitchingFileAppender;
-} else {
-    import tango.util.log.RollingFileAppender : RollingFileAppender;
-}
-import tango.stdc.stringz : fromStringz;
-
-/**
- * Static CTOR
- *
- * This should handle a minimal amount of functionality where useful. For instance, configuring the
- * logger here and not in Init allows unittests to use the logger.
- */
-static this()
-{
-    // Find/create paths:
-    try {
-        paths.resolvePaths();
-    } catch (Exception e) {
-        throw new InitException ("Resolving paths failed: " ~ e.msg);
-    }
-    
-    // Set up the logger:
-    {
-        // Where logging is done to is determined at compile-time, currently just via static ifs.
-        Logger root = Log.getRootLogger();
-                
-        static if (true ) { // Log to the console
-            root.addAppender(new ConsoleAppender);
-        }
-        static if (true ) { // Log to files
-            version (SwitchAppender) {
-                root.addAppender (new SwitchingFileAppender (paths.logDir~"/log-.txt", 5));
-            } else {
-                // Use 2 log files with a maximum size of 1 MB:
-                root.addAppender (new RollingFileAppender (paths.logDir~"/log-.txt", 2, 1024*1024));
-            }
-        }
-        
-        // 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);
-    }
-}
-static ~this()
-{
-}
-
-// Global instance, since called init functions need to interact:
-scope Init init;
-
-/**
- * Init class
- *
- * A scope class created at beginning of the program and destroyed at the end; thus the CTOR
- * handles program initialisation and the DTOR handles program cleanup.
- */
-scope class Init
-{
-    private static Logger logger;
-    static this() {
-        logger = Log.getLogger ("mde.init.Init");
-    }
-    
-    /** CTOR − initialisation
-    *
-    * Runs general initialisation code, in a threaded manner where this isn't difficult.
-    *
-    * If any init fails, it must run necessary cleanup first since the DTOR cannot(?) be run. */
-    /* In a single-threaded function this could be done with:
-    * scope(failure) cleanup;
-    * This won't work with a threaded init function since any threads completing succesfully will
-    * not clean-up, and a fixed list of clean-up functions cannot be used since clean-up functions
-    * must not run where the initialisation functions have failed.
-    * Hence a list of clean-up functions is built similarly to scope(failure) --- see addCleanupFct.
-    */
-    this()
-    {
-        logger.info ("Init: starting");
-        
-        //BEGIN Pre-init (loading options)
-        /* 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) {
-            throw new InitException ("Loading options failed; message: " ~ e.msg);
-        }
-        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
-        
-        
-        //BEGIN Init (actual function calling)
-        /* Call init functions.
-        *
-        * Current method is to try using threads, and on failure assume no threads were actually
-        * created and run functions in a non-threaded manner.
-        */
-        ThreadGroup tg;                     // Not necessarily used
-        
-        if (Options.misc.useThreads) {           // Threaded init (thread creation)
-            try {                           // creating/starting threads could fail
-                tg = new ThreadGroup;
-                foreach (func; initFuncs) tg.create(func);  // Start all threads
-            } catch (ThreadException e) {   // Problem with threading; try without threads
-                logger.warn ("Caught exception while trying to create threads:");
-                logger.warn (e.msg);
-                logger.warn ("Will continue in a non-threaded manner.");
-            
-                Options.misc.useThreads = false;
-            }
-        }
-        
-        if (!Options.misc.useThreads) {          // Non-threaded init
-            
-            foreach (func; initFuncs) {     // for each delegate
-                // since we can stop at any point, we might as well if there's a problem:
-                if (initFailure) break;
-            
-                func();
-            }
-        }
-        
-        if (Options.misc.useThreads) {           // Threaded init (thread joining)
-            /* Wait for all threads to complete.
-            *
-            * If something went wrong, we still need to do this before cleaning up.
-            *
-            * For non-threaded usage, tg contains no threads so loop won't run.
-            */
-            foreach (t; tg) {
-                try {
-                    t.join (true);
-                    /+ We might as well catch and report any exception, rather than just ThreadExceptions:
-                    } catch (ThreadException e) {	// Any threading exception
-                    +/
-                } catch (Exception e) {		// Any other exception, i.e. caught from thread.
-                    // Relying on catching exceptions thrown by other threads is a bad idea.
-                    // Hence all threads should catch their own exceptions and return safely.
-                
-                    logger.fatal ("Exception caught during init:");
-                    logger.fatal (e.msg);
-                    logger.fatal ("Please report this; it should NEVER happen.");
-                
-                    setFailure ();		// abort (but join other threads first)
-                }
-            }
-        }
-        
-        if (initFailure) {
-            // All cleanup-on-failure must be done here.
-            runCleanupFcts();
-            
-            logger.fatal ("Init: failed");
-            // Throw an exception to signal failure and prevent DTOR from also running.
-            throw new InitException ("Initialisation failed due to above exceptions.");
-        } else logger.info ("Init: done");
-        //END Init
-    }
-    
-    /** DTOR - runs cleanup.
-    *
-    * Currently unthreaded; probably might as well stay that way. */
-    ~this()
-    {
-        if (!initFailure) {
-            logger.trace ("Init: cleaning up");
-            runCleanupFcts();	// if threading, note not all functions can be called simultaeneously
-            logger.trace ("Init: done");
-        }
-    }
-    
-    // Stop init and start cleanup.
-    void setFailure () {
-        synchronized initFailure = true;
-    }
-    
-    static {
-        /* Initialisation functions.
-        *
-        * These should each handle a separate area of initialisation so that these functions can
-        * be run simultaneously in separate threads. */
-        private void function() [] initFuncs;
-        
-        // Add another init function. Should be called from a static this().
-        static void addFunc (void function() fct) {
-            initFuncs ~= fct;
-        }
-        
-        private void function ()[] cleanup;		// all functions to be run for cleanup
-        
-        // Adding cleanup functions must be synchronized; use:
-        void addCleanupFct (void function () fct) {
-            synchronized cleanup ~= fct;
-        }
-        
-        // Clean-up fcts are run in reverse order to how they're added:
-        private void runCleanupFcts () {
-            foreach_reverse (fct; cleanup) fct();
-        }
-    }
-    
-    /* This is set true by this() if something goes wrong and we need to abort.
-    * If it's true, no cleanup should be done by the DTOR (since the DTOR still runs after an
-    * exception has been thrown). */
-    private bool initFailure = false;
-    
-    debug (mdeUnitTest) unittest {
-        /* Fake init and cleanup. This happens before the CTOR runs so the extra Init.runCleanupFcts()
-        * call isn't going to mess things up. The extra function called by runCleanupFcts won't cause
-        * any harm either. */
-        static bool initialised = false;
-        static void cleanup () {	initialised = false;	}
-        
-        static void init () {
-            initialised = true;
-            Init.addCleanupFct (&cleanup);
-        }
-        
-        init();
-        assert (initialised);
-        Init.runCleanupFcts();
-        assert (!initialised);
-
-        logger.info ("Unittest complete.");
-    }
-}
--- a/mde/SDL.d	Fri Mar 21 12:31:21 2008 +0000
+++ b/mde/SDL.d	Sat Mar 22 16:22:59 2008 +0000
@@ -17,48 +17,74 @@
 */
 module mde.SDL;
 
-import mde.Init;
+import mde.scheduler.InitStage;
 import mde.input.joystick;
 
 import tango.util.log.Log : Log, Logger;
+import tango.stdc.stringz;
 
 import derelict.sdl.sdl;
+import derelict.opengl.gl;
 import derelict.util.exception;
 
 Logger logger;
 static this() {
-    logger = Log.getLogger ("mde.init.SDL");
+    logger = Log.getLogger ("mde.SDL");
     
-    Init.addFunc (&initSDL);
+    init2.addFunc (&initSdlAndGl);
+    init4.addFunc (&setupWindow);
 }
 
-void initSDL() {
-            
-    // Inits SDL and related stuff (joystick).
+void initSdlAndGl() {   // init2 func
+    // Load SDL and GL dynamic libs
     try {
         DerelictSDL.load();
+        DerelictGL.load();
     } catch (DerelictException de) {
         logger.fatal ("Loading dynamic library failed:");
         logger.fatal (de.msg);
-                
-        init.setFailure ();		// abort
+        
+        init2.setFailure ();
         return;
     }
-    logger.trace ("Derelict: loaded SDL");
-            
+    logger.trace ("Derelict: loaded SDL and OpenGL");
+    
+    // Initialise SDL
     if (SDL_Init (SDL_INIT_VIDEO | SDL_INIT_JOYSTICK /+| SDL_INIT_EVENTTHREAD+/)) {
         logger.fatal ("SDL initialisation failed:");
         char* msg = SDL_GetError ();
         logger.fatal (msg ? fromStringz(msg) : "no reason available");
-                
-        init.setFailure ();		// abort
+        
+        init2.setFailure ();
         return;
     }
-            
-    init.addCleanupFct (&cleanupSDL);
+    
+    cleanup2.addFunc (&cleanupSDL);
     logger.trace ("SDL initialised");
-            
-    /+ Load of info-printing stuff
+    
+    // Must be called after SDL has been initialised, so cannot be a separate Init function.
+    openJoysticks ();                   // after SDL init
+    cleanup2.addFunc (&closeJoysticks);
+}
+
+void setupWindow() {    // init4 func
+    // Open a window
+    // FIXME: provide a way to re-set video mode and change settings
+    if (SDL_SetVideoMode (800, 600, 0, 0) is null) {   // Can't open in windows!!
+        logger.fatal ("Unable to set video mode:");
+        char* msg = SDL_GetError ();
+        logger.fatal (msg ? fromStringz(msg) : "no reason available");
+        
+        init4.setFailure ();
+        return;
+    }
+}
+
+void cleanupSDL () {
+    SDL_Quit();
+}
+
+    /+ Load of info-printing stuff (currently doesn't have a use)
     // Print a load of info:
     logger.info ("Available video modes:");
     char[128] tmp;
@@ -70,34 +96,16 @@
     logger.info (logger.format (tmp, "\t{}x{}", modes[i].w, modes[i].h));
     }
     }
-            
+    
     SDL_VideoInfo* vi = SDL_GetVideoInfo ();
     if (vi !is null) {
     logger.info ("Video info:");
     logger.info ("Hardware surface support: "~ (vi.flags & SDL_HWSURFACE ? "yes" : "no"));
     logger.info (logger.format (tmp, "Video memory: {}", vi.video_mem));
-                
+    
     if (vi.vfmt !is null) {
     logger.info ("Best video mode:");
     logger.info (logger.format (tmp, "Bits per pixel: {}", vi.vfmt.BitsPerPixel));
     }
     }
     +/
-            
-    // FIXME: make this non-fatal and provide a way to re-set video mode
-    if (SDL_SetVideoMode (800, 600, 0, 0) is null) {   // Can't open in windows!!
-        logger.fatal ("Unable to set video mode:");
-        char* msg = SDL_GetError ();
-        logger.fatal (msg ? fromStringz(msg) : "no reason available");
-                
-        init.setFailure ();
-        return;
-    }
-            
-    openJoysticks ();                        // after SDL init
-    init.addCleanupFct (&closeJoysticks);
-}
-
-void cleanupSDL () {
-    SDL_Quit();
-}
--- a/mde/events.d	Fri Mar 21 12:31:21 2008 +0000
+++ b/mde/events.d	Sat Mar 22 16:22:59 2008 +0000
@@ -16,9 +16,9 @@
 /// Handles all events from SDL_PollEvent.
 module mde.events;
 
-import mde.Init;
+import mde.scheduler.InitStage;
+import mde.scheduler.runTime;
 
-import mde.scheduler;
 import global = mde.global;
 
 import mde.input.input;
@@ -32,18 +32,17 @@
 static this() {
     logger = Log.getLogger ("mde.events");
 
-    Init.addFunc (&initInput);
+    init2.addFunc (&initInput);
 }
 
-// An Init function
-void initInput () {
+void initInput () { // init2 func
     try {
         global.input = new Input();
         global.input.loadConfig ();         // (may also create instance)
                 
         Scheduler.perFrame (&pollEvents);
     } catch (Exception e) {
-        init.setFailure ();                 // must clean up properly
+        init2.setFailure ();                // must clean up properly
     }
 }
 
--- a/mde/exception.d	Fri Mar 21 12:31:21 2008 +0000
+++ b/mde/exception.d	Sat Mar 22 16:22:59 2008 +0000
@@ -40,35 +40,6 @@
     }
 }
 
-/// Thrown when Init fails.
-class InitException : mdeException {
-    // NOTE: if symbol is final, it can't be modified in the static this(), but as const it can
-    static const char[] symbol;
-    static this () {	symbol = super.symbol ~ ".Init";	}
-    char[] getSymbol () {
-        return symbol;
-    }
-    
-    this (char[] msg) {
-        super(msg);
-    }
-}
-
-/* LICENSE BLOCK
-Part of mde: a Modular D game-oriented Engine
-Copyright © 2007-2008 Diggory Hardy
-
-This program is free software; you can redistribute it and/or modify it under the terms of
-the GNU General Public License, version 2, as published by the Free Software Foundation.
-
-This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
-without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
-See the GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License along
-with this program; if not, write to the Free Software Foundation, Inc.,
-51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */
-
 /// Thrown when loading options fails.
 class optionsLoadException : mdeException {
     // NOTE: if symbol is final, it can't be modified in the static this(), but as const it can
@@ -107,13 +78,13 @@
     
     unittest {
         // Check message prepending works correctly.
-        mdeException mE = new InitException("");
-        assert (mE.getSymbol() == "mde.Init", mE.getSymbol());
+        mdeException mE = new optionsLoadException("");
+        assert (mE.getSymbol() == "mde.options", mE.getSymbol());
         try {
-            throw new InitException ("ABC");
+            throw new mdeException ("ABC");
             assert (false);
         } catch (Exception e) {
-            assert (e.msg == "mde.Init: ABC", e.msg);
+            assert (e.msg == "mde: ABC", e.msg);
         }
     
         logger.info ("Unittest complete.");
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mde/gl.d	Sat Mar 22 16:22:59 2008 +0000
@@ -0,0 +1,22 @@
+/* LICENSE BLOCK
+Part of mde: a Modular D game-oriented Engine
+Copyright © 2007-2008 Diggory Hardy
+
+This program is free software; you can redistribute it and/or modify it under the terms of
+the GNU General Public License, version 2, as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */
+
+/** Simple OpenGL functions. */
+module mde.gl;
+
+import derelict.opengl.gl;
+
+static this() {
+}
--- a/mde/i18n/I18nTranslation.d	Fri Mar 21 12:31:21 2008 +0000
+++ b/mde/i18n/I18nTranslation.d	Sat Mar 22 16:22:59 2008 +0000
@@ -202,6 +202,9 @@
     //END Data
     
     debug (mdeUnitTest) unittest {
+        // This gets used before it is normally created (test incase this changes).
+        if (Options.misc is null) Options.misc = new OptionsMisc;
+        
         /* Relies on file: conf/L10n/i18nUnitTest.mtt
         * Contents:
         *********
--- a/mde/input/config.d	Fri Mar 21 12:31:21 2008 +0000
+++ b/mde/input/config.d	Sat Mar 22 16:22:59 2008 +0000
@@ -167,7 +167,7 @@
             else debug logger.warn ("Ended up with DataSection of wrong type; this should never happen.");
         }
         
-        debug {
+        debug (MDE_CONFIG_DUMP) {
             char tmp[128] = void;
             logger.trace (logger.format (tmp, "Loaded {} config sections.", configs.length));
             foreach (id, cfg; configs) {
--- a/mde/mde.d	Fri Mar 21 12:31:21 2008 +0000
+++ b/mde/mde.d	Sat Mar 22 16:22:59 2008 +0000
@@ -20,21 +20,20 @@
 module mde.mde;
 
 // Package imports
-import mde.Init;
 import global = mde.global;
 import mde.events;
-import mde.scheduler;
 import mde.i18n.I18nTranslation;        // greeting message
 import mde.exception;
 
-// This module is ONLY imported because otherwise it wouldn't be compiled in:
-import mde.SDL;
+import mde.SDL; // This module is ONLY imported because otherwise it wouldn't be compiled in
+
+import mde.scheduler.Init;
+import mde.scheduler.runTime;
+import mde.scheduler.exception;
 
 import mde.input.input;
 
-// External library imports
 import tango.core.Thread : Thread;	// for sleep
-//import tango.io.Stdout;
 import tango.time.Clock;
 import tango.util.log.Log : Log, Logger;
 
--- a/mde/scheduler.d	Fri Mar 21 12:31:21 2008 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,99 +0,0 @@
-/* LICENSE BLOCK
-Part of mde: a Modular D game-oriented Engine
-Copyright © 2007-2008 Diggory Hardy
-
-This program is free software; you can redistribute it and/or modify it under the terms of
-the GNU General Public License, version 2, as published by the Free Software Foundation.
-
-This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
-without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
-See the GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License along
-with this program; if not, write to the Free Software Foundation, Inc.,
-51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */
-
-/** Scheduler
-*/
-module mde.scheduler;
-
-import tango.time.Time;
-
-// NOTE: Currently has no support for removing functions. To fix, assign ID and store fct pointers
-// in an associative array, returning the ID [on adding fct pointer].
-// FIXME: support delegates or not?
-/// This class can run scheduled functions per frame or every t seconds (sim-time).
-abstract class Scheduler
-{
-    /** The type of function pointer to be passed to the scheduler.
-    *
-    * The double $(I time) parameter gives the number of (sim) seconds since the function was last
-    * called, or zero on the first run. */
-    alias void function (double time) scheduleFct;
-    
-    /** Add a function to be called per frame. */
-    static void perFrame (scheduleFct fct) {
-        frameFcts ~= fct;
-    }
-    
-    /** Add a function to be called per t secs or n 100-nano-sec intevals.
-    *
-    * Since the scheduler cannot guarantee a maximum time between calls, the interval at which
-    * functions are called is always greater than or equal to the inverval specified here. Of
-    * course, the actual inteval is given when the function is run.
-    */
-    static void perTime (double t, scheduleFct fct) {
-        perTime (TimeSpan.interval(t), fct);
-    }
-    /** ditto */
-    static void perTime (TimeSpan n, scheduleFct fct)
-    in { assert (n > TimeSpan (0L)); }
-    body {
-        timeFcts ~= TimeFct (fct, n);
-    }
-    
-    /** This function should get called by the main loop, once per frame.
-    *
-    * The parameter time should be the current sim-time, using the tango.core.Types.Time enum; all
-    * time evaluations will use this.
-    */
-    static void run (Time time) {
-        double interval;
-        
-        // Call all per-frame functions:
-        if (lastTime == Time (0L)) interval = 0.0;		// 0 interval for first loop
-        else interval = (time-lastTime).interval();
-        
-        foreach (fct; frameFcts) fct(interval);
-        
-        // Call all per-interval functions:
-        foreach (fct; timeFcts) if (time >= fct.nextCall) {
-            if (fct.nextCall == Time (0L)) interval = 0.0;	// 0 interval for first call
-            else interval = (time - (fct.nextCall - fct.interval)).interval();
-            fct.nextCall = time + fct.interval;		// when to call next
-            
-            fct.fct (interval);				// call
-        }
-    }
-    
-    /* Holds details for functions called per time interval. */
-    private struct TimeFct {
-        scheduleFct fct;			// function to call
-        
-        TimeSpan interval;			// interval to call at
-        // Storing nextCall is more efficient than storing lastCall since only this number has to
-        // be compared to time every frame where fct is not called:
-        Time nextCall = Time (0L);
-        
-        static TimeFct opCall (scheduleFct f, TimeSpan t) {	// static CTOR
-            TimeFct ret;
-            ret.fct = f;
-            ret.interval = t;
-            return ret;
-        }
-    }
-    
-    private static Time lastTime = Time (0L);
-    private static scheduleFct[] frameFcts;
-    private static TimeFct[] timeFcts;
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mde/scheduler/Init.d	Sat Mar 22 16:22:59 2008 +0000
@@ -0,0 +1,305 @@
+/* LICENSE BLOCK
+Part of mde: a Modular D game-oriented Engine
+Copyright © 2007-2008 Diggory Hardy
+
+This program is free software; you can redistribute it and/or modify it under the terms of
+the GNU General Public License, version 2, as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */
+
+/**************************************************************************************************
+ * Initialisation setup and exit cleanup module.
+ *
+ * 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.scheduler.Init;
+
+import mde.scheduler.InitStage;
+import mde.scheduler.exception;
+
+import mde.options;
+import paths = mde.resource.paths;
+import mde.exception;
+
+// tango imports
+import tango.core.Thread;
+import tango.core.Exception;
+import tango.stdc.stringz : fromStringz;
+import tango.util.log.Log : Log, Logger;
+import tango.util.log.ConsoleAppender : ConsoleAppender;
+
+version = SwitchAppender;
+version (SwitchAppender) {  // My own variation, currently just a test
+    import tango.util.log.SwitchingFileAppender : SwitchingFileAppender;
+} else {
+    import tango.util.log.RollingFileAppender : RollingFileAppender;
+}
+
+/**
+ * Static CTOR
+ *
+ * This should handle a minimal amount of functionality where useful. For instance, configuring the
+ * logger here and not in Init allows unittests to use the logger.
+ */
+static this()
+{
+    // Find/create paths:
+    try {
+        paths.resolvePaths();
+    } catch (Exception e) {
+        throw new InitException ("Resolving paths failed: " ~ e.msg);
+    }
+    
+    // Set up the logger:
+    {
+        // Where logging is done to is determined at compile-time, currently just via static ifs.
+        Logger root = Log.getRootLogger();
+                
+        static if (true ) { // Log to the console
+            root.addAppender(new ConsoleAppender);
+        }
+        static if (true ) { // Log to files
+            version (SwitchAppender) {
+                root.addAppender (new SwitchingFileAppender (paths.logDir~"/log-.txt", 5));
+            } else {
+                // Use 2 log files with a maximum size of 1 MB:
+                root.addAppender (new RollingFileAppender (paths.logDir~"/log-.txt", 2, 1024*1024));
+            }
+        }
+        
+        // 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);
+    }
+}
+static ~this()
+{
+}
+
+// Global instance, since called init functions need to interact:
+scope Init init;
+
+/**
+ * Init class
+ *
+ * A scope class created at beginning of the program and destroyed at the end; thus the CTOR
+ * handles program initialisation and the DTOR handles program cleanup.
+ */
+scope class Init
+{
+    private static Logger logger;
+    static this() {
+        logger = Log.getLogger ("mde.scheduler.Init.Init");
+    }
+    
+    /** CTOR − initialisation
+    *
+    * Runs general initialisation code, in a threaded manner where this isn't difficult.
+    *
+    * If any init fails, it must run necessary cleanup first since the DTOR cannot(?) be run. */
+    /* In a single-threaded function this could be done with:
+    * scope(failure) cleanup;
+    * This won't work with a threaded init function since any threads completing succesfully will
+    * not clean-up, and a fixed list of clean-up functions cannot be used since clean-up functions
+    * must not run where the initialisation functions have failed.
+    * Hence a list of clean-up functions is built similarly to scope(failure) --- see addCleanupFct.
+    */
+    this()
+    {
+        logger.trace ("Init: starting");
+        
+        //BEGIN Pre-init (stage init0)
+        /* 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) {
+            throw new InitException ("Loading options failed; message: " ~ e.msg);
+        }
+        
+        // Now re-set the logging level:
+        Log.getRootLogger.setLevel (cast(Log.Level) Options.misc.logLevel, true);  // set the stored log level
+        //END Pre-init
+        
+        
+        //BEGIN Init (stages init2, init4)
+        /* Call init functions.
+        *
+        * Current method is to try using threads, and on failure assume no threads were actually
+        * created and run functions in a non-threaded manner. */
+        
+        try {   // init2
+            cleanupStages ~= &cleanup2; // add appropriate cleanup stage
+            
+            if (runStageThreaded (init2)) runStageForward (init2);
+        }
+        catch (InitStageException) {    // This init stage failed.
+            runCleanupStages();
+            throw new InitException ("Initialisation failed during stage init2");
+        }
+        
+        try {   // init4
+            cleanupStages ~= &cleanup4; // add appropriate cleanup stage
+            
+            if (runStageThreaded (init4)) runStageForward (init4);
+        }
+        catch (InitStageException) {    // This init stage failed.
+            runCleanupStages();
+            throw new InitException ("Initialisation failed during stage init4");
+        }
+        //END Init
+        
+        logger.trace ("Init: done");
+    }
+    
+    /** DTOR - runs cleanup functions. */
+    ~this()
+    {
+        logger.trace ("Cleanup: starting");
+        
+        // cleanup1:
+        Options.save(); // save options... do so here for now
+        
+        // cleanup2, 4:
+        runCleanupStages();
+        
+        logger.trace ("Cleanup: done");
+    }
+    
+    
+    //BEGIN runStage...
+    private static {
+        /* The following three functions, runStage*, each run all functions in a stage in some order,
+        * catching any exceptions thrown by the functions (although this isn't guaranteed for threads),
+        * and throw an InitStageException on failure. */
+    
+        const UFE = "Unhandled exception from Init function:";
+        /* Runs all functions consecutively, first-to-last.
+        * If any function fails, halts immediately. */
+        void runStageForward (InitStage s) {
+            foreach (func; s.funcs) {
+                if (s.failure) break;
+                try {
+                    func();
+                } catch (Exception e) {
+                    logger.fatal (UFE);
+                    logger.fatal (e.msg);
+                
+                    s.setFailure();
+                }
+            }
+            if (s.failure) throw new InitStageException;    // Problem running; abort and cleanup from here.
+        }
+        /* Runs all functions consecutively, last-to-first.
+        * If any function fails, continue until all have been run. */
+        void runStageReverse (InitStage s) {
+            foreach_reverse (func; s.funcs) {
+                try {
+                    func();
+                } catch (Exception e) {
+                    logger.fatal (UFE);
+                    logger.fatal (e.msg);
+                
+                    s.setFailure();
+                }
+            }
+            if (s.failure) throw new InitStageException;    // Problem running; abort and cleanup from here.
+        }
+        /* Tries running functions in a threaded way. Returns false if successful, true if not but
+        * functions should be run without threads. */
+        bool runStageThreaded (InitStage s) {
+            if (!Options.misc.useThreads) return true;  // Use unthreaded route instead
+        
+            ThreadGroup tg;
+            try {                           // creating/starting threads could fail
+                tg = new ThreadGroup;
+                foreach (func; s.funcs) tg.create(func);  // Start all threads
+            } catch (ThreadException e) {   // Problem with threading; try without threads
+                logger.warn ("Caught ThreadException while trying to create threads:");
+                logger.warn (e.msg);
+                logger.warn ("Will continue in a non-threaded manner.");
+            
+                Options.misc.useThreads = false;    // Disable threads entirely
+                return true;                // Try again without threads
+            }
+        
+            /* Wait for all threads to complete.
+            *
+            * If something went wrong, we still need to do this before cleaning up.
+            */
+            foreach (t; tg) {
+                try {
+                    t.join (true);
+                } catch (Exception e) {
+                    // Relying on catching exceptions thrown by other threads is a bad idea.
+                    // Hence all threads should catch their own exceptions and return safely.
+                
+                    logger.fatal ("Unhandled exception from Init function:");
+                    logger.fatal (e.msg);
+                
+                    s.setFailure ();        // abort (but join other threads first)
+                }
+            }
+        
+            if (s.failure) throw new InitStageException;    // Problem running; abort and cleanup from here.
+            return false;                   // Done successfully
+        }
+    }
+    //END runStage...
+    
+    private {
+        InitStage*[] cleanupStages;         // All cleanup stages needing to be run later.
+        void runCleanupStages () {
+            foreach_reverse (s; cleanupStages) {
+                try {
+                    runStageReverse (*s);
+                }
+                catch (InitStageException) {
+                    // We're cleaning up anyway, just report and continue
+                    logger.error ("Cleanup function failed! Continuing...");
+                }
+            }
+            cleanupStages = [];             // All have been run, don't want them getting run again
+        }
+    }
+    
+    debug (mdeUnitTest) unittest {
+        /* Fake init and cleanup. Use unittest-specific init and cleanup InitStages to avoid
+        * messing other init/cleanup up. */
+        static InitStage initUT, cleanupUT;
+        
+        static bool initialised = false;
+        static void cleanupFunc1 () {
+            initialised = false;
+        }
+        static void cleanupFunc2 () {
+            assert (initialised == true);
+        }
+                
+        static void initFunc () {
+            initialised = true;
+            cleanupUT.addFunc (&cleanupFunc1);
+            cleanupUT.addFunc (&cleanupFunc2);
+        }
+        
+        initUT.addFunc (&initFunc);
+        
+        runStageForward (initUT);
+        assert (initialised);
+        
+        runStageReverse (cleanupUT);
+        assert (!initialised);
+
+        logger.info ("Unittest complete.");
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mde/scheduler/InitStage.d	Sat Mar 22 16:22:59 2008 +0000
@@ -0,0 +1,89 @@
+/* LICENSE BLOCK
+Part of mde: a Modular D game-oriented Engine
+Copyright © 2007-2008 Diggory Hardy
+
+This program is free software; you can redistribute it and/or modify it under the terms of
+the GNU General Public License, version 2, as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */
+
+/** This module is used to create a list of all functions required to run for initialisation and
+* cleanup.
+*
+* It has been separated out of Init.d to massively reduce dependancies of modules adding init
+* functions.
+*/
+module mde.scheduler.InitStage;
+
+/** Represents all functions to be called for a particular init stage.
+*
+* No code is included here to run the functions intentionally, to keep dependancies minimal.
+*/
+struct InitStage
+{
+    alias void function() InitFunction; /// Alias
+    
+    /** Add a function to be called during this init stage.
+    *
+    * Called in order added when not threaded (reverse order for cleanup).
+    *
+    * Exceptions should never be thrown, since each function may run as a thread, and catching
+    * thread exceptions is not guaranteed to work. Log a message, call setFailure() and return
+    * instead. */
+    void addFunc (InitFunction f) {
+        funcs ~= f;
+    }
+    
+    /** Should be called by an init function when a failure occurs. */
+    void setFailure () {
+        synchronized failure = true;
+    }
+    
+    package InitFunction[]  funcs   = [];
+    package bool            failure = false;
+}
+
+/** Init can be divided up into these stages, each run in order:
+*
+* init0:
+*   static this() (not represented here)
+*
+* init1:
+*   Also known as pre-init; is where options get loaded by Init (not represented here).
+*
+* init2:
+*   Main symbol and config loading and general low-level stage. This stage is threaded, so all
+*   called functions need to be thread-safe.
+*
+* init3:
+*   Reserved as an unthreaded stage.
+*
+* init4:
+*   Main loading stage for data files and setup for higher-level elements, e.g. windows and input
+*   devices. This stage is threaded, so all called functions need to be thread-safe.
+*
+* cleanupX:
+*   Corresponding cleanup stage to initX. Called in reverse order (e.g. cleanup2 called before
+*   cleanup1). Cleanup is never threaded.
+*
+* The following functions get called (update list as appropriate):
+*
+* init2:
+*   mde.SDL.initSdlAndGl, mde.events.initInput
+*
+* init4:
+*   mde.SDL.setupWindow
+*
+* cleanup2:
+*   (Potentially): mde.SDL.cleanupSDL, mde.init.joystick.closeJoysticks
+*/
+InitStage init2;
+InitStage init4;    /// ditto
+InitStage cleanup2; /// ditto
+InitStage cleanup4; /// ditto
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mde/scheduler/exception.d	Sat Mar 22 16:22:59 2008 +0000
@@ -0,0 +1,40 @@
+/* LICENSE BLOCK
+Part of mde: a Modular D game-oriented Engine
+Copyright © 2007-2008 Diggory Hardy
+
+This program is free software; you can redistribute it and/or modify it under the terms of
+the GNU General Public License, version 2, as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */
+
+/// Contains the exception classes for Init.
+module mde.scheduler.exception;
+
+import mde.exception;
+
+/// Thrown when Init fails.
+class InitException : mdeException {
+    // NOTE: if symbol is final, it can't be modified in the static this(), but as const it can
+    static const char[] symbol;
+    static this () {	symbol = super.symbol ~ ".Init";	}
+    char[] getSymbol () {
+        return symbol;
+    }
+    
+    this (char[] msg) {
+        super(msg);
+    }
+}
+
+/// Thrown when an init stage fails.
+class InitStageException : InitException {
+    this () {
+        super("");
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mde/scheduler/runTime.d	Sat Mar 22 16:22:59 2008 +0000
@@ -0,0 +1,99 @@
+/* LICENSE BLOCK
+Part of mde: a Modular D game-oriented Engine
+Copyright © 2007-2008 Diggory Hardy
+
+This program is free software; you can redistribute it and/or modify it under the terms of
+the GNU General Public License, version 2, as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */
+
+/** Scheduler
+*/
+module mde.scheduler.runTime;
+
+import tango.time.Time;
+
+// NOTE: Currently has no support for removing functions. To fix, assign ID and store fct pointers
+// in an associative array, returning the ID [on adding fct pointer].
+// FIXME: support delegates or not?
+/// This class can run scheduled functions per frame or every t seconds (sim-time).
+abstract class Scheduler
+{
+    /** The type of function pointer to be passed to the scheduler.
+    *
+    * The double $(I time) parameter gives the number of (sim) seconds since the function was last
+    * called, or zero on the first run. */
+    alias void function (double time) scheduleFct;
+    
+    /** Add a function to be called per frame. */
+    static void perFrame (scheduleFct fct) {
+        frameFcts ~= fct;
+    }
+    
+    /** Add a function to be called per t secs or n 100-nano-sec intevals.
+    *
+    * Since the scheduler cannot guarantee a maximum time between calls, the interval at which
+    * functions are called is always greater than or equal to the inverval specified here. Of
+    * course, the actual inteval is given when the function is run.
+    */
+    static void perTime (double t, scheduleFct fct) {
+        perTime (TimeSpan.interval(t), fct);
+    }
+    /** ditto */
+    static void perTime (TimeSpan n, scheduleFct fct)
+    in { assert (n > TimeSpan (0L)); }
+    body {
+        timeFcts ~= TimeFct (fct, n);
+    }
+    
+    /** This function should get called by the main loop, once per frame.
+    *
+    * The parameter time should be the current sim-time, using the tango.core.Types.Time enum; all
+    * time evaluations will use this.
+    */
+    static void run (Time time) {
+        double interval;
+        
+        // Call all per-frame functions:
+        if (lastTime == Time (0L)) interval = 0.0;		// 0 interval for first loop
+        else interval = (time-lastTime).interval();
+        
+        foreach (fct; frameFcts) fct(interval);
+        
+        // Call all per-interval functions:
+        foreach (fct; timeFcts) if (time >= fct.nextCall) {
+            if (fct.nextCall == Time (0L)) interval = 0.0;	// 0 interval for first call
+            else interval = (time - (fct.nextCall - fct.interval)).interval();
+            fct.nextCall = time + fct.interval;		// when to call next
+            
+            fct.fct (interval);				// call
+        }
+    }
+    
+    /* Holds details for functions called per time interval. */
+    private struct TimeFct {
+        scheduleFct fct;			// function to call
+        
+        TimeSpan interval;			// interval to call at
+        // Storing nextCall is more efficient than storing lastCall since only this number has to
+        // be compared to time every frame where fct is not called:
+        Time nextCall = Time (0L);
+        
+        static TimeFct opCall (scheduleFct f, TimeSpan t) {	// static CTOR
+            TimeFct ret;
+            ret.fct = f;
+            ret.interval = t;
+            return ret;
+        }
+    }
+    
+    private static Time lastTime = Time (0L);
+    private static scheduleFct[] frameFcts;
+    private static TimeFct[] timeFcts;
+}
--- a/test/mdeTest.d	Fri Mar 21 12:31:21 2008 +0000
+++ b/test/mdeTest.d	Sat Mar 22 16:22:59 2008 +0000
@@ -27,9 +27,8 @@
 import mde.mergetag.DataSet;
 import mde.mergetag.mtunittest;
 import mde.exception;
-import mde.Init;
+import mde.scheduler.Init;
 import mde.i18n.I18nTranslation;
-import mde.options;
 
 import tango.util.log.Log : Log, Logger;
 
@@ -41,9 +40,6 @@
     Log.getRootLogger.addAppender(new ConsoleAppender);
     
     logger = Log.getLogger ("test.mdeTest");
-    
-    // This needs to be created for the unittests (normally created during init)
-    Options.misc = new OptionsMisc;
 }
 
 void main() {