changeset 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
files codeDoc/jobs.txt data/L10n/OptionsMisc.mtt data/L10n/mde.mtt data/conf/gui.mtt data/conf/options.mtt mde/SDL.d mde/events.d mde/exception.d mde/gui/Widget.d mde/gui/gui.d mde/input/joystick.d mde/scheduler/Init.d mde/scheduler/InitFunctions.d
diffstat 13 files changed, 151 insertions(+), 63 deletions(-) [+]
line wrap: on
line diff
--- a/codeDoc/jobs.txt	Tue Apr 08 15:52:21 2008 +0100
+++ b/codeDoc/jobs.txt	Sat Apr 12 14:10:13 2008 +0100
@@ -3,7 +3,6 @@
 
 
 In progress:
-GUI work...
 
 
 To do:
@@ -41,4 +40,6 @@
 
 
 Done (for git log message):
-Fixed a bug in paths.mdeDirectory.makeMTReader when called with readOrder == PRIORITY.HIGH_ONLY.
+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.
\ No newline at end of file
--- a/data/L10n/OptionsMisc.mtt	Tue Apr 08 15:52:21 2008 +0100
+++ b/data/L10n/OptionsMisc.mtt	Sat Apr 12 14:10:13 2008 +0100
@@ -2,4 +2,4 @@
 {en-GB}
 <entry|useThreads=["Use threads","Global option for threading in mde."]>
 <entry|logLevel=["Logging level","Controls which messages are logged, from 0=trace to 6=none (default: 1=info)."]>
-<entry|=["Localisation","Specifies the language to use."]>
+<entry|L10n=["Localisation","Specifies the language to use."]>
--- a/data/L10n/mde.mtt	Tue Apr 08 15:52:21 2008 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,3 +0,0 @@
-{MT01}
-{en-GB}
-<entry|greeting=["Hello, and welcome to mde!"]>
--- a/data/conf/gui.mtt	Tue Apr 08 15:52:21 2008 +0100
+++ b/data/conf/gui.mtt	Sat Apr 12 14:10:13 2008 +0100
@@ -6,4 +6,4 @@
 {W2}
 <int|x=150>
 <int|y=200>
-<int[][int]|widgetData=[0:[1002,2],2:[1001,150,150]]>
+<int[][int]|widgetData=[0:[1002,3,2,2,2,3,3,3,3],2:[1001,150,150],3:[1001,100,100]]>
--- a/data/conf/options.mtt	Tue Apr 08 15:52:21 2008 +0100
+++ b/data/conf/options.mtt	Sat Apr 12 14:10:13 2008 +0100
@@ -2,7 +2,7 @@
 {misc}
 <bool|useThreads=false>
 <char[]|L10n="en-GB">
-<int|logLevel=1>
+<int|logLevel=0>
 
 {video}
 <bool|noFrame=false>
--- a/mde/SDL.d	Tue Apr 08 15:52:21 2008 +0100
+++ b/mde/SDL.d	Sat Apr 12 14:10:13 2008 +0100
@@ -27,32 +27,17 @@
 import tango.stdc.stringz;
 
 import derelict.sdl.sdl;
-import derelict.opengl.gl;
-import derelict.util.exception;
 
 private Logger logger;
 static this() {
     logger = Log.getLogger ("mde.SDL");
     
-    init.addFunc (&initSdlAndGl);
+    init.addFunc (&initSdlAndGl, "initSdlAndGl");
 }
 
 private uint flags = 0;
 
 void initSdlAndGl() {   // init 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);
-        
-        setInitFailure ();
-        return;
-    }
-    debug 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:");
@@ -67,7 +52,7 @@
     
     // Must be called after SDL has been initialised, so cannot be a separate Init function.
     openJoysticks ();                   // after SDL init
-    cleanup.addFunc (&cleanupSDL);
+    cleanup.addFunc (&cleanupSDL, "cleanupSDL");
 
     setupWindow();
 }
--- a/mde/events.d	Tue Apr 08 15:52:21 2008 +0100
+++ b/mde/events.d	Sat Apr 12 14:10:13 2008 +0100
@@ -33,7 +33,7 @@
 static this() {
     logger = Log.getLogger ("mde.events");
     
-    init.addFunc (&initInput);
+    init.addFunc (&initInput, "initInput");
 
     Scheduler.perFrame (&mde.events.pollEvents);
 }
--- a/mde/exception.d	Tue Apr 08 15:52:21 2008 +0100
+++ b/mde/exception.d	Sat Apr 12 14:10:13 2008 +0100
@@ -27,8 +27,8 @@
  * "mde.pkg.file.Class" describing where the exception was thrown. (Since only methods overload
  * correctly, symbol is made static and an overloadable method is used to access the correct symbol.)
  */
