changeset 16:9cb7b9310168

Improvements to Options and Init. Revamped Options with sections and auto saving/loading. Moved some of init's functions outside the module. committer: Diggory Hardy <diggory.hardy@gmail.com>
author Diggory Hardy <diggory.hardy@gmail.com>
date Sat, 15 Mar 2008 11:56:13 +0000
parents 4608be19ebe2
children 5f90774ea1ef
files codeDoc/jobs.txt codeDoc/options.txt codeDoc/todo.txt data/conf/options.mtt mde/Init.d mde/SDL.d mde/i18n/I18nTranslation.d mde/mde.d mde/mergetag/iface/IDataSection.d mde/options.d
diffstat 10 files changed, 286 insertions(+), 175 deletions(-) [+]
line wrap: on
line diff
--- a/codeDoc/jobs.txt	Fri Mar 14 11:39:45 2008 +0000
+++ b/codeDoc/jobs.txt	Sat Mar 15 11:56:13 2008 +0000
@@ -1,8 +1,8 @@
 In progress:
 
 To do:
-*   Move init's delegates to appropriate external modules and provide addInitDlg and addCleanupDlg functions callable from other modules' static this() methods.
-*   Control logging level/output via options.
+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
@@ -36,6 +36,5 @@
         +/
 
 Done (for git log message):
-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.
+Revamped Options with sections and auto saving/loading.
+Moved some of init's functions outside the module.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/codeDoc/options.txt	Sat Mar 15 11:56:13 2008 +0000
@@ -0,0 +1,67 @@
+Ideas for extending options to track user-changed options separately from system options so as not to override unchanged system options.
+
+I was inteding on implementing this, until I realised the extra complexity necessary. Also the gains in functionality seem tiny and not always desireable.
+
+Extract of doc. plus example:
+
+/** Base class for handling options. This class itself will handle no options and should not be
+* instantiated, but sub-classes handle options.
+*
+* Each sub-class provides named variables for maximal-speed reading; however these cannot be used
+* for changing options due to only changed options being written to the user file.
+*
+* The static class keeps track of all Options class instances for global loading and saving.
+*
+* Details: Options sub-classes hold associative arrays of pointers to all options, with char[] id.
+* When options are changed, they also load all options from the user file to save back there.
+* When an options GUI window is opened, option names and descriptions are loaded using the id and
+* i18n.I18nTranslation (possibly not by the Options class), and possibly also system-level options
+* are loaded to allow users to revert to these.
+*/
+class Options : IDataSection
+{
+    bool example;
+    
+    bool*[char[]] optsBool;
+    bool[char[]] cgdOptsBool;
+    bool[char[]] sysOptsBool;
+    bool changed; sysLoaded;
+    
+    this () {
+        optsBool = ["example":&example];
+    }
+    
+    ...
+}
+
+From todo.txt:
+Options:
+->  types:
+    ->  bool
+    ->  int
+    ->  char[]
+    ->  float/double/real?
+->  automated saving/loading
+    ->  track whether any options have been changed
+    ->  track all changed options (minus ones reverted to system confif) plus all options loaded from file to save to
+->  symbols (static or associative arrays?)
+    ->  read-only outside of class
+    ->  a merge of "changed" and "system"
+    ->  only part which needs to be loaded when not changing options
+->  "Changed" symbols
+    ->  constists of entries as above (changes above system config)
+    ->  only loaded when an option is first changed?
+    ->  use setter functions
+        ->  change/add/remove from "changed"
+        ->  change base symbols
+->  "System" symbols
+    ->  symbols only loaded from system-level config
+    ->  only to show what the default setting to revert to is (since otherwise when removing a user change its system value wouldn't be known until config is reloaded)
+    ->  only load when required
+->  sections
+    ->  one class per section
+    ->  classes may be derived to provide their own handling
+    ->  use separate files? Otherwise cannot efficiently load options on a per-section status with mtt files.
+->  root or static class
+    ->  parent of all sections
+    ->  handles saving and loading
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/codeDoc/todo.txt	Sat Mar 15 11:56:13 2008 +0000
@@ -0,0 +1,11 @@
+GUI:
+->  Basic OpenGL code to:
+    -> create orthographic projection
+    -> draw boxes
+    -> maybe more (text, textures, ...)
+->  Windows with size & position
+->  Widgets:
+    ->  minimum size but expandable, auto-set
+    ->  grid "layout" widgets
+->  Text rendering
+    -> text library?
--- a/data/conf/options.mtt	Fri Mar 14 11:39:45 2008 +0000
+++ b/data/conf/options.mtt	Sat Mar 15 11:56:13 2008 +0000
@@ -1,4 +1,5 @@
 {MT01}
-{Default}
+{misc}
 <char[]|L10n="en-GB">
 <bool|useThreads=false>
+<int|logLevel=1>
--- a/mde/Init.d	Fri Mar 14 11:39:45 2008 +0000
+++ b/mde/Init.d	Sat Mar 15 11:56:13 2008 +0000
@@ -11,7 +11,6 @@
 import mde.events;
 import global = mde.global;
 import mde.input.input;
-import mde.input.joystick;
 
 // tango imports
 import tango.core.Thread;
@@ -20,9 +19,6 @@
 import tango.util.log.ConsoleAppender : ConsoleAppender;
 import tango.stdc.stringz : fromStringz;
 
-import derelict.sdl.sdl;
-import derelict.util.exception;
-
 /**
  * Static CTOR
  *
@@ -41,11 +37,16 @@
         debug root.setLevel(root.Level.Trace);
         else root.setLevel(root.Level.Info);
     }
+    
+    Init.addFunc (&miscInit);
 }
 static ~this()
 {
 }
 
+// Global instance, since called init functions need to interact:
+scope Init init;
+
 /**
  * Init class
  *
@@ -63,7 +64,7 @@
     *
     * 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. */
+    * 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
@@ -73,97 +74,6 @@
     */
     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)
