changeset 64:cc3763817b8a

Overhauled Options so that it now uses templates and mixins for type-specific internals, and supported types can be adjusted via just one list.
author Diggory Hardy <diggory.hardy@gmail.com>
date Sun, 29 Jun 2008 11:55:55 +0100
parents 66d555da083e
children 891211f034f2
files codeDoc/jobs.txt data/conf/gui.mtt mde/font/font.d mde/lookup/Options.d mde/mde.d mde/setup/Init.d mde/setup/sdl.d
diffstat 7 files changed, 238 insertions(+), 294 deletions(-) [+]
line wrap: on
line diff
--- a/codeDoc/jobs.txt	Fri Jun 27 18:35:33 2008 +0100
+++ b/codeDoc/jobs.txt	Sun Jun 29 11:55:55 2008 +0100
@@ -53,3 +53,5 @@
 
 
 Done (for mercurial log message):
+Added content IDs...
+Added widget strings...
\ No newline at end of file
--- a/data/conf/gui.mtt	Fri Jun 27 18:35:33 2008 +0100
+++ b/data/conf/gui.mtt	Sun Jun 29 11:55:55 2008 +0100
@@ -7,7 +7,8 @@
 {W2}
 <int|x=150>
 <int|y=200>
-<int[][int]|widgetData=[0:[0xB004,5,5,2,1,22,1,2,1,1,1,1,1,2,1,22,1,2,1,1,1,1,1,2,1,22,1,2],1:[0x3001],2:[0x21,0xFFFF00],22:[0x22,0xFF00]]>
+<int[][int]|widgetData=[0:[0xB004,5,5,3,1,22,1,2,1,1,1,1,1,22,1,22,1,22,1,1,1,1,1,2,1,22,1,3],1:[0x3001],2:[0x21,0,0xFFFF00],3:[0x21,1,0xBFFF00],22:[0x22,1,0xFF00]]>
+<char[][int]|widgetStrings=[0:"alpha=α", 1:"beta=β"]>
 {WEmbedded}
 <int|x=20>
 <int|y=100>
--- a/mde/font/font.d	Fri Jun 27 18:35:33 2008 +0100
+++ b/mde/font/font.d	Sun Jun 29 11:55:55 2008 +0100
@@ -82,11 +82,11 @@
                 logger.warn ("Your FreeType 2 library may compiled without support for LCD/sub-pixel rendering.");
                 
                 // Reset the default filter (in case an invalid value was set in config files).
-                Options.setInt ("font", "lcdFilter", FT_LcdFilter.FT_LCD_FILTER_DEFAULT);
+                fontOpts.set!(int) ("lcdFilter", FT_LcdFilter.FT_LCD_FILTER_DEFAULT);
                 
                 /* If no support for LCD filtering, then LCD rendering only emulates NORMAL with 3
                  * times wider glyphs. So disable and save the extra work. */
