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