view mde/Init.d @ 15:4608be19ebe2

Use OS paths (linux only for now), merging multiple paths. Init changes regarding options. Reorganised policies.txt a little. Implemented mde.resource.paths to read config from appropriate paths (currently linux only). Changed Init to load options before all other delegates are run and set logging level from options. committer: Diggory Hardy <diggory.hardy@gmail.com>
author Diggory Hardy <diggory.hardy@gmail.com>
date Fri, 14 Mar 2008 11:39:45 +0000
parents
children 9cb7b9310168
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 : fromStringz;

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()
{
    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);
    }
}
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()
    {
        //BEGIN Methods
        /* Initialisation functions.
        *
        * These should each handle a separate area of initialisation so that these functions can
        * 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.trace ("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 ? fromStringz(msg) : "no reason available");
                
                setFailure ();		// abort
                return;
            }
            
            addCleanupFct (&cleanupSDL);
            logger.trace ("SDL initialised");
            
            /+ Load of info-printing stuff
            // Print a load of info:
            logger.info ("Available video modes:");
            char[128] tmp;
            SDL_Rect** modes = SDL_ListModes (null, SDL_FULLSCREEN);
            if (modes is null) logger.info ("None!");
            else if (modes is cast(SDL_Rect**) -1) logger.info ("All modes are available");
            else {
                for (uint i = 0; modes[i] !is null; ++i) {
                    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");
                
                setFailure ();
                return;
            }
            
            openJoysticks ();		// after SDL init
            addCleanupFct (&closeJoysticks);
        },
        delegate void() {
            /* 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) {
                setFailure ();                      // must clean up properly
            }
        }
        ];
        //END Methods
        
        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.logLevel, true);  // set the stored log level
        char[128] tmp;
        logger.info (logger.format (tmp, "Set logging level: {}", options.logLevel));
        //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.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.useThreads = false;
                options.changed = true;
            }
        }
        
        if (!options.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.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");
        }
    }
    
    /* 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.");
    }
}