Mercurial > projects > mde
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."); } }