view mde/scheduler/Init.d @ 28:b5fadd8d930b

Small addition to GUI, paths work-around for Windows. New GUI widget containing a widget. Paths on windows now uses "." and "./user" as a temporary measure. committer: Diggory Hardy <diggory.hardy@gmail.com>
author Diggory Hardy <diggory.hardy@gmail.com>
date Tue, 08 Apr 2008 15:52:21 +0100
parents 611f7b9063c6
children f985c28c0ec9
line wrap: on
line source

/* 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 as published by the Free Software Foundation, either
version 2 of the License, or (at your option) any later version.

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, see <http://www.gnu.org/licenses/>. */

/**************************************************************************************************
 * 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.InitFunctions;
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) {
        // NOTE: an exception thrown here cannot be caught by main()!
        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 files (first appender so root seperator messages don't show on console)
            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));
                root.info (""); // some kind of separation between runs
                root.info ("");
            }
        }
        static if (true ) { // Log to the console
            root.addAppender(new ConsoleAppender);
        }
        
        // 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()
{
}

/**
 * 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 bool failure = false;       // set true on failure during init, so that clean
    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()
    {
        debug 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: " ~ e.msg);
        }
        
        // Now re-set the logging level:
        Log.getRootLogger.setLevel (cast(Log.Level) miscOpts.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 {
            if (runStageThreaded (init)) runStageForward (init);
        }
        catch (InitStageException) {    // This init stage failed.
            // FIXME: check DTOR still runs
            throw new InitException ("An init function failed (see above message(s))");
        }
        //END Init
        
        debug logger.trace ("Init: done");
    }
    
    /** DTOR - runs cleanup functions. */
    ~this()
    {
        debug logger.trace ("Cleanup: starting");
        
        Options.save(); // save options... do so here for now
        
        // General cleanup:
        try {
            if (runStageThreaded (cleanup)) runStageReverse (cleanup);
        }
        catch (InitStageException) {
            // Nothing else to do but report:
            logger.error ("One or more cleanup functions failed!");
        }
        
        debug 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 initFailure. */
    
        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 (initFailure) break;
                try {
                    func();
                } catch (Exception e) {
                    logger.fatal (UFE);
                    logger.fatal (e.msg);
                
                    setInitFailure();
                }
            }
            
            if (initFailure) 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);
                
                    setInitFailure();
                }
            }
            if (initFailure) 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 (!miscOpts.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.error ("Caught ThreadException while trying to create threads:");
                logger.error (e.msg);
                logger.info ("Will disable threads and continue.");
            
                Options.setBool("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);
                
                    setInitFailure ();        // abort (but join other threads first)
                }
            }
            
            if (initFailure) throw new InitStageException;    // Problem running; abort and cleanup from here.
            return false;                   // Done successfully
        }
    }
    //END runStage...
    
    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.");
    }
}