view mde/init.d @ 9:1885a9080f2a

Joystick button input now works with config. committer: Diggory Hardy <diggory.hardy@gmail.com>
author Diggory Hardy <diggory.hardy@gmail.com>
date Wed, 30 Jan 2008 11:33:56 +0000
parents f63f4f41a2dc
children 4c3575400769
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;

public import mde.exception;

import mde.events;
import mde.input.input;
import mde.input.joystick;

// tango imports
import tango.core.Thread;
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()
{
    // 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. */
        
        bool initFailure = false;	// Set true if something goes wrong and we need to abort.
        void setFailure () {		// For synchronization, although may be unnecessary
            synchronized initFailure = true;
        }
        void delegate() [] initFuncs = [
        delegate void() {
            // Inits SDL and related stuff (joystick).
            try {
                // SDL Joystick, used by mde.input
                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);
        }
        ];
        
        // 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 (Exception e) {			// should be a ThreadException, but catch all Exceptions
            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
        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();
            
            // 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()
    {
        runCleanupFcts();	// if threading, note not all functions can be called simultaeneously
    }
    
    /* 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();
        }
    }
}

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