comparison mde/setup/Init.d @ 63:66d555da083e

Moved many modules/packages to better reflect usage.
author Diggory Hardy <diggory.hardy@gmail.com>
date Fri, 27 Jun 2008 18:35:33 +0100
parents mde/scheduler/Init.d@f9f5e04f20b2
children cc3763817b8a
comparison
equal deleted inserted replaced
62:960206198cbd 63:66d555da083e
1 /* LICENSE BLOCK
2 Part of mde: a Modular D game-oriented Engine
3 Copyright © 2007-2008 Diggory Hardy
4
5 This program is free software: you can redistribute it and/or modify it under the terms
6 of the GNU General Public License as published by the Free Software Foundation, either
7 version 2 of the License, or (at your option) any later version.
8
9 This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
10 without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
11 See the GNU General Public License for more details.
12
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/>. */
15
16 /**************************************************************************************************
17 * Initialisation setup and exit cleanup module.
18 *
19 * This module provides an infrastructure for handling much of the initialisation and
20 * deinitialisation of the program. It does not, however, provide much of the (de)initialisation
21 * code; with the exception of that for the logger.
22 *************************************************************************************************/
23 module mde.setup.Init;
24
25 import mde.setup.init2; // This module is responsible for setting up some init functions.
26 import mde.setup.initFunctions;
27 import mde.setup.exception;
28
29 import mde.lookup.Options;
30 import paths = mde.setup.paths;
31 import mde.exception;
32
33 // tango imports
34 import tango.core.Thread;
35 import tango.core.Exception;
36 import tango.stdc.stringz : fromStringz;
37 import tango.io.Console; // for printing command-line usage
38 import TimeStamp = tango.text.convert.TimeStamp, tango.time.WallClock; // output date in log file
39 import tango.util.Arguments;
40 import tango.util.log.Log : Log, Logger;
41 import tango.util.log.ConsoleAppender : ConsoleAppender;
42
43 //version = SwitchAppender;
44 version (SwitchAppender) { // My own variation, currently just a test
45 import tango.util.log.SwitchingFileAppender : SwitchingFileAppender;
46 } else {
47 import tango.util.log.RollingFileAppender : RollingFileAppender;
48 }
49
50 // Derelict imports
51 import derelict.opengl.gl;
52 import derelict.sdl.sdl;
53 //import derelict.sdl.image; Was to be used... for now isn't
54 import derelict.freetype.ft;
55 import derelict.util.exception;
56
57 /**
58 * Static CTOR
59 *
60 * This should handle a minimal amount of functionality where useful. For instance, configuring the
61 * logger here and not in Init allows unittests to use the logger.
62 */
63 static this()
64 {
65 Logger root = Log.getRootLogger();
66 // Set the level here, but set it again once options have been loaded:
67 debug root.setLevel(root.Level.Trace);
68 else root.setLevel(root.Level.Info);
69 // Temporarily log to the console (until we've found paths and loaded options):
70 root.addAppender(new ConsoleAppender);
71 }
72 static ~this()
73 {
74 }
75
76 /**
77 * Init class
78 *
79 * A scope class created at beginning of the program and destroyed at the end; thus the CTOR
80 * handles program initialisation and the DTOR handles program cleanup.
81 */
82 scope class Init
83 {
84 //private bool failure = false; // set true on failure during init, so that clean
85 private static Logger logger;
86 static this() {
87 logger = Log.getLogger ("mde.setup.Init");
88 }
89
90 /** this() − initialisation
91 *
92 * Runs general initialisation code, in a threaded manner where this isn't difficult.
93 * If any init fails, cleanup is still handled by ~this().
94 *
95 * Init order: 1. Pre-init (loads components needed by most init functions). 2. Dynamic library
96 * loading (load any dynamic libraries first, so that if loading fails nothing else need be
97 * done). 3. Init functions (threaded functions handling the rest of initialisation).
98 */
99 /* In a single-threaded function this could be done with:
100 * scope(failure) cleanup;
101 * This won't work with a threaded init function since any threads completing succesfully will
102 * not clean-up, and a fixed list of clean-up functions cannot be used since clean-up functions
103 * must not run where the initialisation functions have failed.
104 * Hence a list of clean-up functions is built similarly to scope(failure) --- see addCleanupFct.
105 */
106 this(char[][] cmdArgs)
107 {
108 debug logger.trace ("Init: starting");
109
110 //BEGIN Pre-init (stage init0)
111 //FIXME: warn on invalid arguments, including base-path on non-Windows
112 // But Arguments doesn't support this (in tango 0.99.6 and in r3563).
113 Arguments args;
114 try {
115 args = new Arguments();
116 args.define("base-path").parameters(1);
117 args.define("data-path").parameters(1,-1);
118 args.define("conf-path").parameters(1,-1);
119 args.define("paths");
120 args.define("q").aliases(["quick-exit"]);
121 args.define("help").aliases(["h"]);
122 args.parse(cmdArgs);
123 if (args.contains("help")) // lazy way to print help
124 throw new InitException ("Help requested"); // and stop
125 } catch (Exception e) {
126 printUsage(cmdArgs[0]);
127 throw new InitException ("Parsing arguments failed: "~e.msg);
128 }
129
130 // Find/create paths:
131 try {
132 if (args.contains("data-path"))
133 paths.extraDataPath = args["data-path"];
134 if (args.contains("conf-path"))
135 paths.extraConfPath = args["conf-path"];
136 if (args.contains("base-path"))
137 paths.resolvePaths (args["base-path"]);
138 else
139 paths.resolvePaths();
140 } catch (Exception e) {
141 throw new InitException ("Resolving paths failed: " ~ e.msg);
142 }
143 if (args.contains("paths")) {
144 paths.mdeDirectory.printPaths;
145 throw new InitException ("Paths requested"); // lazy way to stop
146 }
147 debug logger.trace ("Init: resolved paths successfully");
148
149 /* Load options now. Don't load in a thread since:
150 * Loading should be fast & most work is probably disk access
151 * It enables logging to be controlled by options
152 * It's a really good idea to let the options apply to all other loading */
153 try {
154 Options.load();
155 } catch (optionsLoadException e) {
156 throw new InitException ("Loading options failed: " ~ e.msg);
157 }
158 debug logger.trace ("Init: loaded options successfully");
159
160 // Set up the logger:
161 Logger root;
162 try {
163 enum LOG {
164 LEVEL = 0xF, // mask for log level
165 CONSOLE = 0x1000, // log to console?
166 ROLLFILE= 0x2000 // use Rolling/Switching File Appender?
167 }
168
169 // Where logging is done to is determined at compile-time, currently just via static ifs.
170 root = Log.getRootLogger();
171 root.clearAppenders; // we may no longer want to log to the console
172
173 // Now re-set the logging level, using the value from the config file:
174 root.setLevel (cast(Log.Level) (miscOpts.logOptions & LOG.LEVEL), true);
175
176 // Log to a file (first appender so root seperator messages don't show on console):
177 if (miscOpts.logOptions & LOG.ROLLFILE) {
178 version (SwitchAppender) {
179 root.addAppender (new SwitchingFileAppender (paths.logDir~"/log-.txt", 5));
180 } else {
181 // Use 2 log files with a maximum size of 1 MB:
182 root.addAppender (new RollingFileAppender (paths.logDir~"/log-.txt", 2, 1024*1024));
183 root.info (""); // some kind of separation between runs
184 root.info ("");
185 }
186 } else if (!(miscOpts.logOptions & LOG.CONSOLE)) {
187 // make sure at least one logger is enabled
188 Options.setInt ("misc", "logOptions", miscOpts.logOptions | LOG.CONSOLE);
189 }
190 if (miscOpts.logOptions & LOG.CONSOLE) { // Log to the console
191 root.addAppender(new ConsoleAppender);
192 }
193 logger.info ("Starting mde [no version] on " ~ TimeStamp.toString(WallClock.now));
194 } catch (Exception e) {
195 // Presumably it was only adding a file appender which failed; set up a new console
196 // logger and if that fails let the exception kill the program.
197 root.clearAppenders;
198 root.addAppender (new ConsoleAppender);
199 logger.warn ("Exception while setting up the logger; logging to the console instead.");
200 }
201
202 // a debugging option:
203 imde.run = !args.contains("q") && !miscOpts.exitImmediately;
204 debug logger.trace ("Init: applied pre-init options");
205
206 //BEGIN Load dynamic libraries
207 /* Can be done by init functions but much neater to do here.
208 * Also means that init functions aren't run if a library fails to load. */
209 try {
210 DerelictSDL.load();
211 // SDLImage was going to be used... for now it isn't because of gl texturing problems
212 //DerelictSDLImage.load();
213 DerelictGL.load();
214 DerelictFT.load();
215 } catch (DerelictException de) {
216 logger.fatal ("Loading dynamic library failed:");
217 logger.fatal (de.msg);
218
219 throw new InitException ("Loading dynamic libraries failed (see above).");
220 }
221 debug logger.trace ("Init: dynamic libraries loaded");
222 //END Load dynamic libraries
223 //END Pre-init
224
225
226 //BEGIN Init (stages init2, init4)
227 /* Call init functions.
228 *
229 * Current method is to try using threads, and on failure assume no threads were actually
230 * created and run functions in a non-threaded manner. */
231
232 try {
233 if (runStageThreaded (init)) runStageForward (init);
234 }
235 catch (InitStageException) { // This init stage failed.
236 // FIXME: check DTOR still runs
237 throw new InitException ("An init function failed (see above message(s))");
238 }
239 //END Init
240
241 debug logger.trace ("Init: done");
242 }
243
244 /** DTOR - runs cleanup functions. */
245 ~this()
246 {
247 debug logger.trace ("Cleanup: starting");
248
249 Options.save(); // save options... do so here for now
250
251 // General cleanup:
252 try {
253 if (runStageThreaded (cleanup)) runStageReverse (cleanup);
254 }
255 catch (InitStageException) {
256 // Nothing else to do but report:
257 logger.error ("One or more cleanup functions failed!");
258 }
259
260 debug logger.trace ("Cleanup: done");
261 }
262
263
264 //BEGIN runStage...
265 private static {
266 /* The following three functions, runStage*, each run all functions in a stage in some order,
267 * catching any exceptions thrown by the functions (although this isn't guaranteed for threads),
268 * and throw an InitStageException on initFailure. */
269
270 const LOG_IF_MSG = "Init function ";
271 const LOG_CF_MSG = "Cleanup function ";
272 const LOG_F_START = " - running";
273 const LOG_F_END = " - completed";
274 const LOG_F_BAD = " - failed";
275 const LOG_F_FAIL = " - failed: ";
276 /* Runs all functions consecutively, first-to-last.
277 * If any function fails, halts immediately. */
278 void runStageForward (InitStage s) {
279 foreach (func; s.funcs) {
280 if (initFailure) break;
281 try {
282 debug logger.trace (LOG_IF_MSG ~ func.name ~ LOG_F_START);
283 func.func();
284 debug logger.trace (LOG_IF_MSG ~ func.name ~ (initFailure ? LOG_F_BAD : LOG_F_END));
285 } catch (Exception e) {
286 logger.fatal (LOG_IF_MSG ~ func.name ~ LOG_F_FAIL ~
287 ((e.msg is null || e.msg == "") ? "(no failure message)" : e.msg) );
288
289 setInitFailure();
290 }
291 }
292
293 if (initFailure) throw new InitStageException; // Problem running; abort and cleanup from here.
294 }
295 /* Runs all functions consecutively, last-to-first.
296 * If any function fails, continue until all have been run. */
297 void runStageReverse (InitStage s) {
298 foreach_reverse (func; s.funcs) {
299 try {
300 debug logger.trace (LOG_CF_MSG ~ func.name ~ LOG_F_START);
301 func.func();
302 debug logger.trace (LOG_CF_MSG ~ func.name ~ (initFailure ? LOG_F_BAD : LOG_F_END));
303 } catch (Exception e) {
304 logger.fatal (LOG_CF_MSG ~ func.name ~ LOG_F_FAIL ~
305 ((e.msg is null || e.msg == "") ? "(no failure message)" : e.msg) );
306
307 setInitFailure();
308 }
309 }
310 if (initFailure) throw new InitStageException; // Problem running; abort and cleanup from here.
311 }
312 /* Tries running functions in a threaded way. Returns false if successful, true if not but
313 * functions should be run without threads. */
314 bool runStageThreaded (InitStage s) {
315 if (!miscOpts.useThreads) return true; // Use unthreaded route instead
316
317 ThreadGroup tg;
318 try { // creating/starting threads could fail
319 tg = new ThreadGroup;
320 foreach (func; s.funcs) { // Start all threads
321 debug logger.trace (LOG_IF_MSG ~ func.name ~ LOG_F_START);
322 tg.create(func.func);
323 debug logger.trace (LOG_IF_MSG ~ func.name ~ (initFailure ? LOG_F_BAD : LOG_F_END));
324 }
325 } catch (ThreadException e) { // Problem with threading; try without threads
326 logger.error ("Caught ThreadException while trying to create threads:");
327 logger.error (e.msg);
328 logger.info ("Will disable threads and continue, assuming no threads were created.");
329
330 Options.setBool("misc", "useThreads", false); // Disable threads entirely
331 return true; // Try again without threads
332 }
333
334 /* Wait for all threads to complete.
335 *
336 * If something went wrong, we still need to do this before cleaning up.
337 */
338 foreach (t; tg) {
339 try {
340 t.join (true);
341 } catch (Exception e) {
342 // Relying on catching exceptions thrown by other threads is a bad idea.
343 // Hence all threads should catch their own exceptions and return safely.
344
345 logger.fatal ("Unhandled exception from Init function:");
346 logger.fatal (e.msg);
347
348 setInitFailure (); // abort (but join other threads first)
349 }
350 }
351
352 if (initFailure) throw new InitStageException; // Problem running; abort and cleanup from here.
353 return false; // Done successfully
354 }
355 //END runStage...
356
357 void printUsage (char[] progName) {
358 Cout ("mde [no version]").newline;
359 Cout ("Usage:").newline;
360 Cout (progName ~ ` [options]`).newline;
361 version(Windows)
362 Cout (
363 ` --base-path path Use path as the base (install) path (Windows only). It
364 should contain the "data" directory.`).newline;
365 Cout (
366 ` --data-path path(s) Add path(s) as a potential location for data files.
367 First path argument becomes the preffered location to
368 load data files from.
369 --conf-path path(s) Add path(s) as a potential location for config files.
370 Configuration in the first path given take highest
371 priority.
372 --paths Print all paths found and exit.
373 --quick-exit, -q Exit immediately, without entering main loop.
374 --help, -h Print this message.`).newline;
375 }
376 }
377
378 debug (mdeUnitTest) unittest {
379 /* Fake init and cleanup. Use unittest-specific init and cleanup InitStages to avoid
380 * messing other init/cleanup up. */
381 static InitStage initUT, cleanupUT;
382
383 static bool initialised = false;
384 static void cleanupFunc1 () {
385 initialised = false;
386 }
387 static void cleanupFunc2 () {
388 assert (initialised == true);
389 }
390
391 static void initFunc () {
392 initialised = true;
393 cleanupUT.addFunc (&cleanupFunc1, "UT cleanup 1");
394 cleanupUT.addFunc (&cleanupFunc2, "UT cleanup 2");
395 }
396
397 initUT.addFunc (&initFunc, "UT init");
398
399 runStageForward (initUT);
400 assert (initialised);
401
402 runStageReverse (cleanupUT);
403 assert (!initialised);
404
405 logger.info ("Unittest complete.");
406 }
407 }