# HG changeset patch # User Diggory Hardy # Date 1203584733 0 # Node ID b940f267419e8fd6613525024ef5c5ba4d622e82 # Parent 4c35754007690aa022bda854d90e30c4e80b96a3 Options class created & changes to mergetag exception messages. Options class created (barebones). Loading/saving from Init. Init no longer runs cleanup functions after initialisation failure. Improved mergetag exception messages & error reporting. committer: Diggory Hardy diff -r 4c3575400769 -r b940f267419e conf/options.mtt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/conf/options.mtt Thu Feb 21 09:05:33 2008 +0000 @@ -0,0 +1,5 @@ +{MT01} +{Default} + + + diff -r 4c3575400769 -r b940f267419e doc/jobs --- a/doc/jobs Mon Feb 18 11:54:56 2008 +0000 +++ b/doc/jobs Thu Feb 21 09:05:33 2008 +0000 @@ -1,7 +1,6 @@ In progress: To do: -* Write Input unittests; remove untested notes * Why doesn't input.config filtering via headers "Configs" work? * add options support; in particular for whether or not to use threads (and adjust Init to use this). * OutOfMemoryException is not currently checked for − it should be at least in critical places (use high-level catching of all errors?). @@ -34,11 +33,6 @@ +/ Done (for git log message): -* Init threads now catch own exceptions. -* Assigned some inputID devisions. -* add remaining SDL event support -* Rewrote most of mde.mergetag.defaultdata using generic programming to generate read & write rules for all types. As a result, defaultdata can now write properly. -* Axis output now stored with short instead of real. -* Input unittest -* Moved mde/text to scrapple -* DefaultData unittest. Then commit. +* Options class created (barebones). Loading/saving from Init. +* Init no longer runs cleanup functions after initialisation failure. +* Improved mergetag exception messages & error reporting. diff -r 4c3575400769 -r b940f267419e dsss.conf --- a/dsss.conf Mon Feb 18 11:54:56 2008 +0000 +++ b/dsss.conf Thu Feb 21 09:05:33 2008 +0000 @@ -8,19 +8,14 @@ } [mde/mde.d] -version (Posix) { - target=bin/mde -} else version (Windows) { - target=bin/mde.exe -} +target=bin/mde [test/mdeTest.d] +buildflags=-debug -debug=mdeUnitTest -unittest +target=bin/mdeTest +noinstall version (Posix) { - buildflags=-L-ldl -debug=mdeUnitTest -unittest - target=bin/mdeTest -} else version (Windows) { - warn Extra linking probably needed! - buildflags=-debug=mdeUnitTest -unittest - target=bin/mdeTest.exe + buildflags+=-L-ldl +} else { + warn Only posix builds have been tested; elsewhere other libraries will probably need to be linked. } -noinstall \ No newline at end of file diff -r 4c3575400769 -r b940f267419e mde/exception.d --- a/mde/exception.d Mon Feb 18 11:54:56 2008 +0000 +++ b/mde/exception.d Thu Feb 21 09:05:33 2008 +0000 @@ -38,12 +38,26 @@ } } +class optionsLoadException : mdeException { + // NOTE: if symbol is final, it can't be modified in the static this(), but as const it can + static const char[] symbol; + static this () { symbol = super.symbol ~ ".options"; } + char[] getSymbol () { + return symbol; + } + + this (char[] msg) { + super(msg); + } +} + debug (mdeUnitTest) { import tango.util.log.Log : Log, Logger; private Logger logger; static this() { logger = Log.getLogger ("mde.events"); + logger.info ("Got logger!"); } unittest { diff -r 4c3575400769 -r b940f267419e mde/init.d --- a/mde/init.d Mon Feb 18 11:54:56 2008 +0000 +++ b/mde/init.d Thu Feb 21 09:05:33 2008 +0000 @@ -7,6 +7,7 @@ import mde.exception; +import mde.options; import mde.events; import global = mde.global; import mde.input.input; @@ -22,8 +23,6 @@ import derelict.sdl.sdl; import derelict.util.exception; -private Logger logger; - /** * Static CTOR * @@ -39,8 +38,6 @@ root.setLevel(root.Level.Trace); root.addAppender(new ConsoleAppender); } - - logger = Log.getLogger ("mde.init"); } static ~this() { @@ -78,13 +75,13 @@ * These should each handle a separate area of initialisation such that these functions could * be run simultaneously in separate threads. */ - bool initFailure = false; // Set true if something goes wrong and we need to abort. void setFailure () { // For synchronization, although shouldn't be necessary synchronized initFailure = true; } void delegate() [] initFuncs = [ delegate void() { - logger = Log.getLogger ("mde.init.Init.SDL"); + Logger logger = Log.getLogger ("mde.init.Init.SDL"); + // Inits SDL and related stuff (joystick). try { DerelictSDL.load(); @@ -113,6 +110,19 @@ openJoysticks (); // after SDL init addCleanupFct (&closeJoysticks); + }, + delegate void() { + Logger logger = Log.getLogger ("mde.init.Init.Options"); + + try { + Options.load(); + } catch (optionsLoadException e) { + logger.fatal ("Loading options failed; message:"); + logger.fatal (e.msg); + setFailure(); + return; + } + addCleanupFct (&Options.save); // not strictly cleanup, but needs to be called somewhere } ]; @@ -157,6 +167,7 @@ // All cleanup-on-failure must be done here. runCleanupFcts(); + logger.fatal ("Init failed"); // Throw an exception to signal failure and prevent DTOR from also running. throw new initException ("Initialisation failed due to above exceptions."); } @@ -167,7 +178,11 @@ * Currently unthreaded; probably might as well stay that way. */ ~this() { - runCleanupFcts(); // if threading, note not all functions can be called simultaeneously + if (!initFailure) { + logger.info ("Cleaning up..."); + runCleanupFcts(); // if threading, note not all functions can be called simultaeneously + logger.info ("Done!"); + } } /* Cleanup Functions. @@ -188,24 +203,29 @@ foreach_reverse (fct; cleanup) fct(); } } -} - -debug (mdeUnitTest) unittest { - /* Fake init and cleanup. This happens before the CTOR runs so the extra Init.runCleanupFcts() - * call isn't going to mess things up. The extra function called by runCleanupFcts won't cause - * any harm either. */ - static bool initialised = false; - static void cleanup () { initialised = false; } + + /* This is set true by this() if something goes wrong and we need to abort. + * If it's true, no cleanup should be done by the DTOR (since the DTOR still runs after an + * exception has been thrown). */ + private bool initFailure = false; + + debug (mdeUnitTest) unittest { + /* Fake init and cleanup. This happens before the CTOR runs so the extra Init.runCleanupFcts() + * call isn't going to mess things up. The extra function called by runCleanupFcts won't cause + * any harm either. */ + static bool initialised = false; + static void cleanup () { initialised = false; } - static void init () { - initialised = true; - Init.addCleanupFct (&cleanup); - } + static void init () { + initialised = true; + Init.addCleanupFct (&cleanup); + } - init(); - assert (initialised); - Init.runCleanupFcts(); - assert (!initialised); + init(); + assert (initialised); + Init.runCleanupFcts(); + assert (!initialised); - logger.info ("Unittest complete."); + logger.info ("Unittest complete."); + } } diff -r 4c3575400769 -r b940f267419e mde/input/input.d --- a/mde/input/input.d Mon Feb 18 11:54:56 2008 +0000 +++ b/mde/input/input.d Thu Feb 21 09:05:33 2008 +0000 @@ -180,14 +180,14 @@ } break; - case SDL_JOYBALLMOTION: // NOTE: untested + case SDL_JOYBALLMOTION: outQueue[]* p = (Config.M.JOYBALL | (event.jball.which << 12) | event.jball.ball) in config.relMotion; if (p) foreach (outQueue q; *p) { mEvent (this, event.jball.xrel, event.jball.yrel, readOutQueue(q)); } break; - case SDL_JOYHATMOTION: // NOTE: untested + case SDL_JOYHATMOTION: static ubyte[uint] oldJHatVals; // necessary to store this to know which "axis" changed uint index = (event.jhat.which << 12) | event.jhat.hat; @@ -297,10 +297,9 @@ bool[inputID] button; // Table of button states short[inputID] axis; // Table of axes states ushort mouse_x, mouse_y; // Current screen coords of the window manager mouse - // FIXME: might need a bit of work... at any rate defining a default ID. RelPair[inputID] relMotion; // Table of relative mouse / joystick ball motions - // FIXME: these need to be more like multimaps, supporting multiple dgs (also some means of removal?) + // FIXME: currently no means of removal ButtonCallback[][inputID] buttonCallbacks; AxisCallback[][inputID] axisCallbacks; RelMotionCallback[][inputID] relMotionCallbacks; diff -r 4c3575400769 -r b940f267419e mde/mde.d --- a/mde/mde.d Mon Feb 18 11:54:56 2008 +0000 +++ b/mde/mde.d Thu Feb 21 09:05:33 2008 +0000 @@ -9,6 +9,7 @@ import global = mde.global; import mde.events; import mde.scheduler; +import mde.options; // greeting message import mde.exception; import mde.input.input; @@ -23,6 +24,7 @@ int main() { + //BEGIN Initialisation Logger logger = Log.getLogger ("mde.mde"); scope Init init; @@ -40,6 +42,9 @@ global.run = false; } } ); + //END Initialisation + + logger.info (options.greeting); while (global.run) { Scheduler.run (Clock.now()); diff -r 4c3575400769 -r b940f267419e mde/mergetag/exception.d --- a/mde/mergetag/exception.d Mon Feb 18 11:54:56 2008 +0000 +++ b/mde/mergetag/exception.d Thu Feb 21 09:05:33 2008 +0000 @@ -18,23 +18,38 @@ this (char[] msg) { super(msg); } - this () {} + this () { // Only called when an unexpected exception/error occurs + super ("Unknown exception"); + } } /** Thrown on file IO errors. */ class MTFileIOException : MTException { - this () {} + this () { + super ("File IO exception"); + } } /** Thrown on unknown format errors; when reading or writing and the filetype cannot be guessed. */ class MTFileFormatException : MTException { - this () {} + this () { + super ("File format exception"); + } +} + +/** Thrown on syntax errors when reading; bad tags or unexpected EOF. */ +class MTSyntaxException : MTException { + this () { + super ("Syntax exception"); + } } /** Thrown by addTag (in classes implementing dataset.DataSection) when a tag is read with an * unrecognised type field. */ class MTUnknownTypeException : MTException { - this () {} + this () { + super ("Unknown type"); + } this (char[] msg) { super (msg); } @@ -43,17 +58,21 @@ /** Thrown by addTag (in classes implementing dataset.DataSection) when a data parsing error occurs * (really just to make whoever called addTag to log a warning saying where the error occured). */ class MTaddTagParseException : MTException { - this () {} + this () { + super ("Parse exception within addTag"); + } } +/+ /// Thrown by TypeView.parse on errors. class MTBadTypeStringException : MTException { this () {} } ++/ /// Thrown by *Writer.write. class MTNoDataSetException : MTException { - this (char[] msg) { - super(msg); + this () { + super ("No dataset"); } } diff -r 4c3575400769 -r b940f267419e mde/mergetag/read.d --- a/mde/mergetag/read.d Mon Feb 18 11:54:56 2008 +0000 +++ b/mde/mergetag/read.d Thu Feb 21 09:05:33 2008 +0000 @@ -38,8 +38,16 @@ * // get your data from foo.dataset. * ----------------------- * - * Only a child-class of MTException will ever be thrown, currently MTFileIOException if the file - * could not be read or MTFileFormatException on any error when parsing the file. + * Throws: + * $(TABLE + * $(TR $(TH Exception) $(TH Thrown when)) + * $(TR $(TD MTFileIOException) $(TD An error occurs while opening the file)) + * $(TR $(TD MTFileFormatException) $(TD The file doesn't start with a recognised header/version)) + * $(TR $(TD MTSyntaxException) $(TD A file syntax error occurs)) + * $(TR $(TD MTException) $(TD An unexpected error occurs)) + * ) + * Note that all exceptions extend MTException and when any exception is thrown the class is + * rendered unusable: any subsequent calls to read will be ignored. * * Threading: Separate instances of Reader should be thread-safe provided access to the same * dataset is synchronized; i.e. no two readers refering to the same dataset should run @@ -161,9 +169,6 @@ } else endOfHeader = parseSection (6,null); } - // Was intended to close file, but file is closed within CTOR anyway. - public ~this () { - } //END METHODS: CTOR / DTOR //BEGIN METHODS: PUBLIC @@ -306,7 +311,7 @@ // Type section of tag: uint pos_s = pos; fbufLocateDataTagChar (pos, false); // find end of type section - if (fbuf[pos] != '|') throwMTErr (ErrDTAG); + if (fbuf[pos] != '|') throwMTErr (ErrDTAG, new MTSyntaxException); char[] type = fbuf[pos_s..pos]; fbufIncrement (pos); @@ -314,15 +319,15 @@ // ID section of tag: pos_s = pos; fbufLocateDataTagChar (pos, false); // find end of type section - if (fbuf[pos] != '=') throwMTErr (ErrDTAG); + if (fbuf[pos] != '=') throwMTErr (ErrDTAG, new MTSyntaxException); ID tagID = cast(ID) fbuf[pos_s..pos]; fbufIncrement (pos); // Data section of tag: pos_s = pos; - fbufLocateDataTagChar (pos, true); // find end of data section - if (fbuf[pos] != '>') throwMTErr (ErrDTAG); + fbufLocateDataTagChar (pos, true); // find end of data section + if (fbuf[pos] != '>') throwMTErr (ErrDTAG, new MTSyntaxException); char[] data = fbuf[pos_s..pos]; if (!comment && dsec != null) { @@ -341,7 +346,7 @@ catch (Exception e) { logger.error ("Unknown error occured" ~ ErrInFile ~ ':'); logger.error (e.msg); - throw e; // Fatal to Reader + throwMTErr (e.msg); // Fatal to Reader } } else comment = false; // cancel comment status now } @@ -365,7 +370,7 @@ comment = true; // starting a comment (or an error) // variable is reset at end of comment } else // must be an error - throwMTErr ("Invalid character (or sequence starting \"!\") outside of tag" ~ ErrInFile); + throwMTErr ("Invalid character (or sequence starting \"!\") outside of tag" ~ ErrInFile, new MTSyntaxException); } // if code execution reaches here, we're at EOF // possible error: last character was ! (but don't bother checking since it's inconsequential) @@ -383,7 +388,7 @@ for (; pos < fbuf.length; ++pos) if (fbuf[pos] == '}' || fbuf[pos] == '{') break; - if (fbuf[pos] != '}') throwMTErr ("Bad section tag format: not {id}" ~ ErrInFile); + if (fbuf[pos] != '}') throwMTErr ("Bad section tag format: not {id}" ~ ErrInFile, new MTSyntaxException); ID id = cast(ID) fbuf[start..pos]; fbufIncrement(pos); return id; @@ -392,10 +397,10 @@ /* Increments pos and checks it hasn't hit fbuf.length . */ private void fbufIncrement(inout uint pos) { ++pos; - if (pos >= fbuf.length) throwMTErr("Unexpected EOF" ~ ErrInFile); + if (pos >= fbuf.length) throwMTErr("Unexpected EOF" ~ ErrInFile, new MTSyntaxException); } - private void throwMTErr (char[] msg, MTException exc = new MTFileFormatException) { + private void throwMTErr (char[] msg, MTException exc = new MTException) { fatal = true; // if anyone catches the error and tries to do anything --- we're dead now logger.error (msg); // report the error throw exc; // and signal our error diff -r 4c3575400769 -r b940f267419e mde/mergetag/write.d --- a/mde/mergetag/write.d Mon Feb 18 11:54:56 2008 +0000 +++ b/mde/mergetag/write.d Thu Feb 21 09:05:33 2008 +0000 @@ -51,12 +51,27 @@ * method parameter and using the filename extension as a fallback. * * An exception is thrown if neither test can deduce the writing method. + * + * Use as: + * ----------------------- + * DataSet dataset; // contains data to write + * IWriter foo; + * try { + * foo = makeWriter("foo.mtt", dataset); + * foo.write(); + * } + * catch (MTException) {} + * ----------------------- + * + * Throws: + * MTFileFormatException if unable to determine writing format or use requested format. */ IWriter makeWriter (char[] path, DataSet dataset = null, WriterMethod method = WriterMethod.Unspecified) { return makeWriter (new FilePath (path), dataset, method); } /** ditto */ -IWriter makeWriter (PathView path, DataSet dataset = null, WriterMethod method = WriterMethod.Unspecified) { +IWriter makeWriter (PathView path, DataSet dataset = null, WriterMethod method = WriterMethod.Unspecified) +{ void throwMTErr (char[] msg, Exception exc = new MTException) { logger.error (msg); throw exc; @@ -70,6 +85,7 @@ if (method == WriterMethod.Binary) throwMTErr ("Binary writing not supported yet!", new MTFileFormatException); else if (method == WriterMethod.Text) return new TextWriter (path, dataset); else if (method == WriterMethod.Both) throwMTErr ("Dual writing not supported yet!", new MTFileFormatException); + else debug throwMTErr ("Bad value of method", new MTFileFormatException); } /// Interface for methods and data necessarily available in TextWriter and/or BinaryWriter. @@ -92,8 +108,17 @@ * Class to write a dataset to a file. * * Files are only actually open for writing while the write() method is running. + * + * Throws: + * $(TABLE + * $(TR $(TH Exception) $(TH Thrown when)) + * $(TR $(TD MTNoDataSetException) $(TD No dataset is available to write from)) + * $(TR $(TD MTFileIOException) $(TD An error occurs while attemting to write the file)) + * $(TR $(TD MTException) $(TD An unexpected error occurs)) + * ) + * Note that all exceptions extend MTException; unlike Reader exceptions don't block further calls. */ -scope class TextWriter : IWriter +class TextWriter : IWriter { //BEGIN DATA /// Get or set the DataSet. @@ -112,11 +137,13 @@ /* The container where data is written from. */ DataSet _dataset; - PathView path; + PathView _path; //END DATA //BEGIN CTOR / DTOR - /** Tries to open file path for writing. + /** Prepares to open file path for writing. + * + * The call doesn't actually execute any code so cannot fail (unless out of memory). * * Params: * path = The name or FilePath of the file to open. @@ -124,12 +151,12 @@ * dataset_ = If null create a new DataSet, else use existing DataSet *dataset_ and merge read * data into it. */ - public this (char[] _path, DataSet ds = null) { - this (new FilePath (_path), ds); + public this (char[] path, DataSet ds = null) { + this (new FilePath (path), ds); } /** ditto */ - public this (PathView _path, DataSet ds = null) { - path = _path; + public this (PathView path, DataSet ds = null) { + _path = path; _dataset = ds; } //END CTOR / DTOR @@ -141,22 +168,17 @@ * header should be written only once. This behaviour could, for instance, be used to write * multiple DataSets into one file without firstly merging them. Note that this behaviour may * be changed when binary support is added. - * - * Throws: - * MTNoDataSetException if the dataset is null, - * MTFileIOException if a file IO error occurs, - * MTException on any other exception (unexpected). */ public void write () { - if (!_dataset) throw new MTNoDataSetException ("write(): Dataset needed to write from!"); + if (!_dataset) throwMTErr ("write(): no Dataset available to write from!", new MTNoDataSetException ()); try { FileConduit conduit; // actual conduit; don't use directly when there's content in the buffer IBuffer buffer; // write strings directly to this (use opCall(void[]) ) // Open a conduit on the file: - conduit = new FileConduit (path, FileConduit.WriteCreate); + conduit = new FileConduit (_path, FileConduit.WriteCreate); scope(exit) conduit.close(); buffer = new Buffer(conduit); // And a buffer diff -r 4c3575400769 -r b940f267419e mde/options.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mde/options.d Thu Feb 21 09:05:33 2008 +0000 @@ -0,0 +1,112 @@ +/** This module handles stored options, currently all except input maps. +* +* The purpose of having all options centrally controlled is to allow generic handling by the GUI +* and ease saving and loading of values. +*/ +module mde.options; + +import mde.exception; + +import mde.mergetag.read; +import mde.mergetag.write; +import mde.mergetag.dataset; +import mde.mergetag.exception; + +import tango.scrapple.text.convert.parseTo : parseTo; +import tango.scrapple.text.convert.parseFrom : parseFrom; + +import tango.io.FilePath : FilePath; +import tango.util.log.Log : Log, Logger; + +private Logger logger; +static this() { + logger = Log.getLogger ("mde.options"); +} + +Options options; + +// Later: add automatic saving/loading for variables, variable lists with GUI name and description. + +/** Class instance stores all options. +* +* All options and handling should be non-static to allow possibility of profiles later. +*/ +class Options : DataSection +{ + /* The options. + * + * For now, static variables. Later, lists (and static variables?). + */ + bool useThreads; + char[] greeting; // just a testing message + + + /* The code to load/save the values. + * + * Uses mergetag. + */ + void addTag (char[] tp, ID id, char[] dt) { + if (tp == "bool") { + if (id == cast(ID)"useThreads") useThreads = parseTo!(bool) (dt); + } else if (tp == "char[]") { + if (id == cast(ID)"greeting") greeting = parseTo!(char[]) (dt); + } else + throw new MTUnknownTypeException ("Only bool currently supported."); + } + void writeAll (ItemDelg dlg) { + dlg ("bool", cast(ID)"useThreads", parseFrom!(bool) (useThreads)); + dlg ("char[]", cast(ID)"greeting", parseFrom!(char[]) (greeting)); + } + + + /* Load/save options from file. + * + * If the file doesn't exist, no reading is attempted (options are left at default values). + */ + private static const fileName = "conf/options.mtt"; + private static const secName = cast(ID)"Default"; + static void load () { + scope(failure) options = new Options(); // FIXME necessary while options is accessed statically and access is unprotected! + + FilePath filePath = new FilePath (fileName); + // If it's not a file, stop. It should still be created on exit (assuming it's not a folder). + if (!filePath.exists || filePath.fileSize == 0u) { + options = new Options(); + return; + } + + Reader reader; + try { + reader = new Reader(filePath); + reader.dataSecCreator = function DataSection(ID) { + return new Options; + }; + reader.read; + } catch (MTException e) { + logger.error ("Mergetag exception occurred:"); + logger.error (e.msg); + throw new optionsLoadException ("Loading aborted: mergetag exception"); + } + + DataSection* secP = secName in reader.dataset.sec; + options = cast(Options) *secP; + if (options is null) { + throw new optionsLoadException ("Loading failed: expected section not found"); + } + // else loading was succesful. + } + static void save () { + DataSet ds = new DataSet(); + ds.sec[secName] = options; + + IWriter writer; + try { + writer = makeWriter (fileName, ds); + writer.write(); + } catch (MTException e) { + logger.error ("Mergetag exception occurred; saving aborted:"); + logger.error (e.msg); + //FIXME: currently nothing done besides logging the error + } + } +} diff -r 4c3575400769 -r b940f267419e test/mdeTest.d --- a/test/mdeTest.d Mon Feb 18 11:54:56 2008 +0000 +++ b/test/mdeTest.d Thu Feb 21 09:05:33 2008 +0000 @@ -2,9 +2,6 @@ */ module test.mdeTest; -// Define this to run unittests: -debug=mdeUnitTest; - // This module should import all mde modules containing unittests: import mde.input.input; import mde.mergetag.dataset;