- class mdeException : Exception {
-     /// Override in derived classes to name the module where the error occured.
+class mdeException : Exception {
+    /// Override in derived classes to name the module where the error occured.
     char[] getSymbol () {
         return "mde";
     }
--- a/mde/gui/Widget.d	Tue Apr 08 15:52:21 2008 +0100
+++ b/mde/gui/Widget.d	Sat Apr 12 14:10:13 2008 +0100
@@ -20,6 +20,7 @@
 import mde.gui.exception;
 
 import gl = mde.gl;
+import mde.scheduler.InitFunctions;
 
 /** Interface for widgets (may become a class).
 *
@@ -70,27 +71,77 @@
     }
 }
 
-/// Encapsulates another widget
-class SingleWidget : Widget
+/// Encapsulates a grid of Widgets
+class GridWidget : Widget
 {
+    const PADDING = 1;  // padding between rows/cols
+    const BORDER = 1;  // border width
     int w, h;           // size
-    Widget subWidget;
+    int rows, cols;     // number of cells in grid
+    int[] rowH;         // row height (highest widget in the row)
+    int[] colW;         // column width (widest widget)
+    int[] rowY;         // cumulative rowH[i-1] + BORDER/PADDING
+    int[] colX;         // cumulative colW[i-1] + BORDER/PADDING
+    Widget[] subWidgets;// all widgets in the grid (by row):
+    /* SubWidget order:    [ 2 3 ]
+    *                      [ 0 1 ] */
     
     this (IWindow window, int[] data) {
-        if (data.length != 1) throw new WidgetDataException;
+        // Get grid size
+        if (data.length < 2) throw new WidgetDataException;
+        rows = data[0];
+        cols = data[1];
         
-        subWidget = window.getWidget (data[0]);
+        // Get all sub-widgets
+        if (data.length != 2 + rows * cols) throw new WidgetDataException;
+        if (rows*cols == 0) {    // special case
+            w = h = 2*BORDER;
+            return;
+        }
+        subWidgets.length = rows*cols;
+        for (uint i = 2; i < data.length; ++i) {
+            subWidgets[i-2] = window.getWidget (data[i]);
+        }
+        
+        // Find the sizes of all subWidgets
+        int[] widgetW = new int[subWidgets.length]; // dimensions
+        int[] widgetH = new int[subWidgets.length];
+        foreach (i,widget; subWidgets) widget.getSize (widgetW[i],widgetH[i]);
         
-        subWidget.getSize (w,h);
-        w += 10;
-        h += 10;
+        // Find row heights and column widths (non cumulative)
+        rowH.length = rows;
+        colW.length = cols;
+        for (uint i = 0; i < subWidgets.length; ++i) {
+            uint x = i / cols;  // row
+            if (rowH[x] < widgetH[i]) rowH[x] = widgetH[i];
+            x = i % cols;       // column
+            if (colW[x] < widgetW[i]) colW[x] = widgetW[i];
+        }
+        
+        // rowY / colX
+        rowY.length = rows;
+        colX.length = cols;
+        int cum = BORDER;
+        foreach (i, x; rowH) {
+            rowY[i] = cum;
+            cum += x + PADDING;
+        }
+        h = cum + BORDER - PADDING;     // total height
+        cum = BORDER;
+        foreach (i, x; colW) {
+            colX[i] = cum;
+            cum += x + PADDING;
+        }
+        w = cum + BORDER - PADDING;     // total width
     }
     
     void draw (int x, int y) {
         gl.setColor (1.0f, 0.6f, 0.0f);
         gl.drawBox (x,x+w, y,y+h);
         
-        subWidget.draw (x+5, y+5);
+        foreach (i,widget; subWidgets) {
+            widget.draw (x + colX[i % cols], y + rowY[i / cols]);
+        }
     }
     
     void getSize (out int w, out int h) {
@@ -101,7 +152,7 @@
 
 // Widget types. Start high so they can be reordered easily later.
 enum WIDGET_TYPES : int {
-    BOX = 1001, SINGLE
+    BOX = 1001, GRID
 }
 
 Widget createWidget (IWindow window, int[] data) {
@@ -110,6 +161,6 @@
     data = data[1..$];      // the rest is passed to the Widget
     
     if (type == WIDGET_TYPES.BOX) return new BoxWidget (window, data);
-    else if (type == WIDGET_TYPES.SINGLE) return new SingleWidget (window, data);
+    else if (type == WIDGET_TYPES.GRID) return new GridWidget (window, data);
     else throw new WidgetDataException ("Bad widget type");
 }
--- a/mde/gui/gui.d	Tue Apr 08 15:52:21 2008 +0100
+++ b/mde/gui/gui.d	Sat Apr 12 14:10:13 2008 +0100
@@ -34,7 +34,7 @@
 static this () {
     logger = Log.getLogger ("mde.gui.gui");
     
-    init.addFunc (&GUI.load);
+    init.addFunc (&GUI.load, "GUI.load");
 }
 
 struct GUI {
--- a/mde/input/joystick.d	Tue Apr 08 15:52:21 2008 +0100
+++ b/mde/input/joystick.d	Sat Apr 12 14:10:13 2008 +0100
@@ -45,12 +45,14 @@
         }
     }
     
-    logger.info (logger.format (tmp, "Opened {} joysticks via SDL, succesfully unless preceding warnings say otherwise.", joysticks.length));
+    logger.info (logger.format (tmp, "Opened {} joysticks via SDL, succesfully unless preceding errors say otherwise.", joysticks.length));
 }
 
 /// Cleanup fct.
 void closeJoysticks () {
     foreach (js; joysticks) {
-        if(js) SDL_JoystickClose(js);	// only close if successfully opened
+        // FIXME: this is sometimes causing a SIGSEGV (Address boundary error)
+        // FIXME: when init fails
+        if(js !is null) SDL_JoystickClose(js);	// only close if successfully opened
     }
 }
--- a/mde/scheduler/Init.d	Tue Apr 08 15:52:21 2008 +0100
+++ b/mde/scheduler/Init.d	Sat Apr 12 14:10:13 2008 +0100
@@ -43,6 +43,12 @@
     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
  *
@@ -101,11 +107,15 @@
         logger = Log.getLogger ("mde.scheduler.Init.Init");
     }
     
