# HG changeset patch # User Diggory Hardy # Date 1214736955 -3600 # Node ID cc3763817b8a34d1b623faa064e643daff9cd6b4 # Parent 66d555da083e890a1ead784e544cf51779e0ed6d Overhauled Options so that it now uses templates and mixins for type-specific internals, and supported types can be adjusted via just one list. diff -r 66d555da083e -r cc3763817b8a codeDoc/jobs.txt --- 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 diff -r 66d555da083e -r cc3763817b8a data/conf/gui.mtt --- 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} - + + {WEmbedded} diff -r 66d555da083e -r cc3763817b8a mde/font/font.d --- 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 diff -r 66d555da083e -r cc3763817b8a mde/lookup/Options.d --- 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 } diff -r 66d555da083e -r cc3763817b8a mde/mde.d --- 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 diff -r 66d555da083e -r cc3763817b8a mde/setup/Init.d --- 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 } diff -r 66d555da083e -r cc3763817b8a mde/setup/sdl.d --- 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) {