view mde/init.d @ 11:b940f267419e

Options class created & changes to mergetag exception messages. Options class created (barebones). Loading/saving from Init. Init no longer runs cleanup functions after initialisation failure. Improved mergetag exception messages & error reporting. committer: Diggory Hardy <diggory.hardy@gmail.com>
author Diggory Hardy <diggory.hardy@gmail.com>
date Thu, 21 Feb 2008 09:05:33 +0000
parents 4c3575400769
children bff0d802cb7d
line wrap: on
line source

/**************************************************************************************************
 * 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;
import mde.input.joystick;

// 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 : fromUtf8z;

import derelict.sdl.sdl;
import derelict.util.exception;

/**
 * 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()
{
    debug(mdeUnitTest) {}   // For unittests, the logger is set up by test.mdeTest
    else {
        // For now, just log to the console:
        Logger root = Log.getRootLogger();
        root.setLevel(root.Level.Trace);
        root.addAppender(new ConsoleAppender);
    }
}
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 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()
    {
        /* Initialisation functions.
        *
        * These should each handle a separate area of initialisation such that these functions could
        * be run simultaneously in separate threads. */
        
        void setFailure () {		// For synchronization, although shouldn't be necessary
            synchronized initFailure = true;
        }
        void delegate() [] initFuncs = [
        delegate void() {
            Logger logger = Log.getLogger ("mde.init.Init.SDL");
            
            // Inits SDL and related stuff (joystick).
            try {
                DerelictSDL.load();
            } catch (DerelictException de) {
                logger.fatal ("Loading dynamic library failed:");
                logger.fatal (de.msg);
                
                setFailure ();		// abort
                return;
            }
            logger.info ("Derelict: loaded 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 ? fromUtf8z(msg) : "no reason available");
                
                setFailure ();		// abort
                return;
            }
            
            SDL_SetVideoMode (800, 600, 0, 0);
            
            addCleanupFct (&cleanupSDL);
            logger.info ("SDL initialised");
            
            openJoysticks ();		// after SDL init
            addCleanupFct (&closeJoysticks);
        },
        delegate void() {
            Logger logger = Log.getLogger ("mde.init.Init.Options");
            
            try {
                Options.load();
            } catch (optionsLoadException e) {
                logger.fatal ("Loading options failed; message:");
                logger.fatal (e.msg);
                setFailure();
                return;
            }
            addCleanupFct (&Options.save);  // not strictly cleanup, but needs to be called somewhere
        }
        ];
        
        // Start all threads
        ThreadGroup tg = new ThreadGroup;	// can't fail since it does nothing (tango 0.99.4) (unless out of memory − anyway should be safe to throw at this point)
        try {	// creating/starting threads can fail
            foreach (func; initFuncs) tg.create(func);
        } catch (ThreadException e) {		// to do 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.");
            
            foreach (func; initFuncs) func();
        }
        
        // Do some initialisation in the main thread
        global.input = new Input();
        global.input.loadConfig ();		// (may also create instance)
        addEventsSchedule ();
        
        // 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);
            /+ Will only catch thread exceptions; but even so something still went badly wrong so we want the same functionality.
            } 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.");
        }
    }
    
    /** DTOR - runs cleanup.
    *
    * Currently unthreaded; probably might as well stay that way. */
    ~this()
    {
        if (!initFailure) {
            logger.info ("Cleaning up...");
            runCleanupFcts();	// if threading, note not all functions can be called simultaeneously
            logger.info ("Done!");
        }
    }
    
    /* Cleanup Functions.
    *
    * These may exist simply as something to add to the cleanup list... */
    static void cleanupSDL () {
        SDL_Quit();
    }
    
    private static {
        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:
        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.");
    }
}