-    /** CTOR − initialisation
+    /** 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().
     *
-    * If any init fails, it must run necessary cleanup first since the DTOR cannot(?) be run. */
+    * 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
@@ -132,6 +142,25 @@
         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.
@@ -177,17 +206,23 @@
         * catching any exceptions thrown by the functions (although this isn't guaranteed for threads),
         * and throw an InitStageException on initFailure. */
     
-        const UFE = "Unhandled exception from Init function:";
+        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 {
-                    func();
+                    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 (UFE);
-                    logger.fatal (e.msg);
+                    logger.fatal (LOG_IF_MSG ~ func.name ~ LOG_F_FAIL ~
+                            ((e.msg is null || e.msg == "") ? "(no failure message)" : e.msg) );
                 
                     setInitFailure();
                 }
@@ -200,10 +235,12 @@
         void runStageReverse (InitStage s) {
             foreach_reverse (func; s.funcs) {
                 try {
-                    func();
+                    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 (UFE);
-                    logger.fatal (e.msg);
+                    logger.fatal (LOG_CF_MSG ~ func.name ~ LOG_F_FAIL ~
+                            ((e.msg is null || e.msg == "") ? "(no failure message)" : e.msg) );
                 
                     setInitFailure();
                 }
@@ -218,11 +255,15 @@
             ThreadGroup tg;
             try {                           // creating/starting threads could fail
                 tg = new ThreadGroup;
-                foreach (func; s.funcs) tg.create(func);  // Start all threads
+                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.");
+                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
--- a/mde/scheduler/InitFunctions.d	Tue Apr 08 15:52:21 2008 +0100
+++ b/mde/scheduler/InitFunctions.d	Sat Apr 12 14:10:13 2008 +0100
@@ -30,17 +30,17 @@
     logger = Log.getLogger ("mde.scheduler.InitFunctions");
 }
 
-/** Should be called by an init function when a failure occurs. */
-void setInitFailure () {
+void setInitFailure () {    /// Call to indicate failure in an init function
     initFailure = true;
 }
 
-package:
-
 /** Represents all functions to be called for a particular init stage. */
 struct InitStage
 {
-    alias void function() InitFunction; /// Alias
+    struct InitFunction {
+        void function() func;       // the actual function
+        char[] name;                // it's name;
+    }
     
     /** Add a function to be called during this init stage.
     *
@@ -49,8 +49,11 @@
     * 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;
+    void addFunc (void function() f, char[] name) {
+        InitFunction s;
+        s.func = f;
+        s.name = name;
+        funcs ~= s;
     }
     
     InitFunction[] funcs = [];
@@ -59,17 +62,25 @@
 InitStage init;     // all functions called during init (all should be thread-safe)
 InitStage cleanup;  // all functions called during cleanup (all should be thread-safe)
 
+package:
 bool initFailure = false;   // set on failure (throwing through threads isn't a good idea)
 
 private:
 Logger logger;
-const FAIL_MSG = "Init function failed: ";
+/+ I keep changing my mind about wrapping all init functions:
+const LOG_MSG = "Init function ";
+const TRACE_START = " - running";
+const TRACE_END = " - completed";
+const FAIL_MSG = " - failed: ";
 // Template to call function, catching exceptions:
-void initInput(alias Func) () {
+void initInput(alias Func, char[] name) () {
     try {
+        debug logger.trace (LOG_MSG ~ name ~ TRACE_START);
         Func();
+        debug logger.trace (LOG_MSG ~ name ~ TRACE_END);
     } catch (Exception e) {
-        logger.fatal (FAIL_MSG ~ e.msg);
+        logger.fatal (LOG_MSG ~ name ~ FAIL_MSG ~ e.msg);
         initFailure = true;
     }
 }
++/