Mercurial > projects > mde
comparison mde/lookup/Options.d @ 64:cc3763817b8a
Overhauled Options so that it now uses templates and mixins for type-specific internals, and supported types can be adjusted via just one list.
author | Diggory Hardy <diggory.hardy@gmail.com> |
---|---|
date | Sun, 29 Jun 2008 11:55:55 +0100 |
parents | 66d555da083e |
children | 108d123238c0 |
comparison
equal
deleted
inserted
replaced
63:66d555da083e | 64:cc3763817b8a |
---|---|
42 * instances and provides loading and saving methods. | 42 * instances and provides loading and saving methods. |
43 * | 43 * |
44 * Each sub-class provides named variables for maximal-speed reading. Local sub-class references | 44 * Each sub-class provides named variables for maximal-speed reading. Local sub-class references |
45 * should be used for reading variables, and via the addOptionsClass() hook will be loaded from | 45 * should be used for reading variables, and via the addOptionsClass() hook will be loaded from |
46 * files during pre-init (init0 stage). Do not write changes directly to the subclasses or they will | 46 * files during pre-init (init0 stage). Do not write changes directly to the subclasses or they will |
47 * not be saved; use, for example, Options.setBool(...). Use an example like OptionsMisc as a | 47 * not be saved; instead use set(), for example, miscOpts.set!(char[])("L10n","en-GB"). Use an |
48 * template for creating a new Options sub-class. | 48 * example like OptionsMisc as a template for creating a new Options sub-class. |
49 * | 49 * |
50 * Details: Options sub-classes hold associative arrays of pointers to all option variables, with a | 50 * Details: Options sub-classes hold associative arrays of pointers to all option variables, with a |
51 * char[] id. This list is used for saving, loading and to provide generic GUI options screens. The | 51 * char[] id. This list is used for saving, loading and to provide generic GUI options screens. The |
52 * built-in support in Options is only for bool, int and char[] types (a float type may get added). | 52 * built-in support in Options is only for bool, int and char[] types (a float type may get added). |
53 * Further to this, a generic class is used to store all options which have been changed, and if any | 53 * Further to this, a generic class is used to store all options which have been changed, and if any |
54 * have been changed, is merged with options from the user conf dir and saved on exit. | 54 * have been changed, is merged with options from the user conf dir and saved on exit. |
55 */ | 55 */ |
56 class Options : IDataSection | 56 class Options : IDataSection |
57 { | 57 { |
58 // No actual options are stored by this class. However, much of the infrastructure is | 58 // All supported types, for generic handling via templates. It should be possible to change |
59 // present since it need not be redefined in sub-classes. | 59 // the supported types simply by changing this list now (untested). |
60 | 60 template store(A...) { alias A store; } |
61 // The "pointer lists": | 61 alias store!(bool, int, double, char[]) TYPES; |
62 protected bool* [ID] optsBool; | 62 //BEGIN Templates: internal |
63 protected int* [ID] optsInt; | 63 private { |
64 protected double*[ID] optsDouble; | 64 // Get name of a type. Basically just stringof, but special handling for arrays. |
65 protected char[]*[ID] optsCharA; | 65 // Use TName!(T) for a valid symbol name, and T.stringof for a type. |
66 template TName(T : T[]) { | |
67 const char[] TName = TName!(T) ~ "A"; | |
68 } | |
69 template TName(T) { | |
70 const char[] TName = T.stringof; | |
71 } | |
72 | |
73 // Pointer lists | |
74 template PLists(T, A...) { | |
75 static if (A.length) { | |
76 const char[] PLists = T.stringof~"*[ID] opts"~TName!(T)~";\n" ~ PLists!(A); | |
77 } else | |
78 const char[] PLists = T.stringof~"*[ID] opts"~TName!(T)~";\n"; | |
79 } | |
80 | |
81 // True if type is one of A | |
82 template TIsIn(T, A...) { | |
83 static if (A.length) { | |
84 static if (is(T == A[0])) | |
85 const bool TIsIn = true; | |
86 else | |
87 const bool TIsIn = TIsIn!(T,A[1..$]); | |
88 } else | |
89 const bool TIsIn = false; // no more possibilities | |
90 } | |
91 | |
92 // For addTag | |
93 template addTagMixin(T, A...) { | |
94 const char[] ifBlock = `if (tp == "`~T.stringof~`") { | |
95 `~T.stringof~`** p = id in opts`~TName!(T)~`; | |
96 if (p !is null) **p = parseTo!(`~T.stringof~`) (dt); | |
97 }`; | |
98 static if (A.length) | |
99 const char[] addTagMixin = ifBlock~` else `~addTagMixin!(A).addTagMixin; | |
100 else | |
101 const char[] addTagMixin = ifBlock; | |
102 } | |
103 | |
104 // For writeAll | |
105 template writeAllMixin(A...) { | |
106 static if (A.length) { | |
107 const char[] writeAllMixin = | |
108 `foreach (ID id, `~A[0].stringof~`* val; opts`~TName!(A[0])~`) dlg ("`~A[0].stringof~`", id, parseFrom!(`~A[0].stringof~`) (*val));` ~ writeAllMixin!(A[1..$]); | |
109 } else | |
110 const char[] writeAllMixin = ``; | |
111 } | |
112 } | |
113 //END Templates: internal | |
114 | |
115 | |
116 //BEGIN Static | |
117 static { | |
118 /** Add an options sub-class to the list for loading and saving. | |
119 * | |
120 * Call from static this() (before Init calls load()). */ | |
121 void addOptionsClass (Options c, char[] i) | |
122 in { // Trap a couple of potential coding errors: | |
123 assert (c !is null); // Instance must be created before calling addOptionsClass | |
124 assert (((cast(ID) i) in subClasses) is null); // Don't allow a silent replacement | |
125 } body { | |
126 subClasses[cast(ID) i] = c; | |
127 } | |
128 | |
129 // Track all sections for saving/loading/other generic handling. | |
130 Options[ID] subClasses; | |
131 bool changed = false; // any changes at all, i.e. do we need to save? | |
132 | |
133 /* Load/save options from file. | |
134 * | |
135 * If the file doesn't exist, no reading is attempted (options are left at default values). | |
136 */ | |
137 private const fileName = "options"; | |
138 private const MT_LOAD_EXC = "Loading options aborted:"; | |
139 void load () { | |
140 // Check it exists (if not it should still be created on exit). | |
141 // Don't bother checking it's not a folder, because it could still be a block or something. | |
142 if (!confDir.exists (fileName)) return; | |
143 | |
144 try { | |
145 IReader reader; | |
146 reader = confDir.makeMTReader (fileName, PRIORITY.LOW_HIGH); | |
147 reader.dataSecCreator = delegate IDataSection(ID id) { | |
148 /* Recognise each defined section, and return null for unrecognised sections. */ | |
149 Options* p = id in subClasses; | |
150 if (p !is null) return *p; | |
151 else return null; | |
152 }; | |
153 reader.read; | |
154 } catch (MTException e) { | |
155 logger.fatal (MT_LOAD_EXC); | |
156 logger.fatal (e.msg); | |
157 throw new optionsLoadException ("Mergetag exception (see above message)"); | |
158 } | |
159 } | |
160 void save () { | |
161 if (!changed) return; // no changes to save | |
162 | |
163 DataSet ds = new DataSet(); | |
164 foreach (id, subOpts; subClasses) ds.sec[id] = subOpts.optionChanges; | |
165 | |
166 // Read locally-stored options | |
167 try { | |
168 IReader reader; | |
169 reader = confDir.makeMTReader (fileName, PRIORITY.HIGH_ONLY, ds); | |
170 reader.dataSecCreator = delegate IDataSection(ID id) { | |
171 return null; // All recognised sections are already in the dataset. | |
172 }; | |
173 reader.read; | |
174 } catch (MTFileIOException) { | |
175 // File either didn't exist or couldn't be opened. | |
176 // Presuming the former, this is not a problem. | |
177 } catch (MTException e) { | |
178 // Log a message and continue, overwriting the file: | |
179 logger.error (MT_LOAD_EXC); | |
180 logger.error (e.msg); | |
181 } | |
182 | |
183 try { | |
184 IWriter writer; | |
185 writer = confDir.makeMTWriter (fileName, ds); | |
186 writer.write(); | |
187 } catch (MTException e) { | |
188 logger.error ("Saving options aborted! Reason:"); | |
189 logger.error (e.msg); | |
190 } | |
191 } | |
192 | |
193 private Logger logger; | |
194 static this() { | |
195 logger = Log.getLogger ("mde.options"); | |
196 } | |
197 } | |
198 //END Static | |
199 | |
200 | |
201 //BEGIN Non-static | |
202 /** Set option symbol of an Options sub-class to val. | |
203 * | |
204 * Due to the way options are handled generically, string IDs must be used to access the options | |
205 * via hash-maps, which is a little slower than direct access but necessary since the option | |
206 * must be changed in two separate places. */ | |
207 void set(T) (char[] symbol, T val) { | |
208 static if (!TIsIn!(T,TYPES)) | |
209 static assert (false, "Options.set does not currently support type "~T.stringof); | |
210 | |
211 mixin (`alias opts`~T.stringof~` optsVars;`); | |
212 | |
213 changed = true; // something got set (don't bother checking this isn't what it already was) | |
214 | |
215 try { | |
216 *(optsVars[cast(ID) symbol]) = val; | |
217 optionChanges.set!(T) (cast(ID) symbol, val); | |
218 } catch (ArrayBoundsException) { | |
219 // log and ignore: | |
220 logger.error ("Options.set: unkw!"); | |
221 } | |
222 } | |
223 | |
224 protected { | |
225 OptionChanges optionChanges; // all changes to options (for saving) | |
226 | |
227 // The "pointer lists": | |
228 mixin (PLists!(TYPES)); | |
229 } | |
66 | 230 |
67 //BEGIN Mergetag loading/saving code | 231 //BEGIN Mergetag loading/saving code |
68 void addTag (char[] tp, ID id, char[] dt) { | 232 void addTag (char[] tp, ID id, char[] dt) { |
69 if (tp == "bool") { | 233 mixin(addTagMixin!(TYPES).addTagMixin); |
70 bool** p = id in optsBool; | |
71 if (p !is null) **p = parseTo!(bool) (dt); | |
72 } else if (tp == "char[]") { | |
73 char[]** p = id in optsCharA; | |
74 if (p !is null) **p = parseTo!(char[]) (dt); | |
75 } else if (tp == "double") { | |
76 double** p = id in optsDouble; | |
77 if (p !is null) **p = parseTo!(double) (dt); | |
78 } else if (tp == "int") { | |
79 int** p = id in optsInt; | |
80 if (p !is null) **p = parseTo!(int) (dt); | |
81 } | |
82 } | 234 } |
83 | 235 |
84 void writeAll (ItemDelg dlg) { | 236 void writeAll (ItemDelg dlg) { |
85 foreach (ID id, bool* val; optsBool) dlg ("bool" , id, parseFrom!(bool ) (*val)); | 237 mixin(writeAllMixin!(TYPES)); |
86 foreach (ID id, char[]* val; optsCharA) dlg ("char[]", id, parseFrom!(char[]) (*val)); | |
87 foreach (ID id, double* val; optsDouble)dlg ("double", id, parseFrom!(double) (*val)); | |
88 foreach (ID id, int* val; optsInt) dlg ("int" , id, parseFrom!(int ) (*val)); | |
89 } | 238 } |
90 //END Mergetag loading/saving code | 239 //END Mergetag loading/saving code |
91 | 240 //END Non-static |
92 //BEGIN Static | 241 |
93 /** Add an options sub-class to the list for loading and saving. | 242 |
94 * | 243 //BEGIN Templates: impl & optionsThis |
95 * Call from static this() (before Init calls load()). */ | |
96 static void addOptionsClass (Options c, char[] i) | |
97 in { // Trap a couple of potential coding errors: | |
98 assert (c !is null); // Instance must be created before calling addOptionsClass | |
99 assert (((cast(ID) i) in subClasses) is null); // Don't allow a silent replacement | |
100 } body { | |
101 subClasses[cast(ID) i] = c; | |
102 subClassChanges[cast(ID) i] = new OptionsGeneric; | |
103 } | |
104 | |
105 /** Set option symbol of Options class subClass to val. | |
106 * | |
107 * Due to the way options are handled generically, string IDs must be used to access the options | |
108 * via hash-maps, which is a little slower than direct access but necessary since the option | |
109 * must be changed in two separate places. */ | |
110 private static const ERR_MSG = "Options.setXXX called with incorrect parameters!"; | |
111 static void setBool (char[] subClass, char[] symbol, bool val) { | |
112 changed = true; // something got set (don't bother checking this isn't what it already was) | |
113 | |
114 try { | |
115 *(subClasses[cast(ID) subClass].optsBool[cast(ID) symbol]) = val; | |
116 subClassChanges[cast(ID) subClass].setBool (cast(ID) symbol, val); | |
117 } catch (ArrayBoundsException) { | |
118 // log and ignore: | |
119 logger.error (ERR_MSG); | |
120 } | |
121 } | |
122 static void setInt (char[] subClass, char[] symbol, int val) { | |
123 changed = true; // something got set (don't bother checking this isn't what it already was) | |
124 | |
125 try { | |
126 *(subClasses[cast(ID) subClass].optsInt[cast(ID) symbol]) = val; | |
127 subClassChanges[cast(ID) subClass].setInt (cast(ID) symbol, val); | |
128 } catch (ArrayBoundsException) { | |
129 // log and ignore: | |
130 logger.error (ERR_MSG); | |
131 } | |
132 } | |
133 static void setDouble (char[] subClass, char[] symbol, double val) { | |
134 changed = true; // something got set (don't bother checking this isn't what it already was) | |
135 | |
136 try { | |
137 *(subClasses[cast(ID) subClass].optsDouble[cast(ID) symbol]) = val; | |
138 subClassChanges[cast(ID) subClass].setDouble (cast(ID) symbol, val); | |
139 } catch (ArrayBoundsException) { | |
140 // log and ignore: | |
141 logger.error (ERR_MSG); | |
142 } | |
143 } | |
144 static void setCharA (char[] subClass, char[] symbol, char[] val) { | |
145 changed = true; // something got set (don't bother checking this isn't what it already was) | |
146 | |
147 try { | |
148 *(subClasses[cast(ID) subClass].optsCharA[cast(ID) symbol]) = val; | |
149 subClassChanges[cast(ID) subClass].setCharA (cast(ID) symbol, val); | |
150 } catch (ArrayBoundsException) { | |
151 // log and ignore: | |
152 logger.error (ERR_MSG); | |
153 } | |
154 } | |
155 | |
156 // Track all sections for saving/loading/other generic handling. | |
157 static Options[ID] subClasses; | |
158 static OptionsGeneric[ID] subClassChanges; | |
159 static bool changed = false; // any changes at all, i.e. do we need to save? | |
160 | |
161 /* Load/save options from file. | |
162 * | |
163 * If the file doesn't exist, no reading is attempted (options are left at default values). | |
164 */ | |
165 private static const fileName = "options"; | |
166 private static const MT_LOAD_EXC = "Loading options aborted:"; | |
167 static void load () { | |
168 // Check it exists (if not it should still be created on exit). | |
169 // Don't bother checking it's not a folder, because it could still be a block or something. | |
170 if (!confDir.exists (fileName)) return; | |
171 | |
172 try { | |
173 IReader reader; | |
174 reader = confDir.makeMTReader (fileName, PRIORITY.LOW_HIGH); | |
175 reader.dataSecCreator = delegate IDataSection(ID id) { | |
176 /* Recognise each defined section, and return null for unrecognised sections. */ | |
177 Options* p = id in subClasses; | |
178 if (p !is null) return *p; | |
179 else return null; | |
180 }; | |
181 reader.read; | |
182 } catch (MTException e) { | |
183 logger.fatal (MT_LOAD_EXC); | |
184 logger.fatal (e.msg); | |
185 throw new optionsLoadException ("Mergetag exception (see above message)"); | |
186 } | |
187 } | |
188 static void save () { | |
189 if (!changed) return; // no changes to save | |
190 | |
191 DataSet ds = new DataSet(); | |
192 foreach (id, sec; subClassChanges) ds.sec[id] = sec; | |
193 | |
194 // Read locally-stored options | |
195 try { | |
196 IReader reader; | |
197 reader = confDir.makeMTReader (fileName, PRIORITY.HIGH_ONLY, ds); | |
198 reader.dataSecCreator = delegate IDataSection(ID id) { | |
199 return null; // All recognised sections are already in the dataset. | |
200 }; | |
201 reader.read; | |
202 } catch (MTFileIOException) { | |
203 // File either didn't exist or couldn't be opened. | |
204 // Presuming the former, this is not a problem. | |
205 } catch (MTException e) { | |
206 // Log a message and continue, overwriting the file: | |
207 logger.error (MT_LOAD_EXC); | |
208 logger.error (e.msg); | |
209 } | |
210 | |
211 try { | |
212 IWriter writer; | |
213 writer = confDir.makeMTWriter (fileName, ds); | |
214 writer.write(); | |
215 } catch (MTException e) { | |
216 logger.error ("Saving options aborted! Reason:"); | |
217 logger.error (e.msg); | |
218 } | |
219 } | |
220 | |
221 private static Logger logger; | |
222 static this() { | |
223 logger = Log.getLogger ("mde.options"); | |
224 } | |
225 //END Static | |
226 | |
227 //BEGIN Templates | |
228 private { | 244 private { |
229 // Return index of first comma, or halts if not found. | 245 // Return index of first comma, or halts if not found. |
230 template cIndex(char[] A) { | 246 template cIndex(char[] A) { |
231 static if (A.length == 0) | 247 static if (A.length == 0) |
232 static assert (false, "Error in implementation"); | 248 static assert (false, "Error in implementation"); |
282 else static if (A[0] == ' ') | 298 else static if (A[0] == ' ') |
283 const char[] listOrNull = listOrNull!(A[1..$]); | 299 const char[] listOrNull = listOrNull!(A[1..$]); |
284 else | 300 else |
285 const char[] listOrNull = "["~A~"]"; | 301 const char[] listOrNull = "["~A~"]"; |
286 } | 302 } |
303 // for recursing on TYPES | |
304 template optionsThisInternal(char[] A, B...) { | |
305 static if (B.length) { | |
306 const char[] optionsThisInternal = `opts`~TName!(B[0])~` = `~listOrNull!(sTC!(aaVars!(parseT!(B[0].stringof,A))))~";\n" ~ optionsThisInternal!(A,B[1..$]); | |
307 } else | |
308 const char[] optionsThisInternal = ``; | |
309 } | |
287 } protected { | 310 } protected { |
288 /** Produces the implementation code to go in the constuctor. */ | 311 /** Produces the implementation code to go in the constuctor. */ |
289 template aaDefs(char[] A) { | 312 template optionsThis(char[] A) { |
290 const char[] aaDefs = | 313 const char[] optionsThis = |
291 "optsBool = " ~ listOrNull!(sTC!(aaVars!(parseT!("bool" , A)))) ~ ";\n" ~ | 314 "optionChanges = new OptionChanges;\n" ~ |
292 "optsInt = " ~ listOrNull!(sTC!(aaVars!(parseT!("int" , A)))) ~ ";\n" ~ | 315 optionsThisInternal!(A,TYPES); |
293 "optsDouble = "~ listOrNull!(sTC!(aaVars!(parseT!("double", A)))) ~ ";\n" ~ | 316 } |
294 "optsCharA = " ~ listOrNull!(sTC!(aaVars!(parseT!("char[]", A)))) ~ ";\n"; | 317 /+ Needs too many custom parameters to be worth it? Plus makes class less readable. |
295 } | 318 /** Produces the implementation code to go in the static constuctor. */ |
296 /+/** Produces the implementation code to go in the static constuctor. */ | |
297 template optClassAdd(char[] symb) { | 319 template optClassAdd(char[] symb) { |
298 const char[] optClassAdd = symb ~ "=new "~classinfo(this).name~";\n";//Options.addOptionsClass("~symb~", );\n"; | 320 const char[] optClassAdd = symb ~ "=new "~classinfo(this).name~";\n";//Options.addOptionsClass("~symb~", );\n"; |
299 }+/ | 321 }+/ |
300 /** mixin impl("type symbol[, symbol[...]];[type symbol[...];][...]") | 322 /** mixin impl("type symbol[, symbol[...]];[type symbol[...];][...]") |
301 * | 323 * |
304 * mixin (impl ("bool a, b; int i;")); | 326 * mixin (impl ("bool a, b; int i;")); |
305 * --- | 327 * --- |
306 * | 328 * |
307 * In case this() needs to be customized, mixin(impl!(A)) is equivalent to: | 329 * In case this() needs to be customized, mixin(impl!(A)) is equivalent to: |
308 * --- | 330 * --- |
309 * mixin (A~"\nthis(){\n"~aaDefs!(A)~"}"); | 331 * mixin (A~` |
332 this () { | |
333 `~optionsThis!(A)~` | |
334 }`); | |
310 * --- | 335 * --- |
311 * | 336 * |
312 * Notes: Only use space as whitespace (no new-lines or tabs). Make sure to add a trailing | 337 * Notes: Only use space as whitespace (no new-lines or tabs). Make sure to add a trailing |
313 * semi-colon (;) or you'll get told off! :D | 338 * semi-colon (;) or you'll get told off! :D |
314 * | |
315 * In general errors aren't reported well. Trial with pragma (msg, impl!(...)); if | 339 * In general errors aren't reported well. Trial with pragma (msg, impl!(...)); if |
316 * necessary. | 340 * necessary. |
317 * | 341 * |
318 * Extending: mixins could also be used for the static this() {...} or even the whole | 342 * Extending: mixins could also be used for the static this() {...} or even the whole |
319 * class, but doing so would rather decrease readability of any implementation. */ | 343 * class, but doing so would rather decrease readability of any implementation. */ |
320 template impl(char[] A /+, char[] symb+/) { | 344 template impl(char[] A /+, char[] symb+/) { |
321 const char[] impl = A~"\nthis(){\n"~aaDefs!(A)~"}"; | 345 const char[] impl = A~"\nthis(){\n"~optionsThis!(A)~"}"; |
322 // ~"\nstatic this(){\n"~optClassAdd!(symb)~"}" | 346 // ~"\nstatic this(){\n"~optClassAdd!(symb)~"}" |
323 } | 347 } |
324 } | 348 } |
325 /+/** mixin impl("type symbol[, symbol[...]];[type symbol[...];][...]") | 349 //END Templates: impl & optionsThis |
326 * | 350 } |
327 * E.g. | 351 |
328 * --- | 352 /* Special class to store all locally changed options, whatever the section. */ |
329 * mixin (impl ("bool a, b; int i;")); | 353 class OptionChanges : Options { |
330 * --- | 354 //BEGIN Templates |
331 * The parser isn't highly accurate. */ | 355 private { |
332 // Try using templates instead? See std.metastrings | 356 template Vars(A...) { |
333 static char[] impl (char[] A) { | 357 static if (A.length) { |
334 char[] bools; | 358 const char[] Vars = A[0].stringof~`[] `~TName!(A[0])~`s;` ~ Vars!(A[1..$]); |
335 char[] ints; | 359 } else |
336 | 360 const char[] Vars = ``; |
337 while (A.length) { | 361 } |
338 // Trim whitespace | 362 |
339 { | 363 // For addTag; different to Options.addTag(). |
340 size_t i = 0; | 364 // Reverse priority: only load symbols not currently existing |
341 while (i < A.length && (A[i] == ' ' || (A[i] >= 9u && A[i] <= 0xD))) | 365 template addTagMixin(T, A...) { |
342 ++i; | 366 const char[] ifBlock = `if (tp == "`~T.stringof~`") { |
343 A = A[i..$]; | 367 if ((id in opts`~TName!(T)~`) is null) { |
344 } | 368 `~TName!(T)~`s ~= parseTo!(`~T.stringof~`) (dt); |
345 if (A.length == 0) break; | 369 opts`~TName!(T)~`[id] = &`~TName!(T)~`s[$-1]; |
346 | 370 } |
347 char[] type; | 371 }`; |
348 for (size_t i = 0; i < A.length; ++i) { | 372 static if (A.length) |
349 if (A[i] == ' ' || (A[i] >= 9u && A[i] <= 0xD)) { | 373 const char[] addTagMixin = ifBlock~` else `~addTagMixin!(A).addTagMixin; |
350 type = A[0..i]; | 374 else |
351 A = A[i+1..$]; | 375 const char[] addTagMixin = ifBlock; |
352 break; | 376 } |
353 } | 377 |
354 } | 378 } |
355 | |
356 char[] symbols; | |
357 for (size_t i = 0; i < A.length; ++i) { | |
358 if (A[i] == ';') { | |
359 symbols = A[0..i]; | |
360 A = A[i+1..$]; | |
361 break; | |
362 } | |
363 } | |
364 | |
365 if (type == "bool") { | |
366 if (bools.length) | |
367 bools = bools ~ "," ~ symbols; | |
368 else | |
369 bools = symbols; | |
370 } | |
371 else if (type == "int") { | |
372 if (ints.length) | |
373 ints = ints ~ "," ~ symbols; | |
374 else | |
375 ints = symbols; | |
376 } | |
377 else { | |
378 // Unfortunately, we cannot output non-const strings (even though func is compile-time) | |
379 // We also cannot use pragma(msg) because the message gets printed even if the code isn't run. | |
380 //pragma(msg, "Warning: impl failed to parse whole input string"); | |
381 // Cannot use Cout / logger either. | |
382 break; | |
383 } | |
384 } | |
385 | |
386 char[] ret; | |
387 if (bools.length) | |
388 ret = "bool "~bools~";\n"; | |
389 if (ints.length) | |
390 ret = ret ~ "int "~ints~";\n"; | |
391 | |
392 | |
393 | |
394 return ret; | |
395 }+/ | |
396 //END Templates | 379 //END Templates |
397 } | |
398 | |
399 /* Special class to store all locally changed options, whatever the section. */ | |
400 class OptionsGeneric : Options { | |
401 // These store the actual values, but are never accessed directly except when initially added. | 380 // These store the actual values, but are never accessed directly except when initially added. |
402 // optsX store pointers to each item added along with the ID and are used for access. | 381 // optsX store pointers to each item added along with the ID and are used for access. |
403 bool[] bools; | 382 mixin(Vars!(TYPES)); |
404 int[] ints; | |
405 double[] doubles; | |
406 char[][] strings; | |
407 | 383 |
408 this () {} | 384 this () {} |
409 | 385 |
410 void setBool (ID id, bool x) { | 386 void set(T) (ID id, T x) { |
411 bool** p = id in optsBool; | 387 static if (!TIsIn!(T,TYPES)) |
388 static assert (false, "OptionChanges.set does not currently support type "~T.stringof); | |
389 | |
390 mixin (`alias opts`~T.stringof~` optsVars;`); | |
391 mixin (`alias `~T.stringof~`s vars;`); | |
392 | |
393 T** p = id in optsVars; | |
412 if (p !is null) **p = x; | 394 if (p !is null) **p = x; |
413 else { | 395 else { |
414 bools ~= x; | 396 vars ~= x; |
415 optsBool[id] = &bools[$-1]; | 397 optsVars[id] = &vars[$-1]; |
416 } | |
417 } | |
418 void setInt (ID id, int x) { | |
419 int** p = id in optsInt; | |
420 if (p !is null) **p = x; | |
421 else { | |
422 ints ~= x; | |
423 optsInt[id] = &ints[$-1]; | |
424 } | |
425 } | |
426 void setDouble (ID id, double x) { | |
427 double** p = id in optsDouble; | |
428 if (p !is null) **p = x; | |
429 else { | |
430 doubles ~= x; | |
431 optsDouble[id] = &doubles[$-1]; | |
432 } | |
433 } | |
434 void setCharA (ID id, char[] x) { | |
435 char[]** p = id in optsCharA; | |
436 if (p !is null) **p = x; | |
437 else { | |
438 strings ~= x; | |
439 optsCharA[id] = &strings[$-1]; | |
440 } | 398 } |
441 } | 399 } |
442 | 400 |
443 //BEGIN Mergetag loading/saving code | 401 //BEGIN Mergetag loading/saving code |
444 // Reverse priority: only load symbols not currently existing | 402 // Reverse priority: only load symbols not currently existing |
445 void addTag (char[] tp, ID id, char[] dt) { | 403 void addTag (char[] tp, ID id, char[] dt) { |
446 if (tp == "bool") { | 404 mixin (addTagMixin!(TYPES).addTagMixin); |
447 if ((id in optsBool) is null) { | |
448 bools ~= parseTo!(bool) (dt); | |
449 optsBool[id] = &bools[$-1]; | |
450 } | |
451 } else if (tp == "char[]") { | |
452 if ((id in optsCharA) is null) { | |
453 strings ~= parseTo!(char[]) (dt); | |
454 optsCharA[id] = &strings[$-1]; | |
455 } | |
456 char[]** p = id in optsCharA; | |
457 if (p !is null) **p = parseTo!(char[]) (dt); | |
458 } else if (tp == "int") { | |
459 if ((id in optsInt) is null) { | |
460 ints ~= parseTo!(int) (dt); | |
461 optsInt[id] = &ints[$-1]; | |
462 } | |
463 } | |
464 } | 405 } |
465 //END Mergetag loading/saving code | 406 //END Mergetag loading/saving code |
466 } | 407 } |
467 | 408 |
468 /* NOTE: Options sub-classes are expected to use a template to ease inserting contents and | 409 /* NOTE: Options sub-classes are expected to use a template to ease inserting contents and |