# HG changeset patch # User Diggory Hardy # Date 1205582173 0 # Node ID 9cb7b931016813c5e6338634fe85d9f3b748a865 # Parent 4608be19ebe2de65b7f8ccddaee5d424a5979aef 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 diff -r 4608be19ebe2 -r 9cb7b9310168 codeDoc/jobs.txt --- 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. diff -r 4608be19ebe2 -r 9cb7b9310168 codeDoc/options.txt --- /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 diff -r 4608be19ebe2 -r 9cb7b9310168 codeDoc/todo.txt --- /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? diff -r 4608be19ebe2 -r 9cb7b9310168 data/conf/options.mtt --- 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} + diff -r 4608be19ebe2 -r 9cb7b9310168 mde/Init.d --- 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 + } +} diff -r 4608be19ebe2 -r 9cb7b9310168 mde/SDL.d --- /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(); +} diff -r 4608be19ebe2 -r 9cb7b9310168 mde/i18n/I18nTranslation.d --- 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 { diff -r 4608be19ebe2 -r 9cb7b9310168 mde/mde.d --- 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) { diff -r 4608be19ebe2 -r 9cb7b9310168 mde/mergetag/iface/IDataSection.d --- 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 diff -r 4608be19ebe2 -r 9cb7b9310168 mde/options.d --- 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]; + } +}