Mercurial > projects > mde
comparison mde/lookup/Options.d @ 104:ee209602770d
Cleaned up Options.d removing old storage method. It's now possible to get a ContentList of the whole of Options.
Tweaked translation strings (added name and desc to Options classes).
Replaced Options.addSubClass (class, "name") with Options.this("name").
author | Diggory Hardy <diggory.hardy@gmail.com> |
---|---|
date | Wed, 26 Nov 2008 13:07:46 +0000 |
parents | 42e241e7be3e |
children | 08651e8a8c51 |
comparison
equal
deleted
inserted
replaced
103:42e241e7be3e | 104:ee209602770d |
---|---|
11 See the GNU General Public License for more details. | 11 See the GNU General Public License for more details. |
12 | 12 |
13 You should have received a copy of the GNU General Public License | 13 You should have received a copy of the GNU General Public License |
14 along with this program. If not, see <http://www.gnu.org/licenses/>. */ | 14 along with this program. If not, see <http://www.gnu.org/licenses/>. */ |
15 | 15 |
16 //FIXME: Ddoc is outdated | 16 /** This module handles loading and saving of, and allows generic access to named option variables |
17 /** This module handles stored options, currently all except input maps. | 17 * of a simple type (see Options.TYPES). */ |
18 * | |
19 * The purpose of having all options centrally controlled is to allow generic handling by the GUI | |
20 * and ease saving and loading of values. The Options class is only really designed around handling | |
21 * small numbers of variables for now. | |
22 * | |
23 * Note: This module uses some non-spec functionality, which "works for me", but may need to be | |
24 * changed if it throws up problems. Specifically: templated virtual functions (Options.set, get | |
25 * and list), and accessing private templates from an unrelated class (Options.TName, TYPES). | |
26 * OptionChanges used to have a templated set member function (used by Options.set), which caused | |
27 * linker problems when the module wasn't compiled from scratch. | |
28 */ | |
29 module mde.lookup.Options; | 18 module mde.lookup.Options; |
30 | 19 |
31 import mde.setup.paths; | 20 import mde.setup.paths; |
32 import mde.exception; | 21 import mde.exception; |
33 | 22 |
43 private Logger logger; | 32 private Logger logger; |
44 static this() { | 33 static this() { |
45 logger = Log.getLogger ("mde.lookup.Options"); | 34 logger = Log.getLogger ("mde.lookup.Options"); |
46 } | 35 } |
47 | 36 |
48 //FIXME: Ddoc is outdated | 37 /************************************************************************************************* |
49 /** Base class for handling options. | 38 * This class and the OptionChanges class contain all the functionality. |
50 * | 39 * |
51 * This class itself handles no options and should not be instantiated, but provides a sub-classable | 40 * Options are stored in derived class instances, tracked by the static portion of Options. Each |
52 * base for generic options handling. Also, the static portion of this class tracks sub-class | 41 * value is stored in a ValueContent class, whose value can be accessed with opCall, opCast and |
53 * instances and provides loading and saving methods. | 42 * opAssign. These class objects can be given callbacks called whenever their value is changed. |
54 * | 43 * |
55 * Each sub-class provides named variables for maximal-speed reading. Local sub-class references | 44 * Public static methods allow getting the list of tracked sub-class instances, and loading and saving |
56 * should be used for reading variables, and via the addOptionsClass() hook will be loaded from | 45 * option values. A public non-static method allows generic access to option variables. |
57 * files during pre-init (init0 stage). Do not write changes directly to the subclasses or they will | 46 * |
58 * not be saved; instead use set(), for example, miscOpts.set!(char[])("L10n","en-GB"). Use an | 47 * Generic access to Options is of most use to a gui, allowing Options to be edited generically. |
59 * example like MiscOptions as a template for creating a new Options sub-class. | 48 * |
60 * | 49 * The easiest way to use Options is to use an existing sub-class as a template, e.g. MiscOptions. |
61 * Optionally, overload the validate() function. This is called after loading, allowing conditions | 50 *************************************************************************************************/ |
62 * to be enforced on variables. Use set!()() to change the variables. If an exception is thrown, | |
63 * init will abort and the executable won't start. | |
64 * | |
65 * Details: Options sub-classes hold associative arrays of pointers to all option variables, with a | |
66 * char[] id. This list is used for saving, loading and to provide generic GUI options screens. The | |
67 * built-in support in Options is only for bool, int and char[] types (a float type may get added). | |
68 * Further to this, a generic class is used to store all options which have been changed, and if any | |
69 * have been changed, is merged with options from the user conf dir and saved on exit. | |
70 */ | |
71 /* An idea for potentially extending Options, but which doesn't seem necessary now: | |
72 Move static code from Options to an OptionSet class, which may be sub-classed. These sub-classes | |
73 may be hooked in to the master OptionSet class to shadow all Options classes and be notified of | |
74 changes, and may or may not have values loaded from files during init. Change-sets could be | |
75 rewritten to use this. | |
76 However, only the changesets should need to be notified of each change (gui interfaces could simply | |
77 be notified that a change occured and redraw everything; users of options can just re-take their | |
78 values every time they use them). */ | |
79 class Options : IDataSection | 51 class Options : IDataSection |
80 { | 52 { |
81 protected this() {} /// Do not instantiate directly. | 53 /** Do not instantiate directly; use a sub-class. |
82 | 54 * |
83 // All supported types, for generic handling via templates. It should be possible to change | 55 * CTOR adds any created instance to the list of classes tracked statically for loading/saving |
84 // the supported types simply by changing this list now (untested). | 56 * and generic access. |
85 template store(A...) { alias A store; } | 57 * |
86 // NOTE: currently all types have transitioned to the new method, but the old method remains | 58 * Normally instances are created by a static CTOR. */ |
87 alias store!(bool, int, double, char[]) TYPES; // all types | 59 protected this(char[] name) |
88 alias store!(bool, int, double, char[]) CTYPES; // types stored with a content | 60 in { |
61 assert (((cast(ID) name) in subClasses) is null); // Don't allow a silent replacement | |
62 } body { | |
63 subClasses[cast(ID) name] = this; | |
64 } | |
65 | |
89 //BEGIN Templates: internal | 66 //BEGIN Templates: internal |
90 private { | 67 package { |
91 // Get name of a type. Basically just stringof, but special handling for arrays. | 68 // All supported types, for generic handling via templates. It should be possible to change |
69 // the supported types simply by changing this list. | |
70 template store(A...) { alias A store; } | |
71 alias store!(bool, int, double, char[]) TYPES; // types handled | |
72 | |
73 // Get name of a type. Basically just stringof, but special handling for arrays. | |
92 // Use TName!(T) for a valid symbol name, and T.stringof for a type. | 74 // Use TName!(T) for a valid symbol name, and T.stringof for a type. |
93 template TName(T : T[]) { | 75 template TName(T : T[]) { |
94 const char[] TName = TName!(T) ~ "A"; | 76 const char[] TName = TName!(T) ~ "A"; |
95 } | 77 } |
96 template TName(T) { | 78 template TName(T) { |
97 const char[] TName = T.stringof; | 79 const char[] TName = T.stringof; |
98 } | 80 } |
99 | 81 } |
100 // Pointer lists | 82 private { |
101 template PLists(A...) { | |
102 static if (A.length) { | |
103 static if (TIsIn!(A[0], CTYPES)) { | |
104 const char[] PLists = PLists!(A[1..$]); | |
105 } else | |
106 const char[] PLists = A[0].stringof~"*[ID] opts"~TName!(A[0])~";\n" ~ PLists!(A[1..$]); | |
107 } else | |
108 const char[] PLists = ""; | |
109 } | |
110 | |
111 // True if type is one of A | 83 // True if type is one of A |
112 template TIsIn(T, A...) { | 84 template TIsIn(T, A...) { |
113 static if (A.length) { | 85 static if (A.length) { |
114 static if (is(T == A[0])) | 86 static if (is(T == A[0])) |
115 const bool TIsIn = true; | 87 const bool TIsIn = true; |
119 const bool TIsIn = false; // no more possibilities | 91 const bool TIsIn = false; // no more possibilities |
120 } | 92 } |
121 | 93 |
122 // For addTag | 94 // For addTag |
123 template addTagMixin(T, A...) { | 95 template addTagMixin(T, A...) { |
124 static if (TIsIn!(T, CTYPES)) { | 96 const char[] ifBlock = `if (tp == "`~T.stringof~`") { |
125 const char[] ifBlock = `if (tp == "`~T.stringof~`") { | |
126 auto p = id in opts; | 97 auto p = id in opts; |
127 if (p) { | 98 if (p) { |
128 auto q = cast(`~VContentN!(T)~`) (*p); | 99 auto q = cast(`~VContentN!(T)~`) (*p); |
129 if (q) q.assignNoCB = parseTo!(`~T.stringof~`) (dt); | 100 if (q) q.assignNoCB = parseTo!(`~T.stringof~`) (dt); |
130 } | 101 } |
131 }`; | 102 }`; |
132 } else | |
133 const char[] ifBlock = `if (tp == "`~T.stringof~`") { | |
134 `~T.stringof~`** p = id in opts`~TName!(T)~`; | |
135 if (p !is null) **p = parseTo!(`~T.stringof~`) (dt); | |
136 }`; | |
137 static if (A.length) | 103 static if (A.length) |
138 const char[] addTagMixin = ifBlock~` else `~addTagMixin!(A).addTagMixin; | 104 const char[] addTagMixin = ifBlock~` else `~addTagMixin!(A).addTagMixin; |
139 else | 105 else |
140 const char[] addTagMixin = ifBlock; | 106 const char[] addTagMixin = ifBlock; |
141 } | 107 } |
142 | |
143 // For list | |
144 template listMixin(A...) { | |
145 static if (A.length) { | |
146 static if (TIsIn!(A, CTYPES)) | |
147 const char[] listMixin = listMixin!(A[1..$]); | |
148 else | |
149 const char[] listMixin = `ret ~= opts`~TName!(A[0])~`.keys;` ~ listMixin!(A[1..$]); | |
150 } else | |
151 const char[] listMixin = ``; | |
152 } | |
153 } | 108 } |
154 //END Templates: internal | 109 //END Templates: internal |
155 | 110 |
156 | 111 |
157 //BEGIN Static | 112 //BEGIN Static |
158 static { | 113 static { |
159 /** Add an options sub-class to the list for loading and saving. | 114 /** Get the hash map of Options classes. READ-ONLY. */ |
160 * | |
161 * Call from static this() (before Init calls load()). */ | |
162 void addOptionsClass (Options c, char[] i) | |
163 in { // Trap a couple of potential coding errors: | |
164 assert (c !is null); // Instance must be created before calling addOptionsClass | |
165 assert (((cast(ID) i) in subClasses) is null); // Don't allow a silent replacement | |
166 } body { | |
167 c.secName = i; | |
168 subClasses[cast(ID) i] = c; | |
169 } | |
170 | |
171 /** Get the hash map of Options classes. */ | |
172 Options[ID] optionsClasses () { | 115 Options[ID] optionsClasses () { |
173 return subClasses; | 116 return subClasses; |
174 } | 117 } |
175 | 118 |
176 // Track all sections for saving/loading/other generic handling. | 119 // Track all sections for saving/loading/other generic handling. |
237 } | 180 } |
238 //END Static | 181 //END Static |
239 | 182 |
240 | 183 |
241 //BEGIN Non-static | 184 //BEGIN Non-static |
242 /+ NOTE: according to spec: "Templates cannot be used to add non-static members or virtual | |
243 functions to classes." However, this appears to work (but linking problems did occur). | |
244 Alternative: use mixins. From OptionsChanges: | |
245 // setT (used to be a template, but: | |
246 // Templates cannot be used to add non-static members or virtual functions to classes. ) | |
247 template setMixin(A...) { | |
248 static if (A.length) { | |
249 const char[] setMixin = `void set`~TName!(A[0])~` (ID id, `~A[0].stringof~` x) { | |
250 `~TName!(T)~`s[id] = x; | |
251 } | |
252 ` ~ setMixin!(A[1..$]); | |
253 } else | |
254 const char[] setMixin = ``; | |
255 }+/ | |
256 /+ | |
257 /** Set option symbol of an Options sub-class to val. | |
258 * | |
259 * Due to the way options are handled generically, string IDs must be used to access the options | |
260 * via hash-maps, which is a little slower than direct access but necessary since the option | |
261 * must be changed in two separate places. */ | |
262 /+deprecated void set(T) (char[] symbol, T val) { | |
263 static assert (TIsIn!(T,TYPES) && !TIsIn!(T, CTYPES), "Options.set does not support type "~T.stringof); | |
264 | |
265 changed = true; // something got set (don't bother checking this isn't what it already was) | |
266 | |
267 try { | |
268 mixin (`*(opts`~TName!(T)~`[cast(ID) symbol]) = val;`); | |
269 mixin (`optionChanges.`~TName!(T)~`s[symbol] = val;`); | |
270 } catch (ArrayBoundsException) { | |
271 // log and ignore: | |
272 logger.error ("Options.set: invalid symbol"); | |
273 } | |
274 }+/ | |
275 /** Get option symbol of an Options sub-class. | |
276 * | |
277 * Using this method to read an option is not necessary, but allows for generic use. */ | |
278 deprecated T get(T) (char[] symbol) { | |
279 static assert (TIsIn!(T,TYPES), "Options does not support type "~T.stringof); | |
280 | |
281 mixin (`alias opts`~TName!(T)~` optsVars;`); | |
282 | |
283 try { | |
284 return *(optsVars[cast(ID) symbol]); | |
285 } catch (ArrayBoundsException) { | |
286 // log and ignore: | |
287 logger.error ("Options.get: invalid symbol"); | |
288 } | |
289 }+/ | |
290 | |
291 /** List the names of all options of a specific type. */ | |
292 deprecated char[][] list () { | |
293 char[][] ret; | |
294 mixin (listMixin!(TYPES)); | |
295 return ret; | |
296 } | |
297 | |
298 /// Get all Options stored with a ValueContent. | 185 /// Get all Options stored with a ValueContent. |
299 ValueContent[char[]] content() { | 186 ValueContent[char[]] content() { |
300 return opts; | 187 return opts; |
301 } | 188 } |
302 | 189 |
303 /** Variable validate function, called when options are loaded from file. This implementation | 190 /** Variable validate function, called when options are loaded from file. |
304 * does nothing. */ | 191 * |
305 void validate() {} | 192 * This can be overridden to enforce limits on option variables, etc. */ |
306 | 193 protected void validate() {} |
307 /** Boolean, telling whether translation strings have been loaded for the instance. */ | 194 |
308 bool transLoaded; | 195 /** Translated name and description of the instance. mde.gui.content.Items loads these and the |
196 * translation strings of all enclosed options simultaneously. */ | |
197 char[] name, desc; | |
309 | 198 |
310 protected { | 199 protected { |
311 char[] secName; // name of this option setting; set null after translation is loaded | |
312 OptionChanges optionChanges; // all changes to options (for saving) | 200 OptionChanges optionChanges; // all changes to options (for saving) |
313 | 201 ValueContent[char[]] opts; // generic list of option values |
314 // The "pointer lists", e.g. char[]*[ID] optscharA; | |
315 mixin (PLists!(TYPES)); | |
316 ValueContent[char[]] opts; // generic list of option values | |
317 } | 202 } |
318 | 203 |
319 //BEGIN Mergetag loading/saving code | 204 //BEGIN Mergetag loading/saving code |
320 void addTag (char[] tp, ID id, char[] dt) { | 205 void addTag (char[] tp, ID id, char[] dt) { |
321 mixin(addTagMixin!(TYPES).addTagMixin); | 206 mixin(addTagMixin!(TYPES).addTagMixin); |
326 //END Non-static | 211 //END Non-static |
327 | 212 |
328 | 213 |
329 //BEGIN Templates: impl & optionsThis | 214 //BEGIN Templates: impl & optionsThis |
330 private { | 215 private { |
331 // Replace, e.g., bool, with BoolContent | |
332 template contentName(A) { | |
333 static if (TIsIn!(A, CTYPES)) { | |
334 const char[] contentName = VContentN!(A); | |
335 } else | |
336 const char[] contentName = A.stringof; | |
337 } | |
338 // Return index of first comma, or halts if not found. | 216 // Return index of first comma, or halts if not found. |
339 template cIndex(char[] A) { | 217 template cIndex(char[] A) { |
340 static if (A.length == 0) | 218 static if (A.length == 0) |
341 static assert (false, "Error in implementation"); | 219 static assert (false, "Error in implementation"); |
342 else static if (A[0] == ',') | 220 else static if (A[0] == ',') |
366 const char[] parseT = A[type.length+1 .. scIndex!(A)] ~ "," ~ | 244 const char[] parseT = A[type.length+1 .. scIndex!(A)] ~ "," ~ |
367 parseT!(type, A[scIndex!(A)+1 .. $]); | 245 parseT!(type, A[scIndex!(A)+1 .. $]); |
368 else // no match | 246 else // no match |
369 const char[] parseT = parseT!(type, A[scIndex!(A)+1 .. $]); | 247 const char[] parseT = parseT!(type, A[scIndex!(A)+1 .. $]); |
370 } | 248 } |
371 // May have a trailing comma. Assumes cIndex always returns less than A.$ . | |
372 template aaVars(char[] A) { | |
373 static if (A.length == 0) | |
374 const char[] aaVars = ""; | |
375 else static if (A[0] == ' ') | |
376 const char[] aaVars = aaVars!(A[1..$]); | |
377 else | |
378 const char[] aaVars = "\""~A[0..cIndex!(A)]~"\"[]:&"~A[0..cIndex!(A)] ~ "," ~ | |
379 aaVars!(A[cIndex!(A)+1..$]); | |
380 } | |
381 // May have a trailing comma. Assumes cIndex always returns less than A.$ . | |
382 template aaVarsContent(char[] A) {//FIXME | |
383 static if (A.length == 0) | |
384 const char[] aaVarsContent = ""; | |
385 else static if (A[0] == ' ') | |
386 const char[] aaVarsContent = aaVarsContent!(A[1..$]); | |
387 else | |
388 const char[] aaVarsContent = "\""~A[0..cIndex!(A)]~"\"[]:cast(ValueContent)"~A[0..cIndex!(A)] ~ "," ~ | |
389 aaVarsContent!(A[cIndex!(A)+1..$]); | |
390 } | |
391 // strip Trailing Comma | 249 // strip Trailing Comma |
392 template sTC(char[] A) { | 250 template sTC(char[] A) { |
393 static if (A.length && A[$-1] == ',') | 251 static if (A.length && A[$-1] == ',') |
394 const char[] sTC = A[0..$-1]; | 252 const char[] sTC = A[0..$-1]; |
395 else | 253 else |
396 const char[] sTC = A; | 254 const char[] sTC = A; |
397 } | |
398 // if string is empty (other than space) return null, otherwise enclose: [A] | |
399 template listOrNull(char[] A) { | |
400 static if (A.length == 0) | |
401 const char[] listOrNull = "null"; | |
402 else static if (A[0] == ' ') | |
403 const char[] listOrNull = listOrNull!(A[1..$]); | |
404 else | |
405 const char[] listOrNull = "["~A~"]"; | |
406 } | 255 } |
407 // if B is empty return an empty string otherswise return what's below: | 256 // if B is empty return an empty string otherswise return what's below: |
408 template catOrNothing(char[] A,char[] B) { | 257 template catOrNothing(char[] A,char[] B) { |
409 static if (B.length) | 258 static if (B.length) |
410 const char[] catOrNothing = A~` `~sTC!(B)~";\n"; | 259 const char[] catOrNothing = A~` `~sTC!(B)~";\n"; |
422 createContents!(T,A[cIndex!(A)+1..$]); | 271 createContents!(T,A[cIndex!(A)+1..$]); |
423 } | 272 } |
424 // for recursing on TYPES | 273 // for recursing on TYPES |
425 template optionsThisInternal(char[] A, B...) { | 274 template optionsThisInternal(char[] A, B...) { |
426 static if (B.length) { | 275 static if (B.length) { |
427 static if (TIsIn!(B[0], CTYPES)) { | 276 const char[] optionsThisInternal = createContents!(B[0],parseT!(B[0].stringof,A))~ |
428 const char[] optionsThisInternal = createContents!(B[0],parseT!(B[0].stringof,A))~ | 277 optionsThisInternal!(A,B[1..$]); |
429 optionsThisInternal!(A,B[1..$]); | 278 } else |
430 } else | |
431 const char[] optionsThisInternal = `opts`~TName!(B[0])~` = `~listOrNull!(sTC!(aaVars!(parseT!(B[0].stringof,A))))~";\n" ~ optionsThisInternal!(A,B[1..$]); | |
432 } else | |
433 const char[] optionsThisInternal = ``; | 279 const char[] optionsThisInternal = ``; |
434 } | 280 } |
435 template declValsInternal(char[] A, B...) { | 281 template declValsInternal(char[] A, B...) { |
436 static if (B.length) { | 282 static if (B.length) { |
437 const char[] declValsInternal = catOrNothing!(contentName!(B[0]),parseT!(B[0].stringof,A)) ~ declValsInternal!(A,B[1..$]); | 283 const char[] declValsInternal = catOrNothing!(VContentN!(B[0]),parseT!(B[0].stringof,A)) ~ declValsInternal!(A,B[1..$]); |
438 } else | 284 } else |
439 const char[] declValsInternal = ``; | 285 const char[] declValsInternal = ``; |
440 } | 286 } |
441 } protected { | 287 } protected { |
442 /** Declares the values. | 288 /** Declares the values. |
448 } | 294 } |
449 /** Produces the implementation code to go in the constuctor. */ | 295 /** Produces the implementation code to go in the constuctor. */ |
450 template optionsThis(char[] A) { | 296 template optionsThis(char[] A) { |
451 const char[] optionsThis = | 297 const char[] optionsThis = |
452 "optionChanges = new OptionChanges;\n" ~ | 298 "optionChanges = new OptionChanges;\n" ~ |
299 "super (name);\n" ~ | |
453 optionsThisInternal!(A,TYPES); | 300 optionsThisInternal!(A,TYPES); |
454 } | 301 } |
455 /+ Needs too many custom parameters to be worth it? Plus makes class less readable. | 302 /+ Needs too many custom parameters to be worth it? Plus makes class less readable. |
456 /** Produces the implementation code to go in the static constuctor. */ | 303 /** Produces the implementation code to go in the static constuctor. */ |
457 template optClassAdd(char[] symb) { | 304 template optClassAdd(char[] symb) { |
465 * --- | 312 * --- |
466 * | 313 * |
467 * In case this() needs to be customized, mixin(impl!(A)) is equivalent to: | 314 * In case this() needs to be customized, mixin(impl!(A)) is equivalent to: |
468 * --- | 315 * --- |
469 * mixin (declVals!(A)~` | 316 * mixin (declVals!(A)~` |
470 this () { | 317 this (char[] name) { |
471 `~optionsThis!(A)~` | 318 `~optionsThis!(A)~` |
472 }`); | 319 }`); |
473 * --- | 320 * --- |
474 * | 321 * |
475 * Notes: Only use space as whitespace (no new-lines or tabs). Make sure to add a trailing | 322 * Notes: Only use space as whitespace (no new-lines or tabs). Make sure to add a trailing |
476 * semi-colon (;) or you'll get told off! :D | 323 * semi-colon (;) or you'll get told off! :D |
478 * necessary. | 325 * necessary. |
479 * | 326 * |
480 * Extending: mixins could also be used for the static this() {...} or even the whole | 327 * Extending: mixins could also be used for the static this() {...} or even the whole |
481 * class, but doing so would rather decrease readability of any implementation. */ | 328 * class, but doing so would rather decrease readability of any implementation. */ |
482 template impl(char[] A /+, char[] symb+/) { | 329 template impl(char[] A /+, char[] symb+/) { |
483 const char[] impl = declVals!(A)~"\nthis(){\n"~optionsThis!(A)~"}"; | 330 const char[] impl = declVals!(A)~"\nthis(char[] name){\n"~optionsThis!(A)~"}"; |
484 // ~"\nstatic this(){\n"~optClassAdd!(symb)~"}" | 331 // ~"\nstatic this(){\n"~optClassAdd!(symb)~"}" |
485 } | 332 } |
486 } | 333 } |
487 //END Templates: impl & optionsThis | 334 //END Templates: impl & optionsThis |
488 } | 335 } |
489 | 336 |
490 /** Special class to store all locally changed options, whatever the section. */ | 337 /************************************************************************************************* |
338 * Special class to store all locally changed options. | |
339 * | |
340 * This allows only changed options and those already stored in the user directory to be saved, so | |
341 * that other options can be merged in from a global directory, allowing any options not locally | |
342 * set to be changed globally. | |
343 *************************************************************************************************/ | |
491 class OptionChanges : IDataSection | 344 class OptionChanges : IDataSection |
492 { | 345 { |
493 //BEGIN Templates | 346 //BEGIN Templates |
494 private { | 347 private { |
495 alias Options.TName TName; | 348 alias Options.TName TName; |
551 mixin(writeAllMixin!(TYPES)); | 404 mixin(writeAllMixin!(TYPES)); |
552 } | 405 } |
553 //END Mergetag loading/saving code | 406 //END Mergetag loading/saving code |
554 } | 407 } |
555 | 408 |
556 /* NOTE: Options sub-classes are expected to use a template to ease inserting contents and | 409 /** A home for all miscellaneous options. |
557 * hide some of the "backend" functionality. Use impl as below, or read the documentation for impl. | 410 * |
558 * | 411 * Also a template for deriving Options; comments explain what does what. |
559 * Each entry should have a Translation entry with humanized names and descriptions in | 412 * |
560 * data/L10n/ClassName.mtt | 413 * Translation strings for the options are looked for in data/L10n/SectionName.mtt where |
561 * | 414 * this ("SectionName") names the instance. */ |
562 * To create a new Options sub-class, just copy, paste and adjust. | |
563 */ | |
564 | |
565 /** A home for all miscellaneous options, at least for now. */ | |
566 MiscOptions miscOpts; | 415 MiscOptions miscOpts; |
567 class MiscOptions : Options { | 416 class MiscOptions : Options { |
417 /* The key step is to mixin impl. | |
418 The syntax is just as simple variables are declared, which is how these options used to be | |
419 stored. Now they're enclosed in ValueContent classes; e.g. "char[] L10n;" is replaced with | |
420 "TextContent L10n;". The pragma statement can be uncommented to see what code gets injected | |
421 (note: pragma () gets called each time the module is imported as well as when it's compiled). | |
422 impl creates a this() function; if you want to include your own CTOR see impl's ddoc. */ | |
568 const A = "bool exitImmediately; int maxThreads, logLevel, logOutput; double pollInterval; char[] L10n;"; | 423 const A = "bool exitImmediately; int maxThreads, logLevel, logOutput; double pollInterval; char[] L10n;"; |
569 //pragma (msg, impl!(A)); | 424 //pragma (msg, impl!(A)); |
570 mixin (impl!(A)); | 425 mixin (impl!(A)); |
571 | 426 |
572 void validate() { | 427 // Overriding validate allows limits to be enforced on variables at load time. Currently |
428 // there's no infrastructure for enforcing limits when options are set at run-time. | |
429 override void validate() { | |
573 // Try to enforce sensible values, whilst being reasonably flexible: | 430 // Try to enforce sensible values, whilst being reasonably flexible: |
574 if (maxThreads() < 1 || maxThreads() > 64) { | 431 if (maxThreads() < 1 || maxThreads() > 64) { |
575 logger.warn ("maxThreads must be in the range 1-64. Defaulting to 4."); | 432 logger.warn ("maxThreads must be in the range 1-64. Defaulting to 4."); |
576 maxThreads = 4; | 433 maxThreads = 4; |
577 } | 434 } |
578 if (pollInterval() !<= 1.0 || pollInterval() !>= 0.0) | 435 if (pollInterval() !<= 1.0 || pollInterval() !>= 0.0) |
579 pollInterval = 0.01; | 436 pollInterval = 0.01; |
580 } | 437 } |
581 | 438 |
439 // A static CTOR is a good place to create the instance (it must be created before init runs). | |
582 static this() { | 440 static this() { |
583 miscOpts = new MiscOptions; | 441 // Adds instance to Options's tracking; the string is the section name in the config files. |
584 Options.addOptionsClass (miscOpts, "MiscOptions"); | 442 miscOpts = new MiscOptions ("MiscOptions"); |
585 } | 443 } |
586 } | 444 } |