Changeset 10:4c3575400769

Show
Ignore:
Timestamp:
02/18/08 06:54:56 (11 months ago)
Author:
Diggory Hardy <diggory.hardy@gmail.com>
branch:
default
convert_revision:
51391714b186e9410a17079a4c12b1679fdb8864
Message:

DefaultData? largely rewritten with unittest, SDL input event handling completed with unittest, changes to Init threading.

Init threads now catch own exceptions.
Doc: assigned some inputID devisions.
Added support for remaining SDL events.
Input axes' output is now stored with a short instead of a real.
Input unittest written (for SDL event handling).
Rewrote most of mde.mergetag.defaultdata using generic programming to generate read & write rules for all types. As a direct result, defaultdata can now write properly.
DefaultData? unittest written (also provides testing for mergetag read/write).
Moved mde.text.parse/format to tango.scrapple.text.convert.parseTo/parseFrom with many changes.

committer: Diggory Hardy <diggory.hardy@gmail.com>

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • conf/input.mtt

    r9 r10  
    11{MT01} 
    22<char[][]|Configs=["Std"]> 
     3<char[]|Test="1"> 
    34{Default} 
    4 <uint[][uint]|B=[0x20000000 : [0x100, 3], 0x20000001 : [0x100, 5] ]> 
     5<uint[][uint]|B=[ 0x8800001B : [[0x1000, 0x0]] ]> 
     6{UnitTest} 
     7<uint[][uint]|B=[ 
     8    0x88000124 : [[0x1000, 0x00F0]], 
     9    0x40000003 : [[0x1000, 0x01F0]], 
     10    0x20002005 : [ [0x1000,0x02F0],[0x1000,0x03F0] ], 
     11    0x110D600C : [[0x1000,0x04F0]], 
     12    0x120D600C : [[0x1000,0x05F0]], 
     13    0x140D600C : [[0x1000,0x06F0]], 
     14    0x180D600C : [[0x1000,0x07F0]] ]> 
     15<uint[][uint]|A=[0x80001008 : [[0x1000,0x20F0]],0x130D600C : [[0x1000,0x21F0]] , 0x1C0D600C : [[0x1000,0x22F0]]]> 
     16<uint[][uint]|M=[ 0x88000000 : [[0x1000, 0x10F0], [0x1000, 0x11F0]], 0x40016064:[[0x1000,0x12F0]]]> 
  • doc/jobs

    r9 r10  
    11In progress: 
    2 *   Why doesn't input.config filtering via headers "Configs" work? 
    32 
    43To do: 
    5 *   change init threads (should catch own exceptions) 
    6 *   finish event callback support 
    7 *   add remaining SDL event support 
     4*   Write Input unittests; remove untested notes 
     5*   Why doesn't input.config filtering via headers "Configs" work? 
    86*   add options support; in particular for whether or not to use threads (and adjust Init to use this). 
    97*   OutOfMemoryException is not currently checked for − it should be at least in critical places (use high-level catching of all errors?). 
     8*   Sensitivity adjustments. From es_a_out: 
     9        /+ FIXME: revise. 
     10        + I can't see any point using HALF_RANGE here, since it should really be used dependant on 
     11        + the device attached, not the axis. Also what about adjusted range like X3's throttle? 
     12        + 
     13        + Sensitivity: is this the right place to adjust it? For things like throttle where the 
     14        + ends of the interval must remain fixed, multiplying cannot be used to adjust and adjusting 
     15        + the curve via a power function doesn't seem to be what we want. For things where the 
     16        + end points needn't remain fixed, multiplying seems the right thing to do, but cannot be 
     17        + done here since we don't know the end points can be changed. 
     18         
     19        real y = x; 
     20        uint conf = s.pop(); 
     21        enum : uint { 
     22            HALF_RANGE  = 0x8000_0000u, 
     23            SENSITIVITY = 0x0080_0000u, 
     24        } 
     25        // Convert ranges into standard intervals (with or without reverse values) 
     26        if (conf & HALF_RANGE) y = (y + 32767.0) * 1.5259254737998596e-05;  // range  0.0 - 1.0 
     27        else y *= 3.0518509475997192e-05;                   // range -1.0 - 1.0 
     28        real a; 
     29        if (conf & SENSITIVITY) a = s.pop(); 
     30        /+ When a global sensitivity is available (possibly only use if it's enabled)... 
     31        else a = axis.sensitivity; 
     32        y = sign(y) * pow(abs(y), a);       // sensitivity adjustment by a +/ 
     33        myThis.axis[cast(inputID) s.pop()] = y; 
     34        +/ 
     35 
     36Done (for git log message): 
     37*   Init threads now catch own exceptions. 
     38*   Assigned some inputID devisions. 
     39*   add remaining SDL event support 
     40*   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. 
     41*   Axis output now stored with short instead of real. 
     42*   Input unittest 
     43*       Moved mde/text to scrapple 
     44*   DefaultData unittest. Then commit. 
  • doc/policies.txt

    r8 r10  
    33These are principles, not cast-iron rules, which I (Diggory Hardy) generally try to adhere to. If any other programmers have better principles to apply over these rules, they may do so for their own coding providing they have a good reason (i.e. not simply wanting to be a little different). 
    44 
     5A warning: I have several times changed my mind about items I've written here. So don't expect all existing code to conform, and if you feel that a principle listed is not a good idea don't think you have to conform to it. 
    56 
    6 Coding conventions: Mostly stick to those provided in the D specification. Generally indent with four spaces and use tabs to align comments. Aim to break long lines at around 100 chars (particularly with documentation); this isn't essential but provides a good guide and keeps text looking reasonable. With code, however, breaking lines doesn't always produce better-looking code. 
     7 
     8 
     9Coding conventions: Mostly stick to those provided in the D specification. Generally indent with four spaces and use tabs to align comments. Keep editor's tab-width at 8. Aim to break long lines at around 100 chars (particularly with documentation); this isn't essential but provides a good guide and keeps text looking reasonable. With code, however, breaking lines doesn't always produce better-looking code. 
    710 
    811Identifiers: as for the D spec, use descriptive words for identifiers, although try to keep them from being overlong. Use capital letters to show separate words, not _s. E.g.: file, readFile; not: rdFile, read_file, readFileUsingMyMethodNow. Again, this is only really a guideline. 
     
    1114 
    1215Spelling: Use british or american (or other) spellings as you like, but BE CONSISTANT at least for code symbols within packages (i.e. if you write your own package you choose the spelling, and for comments it doesn't matter, unless it's actually refering to a symbol). For text output to the user there need be no convention until internationalisation support is built-in. As far as internationalisation/localisation is concerned, does it make sense to translate log messages or not? (They are going to be seen by end-users, but will largely be used by developers.) 
     16 
    1317 
    1418 
     
    2024 
    2125Engine-wide initialisation and cleanup should be handled or invoked by mde.init.Init's CTOR and DTOR methods where this is viable. 
     26 
    2227 
    2328 
     
    4146    • error messages indicate that something big is wrong, and if the program still runs it is unlikely to be usable as intended; 
    4247    • fatal messages indicate a problem preventing the program from running. 
    43      
    4448 
    4549 
    4650Thrown errors should use an exception class specific to at least the package involved to enable specific catching of errors. Exception classes should be defined within a module exception.d in the package directory. Exception classes should generally follow the conventions within mde/exception.d to aid in providing reasonable error messages. 
     51 
     52 
     53 
     54Unittests (last block in the module where multiple unittest blocks are used) should end with: 
     55    logger.info ("Unittest complete."); 
     56No more logging should be needed, since if it fails whoever runs the unittest will know about it, and logging messages cannot be used to tell how complete the unittesting is. These messages just confirm that the unittests ran really. 
     57 
     58Unittests may be defined in their own modules or other modules. Unittests should be wrapped in 
     59    debug(mdeUnitTest) 
     60statements (these may also be used to wrap imports only needed by the unittest). Any modules containing unittests should be imported by test.mdeTest. 
  • dsss.conf

    r8 r10  
     1defaulttargets = mde/mde.d 
     2 
     3[*] 
     4version (Posix) { 
     5    buildflags=-L-ldl 
     6} else { 
     7    warn Only posix builds have been tested; elsewhere other libraries will probably need to be linked. 
     8} 
     9 
    110[mde/mde.d] 
    2 buildflags=-L-ldl 
    3 target=bin/mde 
    4 #[mde/mergetag] 
    5 #[mde/input] 
    6 #[test/MTTest.d] 
    7 #target=bin/MTTest 
     11version (Posix) { 
     12    target=bin/mde 
     13} else version (Windows) { 
     14    target=bin/mde.exe 
     15
    816 
     17[test/mdeTest.d] 
     18version (Posix) { 
     19    buildflags=-L-ldl -debug=mdeUnitTest -unittest 
     20    target=bin/mdeTest 
     21} else version (Windows) { 
     22    warn Extra linking probably needed! 
     23    buildflags=-debug=mdeUnitTest -unittest 
     24    target=bin/mdeTest.exe 
     25} 
     26noinstall 
  • mde/events.d

    r8 r10  
    33 
    44import mde.scheduler; 
     5import global = mde.global; 
    56 
    67import mde.input.input; 
     8import mde.input.exception; 
    79 
    810import derelict.sdl.events; 
     
    1416    logger = Log.getLogger ("mde.events"); 
    1517} 
    16  
    17 static bool run = true; 
    1818 
    1919void addEventsSchedule () { 
     
    2727            case SDL_QUIT: 
    2828                logger.info ("Quit requested"); 
    29                 run = false; 
     29                global.run = false; 
    3030                break; 
    3131            default: 
    32                 input (event); 
     32                try { 
     33                    global.input (event); 
     34                } catch (InputClassException e) { 
     35                    logger.warn ("Caught input exception; event will be ignored. Exception was:"); 
     36                    logger.warn (e.msg); 
     37                } 
    3338        } 
    3439    } 
  • mde/exception.d

    r8 r10  
    55 * 
    66 * All packages should have their own base exception type extending this one, and for each package 
    7  * level a CTOR taking a message should pass the message to the super. The const string symbol 
     7 * level a CTOR taking a message should pass the message to the super. 
     8 * A CTOR not taking a message and calling the super without a parameter may also be provided. 
     9 * 
     10 * The static string symbol 
    811 * should be overriden as below so that it ends up as something like "mde.file" or 
    9  * "mde.pkg.file.Class" describing where the exception was thrown. 
    10  * A CTOR not taking a message and calling the super without a parameter may also be provided. 
     12 * "mde.pkg.file.Class" describing where the exception was thrown. (Since only methods overload 
     13 * correctly, symbol is made static and an overloadable method is used to access the correct symbol.) 
    1114 */ 
    1215class mdeException : Exception { 
    13     const symbol = "mde";   /// Override in derived classes to name the module where the error occured. 
     16    static const symbol = "mde";    /// Override in derived classes to name the module where the error occured. 
     17    char[] getSymbol () { 
     18        return symbol; 
     19    } 
    1420    this (char[] msg) { 
    15         super(symbol ~ ": " ~ msg); 
     21        super(getSymbol() ~ ": " ~ msg); 
    1622    } 
    1723    this () {           // No supplied error message. 
     
    2127 
    2228class initException : mdeException { 
    23     const override symbol = super.symbol ~ ".init"; 
     29    // NOTE: if symbol is final, it can't be modified in the static this(), but as const it can 
     30    static const char[] symbol; 
     31    static this () {    symbol = super.symbol ~ ".init";    } 
     32    char[] getSymbol () { 
     33        return symbol; 
     34    } 
     35     
    2436    this (char[] msg) { 
    2537        super(msg); 
    2638    } 
    2739} 
     40 
     41debug (mdeUnitTest) { 
     42    import tango.util.log.Log : Log, Logger; 
     43 
     44    private Logger logger; 
     45    static this() { 
     46        logger = Log.getLogger ("mde.events"); 
     47    } 
     48     
     49    unittest { 
     50        // Check message prepending works correctly. 
     51        mdeException mE = new initException(""); 
     52        assert (mE.getSymbol() == "mde.init", mE.getSymbol()); 
     53        try { 
     54            throw new initException ("ABC"); 
     55            assert (false); 
     56        } catch (Exception e) { 
     57            assert (e.msg == "mde.init: ABC", e.msg); 
     58        } 
     59     
     60        logger.info ("Unittest complete."); 
     61    } 
     62} 
  • mde/init.d

    r9 r10  
    66module mde.init; 
    77 
    8 public import mde.exception; 
     8import mde.exception; 
    99 
    1010import mde.events; 
     11import global = mde.global; 
    1112import mde.input.input; 
    1213import mde.input.joystick; 
     
    1415// tango imports 
    1516import tango.core.Thread; 
     17import tango.core.Exception; 
    1618import tango.util.log.Log : Log, Logger; 
    1719import tango.util.log.ConsoleAppender : ConsoleAppender; 
     
    2022import derelict.sdl.sdl; 
    2123import derelict.util.exception; 
     24 
     25private Logger logger; 
    2226 
    2327/** 
     
    2933static this() 
    3034{ 
    31     // For now, just log to the console: 
    32     Logger root = Log.getRootLogger(); 
    33     root.setLevel(root.Level.Trace); 
    34     root.addAppender(new ConsoleAppender); 
     35    debug(mdeUnitTest) {}   // For unittests, the logger is set up by test.mdeTest 
     36    else { 
     37        // For now, just log to the console: 
     38        Logger root = Log.getRootLogger(); 
     39        root.setLevel(root.Level.Trace); 
     40        root.addAppender(new ConsoleAppender); 
     41    } 
     42     
     43    logger = Log.getLogger ("mde.init"); 
    3544} 
    3645static ~this() 
     
    7180         
    7281        bool initFailure = false;   // Set true if something goes wrong and we need to abort. 
    73         void setFailure () {        // For synchronization, although may be unnecessary 
     82        void setFailure () {        // For synchronization, although shouldn't be necessary 
    7483            synchronized initFailure = true; 
    7584        } 
    7685        void delegate() [] initFuncs = [ 
    7786        delegate void() { 
     87            logger = Log.getLogger ("mde.init.Init.SDL"); 
    7888            // Inits SDL and related stuff (joystick). 
    7989            try { 
    80                 // SDL Joystick, used by mde.input 
    8190                DerelictSDL.load(); 
    8291            } catch (DerelictException de) { 
     
    112121        try {   // creating/starting threads can fail 
    113122            foreach (func; initFuncs) tg.create(func); 
    114         } catch (Exception e) {            // should be a ThreadException, but catch all Exception
     123        } catch (ThreadException e) {      // to do with threading; try without thread
    115124            logger.warn ("Caught exception while trying to create threads:"); 
    116125            logger.warn (e.msg); 
     
    121130         
    122131        // Do some initialisation in the main thread 
    123         input.loadConfig ();        // (may also create instance) 
     132        global.input = new Input(); 
     133        global.input.loadConfig ();     // (may also create instance) 
    124134        addEventsSchedule (); 
    125135         
     
    181191} 
    182192 
    183 unittest { 
     193debug (mdeUnitTest) unittest { 
    184194    /* Fake init and cleanup. This happens before the CTOR runs so the extra Init.runCleanupFcts() 
    185195    * call isn't going to mess things up. The extra function called by runCleanupFcts won't cause 
     
    197207    Init.runCleanupFcts(); 
    198208    assert (!initialised); 
    199 
     209 
     210    logger.info ("Unittest complete."); 
     211
  • mde/input/config.d

    r9 r10  
    22module mde.input.config; 
    33 
    4 debug import mde.text.format
     4debug import tango.scrapple.text.convert.parseFrom : parseFrom
    55 
    6 public import mde.input.exception; 
     6import mde.input.exception; 
    77 
    88import MT = mde.mergetag.read; 
    9 import mde.text.parse
     9import tango.scrapple.text.convert.parseTo : parseTo
    1010 
    1111import tango.util.log.Log : Log, Logger; 
     
    2929     *  These bitcodes are OR'd to the identifier code for the input device, to indicate which type 
    3030     *  of input they are for. E.g. when a key event is recieved with code x, look up 
    31      *  $(_B _B.SDLKEY) | x in button. Keyboard events are SDL-specific since the codes may differ for other 
    32      *  libraries. 
     31     *  $(_B _B.SDLKEY) | x in button. Keyboard events are SDL-specific since the codes may differ 
     32     *  for other libraries. 
    3333     * 
    34      *  For joystick hat events, a motion should be converted into up and down events on separate 
    35      *  U,L,D,R positions and up and down events sent to the appropriate outputs. 
     34     *  For joystick hat events, a motion is converted into up and down events on separate U,L,D,R 
     35     *  positions and up and down events sent to the appropriate outputs (effectively making four 
     36     *  buttons, some pairs of which can be pressed simultaneously). For output to axes, see 
     37     *  A.JOYHAT_* . 
    3638     */ 
    3739    enum B : uint { 
    38         KEY     = 0x8000_0000u,     /// 0x8000_0000u 
    39         SDLKEY      = 0x8800_0000u,     /// 0x8800_0000u 
    40         MOUSE       = 0x4000_0000u,     /// 0x4000_0000u 
    41         JOYBUTTON   = 0x2000_0000u,     /// 0x2000_0000u 
    42         JOYHAT      = 0x1000_0000u,     /// 0x1000_0000u 
    43         JOYHAT_U    = 0x1800_0000u,     /// 0x1800_0000u 
    44         JOYHAT_D    = 0x1400_0000u,     /// 0x1400_0000u 
    45         JOYHAT_L    = 0x1200_0000u,     /// 0x1200_0000u 
    46         JOYHAT_R    = 0x1100_0000u,     /// 0x1100_0000u 
     40        KEY     = 0x8000_0000u,     /// 0x8000_0000 
     41        SDLKEY      = 0x8800_0000u,     /// 0x8800_0000 
     42        MOUSE       = 0x4000_0000u,     /// 0x4000_0000 
     43        JOYBUTTON   = 0x2000_0000u,     /// 0x2000_0000 
     44        JOYHAT      = 0x1000_0000u,     /// 0x1000_0000 
     45        JOYHAT_U    = 0x1800_0000u,     /// 0x1800_0000 
     46        JOYHAT_D    = 0x1400_0000u,     /// 0x1400_0000 
     47        JOYHAT_L    = 0x1200_0000u,     /// 0x1200_0000 
     48        JOYHAT_R    = 0x1100_0000u,     /// 0x1100_0000 
    4749    } 
    4850     
    4951    /** Axis event type bit-codes 
    5052     * 
    51      *  Well, SDL only supports one type of axis now, but this could be extended in the future. 
     53     *  SDL only supports one type of axis now, but this could be extended in the future. 
     54     * 
     55     *  This can also be used to make joystick hats output to two axes (and can be used in 
     56     *  conjuction with B.JOYHAT_* to output as buttons as well). 
    5257    */ 
    5358    enum A : uint { 
    54         JOYAXIS     = 0x8000_0000u,     /// 0x8000_0000u 
     59        JOYAXIS     = 0x8000_0000u,     /// 0x8000_0000 
     60        JOYHAT      = 0x1000_0000u,     /// 0x1000_0000 
     61        JOYHAT_LR   = 0x1300_0000u,     /// 0x1300_0000 
     62        JOYHAT_UD   = 0x1C00_0000u,     /// 0x1C00_0000 
    5563    } 
    5664     
     
    6068     */ 
    6169    enum M : uint { 
    62         MOUSE       = 0x8000_0000u,     /// 0x8000_0000u 
    63         WMMOUSE     = 0x8800_0000u,     /// 0x8800_0000u 
    64         JOYBALL     = 0x4000_0000u,     /// 0x4000_0000u 
     70        MOUSE       = 0x8000_0000u,     /// 0x8000_0000 
     71        WMMOUSE     = 0x8800_0000u,     /// 0x8800_0000 
     72        JOYBALL     = 0x4000_0000u,     /// 0x4000_0000 
    6573    } 
    6674     
    6775    /** Output queues: the core of the input configuration. 
    6876    * 
    69     *  button, axis and mouse each have their own index specifications. This is split into two parts: 
     77    *  These are all mapped with a uint key; upon any event outQueues are looked for in the 
     78    *  appropriate associative array with a key as follows. 
     79    * 
     80    *  The index is split into two parts; 
    7081    *  the first byte specifies the type of input (given by the above enums), and the last three 
    7182    *  bytes define where the input comes from. 
    7283    * 
    73     *  For B.SDLKEY, the last three bytes are for the SDL keysym. 
     84    *  For type B.SDLKEY, the last three bytes are for the SDL keysym. 
    7485    *  For B.MOUSE, B.JOY*, A.JOY* & M.JOY*, the last three bytes are split into two sets of 12 
    7586    *  bits (with masks 0x00FF_F000 and 0x0000_0FFF), the higher of which specifies the device 
    7687    *  (which mouse or joystick), and the lower of which specifies the button/axis/ball. 
    7788    * 
    78     *  The code for mouse motion is currently only M.WMMOUSE. If/when multiple mice are supported 
    79     *  new codes will be defined. 
     89    *  For all three types of output, the outQueues are used as follows. The first value in the queue is 
     90    *  read, and a function is called with the event details dependant on this; most of the time an 
     91    *  output function is called directly. Other functions may be used, however, to allow further 
     92    *  functionality such as modifier keys, timed keys, and key sequences. 
     93    * 
     94    *  The output functions all have code 0x100; these read a single item from the outQueue which 
     95    *  is the inputID the event outputs to (i.e. any callbacks at that ID called and status set for 
     96    *  that ID). 
     97    * 
    8098    */ 
    81     outQueue[uint] button; 
    82     outQueue[uint] axis;  /// ditto 
    83     outQueue[uint] mouse; /// ditto 
     99    outQueue[][uint] button; 
     100    outQueue[][uint] axis;    /// ditto 
     101    outQueue[][uint] relMotion;   /// ditto 
    84102     
     103    // FIXME: 
    85104    char[] name;        /// Name for user to save this under. 
    86105    uint[] inheritants;     /// Other profiles to inherit. 
     
    112131            MT.ID[] file_configs;   // active config sections (may not exist) 
    113132            MT.ID[]* file_configs_p = cast(MT.ID[]*) (CONFIGS in file.dataset.header._charAA); 
    114              
     133            debug foreach (i,d; file.dataset.header._charAA) logger.trace ("ID: "~cast(char[])i); 
     134            debug foreach (i,d; file.dataset.header._charA) logger.trace ("ID: "~cast(char[])i); 
     135                         
    115136            if (file_configs_p) file.read(*file_configs_p); // restrict to this set IF a restriction was given 
    116137            else        file.read();            // otherwise read all 
     
    133154            logger.trace (logger.format (tmp, "Loaded {} config sections.", configs.length)); 
    134155            foreach (id, cfg; configs) { 
    135                 logger.trace ("Section "~id~": " ~ format!(uint[][uint])(cfg.button)); 
     156                logger.trace ("Section " ~ id ~ 
     157                ":\n\tbutton:\t\t" ~ parseFrom!(uint[][][uint])(cfg.button) ~ 
     158                "\n\taxis:\t\t" ~ parseFrom!(uint[][][uint])(cfg.axis) ~ 
     159                "\n\trelMotion:\t" ~ parseFrom!(uint[][][uint])(cfg.relMotion) ); 
    136160            } 
    137161        } 
     
    146170    void addTag (char[] tp, MT.ID id, char[] dt) { 
    147171        if (tp == "uint[][uint]") { 
    148             if (id == QUEUE.BUTTON) button = cast(outQueue[uint]) parse!(uint[][uint]) (dt); 
    149             else if (id == QUEUE.AXIS) axis = cast(outQueue[uint]) parse!(uint[][uint]) (dt); 
    150             else if (id == QUEUE.MOUSE) mouse = cast(outQueue[uint]) parse!(uint[][uint]) (dt); 
     172            if (id == QUEUE.BUTTON) button = cast(outQueue[][uint]) parseTo!(uint[][][uint]) (dt); 
     173            else if (id == QUEUE.AXIS) axis = cast(outQueue[][uint]) parseTo!(uint[][][uint]) (dt); 
     174            else if (id == QUEUE.MOUSE) relMotion = cast(outQueue[][uint]) parseTo!(uint[][][uint]) (dt); 
    151175            else logger.warn ("Unexpected tag encountered with ID " ~ cast(char[])id); 
    152176        } // FIXME: add support for name and inheritants. 
    153         else throw new MT.MTUnknownTypeException ("Input Config: only uint[][uint] type supported"); 
     177        else throw new MT.MTUnknownTypeException ("Input Config: only uint[][][uint] type supported"); 
    154178    } 
    155179    void writeAll (ItemDelg) { 
  • mde/input/exception.d

    r8 r10  
    55/// Base Input exception class. 
    66class inputException : mdeException { 
    7     const override symbol = super.symbol ~ ".input"; 
     7    static const char[] symbol; 
     8    static this () {    symbol = super.symbol ~ ".input";   } 
     9    char[] getSymbol () { 
     10        return symbol; 
     11    } 
     12     
    813    this (char[] msg) { 
    914        super(msg); 
     
    1318 
    1419class InputClassException : inputException { 
    15     const override symbol = super.symbol ~ ".input.Input"; 
     20    static const char[] symbol; 
     21    static this () {    symbol = super.symbol ~ ".input.Input"; } 
     22    char[] getSymbol () { 
     23        return symbol; 
     24    } 
     25     
    1626    this (char[] msg) { 
    1727        super(msg); 
     
    2131 
    2232class ConfigLoadException : inputException { 
    23     const override symbol = super.symbol ~ ".config.Config"; 
     33    static const char[] symbol; 
     34    static this () {    symbol = super.symbol ~ ".config.Config";   } 
     35    char[] getSymbol () { 
     36        return symbol; 
     37    } 
     38     
    2439    this (char[] msg) { 
    2540        super(msg); 
  • mde/input/input.d

    r9 r10  
    77// package imports 
    88import mde.input.config; 
    9 public import mde.input.exception; 
     9import mde.input.exception; 
    1010 
    1111// sdl imports 
    1212import derelict.sdl.events; 
     13import derelict.sdl.types;  // only SDL_PRESSED 
     14import derelict.sdl.joystick;   // SDL_HAT_* 
    1315 
    1416import tango.util.log.Log : Log, Logger; 
    15  
    16 public Input input;         /// Global instance of input. 
    17 static this() { 
    18     input = new Input(); 
    19 } 
    2017 
    2118/// Class encapsulating all input functionality. 
     
    2522    typedef uint                inputID; 
    2623    alias void delegate(inputID, bool)      ButtonCallback; 
    27     alias void delegate(inputID, real)      AxisCallback; 
    28     alias void delegate(inputID, real,real) MouseCallback; 
     24    alias void delegate(inputID, short)     AxisCallback; 
     25    alias void delegate(inputID, real,real) RelMotionCallback; 
     26    alias void delegate(ushort, ushort, ubyte, bool)    MouseClickCallback; 
    2927 
    3028    /** Get key status at this ID. 
     
    3230    * Returns: value (true = down, false = up) or false if no value at this ID. */ 
    3331    bool getButton (inputID id) { 
    34         assert (this is input); 
    3532        bool* retp = id in button; 
    3633        if (retp) return *retp; 
    3734        else return false; 
    3835    } 
     36     
    3937    /** Get axis status at this ID. 
    4038    * 
    41     * Returns: value (range -1.0 .. 1.0) or 0.0 if no value at this ID. */ 
    42     real getAxis (inputID id) { 
    43         real* retp = id in axis; 
     39    * Returns: value (short; range -32767 .. 32767) or 0 if no value at this ID. */ 
     40    short getAxis (inputID id) { 
     41        short* retp = id in axis; 
    4442        if (retp) return *retp; 
     43        else return 0; 
     44    } 
     45    /** Get axis status at this ID. 
     46    * 
     47    * Returns: value (real; range roughly -1.0 .. 1.0) or 0 if no value at this ID. */ 
     48    real getAxis1 (inputID id) { 
     49        short* retp = id in axis; 
     50        if (retp) return (*retp) * 3.0518509475997192e-05; 
    4551        else return 0.0; 
    4652    } 
    47     /** Get mouse pointer position in screen coordinates. 
    48     * 
    49     * Window managers only support one mouse, so there will only be one screen coordinate. 
    50     * Unlike everything else, this is not configurable. 
    51     */ 
    52     void mouseScreenPos (out uint x, out uint y) { 
    53         x = mouse_x;    y = mouse_y; 
    54     } 
    55     /** Get relative mouse position (also for joystick balls). 
     53             
     54    /** Get the relative motion of the mouse or a joystick ball (since last frameReset() call). 
    5655    * 
    5756    * Future: Converts to a real via sensitivity settings (defaults may be set and overriden per item). 
     
    6261    * positions. 
    6362    */ 
    64     void mouseRelativePos (inputID id, out real x = 0.0, out real y = 0.0) { 
    65         RelPair* rp = id in axis_rel
     63    void getRelMotion (inputID id, out real x = 0.0, out real y = 0.0) { 
     64        RelPair* rp = id in relMotion
    6665        if (rp) { 
    6766            x = rp.x;   y = rp.y; 
    6867        } 
    6968    } 
     69    /** Get mouse pointer position in screen coordinates. 
     70    * 
     71    * Window managers only support one mouse, so there will only be one screen coordinate. 
     72    * Unlike nearly everything else, this is not configurable. 
     73    */ 
     74    void getMouseScreenPos (out uint x, out uint y) { 
     75        x = mouse_x;    y = mouse_y; 
     76    } 
    7077    // /// Is this modifier on? 
    7178    //bool modifierStatus (inputID id); 
     
    7683    */ 
    7784    void addButtonCallback (inputID id, ButtonCallback dg) { 
    78         buttonCallbacks[id] = dg; 
     85        buttonCallbacks[id] ~= dg; 
    7986    } 
    8087 
    8188    /** Adds a callback delegate for axis events with this ID. 
    8289    * 
    83     * Delegate receives event status
     90    * Delegate receives event status (as per what getAxis returns)
    8491    */ 
    8592    void addAxisCallback (inputID id, AxisCallback dg) { 
    86         axisCallbacks[id] = dg; 
     93        axisCallbacks[id] ~= dg; 
    8794    } 
    8895 
    8996    /** Adds a callback delegate for mouse motion/joystick ball events with this ID. 
    9097    * 
    91     * Delegate receives event status. (A separate callback for mouse pointer position changes is not 
     98    * Delegate receives event status. As the name suggests, this is relative motion not screen 
     99    * position, with sensitivity adjustments applied. 
     100    * 
     101    * (A separate callback for mouse screen position changes is not 
    92102    * necessary since this will be triggered by the same event - use mouseScreenPos from within the 
    93103    * function to get new screen coordinates.) 
    94104    */ 
    95     void addMouseCallback (inputID id, MouseCallback dg) { 
    96         mouseCallbacks[id] = dg; 
     105    void addRelMotionCallback (inputID id, RelMotionCallback dg) { 
     106        relMotionCallbacks[id] ~= dg; 
     107    } 
     108     
     109    /** Adds a callback delegate for all mouse clicks & releases. 
     110    * 
     111    * Delegate recieves x,y screen position (at time of click/release), button index (1 for left, 
     112    * 2 for middle, 3 for right, 4/5 for wheel, etc.), and whether the button was pressed or 
     113    * released (true if pressed). 
     114    * 
     115    * The point of this over a standard button callback is firstly to avoid mouse configuration for 
     116    * the GUI, and secondly to give the pointer position at the time of the event, not the time the 
     117    * callback gets called. 
     118    */ 
     119    void addMouseClickCallback (MouseClickCallback dg) { 
     120        mouseClickCallbacks ~= dg; 
    97121    } 
    98122 
     
    100124    * 
    101125    * Other types of event functions may be added. Returns true if the event was used, false if not. 
     126    * 
     127    * May throw InputClassExceptions (on configuration errors). Catching the exception and continuing should 
     128    * be fine. 
    102129    */ 
    103130    bool opCall (ref SDL_Event event) { 
    104         assert (this is input); 
    105131        switch (event.type) { 
     132            // Keyboard events: 
     133            case SDL_KEYDOWN: 
     134            case SDL_KEYUP: 
     135                outQueue[]* p = (Config.B.SDLKEY | event.key.keysym.sym) in config.button; 
     136                if (p) foreach (outQueue q; *p) { 
     137                    bEvent (this, event.key.state == SDL_PRESSED, readOutQueue(q)); 
     138                } 
     139                break; 
     140             
     141            // Mouse events: 
     142            case SDL_MOUSEBUTTONDOWN: 
     143            case SDL_MOUSEBUTTONUP: 
     144                // Mouse clicks: 
     145                foreach (dg; mouseClickCallbacks) 
     146                    dg (event.button.x, event.button.y, event.button.button, event.button.state == SDL_PRESSED); 
     147                 
     148                // Button events: 
     149                outQueue[]* p = (Config.B.MOUSE | event.button.button) in config.button; 
     150                if (p) foreach (outQueue q; *p) { 
     151                    bEvent (this, event.button.state == SDL_PRESSED, readOutQueue(q)); 
     152                } 
     153                break; 
     154             
     155            case SDL_MOUSEMOTION: 
     156                // Screen coordinates (set here since relMotion is also used for joystick balls): 
     157                mouse_x = event.motion.x; 
     158                mouse_y = event.motion.y; 
     159                 
     160                // Relative motion: 
     161                outQueue[]* p = (Config.M.WMMOUSE) in config.relMotion; 
     162                if (p) foreach (outQueue q; *p) { 
     163                    mEvent (this, event.motion.xrel, event.motion.yrel, readOutQueue(q)); 
     164                } 
     165                break; 
     166             
     167            // Joystick events: 
    106168            case SDL_JOYBUTTONDOWN: 
    107169            case SDL_JOYBUTTONUP: 
    108                 outQueue* p = (Config.B.JOYBUTTON | (event.jbutton.which << 12) | event.jbutton.button) in config.button; 
    109                 if (p) bEventOut (event.jbutton.state == 0x1, readOutQueue(*p)); 
    110                 break; 
    111              
    112             /+ 
    113             case SDL_KEYDOWN: 
    114             case SDL_KEYUP: 
    115             outQueue* p = (Config.B.SDLKEY | event.key.keysym.sym) in config.button; 
    116             if (p) eventstream.bEventOut (event.key.state == SDL_PRESSED, readOutQueue(*p)); 
    117             break; 
    118             case SDL_MOUSEMOTION: 
    119             mouse_x = event.motion.x; 
    120             mouse_y = event.motion.y; 
    121             outQueue* p = (Config.M.WMMOUSE) in config.mouse; 
    122             if (p) eventstream.mEventOut (event.motion.xrel, event.motion.yrel, readOutQueue(*p)); 
    123             +/ 
     170                outQueue[]* p = (Config.B.JOYBUTTON | (event.jbutton.which << 12) | event.jbutton.button) in config.button; 
     171                if (p) foreach (outQueue q; *p) { 
     172                    bEvent (this, event.jbutton.state == SDL_PRESSED, readOutQueue(q)); 
     173                } 
     174                break; 
     175             
     176            case SDL_JOYAXISMOTION: 
     177                outQueue[]* p = (Config.A.JOYAXIS | (event.jaxis.which << 12) | event.jaxis.axis) in config.axis; 
     178                if (p) foreach (outQueue q; *p) { 
     179                    aEvent (this, event.jaxis.value, readOutQueue(q)); 
     180                } 
     181                break; 
     182             
     183            case SDL_JOYBALLMOTION: // NOTE: untested 
     184                outQueue[]* p = (Config.M.JOYBALL | (event.jball.which << 12) | event.jball.ball) in config.relMotion; 
     185                if (p) foreach (outQueue q; *p) { 
     186                    mEvent (this, event.jball.xrel, event.jball.yrel, readOutQueue(q)); 
     187                } 
     188                break; 
     189             
     190            case SDL_JOYHATMOTION:  // NOTE: untested 
     191                static ubyte[uint] oldJHatVals;     // necessary to store this to know which "axis" changed 
     192         
     193                uint index = (event.jhat.which << 12) | event.jhat.hat; 
     194                ubyte* oVal_p = index in oldJHatVals; 
     195                ubyte oldJHatVal = (oVal_p) ? *oVal_p : SDL_HAT_CENTERED; 
     196                 
     197                // Carry out functionality for an axis. 
     198                void hatExamine (ubyte neg, ubyte pos, Config.B neg_b, Config.B pos_b, Config.A axis) { 
     199                    // Check if there's any change on this axis (if not, nothing to do): 
     200                    ubyte filter = neg | pos; 
     201                    if ((oldJHatVal & filter) != (event.jhat.value & filter)) { 
     202                        // Now we know this axis changed position, so can unset old value and set 
     203                        // new value (if not centre). 
     204                         
     205                        // Cancel old button status: 
     206                        if (oldJHatVal & neg) { 
     207                            outQueue[]* p = (neg_b | index) in config.button; 
     208                            if (p) foreach (outQueue q; *p) bEvent (this, false, readOutQueue(q)); 
     209                        } else if (oldJHatVal & pos) { 
     210                            outQueue[]* p = (pos_b | index) in config.button; 
     211                            if (p) foreach (outQueue q; *p) bEvent (this, false, readOutQueue(q)); 
     212                        } 
     213                         
     214                        // Set new button status and position: 
     215                        short position = 0; 
     216                        if (event.jhat.value & neg) { 
     217                            position = -32767; 
     218                            outQueue[]* p = (neg_b | index) in config.button; 
     219                            if (p) foreach (outQueue q; *p) bEvent (this, true, readOutQueue(q)); 
     220                        } else if (event.jhat.value & pos) { 
     221                            position = 32767; 
     222                            outQueue[]* p = (pos_b | index) in config.button; 
     223                            if (p) foreach (outQueue q; *p) bEvent (this, true, readOutQueue(q)); 
     224                        } 
     225                         
     226                        // New axis event: 
     227                        outQueue[]* p = (axis | index) in config.axis; 
     228                        if (p) foreach (outQueue q; *p) aEvent (this, position, readOutQueue(q)); 
     229                    } 
     230                } 
     231                 
     232                // Now run the code for each axis: 
     233                hatExamine (SDL_HAT_UP, SDL_HAT_DOWN, 
     234                            Config.B.JOYHAT_U, Config.B.JOYHAT_D, Config.A.JOYHAT_UD); 
     235                hatExamine (SDL_HAT_LEFT, SDL_HAT_RIGHT, 
     236                            Config.B.JOYHAT_L, Config.B.JOYHAT_R, Config.A.JOYHAT_LR); 
     237                 
     238                oldJHatVals[index] = event.jhat.value; 
     239                break; 
     240             
     241            // Other events: 
    124242            default: 
    125                 return false; 
    126         } 
    127         return true; 
     243                return false;  // event not used 
     244        } 
     245        return true;       // event used 
    128246    } 
    129247     
     
    134252    */ 
    135253    void frameReset () { 
    136         foreach (rp; axis_rel) { 
     254        foreach (rp; relMotion) { 
    137255            rp.x = rp.y = 0.0; 
    138256        } 
     
    143261    * Returns: true if the requested config id wasn't found. 
    144262    */ 
    145     bool loadConfig () { 
     263    bool loadConfig (char[] profile = "Default") { 
    146264        Config.load("conf/input.mtt");  // FIXME: filename