-                Options.setInt ("font", "renderMode", FT_LOAD_TARGET_NORMAL);
+                fontOpts.set!(int) ("renderMode", FT_LOAD_TARGET_NORMAL);
             }
             
             /* Load font settings
--- a/mde/lookup/Options.d	Fri Jun 27 18:35:33 2008 +0100
+++ b/mde/lookup/Options.d	Sun Jun 29 11:55:55 2008 +0100
@@ -44,8 +44,8 @@
 * Each sub-class provides named variables for maximal-speed reading. Local sub-class references
 * should be used for reading variables, and via the addOptionsClass() hook will be loaded from
 * files during pre-init (init0 stage). Do not write changes directly to the subclasses or they will
-* not be saved; use, for example, Options.setBool(...). Use an example like OptionsMisc as a
-* template for creating a new Options sub-class.
+* not be saved; instead use set(), for example, miscOpts.set!(char[])("L10n","en-GB"). Use an
+* example like OptionsMisc as a template for creating a new Options sub-class.
 *
 * Details: Options sub-classes hold associative arrays of pointers to all option variables, with a
 * char[] id. This list is used for saving, loading and to provide generic GUI options screens. The
@@ -55,176 +55,192 @@
 */
 class Options : IDataSection
 {
-    // No actual options are stored by this class. However, much of the infrastructure is
-    // present since it need not be redefined in sub-classes.
-    
-    // The "pointer lists":
-    protected bool*  [ID]   optsBool;
-    protected int*   [ID]   optsInt;
-    protected double*[ID]   optsDouble;
-    protected char[]*[ID]   optsCharA;
-    
-    //BEGIN Mergetag loading/saving code
-    void addTag (char[] tp, ID id, char[] dt) {
-        if (tp == "bool") {
-            bool** p = id in optsBool;
-            if (p !is null) **p = parseTo!(bool) (dt);
-        } else if (tp == "char[]") {
-            char[]** p = id in optsCharA;
-            if (p !is null) **p = parseTo!(char[]) (dt);
-        } else if (tp == "double") {
-            double** p = id in optsDouble;
-            if (p !is null) **p = parseTo!(double) (dt);
-        } else if (tp == "int") {
-            int** p = id in optsInt;
-            if (p !is null) **p = parseTo!(int) (dt);
+    // All supported types, for generic handling via templates. It should be possible to change
+    // the supported types simply by changing this list now (untested).
+    template store(A...) { alias A store; }
+    alias store!(bool, int, double, char[]) TYPES;
+    //BEGIN Templates: internal
+    private {
+        // Get name of a type. Basically just stringof, but special handling for arrays.
+        // Use TName!(T) for a valid symbol name, and T.stringof for a type.
+        template TName(T : T[]) {
+            const char[] TName = TName!(T) ~ "A";
+        }
+        template TName(T) {
+            const char[] TName = T.stringof;
+        }
+        
+        // Pointer lists
+        template PLists(T, A...) {
+            static if (A.length) {
+                const char[] PLists = T.stringof~"*[ID] opts"~TName!(T)~";\n" ~ PLists!(A);
+            } else
+                const char[] PLists = T.stringof~"*[ID] opts"~TName!(T)~";\n";
+        }
+        
+        // True if type is one of A
+        template TIsIn(T, A...) {
+            static if (A.length) {
+                static if (is(T == A[0]))
+                    const bool TIsIn = true;
+                else
+                    const bool TIsIn = TIsIn!(T,A[1..$]);
+            } else
+                const bool TIsIn = false;	// no more possibilities
+        }
+        
+        // For addTag
+        template addTagMixin(T, A...) {
+            const char[] ifBlock = `if (tp == "`~T.stringof~`") {
+                `~T.stringof~`** p = id in opts`~TName!(T)~`;
+                if (p !is null) **p = parseTo!(`~T.stringof~`) (dt);
+            }`;
+            static if (A.length)
+                const char[] addTagMixin = ifBlock~` else `~addTagMixin!(A).addTagMixin;
+            else
+                const char[] addTagMixin = ifBlock;
+        }
+        
+        // For writeAll
+        template writeAllMixin(A...) {
+            static if (A.length) {
+                const char[] writeAllMixin =
+                        `foreach (ID id, `~A[0].stringof~`* val; opts`~TName!(A[0])~`) dlg ("`~A[0].stringof~`", id, parseFrom!(`~A[0].stringof~`) (*val));` ~ writeAllMixin!(A[1..$]);
+            } else
+                const char[] writeAllMixin = ``;
         }
     }
-
-    void writeAll (ItemDelg dlg) {
-        foreach (ID id, bool*   val; optsBool)  dlg ("bool"  , id, parseFrom!(bool  ) (*val));
-        foreach (ID id, char[]* val; optsCharA) dlg ("char[]", id, parseFrom!(char[]) (*val));
-        foreach (ID id, double* val; optsDouble)dlg ("double", id, parseFrom!(double) (*val));
-        foreach (ID id, int*    val; optsInt)   dlg ("int"   , id, parseFrom!(int   ) (*val));
-    }
-    //END Mergetag loading/saving code
+    //END Templates: internal
+    
     
     //BEGIN Static
-    /** Add an options sub-class to the list for loading and saving.
-    *
-    * Call from static this() (before Init calls load()). */
-    static void addOptionsClass (Options c, char[] i)
-    in {    // Trap a couple of potential coding errors:
-        assert (c !is null);    // Instance must be created before calling addOptionsClass
-        assert (((cast(ID) i) in subClasses) is null);  // Don't allow a silent replacement
-    } body {
-        subClasses[cast(ID) i] = c;
-        subClassChanges[cast(ID) i] = new OptionsGeneric;
-    }
+    static {
+    	/** Add an options sub-class to the list for loading and saving.
+    	 *
+    	 * Call from static this() (before Init calls load()). */
+    	void addOptionsClass (Options c, char[] i)
+    	in {    // Trap a couple of potential coding errors:
+            assert (c !is null);    // Instance must be created before calling addOptionsClass
+            assert (((cast(ID) i) in subClasses) is null);  // Don't allow a silent replacement
+        } body {
+            subClasses[cast(ID) i] = c;
+        }
+    
+    	// Track all sections for saving/loading/other generic handling.
+        Options[ID] subClasses;
+        bool changed = false;    // any changes at all, i.e. do we need to save?
     
-    /** Set option symbol of Options class subClass to val.
-    *
-    * Due to the way options are handled generically, string IDs must be used to access the options
-    * via hash-maps, which is a little slower than direct access but necessary since the option
-    * must be changed in two separate places. */
-    private static const ERR_MSG = "Options.setXXX called with incorrect parameters!";
-    static void setBool (char[] subClass, char[] symbol, bool val) {
-        changed = true;     // something got set (don't bother checking this isn't what it already was)
+    	/* Load/save options from file.
+         *
+         * If the file doesn't exist, no reading is attempted (options are left at default values).
+    	*/
+        private const fileName = "options";
+        private const MT_LOAD_EXC = "Loading options aborted:";
+        void load () {
+        // Check it exists (if not it should still be created on exit).
+        // Don't bother checking it's not a folder, because it could still be a block or something.
+            if (!confDir.exists (fileName)) return;
+        
+            try {
+                IReader reader;
+                reader = confDir.makeMTReader (fileName, PRIORITY.LOW_HIGH);
+                reader.dataSecCreator = delegate IDataSection(ID id) {
+                    /* Recognise each defined section, and return null for unrecognised sections. */
+                    Options* p = id in subClasses;
+                    if (p !is null) return *p;
+                    else return null;
+                };
+                reader.read;
+            } catch (MTException e) {
+                logger.fatal (MT_LOAD_EXC);
+                logger.fatal (e.msg);
+                throw new optionsLoadException ("Mergetag exception (see above message)");
+            }
+        }
+        void save () {
+            if (!changed) return;   // no changes to save
         
-        try {
-            *(subClasses[cast(ID) subClass].optsBool[cast(ID) symbol]) = val;
-            subClassChanges[cast(ID) subClass].setBool (cast(ID) symbol, val);
-        } catch (ArrayBoundsException) {
-            // log and ignore:
-            logger.error (ERR_MSG);
+            DataSet ds = new DataSet();
+            foreach (id, subOpts; subClasses) ds.sec[id] = subOpts.optionChanges;
+        
+            // Read locally-stored options
+            try {
+                IReader reader;
+                reader = confDir.makeMTReader (fileName, PRIORITY.HIGH_ONLY, ds);
+                reader.dataSecCreator = delegate IDataSection(ID id) {
+                    return null;    // All recognised sections are already in the dataset.
+                };
+                reader.read;
+            } catch (MTFileIOException) {
+            	// File either didn't exist or couldn't be opened.
+            	// Presuming the former, this is not a problem.
+            } catch (MTException e) {
+            	// Log a message and continue, overwriting the file:
+                logger.error (MT_LOAD_EXC);
+                logger.error (e.msg);
+            }
+        
+            try {
+                IWriter writer;
+                writer = confDir.makeMTWriter (fileName, ds);
+                writer.write();
+            } catch (MTException e) {
+                logger.error ("Saving options aborted! Reason:");
+                logger.error (e.msg);
+            }
+        }
+    
+        private Logger logger;
+        static this() {
+            logger = Log.getLogger ("mde.options");
         }
     }
-    static void setInt (char[] subClass, char[] symbol, int val) {
-        changed = true;     // something got set (don't bother checking this isn't what it already was)
+    //END Static
+    
+    
+    //BEGIN Non-static
+    /** Set option symbol of an Options sub-class to val.
+     *
+     * Due to the way options are handled generically, string IDs must be used to access the options
+     * via hash-maps, which is a little slower than direct access but necessary since the option
+     * must be changed in two separate places. */
+    void set(T) (char[] symbol, T val) {
+        static if (!TIsIn!(T,TYPES))
+            static assert (false, "Options.set does not currently support type "~T.stringof);
         
-        try {
-            *(subClasses[cast(ID) subClass].optsInt[cast(ID) symbol]) = val;
-            subClassChanges[cast(ID) subClass].setInt (cast(ID) symbol, val);
-        } catch (ArrayBoundsException) {
-            // log and ignore:
-            logger.error (ERR_MSG);
-        }
-    }
-    static void setDouble (char[] subClass, char[] symbol, double val) {
+        mixin (`alias opts`~T.stringof~` optsVars;`);
+        
         changed = true;     // something got set (don't bother checking this isn't what it already was)
         
         try {
-            *(subClasses[cast(ID) subClass].optsDouble[cast(ID) symbol]) = val;
-            subClassChanges[cast(ID) subClass].setDouble (cast(ID) symbol, val);
+            *(optsVars[cast(ID) symbol]) = val;
+            optionChanges.set!(T) (cast(ID) symbol, val);
         } catch (ArrayBoundsException) {
             // log and ignore:
-            logger.error (ERR_MSG);
-        }
-    }
-    static void setCharA (char[] subClass, char[] symbol, char[] val) {
-        changed = true;     // something got set (don't bother checking this isn't what it already was)
-        
-        try {
-            *(subClasses[cast(ID) subClass].optsCharA[cast(ID) symbol]) = val;
-            subClassChanges[cast(ID) subClass].setCharA (cast(ID) symbol, val);
-        } catch (ArrayBoundsException) {
-            // log and ignore:
-            logger.error (ERR_MSG);
+            logger.error ("Options.set: unkw!");
         }
     }
     
-    // Track all sections for saving/loading/other generic handling.
-    static Options[ID] subClasses;
-    static OptionsGeneric[ID] subClassChanges;
-    static bool changed = false;    // any changes at all, i.e. do we need to save?
-    
-    /* Load/save options from file.
-    *
-    * If the file doesn't exist, no reading is attempted (options are left at default values).
-    */
-    private static const fileName = "options";
-    private static const MT_LOAD_EXC = "Loading options aborted:";
-    static void load () {
-        // Check it exists (if not it should still be created on exit).
-        // Don't bother checking it's not a folder, because it could still be a block or something.
-        if (!confDir.exists (fileName)) return;
-        
-        try {
-            IReader reader;
-            reader = confDir.makeMTReader (fileName, PRIORITY.LOW_HIGH);
-            reader.dataSecCreator = delegate IDataSection(ID id) {
-                /* Recognise each defined section, and return null for unrecognised sections. */
-                Options* p = id in subClasses;
-                if (p !is null) return *p;
-                else return null;
-            };
-            reader.read;
-        } catch (MTException e) {
-            logger.fatal (MT_LOAD_EXC);
-            logger.fatal (e.msg);
-            throw new optionsLoadException ("Mergetag exception (see above message)");
-        }
-    }
-    static void save () {
-        if (!changed) return;   // no changes to save
-        
-        DataSet ds = new DataSet();
-        foreach (id, sec; subClassChanges) ds.sec[id] = sec;
-        
-        // Read locally-stored options
-        try {
-            IReader reader;
-            reader = confDir.makeMTReader (fileName, PRIORITY.HIGH_ONLY, ds);
-            reader.dataSecCreator = delegate IDataSection(ID id) {
-                return null;    // All recognised sections are already in the dataset.
-            };
-            reader.read;
-        } catch (MTFileIOException) {
-            // File either didn't exist or couldn't be opened.
-            // Presuming the former, this is not a problem.
-        } catch (MTException e) {
-            // Log a message and continue, overwriting the file:
-            logger.error (MT_LOAD_EXC);
-            logger.error (e.msg);
-        }
-        
-        try {
-            IWriter writer;
-            writer = confDir.makeMTWriter (fileName, ds);
-            writer.write();
-        } catch (MTException e) {
-            logger.error ("Saving options aborted! Reason:");
-            logger.error (e.msg);
-        }
+    protected {
+        OptionChanges optionChanges;	// all changes to options (for saving)
+    	
+    	// The "pointer lists":
+        mixin (PLists!(TYPES));
     }
     
-    private static Logger logger;
-    static this() {
-        logger = Log.getLogger ("mde.options");
+    //BEGIN Mergetag loading/saving code
+    void addTag (char[] tp, ID id, char[] dt) {
+        mixin(addTagMixin!(TYPES).addTagMixin);
     }
-    //END Static
+
+    void writeAll (ItemDelg dlg) {
+        mixin(writeAllMixin!(TYPES));
+    }
+    //END Mergetag loading/saving code
+    //END Non-static
     
-    //BEGIN Templates
+    
+    //BEGIN Templates: impl & optionsThis
     private {
         // Return index of first comma, or halts if not found.
         template cIndex(char[] A) {
@@ -284,16 +300,22 @@
             else
                 const char[] listOrNull = "["~A~"]";
         }
+        // for recursing on TYPES
+        template optionsThisInternal(char[] A, B...) {
+            static if (B.length) {
+                const char[] optionsThisInternal = `opts`~TName!(B[0])~` = `~listOrNull!(sTC!(aaVars!(parseT!(B[0].stringof,A))))~";\n" ~ optionsThisInternal!(A,B[1..$]);
+            } else
+                const char[] optionsThisInternal = ``;
+        }
     } protected {
         /** Produces the implementation code to go in the constuctor. */
-        template aaDefs(char[] A) {
-            const char[] aaDefs =
-                    "optsBool = "  ~ listOrNull!(sTC!(aaVars!(parseT!("bool"  , A)))) ~ ";\n" ~
-                    "optsInt = "   ~ listOrNull!(sTC!(aaVars!(parseT!("int"   , A)))) ~ ";\n" ~
-                    "optsDouble = "~ listOrNull!(sTC!(aaVars!(parseT!("double", A)))) ~ ";\n" ~
-                    "optsCharA = " ~ listOrNull!(sTC!(aaVars!(parseT!("char[]", A)))) ~ ";\n";
+        template optionsThis(char[] A) {
+            const char[] optionsThis =
+                    "optionChanges = new OptionChanges;\n" ~
+                    optionsThisInternal!(A,TYPES);
         }
-        /+/** Produces the implementation code to go in the static constuctor. */
+        /+ Needs too many custom parameters to be worth it? Plus makes class less readable.
+        /** Produces the implementation code to go in the static constuctor. */
         template optClassAdd(char[] symb) {
             const char[] optClassAdd = symb ~ "=new "~classinfo(this).name~";\n";//Options.addOptionsClass("~symb~", );\n";
         }+/
@@ -306,161 +328,80 @@
          *
          * In case this() needs to be customized, mixin(impl!(A)) is equivalent to:
          * ---
-         * mixin (A~"\nthis(){\n"~aaDefs!(A)~"}");
+         * mixin (A~`
+        this () {
+    	`~optionsThis!(A)~`
+	}`);
          * ---
          *
          * Notes: Only use space as whitespace (no new-lines or tabs). Make sure to add a trailing
          * semi-colon (;) or you'll get told off! :D
-         *
          * In general errors aren't reported well. Trial with pragma (msg, impl!(...)); if
          * necessary.
          *
          * Extending: mixins could also be used for the static this() {...} or even the whole
          * class, but doing so would rather decrease readability of any implementation. */
         template impl(char[] A /+, char[] symb+/) {
-            const char[] impl = A~"\nthis(){\n"~aaDefs!(A)~"}";
+            const char[] impl = A~"\nthis(){\n"~optionsThis!(A)~"}";
             // ~"\nstatic this(){\n"~optClassAdd!(symb)~"}"
         }
     }
-    /+/** mixin impl("type symbol[, symbol[...]];[type symbol[...];][...]")
-     *
-     * E.g.
-     * ---
-     * mixin (impl ("bool a, b; int i;"));
-     * ---
-     * The parser isn't highly accurate. */
-    // Try using templates instead? See std.metastrings
-    static char[] impl (char[] A) {
-        char[] bools;
-        char[] ints;
-        
-        while (A.length) {
-            // Trim whitespace
-            {
-                size_t i = 0;
-                while (i < A.length && (A[i] == ' ' || (A[i] >= 9u && A[i] <= 0xD)))
-                    ++i;
-                A = A[i..$];
-            }
-            if (A.length == 0) break;
-            
-            char[] type;
-            for (size_t i = 0; i < A.length; ++i) {
-                if (A[i] == ' ' || (A[i] >= 9u && A[i] <= 0xD)) {
-                    type = A[0..i];
-                    A = A[i+1..$];
-                    break;
-                }
-            }
-            
-            char[] symbols;
-            for (size_t i = 0; i < A.length; ++i) {
-                if (A[i] == ';') {
-                    symbols = A[0..i];
-                    A = A[i+1..$];
-                    break;
-                }
-            }
-            
-            if (type == "bool") {
-                if (bools.length)
-                    bools = bools ~ "," ~ symbols;
-                else
-                    bools = symbols;
-            }
-            else if (type == "int") {
-                if (ints.length)
-                    ints = ints ~ "," ~ symbols;
-                else
-                    ints = symbols;
-            }
-            else {
-                // Unfortunately, we cannot output non-const strings (even though func is compile-time)
-                // We also cannot use pragma(msg) because the message gets printed even if the code isn't run.
-                //pragma(msg, "Warning: impl failed to parse whole input string");
-                // Cannot use Cout / logger either.
-                break;
-            }
-        }
-        
-        char[] ret;
-        if (bools.length)
-            ret = "bool "~bools~";\n";
-        if (ints.length)
-            ret = ret ~ "int "~ints~";\n";
-        
-        
-        
-        return ret;
-    }+/
-    //END Templates
+    //END Templates: impl & optionsThis
 }
 
 /* Special class to store all locally changed options, whatever the section. */
-class OptionsGeneric : Options {
+class OptionChanges : Options {
+    //BEGIN Templates
+    private {
+        template Vars(A...) {
+            static if (A.length) {
+                const char[] Vars = A[0].stringof~`[] `~TName!(A[0])~`s;` ~ Vars!(A[1..$]);
+            } else
+                const char[] Vars = ``;
+        }
+        
+        // For addTag; different to Options.addTag().
+    	// Reverse priority: only load symbols not currently existing
+        template addTagMixin(T, A...) {
+            const char[] ifBlock = `if (tp == "`~T.stringof~`") {
+                if ((id in opts`~TName!(T)~`) is null) {
+                    `~TName!(T)~`s ~= parseTo!(`~T.stringof~`) (dt);
+                    opts`~TName!(T)~`[id] = &`~TName!(T)~`s[$-1];
+        	}
+            }`;
+            static if (A.length)
+                    const char[] addTagMixin = ifBlock~` else `~addTagMixin!(A).addTagMixin;
+            else
+                const char[] addTagMixin = ifBlock;
+        }
+
+    }
+    //END Templates
     // These store the actual values, but are never accessed directly except when initially added.
     // optsX store pointers to each item added along with the ID and are used for access.
-    bool[] bools;
-    int[] ints;
-    double[] doubles;
-    char[][] strings;
+    mixin(Vars!(TYPES));
     
     this () {}
     
-    void setBool (ID id, bool x) {
-        bool** p = id in optsBool;
-        if (p !is null) **p = x;
-        else {
-            bools ~= x;
-            optsBool[id] = &bools[$-1];
-        }
-    }
-    void setInt (ID id, int x) {
-        int** p = id in optsInt;
+    void set(T) (ID id, T x) {
+        static if (!TIsIn!(T,TYPES))
+            static assert (false, "OptionChanges.set does not currently support type "~T.stringof);
+        
+        mixin (`alias opts`~T.stringof~` optsVars;`);
+        mixin (`alias `~T.stringof~`s vars;`);
+        
+        T** p = id in optsVars;
         if (p !is null) **p = x;
         else {
-            ints ~= x;
-            optsInt[id] = &ints[$-1];
-        }
-    }
-    void setDouble (ID id, double x) {
-        double** p = id in optsDouble;
-        if (p !is null) **p = x;
-        else {
-            doubles ~= x;
-            optsDouble[id] = &doubles[$-1];
-        }
-    }
-    void setCharA (ID id, char[] x) {
-        char[]** p = id in optsCharA;
-        if (p !is null) **p = x;
-        else {
-            strings ~= x;
-            optsCharA[id] = &strings[$-1];
+            vars ~= x;
+            optsVars[id] = &vars[$-1];
         }
     }
     
     //BEGIN Mergetag loading/saving code
     // Reverse priority: only load symbols not currently existing
     void addTag (char[] tp, ID id, char[] dt) {
-        if (tp == "bool") {
-            if ((id in optsBool) is null) {
-                bools ~= parseTo!(bool) (dt);
-                optsBool[id] = &bools[$-1];
-            }
-        } else if (tp == "char[]") {
-            if ((id in optsCharA) is null) {
-                strings ~= parseTo!(char[]) (dt);
-                optsCharA[id] = &strings[$-1];
-            }
-            char[]** p = id in optsCharA;
-            if (p !is null) **p = parseTo!(char[]) (dt);
-        } else if (tp == "int") {
-            if ((id in optsInt) is null) {
-                ints ~= parseTo!(int) (dt);
-                optsInt[id] = &ints[$-1];
-            }
-        }
+        mixin (addTagMixin!(TYPES).addTagMixin);
     }
     //END Mergetag loading/saving code
 }
--- a/mde/mde.d	Fri Jun 27 18:35:33 2008 +0100
+++ b/mde/mde.d	Sun Jun 29 11:55:55 2008 +0100
@@ -54,7 +54,7 @@
     }
     
     if (miscOpts.pollInterval !<= 1.0 || miscOpts.pollInterval !>= 0.0)
-        Options.setDouble ("misc", "pollInterval", 0.01);
+        miscOpts.set!(double) ("pollInterval", 0.01);
     //END Initialisation
     
     //BEGIN Main loop setup
--- a/mde/setup/Init.d	Fri Jun 27 18:35:33 2008 +0100
+++ b/mde/setup/Init.d	Sun Jun 29 11:55:55 2008 +0100
@@ -185,7 +185,7 @@
                 }
             } else if (!(miscOpts.logOptions & LOG.CONSOLE)) {
                 // make sure at least one logger is enabled
-                Options.setInt ("misc", "logOptions", miscOpts.logOptions | LOG.CONSOLE);
+                miscOpts.set!(int) ("logOptions", miscOpts.logOptions | LOG.CONSOLE);
             }
             if (miscOpts.logOptions & LOG.CONSOLE) {	// Log to the console
                 root.addAppender(new ConsoleAppender);
@@ -327,7 +327,7 @@
                 logger.error (e.msg);
                 logger.info ("Will disable threads and continue, assuming no threads were created.");
             
-                Options.setBool("misc", "useThreads", false);   // Disable threads entirely
+                miscOpts.set!(bool)("useThreads", false);   // Disable threads entirely
                 return true;                // Try again without threads
             }
         
--- a/mde/setup/sdl.d	Fri Jun 27 18:35:33 2008 +0100
+++ b/mde/setup/sdl.d	Sun Jun 29 11:55:55 2008 +0100
@@ -158,11 +158,11 @@
 
 void resizeWindow (int w, int h) {
     if (vidOpts.fullscreen) {
-        Options.setInt ("video", "screenW", w);
-        Options.setInt ("video", "screenH", h);
+        vidOpts.set!(int) ("screenW", w);
+        vidOpts.set!(int) ("screenH", h);
     } else {
-        Options.setInt ("video", "windowW", w);
-        Options.setInt ("video", "windowH", h);
+        vidOpts.set!(int) ("windowW", w);
+        vidOpts.set!(int) ("windowH", h);
     }
     
     if (SDL_SetVideoMode (w, h, 32, flags) is null) {