Mercurial > projects > mde
view mde/options.d @ 26:611f7b9063c6
Changed the licensing and removed a few dead files.
Changed licensing to "GPL version 2 or later" to avoid future compatibility issues.
Also a unittest fix to the previous commit.
committer: Diggory Hardy <diggory.hardy@gmail.com>
author | Diggory Hardy <diggory.hardy@gmail.com> |
---|---|
date | Thu, 03 Apr 2008 18:15:02 +0100 |
parents | 2c28ee04a4ed |
children | 0aa621b3e070 |
line wrap: on
line source
/* LICENSE BLOCK Part of mde: a Modular D game-oriented Engine Copyright © 2007-2008 Diggory Hardy This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ /** 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. The Options class is only really designed around handling * small numbers of variables for now. */ module mde.options; import mde.exception; import mde.mergetag.Reader; import mde.mergetag.Writer; import mde.mergetag.DataSet; import mde.mergetag.exception; import mde.resource.paths; import tango.scrapple.text.convert.parseTo : parseTo; import tango.scrapple.text.convert.parseFrom : parseFrom; import tango.core.Exception : ArrayBoundsException; import tango.util.log.Log : Log, Logger; /** Base class for handling options. * * This class itself handles no options and should not be instantiated, but provides a sub-classable * base for generic options handling. Also, the static portion of this class tracks sub-class * instances and provides loading and saving methods. * * 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(...). * * 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). * Further to this, a generic class is used to store all options which have been changed, and if any * have been changed, is merged with options from the user conf dir and saved on exit. */ 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 char[]*[ID] optsCharA; protected int* [ID] optsInt; //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 == "int") { int** p = id in optsInt; if (p !is null) **p = parseTo!(int) (dt); } } 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, int* val; optsInt) dlg ("int" , id, parseFrom!(int ) (*val)); } //END Mergetag loading/saving code //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; } /** 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) 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); } } 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) 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 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); } } // 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); } } private static Logger logger; static this() { logger = Log.getLogger ("mde.options"); } //END Static //BEGIN Templates template store(A...) { alias A a; } template decRecurse(char[] A, B...) { static if (B.length) const char[] decRecurse = A ~ ", " ~ decRecurse!(B); else const char[] decRecurse = A; } template decBool(A...) { const char[] decBool = "bool " ~ decRecurse!(A) ~ ";\n"; } template decCharA(A...) { const char[] decCharA = "char[] " ~ decRecurse!(A) ~ ";\n"; } template decInt(A...) { const char[] decInt = "int " ~ decRecurse!(A) ~ ";\n"; } template aaRecurse(char[] A, B...) { static if (B.length) const char[] aaRecurse = "\""~A~"\"[]:&"~A ~ ", " ~ aaRecurse!(B); else const char[] aaRecurse = "\""~A~"\"[]:&"~A; } template aaBool(A...) { const char[] aaBool = "optsBool = [" ~ aaRecurse!(A) ~ "];\n"; } template aaInt(A...) { const char[] aaInt = "optsInt = [" ~ aaRecurse!(A) ~ "];\n"; } template aaCharA(A...) { const char[] aaCharA = "optsCharA = [" ~ aaRecurse!(A) ~ "];\n"; } //END Templates } /* Special class to store all locally changed options, whatever the section. */ class OptionsGeneric : Options { // 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; char[][] strings; 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; if (p !is null) **p = x; else { ints ~= x; optsInt[id] = &ints[$-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]; } } //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]; } } } //END Mergetag loading/saving code } /* NOTE: These Options classes use templates to ease inserting contents. * * Each entry has an I18nTranslation entry; see data/L10n/ClassName.mtt for descriptions. * * To create a new class, just copy and paste anywhere and adjust. */ /** A home for all miscellaneous options, at least for now. */ OptionsMisc miscOpts; class OptionsMisc : Options { alias store!("useThreads") BOOL; alias store!("logLevel") INT; alias store!("L10n") CHARA; mixin (decBool!(BOOL.a)); mixin (decInt!(INT.a)); mixin (decCharA!(CHARA.a)); this () { mixin (aaBool!(BOOL.a)); mixin (aaInt!(INT.a)); mixin (aaCharA!(CHARA.a)); } static this() { miscOpts = new OptionsMisc; Options.addOptionsClass (miscOpts, "misc"); } }