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 }