# HG changeset patch # User Diggory Hardy # Date 1206202979 0 # Node ID 838577503598b1c7c66cb2a03fb6f06a528252bb # Parent db0b48f02b6942636032419dc09fd303f3187c9f Reworked much of Init. Moved mde.Init to mde.scheduler.Init and largely cleaned up the code. Implemented mde.scheduler.InitStage to reduce dependancies of modules running Init functions. committer: Diggory Hardy diff -r db0b48f02b69 -r 838577503598 codeDoc/imports.txt --- a/codeDoc/imports.txt Fri Mar 21 12:31:21 2008 +0000 +++ b/codeDoc/imports.txt Sat Mar 22 16:22:59 2008 +0000 @@ -1,7 +1,7 @@ Import relationships: (only imports from mde, to show inter-code relationships) As of commit 2aacda05d942e74cdeefe952e701be7b5d3342d6, Sat Mar 15 15:14:25 2008 +0000 -As of 18th March, updated but index/letters no longer current. +As of 22th March, updated but index/letters no longer current. Index (n) is such that it is greater than that of any module importing the module. Additionally, each module importing nothing is given a unique letter. Then each module depending (directly or indirectly) on this is given this letter. @@ -16,7 +16,8 @@ -> mde.global -> mde.Init -> mde.input.input --> mde.scheduler +-> mde.scheduler.Init +-> mde.scheduler.runTime <- mde.mde mde.exception (12e): @@ -41,14 +42,6 @@ -> mde.resource.paths <- mde.mde -mde.Init (): --> mde.exception --> mde.options --> mde.resource.paths -<- mde.events -<- mde.mde -<- mde.SDL - mde.input.config (6):cde -> mde.input.exception -> mde.mergetag.Reader @@ -75,9 +68,10 @@ -> mde.exception -> mde.global -> mde.i18n.I18nTranslation --> mde.Init -> mde.input.input --> mde.scheduler +-> mde.scheduler.exception +-> mde.scheduler.Init +-> mde.scheduler.runTime -> mde.SDL (not a dependancy) mde.mergetag.DataSet (9):de @@ -160,11 +154,29 @@ <- mde.input.config <- mde.options -mde.scheduler (4b): +mde.scheduler.exception: +-> mde.exception +<- mde.scheduler.Init +<- mde.mde + +mde.scheduler.Init: +-> mde.exception +-> mde.options +-> mde.resource.paths +-> mde.scheduler.exception +-> mde.scheduler.InitStage +<- mde.mde + +mde.scheduler.InitStage: +<- mde.events +<- mde.scheduler.Init +<- mde.SDL + +mde.scheduler.runTime: <- mde.events <- mde.mde mde.SDL (1):abcde --> mde.Init +-> mde.scheduler.InitStage -> mde.input.joystick <- mde.mde diff -r db0b48f02b69 -r 838577503598 codeDoc/jobs.txt --- a/codeDoc/jobs.txt Fri Mar 21 12:31:21 2008 +0000 +++ b/codeDoc/jobs.txt Sat Mar 22 16:22:59 2008 +0000 @@ -6,11 +6,10 @@ To do: +* SDL window settings from options. Design to be resetable. Also see todo.txt. -* Control logging output via options. * Windows building/compatibility (currently partial) * gdc building/compatibility (wait for tango 0.99.5 release?) -* Config loaded from correct places * OutOfMemoryException is not currently checked for − it should be at least in critical places (use high-level catching of all errors?). * Sensitivity adjustments. From es_a_out: /+ FIXME: revise. @@ -42,5 +41,3 @@ Done (for git log message): -Moved an Init function; hence events now depends on Init rather than vice-versa. -Paths and logging set up by Init's static this(). Logging location set at compile time. diff -r db0b48f02b69 -r 838577503598 codeDoc/other_projects.txt --- a/codeDoc/other_projects.txt Fri Mar 21 12:31:21 2008 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,32 +0,0 @@ -What this is: a list of projects (mostly from dsource.org) which may interest me, and some I've looked at and realised don't. -What I'm interested in: -1: a platform independant windowing & event library, like SDL or GLFW -2: a graphics engine (or part thereof) - - -Possible projects to use/cooperate with: - -GLFW http://glfw.sourceforge.net/ replacement for SDL; details on website look good -Schooner http://www.dsource.org/projects/schooner GLFW in D + freetype font library (definitely take a better look; but is phobos based and needs a new build method) - -Odin's eye http://www.dsource.org/projects/odinseye 3d engine; ~16000 lines. Lots of classes, many of which don't do a lot. Not sure, possibly just take any code I want. - - -Projects I'm less interested in: - -http://www.dsource.org/projects/universal some kind of programming framework, only 448 lines of code, long untouched - -gefuege http://www.dsource.org/projects/gefuege game engine; ~7500 lines, don't like the coding -Torus Trooper (tt) (URL?) simple game; not very generic coding - -http://www.dsource.org/projects/l8night win32-based GUI toolkit -http://www.dsource.org/projects/dwt win32/GTK (?) based GUI toolkit -http://www.dsource.org/projects/cursestui win32-based GUI toolkit -http://www.dsource.org/projects/gtkd GTK wrapper -http://www.dsource.org/projects/dxul abandoned; no source - -Other: - -Sinbad http://www.dsource.org/projects/sinbad reimplementation of Ogre in D (untouched for a long time; only appears to contain the function/class prototypes and very little actual code) - -Element http://www.dsource.org/projects/element replacement for SDL + windowing toolkit (alpha); cool demo but no recent activity & source deleted from repo 25/1/08 (before more recent changes): now dead? diff -r db0b48f02b69 -r 838577503598 mde/Init.d --- a/mde/Init.d Fri Mar 21 12:31:21 2008 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,265 +0,0 @@ -/* 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, version 2, as published by the Free Software Foundation. - -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, write to the Free Software Foundation, Inc., -51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ - -/************************************************************************************************** - * 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.Init; - -import mde.exception; -import mde.options; -import paths = mde.resource.paths; - -// tango imports -import tango.core.Thread; -import tango.core.Exception; -import tango.util.log.Log : Log, Logger; -import tango.util.log.ConsoleAppender : ConsoleAppender; -version (SwitchAppender) { // My own variation, currently just a test - import tango.util.log.SwitchingFileAppender : SwitchingFileAppender; -} else { - import tango.util.log.RollingFileAppender : RollingFileAppender; -} -import tango.stdc.stringz : fromStringz; - -/** - * 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) { - 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 the console - root.addAppender(new ConsoleAppender); - } - static if (true ) { // Log to files - 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)); - } - } - - // 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() -{ -} - -// Global instance, since called init functions need to interact: -scope Init init; - -/** - * 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() - { - logger.info ("Init: starting"); - - //BEGIN Pre-init (loading options) - /* 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; message: " ~ e.msg); - } - addCleanupFct (&Options.save); // not strictly cleanup, but needs to be called somewhere - - // Now re-set the logging level: - Log.getRootLogger.setLevel (cast(Log.Level) Options.misc.logLevel, true); // set the stored log level - //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.misc.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.misc.useThreads = false; - } - } - - if (!Options.misc.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.misc.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"); - } - } - - // Stop init and start cleanup. - void setFailure () { - synchronized initFailure = true; - } - - static { - /* Initialisation functions. - * - * These should each handle a separate area of initialisation so that these functions can - * be run simultaneously in separate threads. */ - private void function() [] initFuncs; - - // Add another init function. Should be called from a static this(). - static void addFunc (void function() fct) { - initFuncs ~= fct; - } - - private 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: - private 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."); - } -} diff -r db0b48f02b69 -r 838577503598 mde/SDL.d --- a/mde/SDL.d Fri Mar 21 12:31:21 2008 +0000 +++ b/mde/SDL.d Sat Mar 22 16:22:59 2008 +0000 @@ -17,48 +17,74 @@ */ module mde.SDL; -import mde.Init; +import mde.scheduler.InitStage; import mde.input.joystick; import tango.util.log.Log : Log, Logger; +import tango.stdc.stringz; import derelict.sdl.sdl; +import derelict.opengl.gl; import derelict.util.exception; Logger logger; static this() { - logger = Log.getLogger ("mde.init.SDL"); + logger = Log.getLogger ("mde.SDL"); - Init.addFunc (&initSDL); + init2.addFunc (&initSdlAndGl); + init4.addFunc (&setupWindow); } -void initSDL() { - - // Inits SDL and related stuff (joystick). +void initSdlAndGl() { // init2 func + // Load SDL and GL dynamic libs try { DerelictSDL.load(); + DerelictGL.load(); } catch (DerelictException de) { logger.fatal ("Loading dynamic library failed:"); logger.fatal (de.msg); - - init.setFailure (); // abort + + init2.setFailure (); return; } - logger.trace ("Derelict: loaded SDL"); - + logger.trace ("Derelict: loaded SDL and OpenGL"); + + // Initialise 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"); - - init.setFailure (); // abort + + init2.setFailure (); return; } - - init.addCleanupFct (&cleanupSDL); + + cleanup2.addFunc (&cleanupSDL); logger.trace ("SDL initialised"); - - /+ Load of info-printing stuff + + // Must be called after SDL has been initialised, so cannot be a separate Init function. + openJoysticks (); // after SDL init + cleanup2.addFunc (&closeJoysticks); +} + +void setupWindow() { // init4 func + // Open a window + // FIXME: provide a way to re-set video mode and change settings + 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"); + + init4.setFailure (); + return; + } +} + +void cleanupSDL () { + SDL_Quit(); +} + + /+ Load of info-printing stuff (currently doesn't have a use) // Print a load of info: logger.info ("Available video modes:"); char[128] tmp; @@ -70,34 +96,16 @@ 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"); - - init.setFailure (); - return; - } - - openJoysticks (); // after SDL init - init.addCleanupFct (&closeJoysticks); -} - -void cleanupSDL () { - SDL_Quit(); -} diff -r db0b48f02b69 -r 838577503598 mde/events.d --- a/mde/events.d Fri Mar 21 12:31:21 2008 +0000 +++ b/mde/events.d Sat Mar 22 16:22:59 2008 +0000 @@ -16,9 +16,9 @@ /// Handles all events from SDL_PollEvent. module mde.events; -import mde.Init; +import mde.scheduler.InitStage; +import mde.scheduler.runTime; -import mde.scheduler; import global = mde.global; import mde.input.input; @@ -32,18 +32,17 @@ static this() { logger = Log.getLogger ("mde.events"); - Init.addFunc (&initInput); + init2.addFunc (&initInput); } -// An Init function -void initInput () { +void initInput () { // init2 func try { global.input = new Input(); global.input.loadConfig (); // (may also create instance) Scheduler.perFrame (&pollEvents); } catch (Exception e) { - init.setFailure (); // must clean up properly + init2.setFailure (); // must clean up properly } } diff -r db0b48f02b69 -r 838577503598 mde/exception.d --- a/mde/exception.d Fri Mar 21 12:31:21 2008 +0000 +++ b/mde/exception.d Sat Mar 22 16:22:59 2008 +0000 @@ -40,35 +40,6 @@ } } -/// Thrown when Init fails. -class InitException : mdeException { - // NOTE: if symbol is final, it can't be modified in the static this(), but as const it can - static const char[] symbol; - static this () { symbol = super.symbol ~ ".Init"; } - char[] getSymbol () { - return symbol; - } - - this (char[] msg) { - super(msg); - } -} - -/* 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, version 2, as published by the Free Software Foundation. - -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, write to the Free Software Foundation, Inc., -51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ - /// Thrown when loading options fails. class optionsLoadException : mdeException { // NOTE: if symbol is final, it can't be modified in the static this(), but as const it can @@ -107,13 +78,13 @@ unittest { // Check message prepending works correctly. - mdeException mE = new InitException(""); - assert (mE.getSymbol() == "mde.Init", mE.getSymbol()); + mdeException mE = new optionsLoadException(""); + assert (mE.getSymbol() == "mde.options", mE.getSymbol()); try { - throw new InitException ("ABC"); + throw new mdeException ("ABC"); assert (false); } catch (Exception e) { - assert (e.msg == "mde.Init: ABC", e.msg); + assert (e.msg == "mde: ABC", e.msg); } logger.info ("Unittest complete."); diff -r db0b48f02b69 -r 838577503598 mde/gl.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mde/gl.d Sat Mar 22 16:22:59 2008 +0000 @@ -0,0 +1,22 @@ +/* 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, version 2, as published by the Free Software Foundation. + +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, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ + +/** Simple OpenGL functions. */ +module mde.gl; + +import derelict.opengl.gl; + +static this() { +} diff -r db0b48f02b69 -r 838577503598 mde/i18n/I18nTranslation.d --- a/mde/i18n/I18nTranslation.d Fri Mar 21 12:31:21 2008 +0000 +++ b/mde/i18n/I18nTranslation.d Sat Mar 22 16:22:59 2008 +0000 @@ -202,6 +202,9 @@ //END Data debug (mdeUnitTest) unittest { + // This gets used before it is normally created (test incase this changes). + if (Options.misc is null) Options.misc = new OptionsMisc; + /* Relies on file: conf/L10n/i18nUnitTest.mtt * Contents: ********* diff -r db0b48f02b69 -r 838577503598 mde/input/config.d --- a/mde/input/config.d Fri Mar 21 12:31:21 2008 +0000 +++ b/mde/input/config.d Sat Mar 22 16:22:59 2008 +0000 @@ -167,7 +167,7 @@ else debug logger.warn ("Ended up with DataSection of wrong type; this should never happen."); } - debug { + debug (MDE_CONFIG_DUMP) { char tmp[128] = void; logger.trace (logger.format (tmp, "Loaded {} config sections.", configs.length)); foreach (id, cfg; configs) { diff -r db0b48f02b69 -r 838577503598 mde/mde.d --- a/mde/mde.d Fri Mar 21 12:31:21 2008 +0000 +++ b/mde/mde.d Sat Mar 22 16:22:59 2008 +0000 @@ -20,21 +20,20 @@ module mde.mde; // Package imports -import mde.Init; import global = mde.global; import mde.events; -import mde.scheduler; import mde.i18n.I18nTranslation; // greeting message import mde.exception; -// This module is ONLY imported because otherwise it wouldn't be compiled in: -import mde.SDL; +import mde.SDL; // This module is ONLY imported because otherwise it wouldn't be compiled in + +import mde.scheduler.Init; +import mde.scheduler.runTime; +import mde.scheduler.exception; import mde.input.input; -// External library imports import tango.core.Thread : Thread; // for sleep -//import tango.io.Stdout; import tango.time.Clock; import tango.util.log.Log : Log, Logger; diff -r db0b48f02b69 -r 838577503598 mde/scheduler.d --- a/mde/scheduler.d Fri Mar 21 12:31:21 2008 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,99 +0,0 @@ -/* 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, version 2, as published by the Free Software Foundation. - -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, write to the Free Software Foundation, Inc., -51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ - -/** Scheduler -*/ -module mde.scheduler; - -import tango.time.Time; - -// NOTE: Currently has no support for removing functions. To fix, assign ID and store fct pointers -// in an associative array, returning the ID [on adding fct pointer]. -// FIXME: support delegates or not? -/// This class can run scheduled functions per frame or every t seconds (sim-time). -abstract class Scheduler -{ - /** The type of function pointer to be passed to the scheduler. - * - * The double $(I time) parameter gives the number of (sim) seconds since the function was last - * called, or zero on the first run. */ - alias void function (double time) scheduleFct; - - /** Add a function to be called per frame. */ - static void perFrame (scheduleFct fct) { - frameFcts ~= fct; - } - - /** Add a function to be called per t secs or n 100-nano-sec intevals. - * - * Since the scheduler cannot guarantee a maximum time between calls, the interval at which - * functions are called is always greater than or equal to the inverval specified here. Of - * course, the actual inteval is given when the function is run. - */ - static void perTime (double t, scheduleFct fct) { - perTime (TimeSpan.interval(t), fct); - } - /** ditto */ - static void perTime (TimeSpan n, scheduleFct fct) - in { assert (n > TimeSpan (0L)); } - body { - timeFcts ~= TimeFct (fct, n); - } - - /** This function should get called by the main loop, once per frame. - * - * The parameter time should be the current sim-time, using the tango.core.Types.Time enum; all - * time evaluations will use this. - */ - static void run (Time time) { - double interval; - - // Call all per-frame functions: - if (lastTime == Time (0L)) interval = 0.0; // 0 interval for first loop - else interval = (time-lastTime).interval(); - - foreach (fct; frameFcts) fct(interval); - - // Call all per-interval functions: - foreach (fct; timeFcts) if (time >= fct.nextCall) { - if (fct.nextCall == Time (0L)) interval = 0.0; // 0 interval for first call - else interval = (time - (fct.nextCall - fct.interval)).interval(); - fct.nextCall = time + fct.interval; // when to call next - - fct.fct (interval); // call - } - } - - /* Holds details for functions called per time interval. */ - private struct TimeFct { - scheduleFct fct; // function to call - - TimeSpan interval; // interval to call at - // Storing nextCall is more efficient than storing lastCall since only this number has to - // be compared to time every frame where fct is not called: - Time nextCall = Time (0L); - - static TimeFct opCall (scheduleFct f, TimeSpan t) { // static CTOR - TimeFct ret; - ret.fct = f; - ret.interval = t; - return ret; - } - } - - private static Time lastTime = Time (0L); - private static scheduleFct[] frameFcts; - private static TimeFct[] timeFcts; -} diff -r db0b48f02b69 -r 838577503598 mde/scheduler/Init.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mde/scheduler/Init.d Sat Mar 22 16:22:59 2008 +0000 @@ -0,0 +1,305 @@ +/* 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, version 2, as published by the Free Software Foundation. + +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, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ + +/************************************************************************************************** + * 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.InitStage; +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; +} + +/** + * 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) { + 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 the console + root.addAppender(new ConsoleAppender); + } + static if (true ) { // Log to files + 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)); + } + } + + // 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() +{ +} + +// Global instance, since called init functions need to interact: +scope Init init; + +/** + * 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.scheduler.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() + { + 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; message: " ~ e.msg); + } + + // Now re-set the logging level: + Log.getRootLogger.setLevel (cast(Log.Level) Options.misc.logLevel, true); // set the stored log level + //END Pre-init + + + //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 { // init2 + cleanupStages ~= &cleanup2; // add appropriate cleanup stage + + if (runStageThreaded (init2)) runStageForward (init2); + } + catch (InitStageException) { // This init stage failed. + runCleanupStages(); + throw new InitException ("Initialisation failed during stage init2"); + } + + try { // init4 + cleanupStages ~= &cleanup4; // add appropriate cleanup stage + + if (runStageThreaded (init4)) runStageForward (init4); + } + catch (InitStageException) { // This init stage failed. + runCleanupStages(); + throw new InitException ("Initialisation failed during stage init4"); + } + //END Init + + logger.trace ("Init: done"); + } + + /** DTOR - runs cleanup functions. */ + ~this() + { + logger.trace ("Cleanup: starting"); + + // cleanup1: + Options.save(); // save options... do so here for now + + // cleanup2, 4: + runCleanupStages(); + + 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 failure. */ + + const UFE = "Unhandled exception from Init function:"; + /* Runs all functions consecutively, first-to-last. + * If any function fails, halts immediately. */ + void runStageForward (InitStage s) { + foreach (func; s.funcs) { + if (s.failure) break; + try { + func(); + } catch (Exception e) { + logger.fatal (UFE); + logger.fatal (e.msg); + + s.setFailure(); + } + } + if (s.failure) 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 { + func(); + } catch (Exception e) { + logger.fatal (UFE); + logger.fatal (e.msg); + + s.setFailure(); + } + } + if (s.failure) 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 (!Options.misc.useThreads) return true; // Use unthreaded route instead + + ThreadGroup tg; + try { // creating/starting threads could fail + tg = new ThreadGroup; + foreach (func; s.funcs) tg.create(func); // Start all threads + } catch (ThreadException e) { // Problem with threading; try without threads + logger.warn ("Caught ThreadException while trying to create threads:"); + logger.warn (e.msg); + logger.warn ("Will continue in a non-threaded manner."); + + Options.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); + + s.setFailure (); // abort (but join other threads first) + } + } + + if (s.failure) throw new InitStageException; // Problem running; abort and cleanup from here. + return false; // Done successfully + } + } + //END runStage... + + private { + InitStage*[] cleanupStages; // All cleanup stages needing to be run later. + void runCleanupStages () { + foreach_reverse (s; cleanupStages) { + try { + runStageReverse (*s); + } + catch (InitStageException) { + // We're cleaning up anyway, just report and continue + logger.error ("Cleanup function failed! Continuing..."); + } + } + cleanupStages = []; // All have been run, don't want them getting run again + } + } + + 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."); + } +} diff -r db0b48f02b69 -r 838577503598 mde/scheduler/InitStage.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mde/scheduler/InitStage.d Sat Mar 22 16:22:59 2008 +0000 @@ -0,0 +1,89 @@ +/* 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, version 2, as published by the Free Software Foundation. + +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, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ + +/** This module is used to create a list of all functions required to run for initialisation and +* cleanup. +* +* It has been separated out of Init.d to massively reduce dependancies of modules adding init +* functions. +*/ +module mde.scheduler.InitStage; + +/** Represents all functions to be called for a particular init stage. +* +* No code is included here to run the functions intentionally, to keep dependancies minimal. +*/ +struct InitStage +{ + alias void function() InitFunction; /// Alias + + /** Add a function to be called during this init stage. + * + * Called in order added when not threaded (reverse order for cleanup). + * + * Exceptions should never be thrown, since each function may run as a thread, and catching + * thread exceptions is not guaranteed to work. Log a message, call setFailure() and return + * instead. */ + void addFunc (InitFunction f) { + funcs ~= f; + } + + /** Should be called by an init function when a failure occurs. */ + void setFailure () { + synchronized failure = true; + } + + package InitFunction[] funcs = []; + package bool failure = false; +} + +/** Init can be divided up into these stages, each run in order: +* +* init0: +* static this() (not represented here) +* +* init1: +* Also known as pre-init; is where options get loaded by Init (not represented here). +* +* init2: +* Main symbol and config loading and general low-level stage. This stage is threaded, so all +* called functions need to be thread-safe. +* +* init3: +* Reserved as an unthreaded stage. +* +* init4: +* Main loading stage for data files and setup for higher-level elements, e.g. windows and input +* devices. This stage is threaded, so all called functions need to be thread-safe. +* +* cleanupX: +* Corresponding cleanup stage to initX. Called in reverse order (e.g. cleanup2 called before +* cleanup1). Cleanup is never threaded. +* +* The following functions get called (update list as appropriate): +* +* init2: +* mde.SDL.initSdlAndGl, mde.events.initInput +* +* init4: +* mde.SDL.setupWindow +* +* cleanup2: +* (Potentially): mde.SDL.cleanupSDL, mde.init.joystick.closeJoysticks +*/ +InitStage init2; +InitStage init4; /// ditto +InitStage cleanup2; /// ditto +InitStage cleanup4; /// ditto diff -r db0b48f02b69 -r 838577503598 mde/scheduler/exception.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mde/scheduler/exception.d Sat Mar 22 16:22:59 2008 +0000 @@ -0,0 +1,40 @@ +/* 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, version 2, as published by the Free Software Foundation. + +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, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ + +/// Contains the exception classes for Init. +module mde.scheduler.exception; + +import mde.exception; + +/// Thrown when Init fails. +class InitException : mdeException { + // NOTE: if symbol is final, it can't be modified in the static this(), but as const it can + static const char[] symbol; + static this () { symbol = super.symbol ~ ".Init"; } + char[] getSymbol () { + return symbol; + } + + this (char[] msg) { + super(msg); + } +} + +/// Thrown when an init stage fails. +class InitStageException : InitException { + this () { + super(""); + } +} diff -r db0b48f02b69 -r 838577503598 mde/scheduler/runTime.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mde/scheduler/runTime.d Sat Mar 22 16:22:59 2008 +0000 @@ -0,0 +1,99 @@ +/* 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, version 2, as published by the Free Software Foundation. + +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, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ + +/** Scheduler +*/ +module mde.scheduler.runTime; + +import tango.time.Time; + +// NOTE: Currently has no support for removing functions. To fix, assign ID and store fct pointers +// in an associative array, returning the ID [on adding fct pointer]. +// FIXME: support delegates or not? +/// This class can run scheduled functions per frame or every t seconds (sim-time). +abstract class Scheduler +{ + /** The type of function pointer to be passed to the scheduler. + * + * The double $(I time) parameter gives the number of (sim) seconds since the function was last + * called, or zero on the first run. */ + alias void function (double time) scheduleFct; + + /** Add a function to be called per frame. */ + static void perFrame (scheduleFct fct) { + frameFcts ~= fct; + } + + /** Add a function to be called per t secs or n 100-nano-sec intevals. + * + * Since the scheduler cannot guarantee a maximum time between calls, the interval at which + * functions are called is always greater than or equal to the inverval specified here. Of + * course, the actual inteval is given when the function is run. + */ + static void perTime (double t, scheduleFct fct) { + perTime (TimeSpan.interval(t), fct); + } + /** ditto */ + static void perTime (TimeSpan n, scheduleFct fct) + in { assert (n > TimeSpan (0L)); } + body { + timeFcts ~= TimeFct (fct, n); + } + + /** This function should get called by the main loop, once per frame. + * + * The parameter time should be the current sim-time, using the tango.core.Types.Time enum; all + * time evaluations will use this. + */ + static void run (Time time) { + double interval; + + // Call all per-frame functions: + if (lastTime == Time (0L)) interval = 0.0; // 0 interval for first loop + else interval = (time-lastTime).interval(); + + foreach (fct; frameFcts) fct(interval); + + // Call all per-interval functions: + foreach (fct; timeFcts) if (time >= fct.nextCall) { + if (fct.nextCall == Time (0L)) interval = 0.0; // 0 interval for first call + else interval = (time - (fct.nextCall - fct.interval)).interval(); + fct.nextCall = time + fct.interval; // when to call next + + fct.fct (interval); // call + } + } + + /* Holds details for functions called per time interval. */ + private struct TimeFct { + scheduleFct fct; // function to call + + TimeSpan interval; // interval to call at + // Storing nextCall is more efficient than storing lastCall since only this number has to + // be compared to time every frame where fct is not called: + Time nextCall = Time (0L); + + static TimeFct opCall (scheduleFct f, TimeSpan t) { // static CTOR + TimeFct ret; + ret.fct = f; + ret.interval = t; + return ret; + } + } + + private static Time lastTime = Time (0L); + private static scheduleFct[] frameFcts; + private static TimeFct[] timeFcts; +} diff -r db0b48f02b69 -r 838577503598 test/mdeTest.d --- a/test/mdeTest.d Fri Mar 21 12:31:21 2008 +0000 +++ b/test/mdeTest.d Sat Mar 22 16:22:59 2008 +0000 @@ -27,9 +27,8 @@ import mde.mergetag.DataSet; import mde.mergetag.mtunittest; import mde.exception; -import mde.Init; +import mde.scheduler.Init; import mde.i18n.I18nTranslation; -import mde.options; import tango.util.log.Log : Log, Logger; @@ -41,9 +40,6 @@ Log.getRootLogger.addAppender(new ConsoleAppender); logger = Log.getLogger ("test.mdeTest"); - - // This needs to be created for the unittests (normally created during init) - Options.misc = new OptionsMisc; } void main() {