Mercurial > projects > mde
view mde/scheduler/Init.d @ 29:f985c28c0ec9
A new GUI widget plus changes to the init system.
GUI: Implemented a GridWidget to layout several sub-widgets.
Improved log messages about init functions.
Moved all dynamic-library loading into a separate init stage.
committer: Diggory Hardy <diggory.hardy@gmail.com>
author | Diggory Hardy <diggory.hardy@gmail.com> |
---|---|
date | Sat, 12 Apr 2008 14:10:13 +0100 |
parents | b5fadd8d930b |
children | 467c74d4804d |
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.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: Log.getRootLogger.setLevel (cast(Log.Level) miscOpts.logLevel, true); // set the stored log level //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)."); return; } //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_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 ~ 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 ~ 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 ~ 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); cleanupUT.addFunc (&cleanupFunc2); } initUT.addFunc (&initFunc); runStageForward (initUT); assert (initialised); runStageReverse (cleanupUT); assert (!initialised); logger.info ("Unittest complete."); } }