Changeset 53:f000d6cd0f74

Show
Ignore:
Timestamp:
06/05/08 12:16:52 (7 months ago)
Author:
Diggory Hardy <diggory.hardy@gmail.com>
branch:
default
Message:

Changes to paths, command line arguments and font LCD rendering.

Use "./" instead of "" as default install dir on windows.
Implemented a command-line argument parser.
Changes to LCD filter/render-mode option handling after testing what actually happens.
Changed some FontTexture? messages and internals.

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • codeDoc/jobs.txt

    r50 r53  
    5353 
    5454Done (for git log message): 
    55 Options: impl template to ease creating Options sub-classes. 
    56 New OptionsFont class (currently only controls render mode and hinting). 
  • data/conf/options.mtt

    r51 r53  
    55<char[]|L10n="en-GB"> 
    66<int|logLevel=1> 
     7<int|logOptions=0x1003> 
    78<double|pollInterval=0.01> 
    89 
    910{font} 
    10 <int|lcdFilter=0
    11 <int|renderMode=0
     11<int|lcdFilter=2
     12<int|renderMode=0x30000
    1213 
    1314{video} 
     
    1718<bool|fullscreen=false> 
    1819<int|screenW=1280> 
    19 <int|windowW=1272
     20<int|windowW=800
    2021<int|screenH=1024> 
    21 <int|windowH=993
     22<int|windowH=600
    2223 
  • mde/Options.d

    r50 r53  
    476476OptionsMisc miscOpts; 
    477477class OptionsMisc : Options { 
    478     mixin (impl!("bool useThreads, exitImmediately; int logLevel; double pollInterval; char[] L10n;")); 
     478    mixin (impl!("bool useThreads, exitImmediately; int logOptions; double pollInterval; char[] L10n;")); 
    479479     
    480480    static this() { 
  • mde/mde.d

    r45 r53  
    3737import tango.util.log.Log : Log, Logger; 
    3838 
    39 int main(
     39int main(char[][] args
    4040{ 
    4141    //BEGIN Initialisation 
    4242    Logger logger = Log.getLogger ("mde.mde"); 
    43     logger.info ("Starting mde..."); 
    4443     
    4544    // Create instances now, so they can be used during init (if necessary) 
     
    4948    scope Init init; 
    5049    try { 
    51         init = new Init();    // initialisation 
     50        init = new Init(args);    // initialisation 
    5251    } catch (InitException e) { 
    5352        logger.fatal ("Initialisation failed: " ~ e.msg); 
  • mde/resource/FontTexture.d

    r51 r53  
    3333import Utf = tango.text.convert.Utf; 
    3434import tango.util.log.Log : Log, Logger; 
    35 import tango.io.Stdout; 
    3635 
    3736private Logger logger; 
     
    7574    { 
    7675        debug scope (failure) 
    77                 logger.warn ("updateCache failed"); 
     76                logger.error ("updateCache failed"); 
    7877         
    7978        if (cache.cacheVer == cacheVer) // Existing cache is up-to-date 
     
    166165        updateCache (face, str, cache); // update if necessary 
    167166        debug scope (failure) 
    168                 logger.warn ("drawTextCache failed"); 
     167                logger.error ("drawTextCache failed"); 
    169168         
    170169        glEnable (GL_TEXTURE_2D); 
     
    199198    void addGlyph (FT_Face face, dchar chr) { 
    200199        debug scope (failure) 
    201                 logger.warn ("FontTexture.addGlyph failed!"); 
     200                logger.error ("FontTexture.addGlyph failed!"); 
    202201         
    203202        auto gi = FT_Get_Char_Index (face, chr); 
     
    209208         
    210209        auto b = g.bitmap; 
    211         if (b.pitch != b.width) { 
    212             char[128] tmp; 
    213             logger.warn (logger.format (tmp, "b.pitch is {}, b.width is {}", b.pitch, b.width)); 
    214             //throw new fontGlyphException ("Unsupported freetype bitmap: b.pitch != b.width"); 
    215         } 
    216210        glPixelStorei(GL_UNPACK_ALIGNMENT, 1); 
    217211        //glPixelStorei (GL_UNPACK_ROW_LENGTH, b.pitch); 
     
    235229        // if here, no existing texture had the room for the glyph so create a new texture 
    236230        // NOTE: check if using more than one texture impacts performance due to texture switching 
    237         logger.info ("Creating a font texture."); 
     231        logger.info ("Creating a new font texture."); 
    238232        tex ~= TexPacker.create(); 
    239233        assert (tex[$-1].addGlyph (ga), "Failed to fit glyph in a new texture but addGlyph didn't throw"); 
     
    244238        ubyte[] buffer; // use our own pointer, since for LCD modes we need to perform a conversion 
    245239        if (b.pixel_mode == FT_Pixel_Mode.FT_PIXEL_MODE_GRAY && b.num_grays == 256) { 
     240            assert (b.pitch == b.width, "Have assumed b.pitch == b.width for gray glyphs."); 
    246241            buffer = b.buffer[0..b.pitch*b.rows]; 
    247242            format = GL_LUMINANCE; 
     
    358353            throw new fontGlyphException ("Glyph too large to fit texture!"); 
    359354         
     355        attr.texID = texID;     // Set now. Possibly reset if new texture is needed. 
     356        if (attr.w == 0) return true;   // 0 sized glyph; x and y are unimportant. 
     357         
    360358        bool cantFitExtraLine = nextYPos + attr.h >= dimH; 
    361359        foreach (ref line; lines) { 
     
    381379        line.yPos = nextYPos; 
    382380        line.height = attr.h * EXTRA_H; 
    383         Stdout.format ("Creating new line of height {}", line.height).newline; 
    384381        line.length = attr.w; 
    385382        size_t i = 0; 
     
    390387        attr.x = 0; // first glyph in the line 
    391388        attr.y = line.yPos; 
    392         attr.texID = texID; 
    393389        return true; 
    394390    } 
  • mde/resource/font.d

    r51 r53  
    7272            } 
    7373             
    74             // Set LCD filtering method (note: FT_Library_SetLcdFilter can still complain with 
    75             // FT_LcdFilter.FT_LCD_FILTER_NONE). 
    76             if (fontOpts.lcdFilter && FT_Library_SetLcdFilter(library, cast(FT_LcdFilter)fontOpts.lcdFilter)) { 
    77                 // If setting failed, leave at default (disabled status). Note: it is disabled by 
    78                 // default because the code isn't compiled in by default, to avoid patents. 
    79                 logger.warn ("Bad/unsupported LCD filter option; disabling LCD filtering."); 
     74            // Set LCD filtering method if LCD rendering is enabled. 
     75            const RMF = FT_LOAD_TARGET_LCD | FT_LOAD_TARGET_LCD_V; 
     76            if (fontOpts.renderMode & RMF && 
     77                FT_Library_SetLcdFilter(library, cast(FT_LcdFilter)fontOpts.lcdFilter)) { 
     78                /* An error occurred, presumably because LCD rendering support 
     79                * is not compiled into the library. */ 
     80                logger.warn ("Bad/unsupported LCD filter option; disabling LCD font rendering."); 
    8081                logger.warn ("Your FreeType 2 library may compiled without support for LCD/sub-pixel rendering."); 
    81                 Options.setInt ("font", "lcdFilter", FT_LcdFilter.FT_LCD_FILTER_NONE); 
    82             } 
    83             const RMF = FT_LOAD_TARGET_MONO | FT_LOAD_TARGET_LCD | FT_LOAD_TARGET_LCD_V; 
    84             if (fontOpts.renderMode & RMF && fontOpts.lcdFilter == 0) { 
    85                 logger.warn ("Using LCD rendering when LCD filtering is disabled has no effect; disabling."); 
     82                 
     83                // Reset the default filter (in case an invalid value was set in config files). 
     84                Options.setInt ("font", "lcdFilter", FT_LcdFilter.FT_LCD_FILTER_DEFAULT); 
     85                 
     86                /* If no support for LCD filtering, then LCD rendering only emulates NORMAL with 3 
     87                 * times wider glyphs. So disable and save the extra work. */ 
    8688                Options.setInt ("font", "renderMode", FT_LOAD_TARGET_NORMAL); 
    8789            } 
  • mde/resource/paths.d

    r47 r53  
    4040import tango.io.Console; 
    4141import tango.io.FilePath; 
    42 import tango.stdc.stdlib; 
    43 import tango.stdc.stringz; 
     42import tango.sys.Environment; 
    4443//import tango.scrapple.sys.win32.Registry;     // Trouble getting this to work 
    4544 
     
    116115    } 
    117116     
     117    /// Print all paths found. 
     118    static void printPaths () { 
     119        Cout ("Data paths found:"); 
     120        dataDir.coutPaths; 
     121        Cout ("\nConf paths found:"); 
     122        confDir.coutPaths; 
     123        Cout ("\nLog file directory:\n\t")(logDir).newline; 
     124    } 
     125     
    118126private: 
    119127    PathView[] getFiles (char[] filename, PRIORITY readOrder) 
     
    166174    } 
    167175     
     176    void coutPaths () { 
     177        if (pathsLen) { 
     178            for (size_t i = 0; i < pathsLen; ++i) 
     179                Cout ("\n\t" ~ paths[i]); 
     180        } else 
     181            Cout ("[none]"); 
     182    } 
     183     
    168184    // Use a static array to store all possible paths with separate length counters. 
    169185    // Lowest priority paths are first. 
     
    186202// FIXME: use tango/sys/Environment.d 
    187203version (linux) { 
    188     void resolvePaths () { 
     204    // base-path not used on posix 
     205    void resolvePaths (char[] = null) { 
    189206        // Home directory: 
    190         char[] HOME = fromStringz (getenv (toStringz ("HOME"))); 
     207        char[] HOME = Environment.get("HOME", "."); 
    191208         
    192209        // Base paths: 
    193210        // Static data (must exist): 
    194         PathView staticPath = findPath (false, "/usr/share/games/mde", "/usr/local/share/games/mde", "data"); 
     211        PathView staticPath = 
     212                findPath (false, "/usr/share/games/mde", "/usr/local/share/games/mde", "data"); 
    195213        // Config (can just use defaults if necessary, so long as we can save afterwards): 
    196214        PathView userPath = findPath (true, HOME~"/.config/mde", HOME~"/.mde"); 
     
    199217        dataDir.addPath (staticPath.toString);      // we know this is valid anyway 
    200218        dataDir.tryPath (userPath.toString ~ DATA); 
     219        if (extraDataPath) dataDir.tryPath (extraDataPath); 
    201220        if (!dataDir.pathsLen) throw new mdeException ("Fatal: no data path found!"); 
    202221         
     
    205224        confDir.tryPath ("/etc/mde"); 
    206225        confDir.tryPath (userPath.toString ~ CONF, true); 
     226        if (extraConfPath) confDir.tryPath (extraConfPath); 
    207227        if (!confDir.pathsLen) throw new mdeException ("Fatal: no conf path found!"); 
    208228         
     
    211231    } 
    212232} else version (Windows) { 
    213     void resolvePaths () { 
     233    void resolvePaths (char[] base = "./") { 
    214234        //FIXME: Get path from registry 
    215235        //FIXME: Get user path (Docs&Settings/USER/Local Settings/Application data/mde) 
     236        //http://www.dsource.org/projects/tango/forums/topic/187 
    216237         
    217238        // Base paths: 
    218         PathView installPath = findPath (false, "");
    219         PathView userPath = findPath (true, "user");   // FIXME: see above 
    220         PathView staticPath = findPath (false, "data"); 
     239        PathView installPath = findPath (false, base)
     240        PathView staticPath = findPath (false, installPath.append("data").toString); 
     241        PathView userPath = findPath (true, installPath.append("user").toString);   // FIXME: see above 
    221242         
    222243        // Static data paths: 
    223244        dataDir.addPath (staticPath.toString);   // we know this is valid anyway 
    224245        dataDir.tryPath (userPath.toString ~ DATA); 
     246        if (extraDataPath) dataDir.tryPath (extraDataPath); 
    225247        if (!dataDir.pathsLen) throw new mdeException ("Fatal: no data path found!"); 
    226248         
    227249        // Configuration paths: 
    228250        confDir.tryPath (staticPath.toString ~ CONF); 
    229         confDir.tryPath ("conf"); 
     251        confDir.tryPath (installPath.append("user").toString); 
    230252        confDir.tryPath (userPath.toString ~ CONF, true); 
     253        if (extraConfPath) confDir.tryPath (extraConfPath); 
    231254        if (!confDir.pathsLen) throw new mdeException ("Fatal: no conf path found!"); 
    232255         
     
    238261} 
    239262 
    240 private: 
     263/// For command line args: these paths are added if non-null, with highest priority. 
     264char[] extraDataPath, extraConfPath; 
     265 
     266private { 
     267    class PathException : mdeException { 
     268        this(char[] msg) { 
     269            super (msg); 
     270        } 
     271    } 
     272     
    241273// The maximum number of paths for any one "directory". 
    242274// There are NO CHECKS that this is not exceeded. 
    243 const MAX_PATHS = 3; 
    244  
    245 /* Try each path in succession, returning the first to exist and be a folder. 
    246 * If none are valid and create is true, will try creating each in turn. 
    247 * If still none are valid, throws. */ 
    248 PathView findPath (bool create, char[][] paths ...) { 
    249     foreach (path; paths) { 
    250         PathView pv = new FilePath (path); 
    251         if (pv.exists && pv.isFolder) return pv;    // got a valid path 
    252     } 
    253     if (create) {   // try to create a folder, using each path in turn until succesful 
    254         foreach (path; paths) { 
    255             FilePath fp = new FilePath (path); 
    256             try { 
    257                 return fp.create; 
    258             } 
    259             catch (Exception e) {} 
    260         } 
    261     } 
     275    const MAX_PATHS = 4; 
     276 
     277    /* Try each path in succession, returning the first to exist and be a folder. 
     278     * If none are valid and create is true, will try creating each in turn. 
     279     * If still none are valid, throws. */ 
     280    PathView findPath (bool create, char[][] paths ...) { 
     281        FilePath[] fps; 
     282        fps.length = paths.length; 
     283        foreach (i,path; paths) { 
     284            FilePath pv = new FilePath (path); 
     285            if (pv.exists && pv.isFolder) return pv;    // got a valid path 
     286            fps[i] = pv; 
     287        } 
     288        if (create) {   // try to create a folder, using each path in turn until succesful 
     289            foreach (fp; fps) { 
     290                try { 
     291                    return fp.create; 
     292                } 
     293                catch (Exception e) {} 
     294            } 
     295        } 
    262296    // no valid path... 
    263     char[] msg = "Unable to find"~(create ? " or create" : "")~" a required path! The following were tried:"; 
    264     foreach (path; paths) msg ~= "  \"" ~ path ~ '\"'; 
    265     throw new mdeException (msg); 
    266 
     297        char[] msg = "Unable to find"~(create ? " or create" : "")~" a required path! The following were tried:"; 
     298        foreach (path; paths) msg ~= "  \"" ~ path ~ '\"'; 
     299        throw new PathException (msg); 
     300   
    267301//END Path resolution 
    268302 
    269 /** A special adapter for reading from multiple mergetag files with the same relative path to an 
    270 * mdeDirectory simultaneously. 
    271 */ 
    272 class mdeReader : IReader 
    273 
    274     private this (PathView[] files, DataSet ds, bool rdHeader) 
    275     in { 
    276         assert (files !is null, "mdeReader.this: files is null"); 
    277     } body { 
    278         if (ds is null) ds = new DataSet; 
    279          
    280         foreach (file; files) { 
    281             IReader r = makeReader (file, ds, rdHeader); 
     303    /** A special adapter for reading from multiple mergetag files with the same relative path to an 
     304     * mdeDirectory simultaneously. 
     305     */ 
     306    class mdeReader : IReader 
     307   
     308        private this (PathView[] files, DataSet ds, bool rdHeader) 
     309        in { 
     310            assert (files !is null, "mdeReader.this: files is null"); 
     311        } body { 
     312            if (ds is null) ds = new DataSet; 
     313         
     314            foreach (file; files) { 
     315                IReader r = makeReader (file, ds, rdHeader); 
    282316             
    283             readers[readersLen++] = r; 
    284        
    285    
    286      
    287     DataSet dataset () {                /// Get the DataSet 
    288         return readers[0].dataset;      // all readers share the same dataset 
    289    
    290     void dataset (DataSet ds) {         /// Set the DataSet 
    291         for (uint i = 0; i < readersLen; ++i) readers[i].dataset (ds); 
    292    
    293      
    294     void dataSecCreator (IDataSection delegate (ID) dsC) {  /// Set the dataSecCreator 
    295         for (uint i = 0; i < readersLen; ++i) readers[i].dataSecCreator = dsC; 
    296    
     317                readers[readersLen++] = r; 
     318           
     319       
     320     
     321        DataSet dataset () {                /// Get the DataSet 
     322            return readers[0].dataset;      // all readers share the same dataset 
     323       
     324        void dataset (DataSet ds) {         /// Set the DataSet 
     325            for (uint i = 0; i < readersLen; ++i) readers[i].dataset (ds); 
     326       
     327     
     328        void dataSecCreator (IDataSection delegate (ID) dsC) {  /// Set the dataSecCreator 
     329            for (uint i = 0; i < readersLen; ++i) readers[i].dataSecCreator = dsC; 
     330       
    297331     
    298332    /** Get identifiers for all sections. 
    299    
    300     * Note: the identifiers from all sections in all files are just strung together, starting with 
    301     * the highest-priority file. */ 
     333   
     334    * Note: the identifiers from all sections in all files are just strung together, starting with 
     335    * the highest-priority file. */ 
    302336    ID[] getSectionNames () { 
    303337        ID[] names; 
    304338        for (int i = readersLen-1; i >= 0; --i) names ~= readers[i].getSectionNames; 
    305339        return names; 
    306     } 
    307     void read () {                      /// Commence reading 
    308         for (uint i = 0; i < readersLen; ++i) readers[i].read(); 
    309     } 
    310     void read (ID[] secSet) {           /// ditto 
    311         for (uint i = 0; i < readersLen; ++i) readers[i].read(secSet); 
    312     } 
    313     void read (View!(ID) secSet) {      /// ditto 
    314         for (uint i = 0; i < readersLen; ++i) readers[i].read(secSet); 
    315     } 
    316          
    317     private: 
    318     IReader[MAX_PATHS] readers; 
    319     ubyte readersLen = 0; 
    320      
    321     PRIORITY rdOrder; 
     340            } 
     341            void read () {                      /// Commence reading 
     342                for (uint i = 0; i < readersLen; ++i) readers[i].read(); 
     343            } 
     344            void read (ID[] secSet) {           /// ditto 
     345                for (uint i = 0; i < readersLen; ++i) readers[i].read(secSet); 
     346            } 
     347            void read (View!(ID) secSet) {      /// ditto 
     348                for (uint i = 0; i < readersLen; ++i) readers[i].read(secSet); 
     349            } 
     350         
     351        private: 
     352            IReader[MAX_PATHS] readers; 
     353            ubyte readersLen = 0; 
     354     
     355            PRIORITY rdOrder; 
     356    } 
    322357} 
  • mde/scheduler/Init.d

    r45 r53  
    3535import tango.core.Exception; 
    3636import tango.stdc.stringz : fromStringz; 
     37import tango.io.Console;    // for printing command-line usage 
     38import TimeStamp = tango.text.convert.TimeStamp, tango.time.WallClock;  // output date in log file 
     39import tango.util.Arguments; 
    3740import tango.util.log.Log : Log, Logger; 
    3841import tango.util.log.ConsoleAppender : ConsoleAppender; 
     
    5962static this() 
    6063{ 
    61     // Find/create paths: 
    62     try { 
    63         paths.resolvePaths(); 
    64     } catch (Exception e) { 
    65         // NOTE: an exception thrown here cannot be caught by main()! 
    66         throw new InitException ("Resolving paths failed: " ~ e.msg); 
    67     } 
    68      
    69     // Set up the logger: 
    70     { 
    71         // Where logging is done to is determined at compile-time, currently just via static ifs. 
    72         Logger root = Log.getRootLogger(); 
    73          
    74         static if (true ) { // Log to files (first appender so root seperator messages don't show on console) 
    75             version (SwitchAppender) { 
    76                 root.addAppender (new SwitchingFileAppender (paths.logDir~"/log-.txt", 5)); 
    77             } else { 
    78                 // Use 2 log files with a maximum size of 1 MB: 
    79                 root.addAppender (new RollingFileAppender (paths.logDir~"/log-.txt", 2, 1024*1024)); 
    80                 root.info (""); // some kind of separation between runs 
    81                 root.info (""); 
    82             } 
    83         } 
    84         static if (true ) { // Log to the console 
    85             root.addAppender(new ConsoleAppender); 
    86         } 
    87          
    88         // Set the level here, but set it again once options have been loaded: 
    89         debug root.setLevel(root.Level.Trace); 
    90         else root.setLevel(root.Level.Info); 
    91     } 
     64    Logger root = Log.getRootLogger(); 
     65    // Set the level here, but set it again once options have been loaded: 
     66    debug root.setLevel(root.Level.Trace); 
     67    else root.setLevel(root.Level.Info); 
     68    // Temporarily log to the console (until we've found paths and loaded options): 
     69    root.addAppender(new ConsoleAppender); 
    9270} 
    9371static ~this() 
     
    125103    * Hence a list of clean-up functions is built similarly to scope(failure) --- see addCleanupFct. 
    126104    */ 
    127     this(
     105    this(char[][] cmdArgs
    128106    { 
    129107        debug logger.trace ("Init: starting"); 
    130108         
    131109        //BEGIN Pre-init (stage init0) 
     110        //FIXME: warn on invalid arguments, including base-path on non-Windows 
     111        // But Arguments doesn't support this (in tango 0.99.6 and in r3563). 
     112        Arguments args; 
     113        try { 
     114            args = new Arguments(); 
     115            args.define("base-path").parameters(1); 
     116            args.define("data-path").parameters(1,-1); 
     117            args.define("conf-path").parameters(1,-1); 
     118            args.define("paths"); 
     119            args.define("q").aliases(["quick-exit"]); 
     120            args.define("help").aliases(["h"]); 
     121            args.parse(cmdArgs); 
     122            if (args.contains("help"))  // lazy way to print help 
     123                throw new InitException ("Help requested"); // and stop 
     124        } catch (Exception e) { 
     125            printUsage(cmdArgs[0]); 
     126            throw new InitException ("Parsing arguments failed: "~e.msg); 
     127        } 
     128         
     129    // Find/create paths: 
     130    try { 
     131            if (args.contains("data-path")) 
     132                paths.extraDataPath = args["data-path"]; 
     133            if (args.contains("conf-path")) 
     134                paths.extraConfPath = args["conf-path"]; 
     135            if (args.contains("base-path")) 
     136                paths.resolvePaths (args["base-path"]); 
     137            else 
     138                paths.resolvePaths(); 
     139    } catch (Exception e) { 
     140            throw new InitException ("Resolving paths failed: " ~ e.msg); 
     141    } 
     142        if (args.contains("paths")) { 
     143            paths.mdeDirectory.printPaths; 
     144            throw new InitException ("Paths requested");    // lazy way to stop 
     145        } 
     146        debug logger.trace ("Init: resolved paths successfully"); 
     147     
    132148        /* Load options now. Don't load in a thread since: 
    133         *   Loading should be fast 
    134         *   Most work is probably disk acces
    135         *   It's a really good idea to let the options apply to all other loading. */ 
     149        *   Loading should be fast & most work is probably disk access 
     150        *   It enables logging to be controlled by option
     151        *   It's a really good idea to let the options apply to all other loading */ 
    136152        try { 
    137153            Options.load(); 
     
    139155            throw new InitException ("Loading options failed: " ~ e.msg); 
    140156        } 
    141          
    142         // Now re-set the logging level, using the value from the config file: 
    143         Log.getRootLogger.setLevel (cast(Log.Level) miscOpts.logLevel, true); 
    144         // And set this (debug option): 
    145         imde.run = !miscOpts.exitImmediately; 
    146         //END Pre-init 
    147          
    148         debug logger.trace ("Init: pre-init done"); 
     157        debug logger.trace ("Init: loaded options successfully"); 
     158         
     159    // Set up the logger: 
     160        Logger root; 
     161    try { 
     162            enum LOG { 
     163                LEVEL   = 0x10,     // mask for log level 
     164                CONSOLE = 0x1001,   // log to console? 
     165                ROLLFILE= 0x1002    // use Rolling/Switching File Appender? 
     166            } 
     167             
     168        // Where logging is done to is determined at compile-time, currently just via static ifs. 
     169            root = Log.getRootLogger(); 
     170        root.clearAppenders;    // we may no longer want to log to the console 
     171         
     172            // Now re-set the logging level, using the value from the config file: 
     173            Log.getRootLogger.setLevel (cast(Log.Level) (miscOpts.logOptions & LOG.LEVEL), true); 
     174             
     175            // Log to a file (first appender so root seperator messages don't show on console): 
     176            if (miscOpts.logOptions & LOG.ROLLFILE) { 
     177                version (SwitchAppender) { 
     178                    root.addAppender (new SwitchingFileAppender (paths.logDir~"/log-.txt", 5)); 
     179                } else { 
     180                // Use 2 log files with a maximum size of 1 MB: 
     181                    root.addAppender (new RollingFileAppender (paths.logDir~"/log-.txt", 2, 1024*1024)); 
     182                    root.info (""); // some kind of separation between runs 
     183                    root.info (""); 
     184                } 
     185            } else if (!(miscOpts.logOptions & LOG.CONSOLE)) { 
     186                // make sure at least one logger is enabled 
     187                Options.setInt ("misc", "logOptions", miscOpts.logOptions | LOG.CONSOLE); 
     188            } 
     189            if (miscOpts.logOptions & LOG.CONSOLE) {    // Log to the console 
     190                root.addAppender(new ConsoleAppender); 
     191            } 
     192            logger.info ("Starting mde [no version] on " ~ TimeStamp.toString(WallClock.now)); 
     193        } catch (Exception e) { 
     194            // Presumably it was only adding a file appender which failed; set up a new console 
     195            // logger and if that fails let the exception kill the program. 
     196            root.clearAppenders; 
     197            root.addAppender (new ConsoleAppender); 
     198            logger.warn ("Exception while setting up the logger; logging to the console instead."); 
     199        } 
     200         
     201        // a debugging option: 
     202        imde.run = !args.contains("q") && !miscOpts.exitImmediately; 
     203        debug logger.trace ("Init: applied pre-init options"); 
    149204         
    150205        //BEGIN Load dynamic libraries 
     
    161216            throw new InitException ("Loading dynamic libraries failed (see above)."); 
    162217        } 
     218        debug logger.trace ("Init: dynamic libraries loaded"); 
    163219        //END Load dynamic libraries 
    164          
    165         debug logger.trace ("Init: dynamic libraries loaded"); 
     220        //END Pre-init 
     221         
    166222         
    167223        //BEGIN Init (stages init2, init4) 
     
    294350            return false;                   // Done successfully 
    295351        } 
     352    //END runStage... 
     353         
     354        void printUsage (char[] progName) { 
     355            Cout ("mde [no version]").newline; 
     356            Cout ("Usage:").newline; 
     357            Cout (progName ~ ` [options]`).newline; 
     358            version(Windows) 
     359                    Cout ( 
     360`  --base-path path Use path as the base (install) path (Windows only). It 
     361            should contain the "data" directory.`).newline; 
     362            Cout ( 
     363`  --data-path path(s)  Add path(s) as a potential location for data files. 
     364            First path argument becomes the preffered location to 
     365            load data files from. 
     366  --conf-path path(s)   Add path(s) as a potential location for config files. 
     367            Configuration in the first path given take highest 
     368            priority. 
     369  --paths       Print all paths found and exit. 
     370  --quick-exit, -q  Exit immediately, without entering main loop. 
     371  --help, -h        Print this message.`).newline; 
     372        } 
    296373    } 
    297     //END runStage... 
    298374     
    299375    debug (mdeUnitTest) unittest {