view mde/Init.d @ 17:5f90774ea1ef

Applied the GNU GPL v2 to mde. committer: Diggory Hardy <diggory.hardy@gmail.com>
author Diggory Hardy <diggory.hardy@gmail.com>
date Sat, 15 Mar 2008 15:14:25 +0000
parents 9cb7b9310168
children 56a42ec95024
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, 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 controls most of the initialisation and deinitialisation of the program.
 *************************************************************************************************/
module mde.Init;

import mde.exception;

import mde.options;
import mde.events;
import global = mde.global;
import mde.input.input;

// 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.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()
{
    version (mdeTest) {}   // test.mdeTest sets up its own root logger
    else {
        // For now, just log to the console:
        Logger root = Log.getRootLogger();
        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);
    }
    
    Init.addFunc (&miscInit);
}
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 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.
        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
        
        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.");
    }
}

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
    }
}