view mde/scheduler/Init.d @ 45:0fd51d2c6c8a

Several changes to resising windows and layout widgets. This commit still has some bugs. Moved the implementable widgets from mde.gui.widget.Widget to miscWidgets, leaving base widgets in Widget. Rewrote some of GridLayoutWidget's implementation. Made many operations general to work for either columns or rows. Some optimisations were intended but ended up being removed due to problems. Allowed layout's to resize from either direction (only with window resizes). committer: Diggory Hardy <diggory.hardy@gmail.com>
author Diggory Hardy <diggory.hardy@gmail.com>
date Thu, 22 May 2008 11:34:09 +0100
parents 1530d9c04d4d
children f000d6cd0f74
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.init2;     // This module is responsible for setting up some init functions.
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;
}

// Derelict imports
import derelict.opengl.gl;
import derelict.sdl.sdl;
import derelict.freetype.ft;
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()
{
    // 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");
    }
    
    /** this() − initialisation
    *
    * Runs general initialisation code, in a threaded manner where this isn't difficult.
    * If any init fails, cleanup is still handled by ~this().
    *
    * Init order: 1. Pre-init (loads components needed by most init functions). 2. Dynamic library
    * loading (load any dynamic libraries first, so that if loading fails nothing else need be
    * done). 3. Init functions (threaded functions handling the rest of initialisation).
    */
    /* 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, using the value from the config file:
        Log.getRootLogger.setLevel (cast(Log.Level) miscOpts.logLevel, true);
        // And set this (debug option):
        imde.run = !miscOpts.exitImmediately;
        //END Pre-init
        
        debug logger.trace ("Init: pre-init done");
        
        //BEGIN Load dynamic libraries
        /* Can be done by init functions but much neater to do here.
        * Also means that init functions aren't run if a library fails to load. */
        try {
            DerelictSDL.load();
            DerelictGL.load();
            DerelictFT.load();
        } catch (DerelictException de) {
            logger.fatal ("Loading dynamic library failed:");
            logger.fatal (de.msg);
            
            throw new InitException ("Loading dynamic libraries failed (see above).");
        }
        //END Load dynamic libraries
        
        debug logger.trace ("Init: dynamic libraries loaded");
        
        //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 LOG_IF_MSG = "Init function ";
        const LOG_CF_MSG = "Cleanup function ";
        const LOG_F_START = " - running";
        const LOG_F_END = " - completed";
        const LOG_F_BAD = " - failed";
        const LOG_F_FAIL = " - failed: ";
        /* 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 {
                    debug logger.trace (LOG_IF_MSG ~ func.name ~ LOG_F_START);
                    func.func();
                    debug logger.trace (LOG_IF_MSG ~ func.name ~ (initFailure ? LOG_F_BAD : LOG_F_END));
                } catch (Exception e) {
                    logger.fatal (LOG_IF_MSG ~ func.name ~ LOG_F_FAIL ~
                            ((e.msg is null || e.msg == "") ? "(no failure message)" : 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 {
                    debug logger.trace (LOG_CF_MSG ~ func.name ~ LOG_F_START);
                    func.func();
                    debug logger.trace (LOG_CF_MSG ~ func.name ~ (initFailure ? LOG_F_BAD : LOG_F_END));
                } catch (Exception e) {
                    logger.fatal (LOG_CF_MSG ~ func.name ~ LOG_F_FAIL ~
                            ((e.msg is null || e.msg == "") ? "(no failure message)" : 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) {   // Start all threads
                    debug logger.trace (LOG_IF_MSG ~ func.name ~ LOG_F_START);
                    tg.create(func.func);
                    debug logger.trace (LOG_IF_MSG ~ func.name ~ (initFailure ? LOG_F_BAD : LOG_F_END));
                }
            } 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, assuming no threads were created.");
            
                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, "UT cleanup 1");
            cleanupUT.addFunc (&cleanupFunc2, "UT cleanup 2");
        }
        
        initUT.addFunc (&initFunc, "UT init");
        
        runStageForward (initUT);
        assert (initialised);
        
        runStageReverse (cleanupUT);
        assert (!initialised);

        logger.info ("Unittest complete.");
    }
}