@@ -176,9 +86,7 @@
         }
         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));
+        Log.getRootLogger.setLevel (cast(Log.Level) Options.misc.logLevel, true);  // set the stored log level
         //END Pre-init
         
         
@@ -190,7 +98,7 @@
         */
         ThreadGroup tg;                     // Not necessarily used
         
-        if (options.useThreads) {           // Threaded init (thread creation)
+        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
@@ -199,12 +107,11 @@
                 logger.warn (e.msg);
                 logger.warn ("Will continue in a non-threaded manner.");
             
-                options.useThreads = false;
-                options.changed = true;
+                Options.misc.useThreads = false;
             }
         }
         
-        if (!options.useThreads) {          // Non-threaded init
+        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:
@@ -214,7 +121,7 @@
             }
         }
         
-        if (options.useThreads) {           // Threaded init (thread joining)
+        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.
@@ -263,21 +170,32 @@
         }
     }
     
-    /* Cleanup Functions.
-    *
-    * These may exist simply as something to add to the cleanup list... */
-    static void cleanupSDL () {
-        SDL_Quit();
+    // Stop init and start cleanup.
+    void setFailure () {
+        synchronized initFailure = true;
     }
     
-    private static {
-        void function ()[] cleanup;		// all functions to be run for cleanup
+    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:
-        void runCleanupFcts () {
+        private void runCleanupFcts () {
             foreach_reverse (fct; cleanup) fct();
         }
     }
@@ -307,3 +225,18 @@
         logger.info ("Unittest complete.");
     }
 }
+
+void miscInit () {
+    /* 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) {
+        init.setFailure ();                // must clean up properly
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mde/SDL.d	Sat Mar 15 11:56:13 2008 +0000
@@ -0,0 +1,88 @@
+/** Just a temporary place to put SDL Init and Video stuff.
+*/
+module mde.SDL;
+
+import mde.Init;
+import mde.input.joystick;
+
+import tango.util.log.Log : Log, Logger;
+
+import derelict.sdl.sdl;
+import derelict.util.exception;
+
+Logger logger;
+static this() {
+    logger = Log.getLogger ("mde.init.SDL");
+    
+    Init.addFunc (&initSDL);
+}
+
+void initSDL() {
+            
+    // Inits SDL and related stuff (joystick).
+    try {
+        DerelictSDL.load();
+    } catch (DerelictException de) {
+        logger.fatal ("Loading dynamic library failed:");
+        logger.fatal (de.msg);
+                
+        init.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");
+                
+        init.setFailure ();		// abort
+        return;
+    }
+            
+    init.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");
+                
+        init.setFailure ();
+        return;
+    }
+            
+    openJoysticks ();                        // after SDL init
+    init.addCleanupFct (&closeJoysticks);
+}
+
+void cleanupSDL () {
+    SDL_Quit();
+}
--- a/mde/i18n/I18nTranslation.d	Fri Mar 14 11:39:45 2008 +0000
+++ b/mde/i18n/I18nTranslation.d	Sat Mar 15 11:56:13 2008 +0000
@@ -79,9 +79,9 @@
     {
         bool[ID] loadedSecs;        // set of all locales/sections loaded; used to prevent circular loading
         ID[] secsToLoad             // locales/sections to load (dependancies may be added)
-        = [cast(ID) options.L10n];  // start by loading the current locale
+        = [cast(ID) Options.misc.L10n];  // start by loading the current locale
         
-        I18nTranslation transl = new I18nTranslation (name, options.L10n);
+        I18nTranslation transl = new I18nTranslation (name, Options.misc.L10n);
         
         IReader reader;
         try {
--- a/mde/mde.d	Fri Mar 14 11:39:45 2008 +0000
+++ b/mde/mde.d	Sat Mar 15 11:56:13 2008 +0000
@@ -12,6 +12,9 @@
 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.input.input;
 
 // External library imports
@@ -27,7 +30,6 @@
     //BEGIN Initialisation
     Logger logger = Log.getLogger ("mde.mde");
     
-    scope Init init;
     try {
         init = new Init();	// initialisation
     } catch (InitException e) {
--- a/mde/mergetag/iface/IDataSection.d	Fri Mar 14 11:39:45 2008 +0000
+++ b/mde/mergetag/iface/IDataSection.d	Sat Mar 15 11:56:13 2008 +0000
@@ -7,8 +7,10 @@
 */
 module mde.mergetag.iface.IDataSection;
 
-/** Typedef for data & section indexes (can be changed to ulong if necessary.) */
-typedef char[] ID;
+/** Typedef for data & section indexes.
+*
+* Make it an alias, there doesn't appear to be any point having it as a typedef. */
+alias char[] ID;
 
 /**
  * Interface for data storage classes, generally called DataSections, which contain all data-tags
--- a/mde/options.d	Fri Mar 14 11:39:45 2008 +0000
+++ b/mde/options.d	Sat Mar 15 11:56:13 2008 +0000
@@ -1,7 +1,8 @@
 /** This module handles stored options, currently all except input maps.
 *
 * The purpose of having all options centrally controlled is to allow generic handling by the GUI
-* and ease saving and loading of values.
+* and ease saving and loading of values. The Options class is only really designed around handling
+* small numbers of variables for now.
 */
 module mde.options;
 
@@ -18,64 +19,61 @@
 
 import tango.util.log.Log : Log, Logger;
 
-Options options;
-static this() {
-    // Since options is used in many places, creating a class of default values to use until
-    // options have been loaded makes its use safer.
-    // In addition, loading should be atomic.
-    options = new Options();
-}
-
-// Later: add automatic saving/loading for variables, variable lists with GUI name and description.
-
-/** Class instance stores all options.
+/** Base class for handling options. This class itself will handle no options and should not be
+* instantiated, but sub-classes handle options.
+*
+* Each sub-class provides named variables for maximal-speed reading & writing. Sub-class references
+* are stored by name, and will be available after pre-init has run (DO NOT access before this).
 *
-* All options and handling should be non-static to allow possibility of profiles later.
+* The static class keeps track of all Options class instances for global loading and saving.
+*
+* 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
+* built-in support in Options is only for bool, int and char[] types (a float type may get added).
 */
 class Options : IDataSection
 {
-    /* The options.
-    *
-    * For now, named variables. Later, lists (and named variables?).
-    *
-    * They can be read and set directly by other code (so not thread-safe for writing currently).
-    */
-    char[] L10n;        // locale, e.g. en-GB
-    bool useThreads;    // set 0 to disable threading
-    int logLevel;       // tango logger level
+    // 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 char[]*[ID]   optsCharA;
+    protected int*   [ID]   optsInt;
     
-    /* Have any options been changed? Only bother writing if true.
-    *
-    * Maybe update this in the future to save if the last file edit was not made by mde. */
-    bool changed = false;
-    
-    /* The code to load/save the values.
-    *
-    * Uses mergetag.
-    */
+    //BEGIN Mergtag loading/saving code
     void addTag (char[] tp, ID id, char[] dt) {
-        if (tp == "char[]") {
-            if (id == cast(ID)"L10n") L10n = parseTo!(char[]) (dt);
-        } else if (tp == "bool") {
-            if (id == cast(ID)"useThreads") useThreads = parseTo!(bool) (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 == "int") {
-            if (id == cast(ID)"logLevel") logLevel = parseTo!(int) (dt);
+            int** p = id in optsInt;
+            if (p !is null) **p = parseTo!(int) (dt);
         }
     }
     void writeAll (ItemDelg dlg) {
-        dlg ("char[]", cast(ID)"L10n", parseFrom!(char[]) (L10n));
-        dlg ("bool", cast(ID)"useThreads", parseFrom!(bool) (useThreads));
-        dlg ("int", cast(ID)"logLevel", parseFrom!(int) (logLevel));
+        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, int*    val; optsInt)   dlg ("int"   , id, parseFrom!(int   ) (*val));
     }
-    
+    //END Mergtag loading/saving code
+        
+    //BEGIN Static
+    // Each individual section
+    static OptionsMisc  misc;
     
     /* 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 secName = cast(ID)"Default";
     static void load () {
+        // Create all uncreated sections now, so that if we return early they are still created.
+        if (misc is null) misc = new OptionsMisc;
+        
         // 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;
@@ -83,8 +81,11 @@
         IReader reader;
         try {
             reader = confDir.makeMTReader (fileName, PRIORITY.LOW_HIGH);
-            reader.dataSecCreator = delegate IDataSection(ID) {
-                return new Options;
+            reader.dataSecCreator = delegate IDataSection(ID id) {
+                /* Recognise each defined section, and return null for unrecognised sections. */
+                
+                if (id == cast(ID) "misc") return misc;
+                else return null;
             };
             reader.read;
         } catch (MTException e) {
@@ -93,18 +94,11 @@
             throw new optionsLoadException ("Loading aborted: mergetag exception");
         }
         
-        IDataSection* secP = secName in reader.dataset.sec;
-        Options o = cast(Options) *secP;
-        if (o is null) {
-            throw new optionsLoadException ("Loading failed: expected section not found");
-        }
-        else options = o;   // loading was succesful.
+        if (misc is null) throw new optionsLoadException ("Loading failed: section \"misc\" not found");
     }
     static void save () {
-        if (!options.changed) return;   // skip
-
         DataSet ds = new DataSet();
-        ds.sec[secName] = options;
+        ds.sec[cast(ID) "misc"] = misc;
         
         IWriter writer;
         try {
@@ -121,4 +115,18 @@
     static this() {
         logger = Log.getLogger ("mde.options");
     }
+    //END Static
 }
+
+/** A home for all miscellaneous options, at least for now. */
+class OptionsMisc : Options {
+    bool useThreads;    // set 0 to disable threading
+    char[] L10n;        // locale, e.g. en-GB
+    int logLevel;       // tango logger level
+    
+    this () {
+        optsBool    = ["useThreads":&useThreads];
+        optsCharA   = ["L10n":&L10n];
+        optsInt     = ["logLevel":&logLevel];
+    }
+}