make Input have its own timer, and add key-repeat handling, and fix some warnings
authorterom
Mon, 08 Dec 2008 21:18:08 +0000
changeset 311 440763821484
parent 310 7e0cfc5f0944
child 312 10743f190aab
make Input have its own timer, and add key-repeat handling, and fix some warnings
src/Config.hh
src/Graphics.cc
src/Graphics.hh
src/Input.cc
src/Input.hh
src/Projectile.cc
src/Rope.cc
src/Types.hh
--- a/src/Config.hh	Mon Dec 08 21:15:52 2008 +0000
+++ b/src/Config.hh	Mon Dec 08 21:18:08 2008 +0000
@@ -25,6 +25,12 @@
 // Simulation
 const uint16_t PHYSICS_TICK_MS = 10;
 
+/** Input handling keyboard poll interval */
+const TimeMS INPUT_POLL_INTERVAL = 10;
+
+/** How long to block INPUT_FLAG_NOREPEAT for */
+const TimeMS INPUT_REPEAT_DELAY = 250;
+
 /**
  * The force of gravity on objects, exerted on the Y axis
  */
--- a/src/Graphics.cc	Mon Dec 08 21:15:52 2008 +0000
+++ b/src/Graphics.cc	Mon Dec 08 21:18:08 2008 +0000
@@ -20,9 +20,10 @@
     update_timer.start();
 }
 
-void Graphics::check_input (TimeMS dt) {
+void Graphics::check_input (void) {
     LocalPlayer *player;
-    PlayerInput input_mask = 0;
+    PlayerInput input_mask;
+    TimeMS input_dt;
     
     // update gui flags
     this->flags = input.readGuiInput();
@@ -43,11 +44,11 @@
         player->printDebugInfo();
     
     // build input_mask
-    input_mask = input.readPlayerInput();
+    input.readPlayerInput(input_mask, input_dt);
     
     // apply input if there was any
     if (input_mask)
-        player->handleInput(input_mask, dt);
+        player->handleInput(input_mask, input_dt);
 }
 
 static PixelDimension value_between (PixelDimension low, PixelDimension value, PixelDimension high) {
@@ -91,8 +92,10 @@
 }
 
 void Graphics::on_update (TimeMS tick_length) {
+    (void) tick_length;
+
     // check keyboard input
-    check_input(tick_length);
+    check_input();
 
     // redraw display
     do_redraw();
--- a/src/Graphics.hh	Mon Dec 08 21:15:52 2008 +0000
+++ b/src/Graphics.hh	Mon Dec 08 21:18:08 2008 +0000
@@ -42,7 +42,7 @@
     CL_Font& getSimpleFont (void) { return simple_font; }
     
 private:
-    void check_input (TimeMS dt);
+    void check_input (void);
     void do_redraw (void);
     
     void on_update (TimeMS tick_length);
--- a/src/Input.cc	Mon Dec 08 21:15:52 2008 +0000
+++ b/src/Input.cc	Mon Dec 08 21:18:08 2008 +0000
@@ -1,44 +1,108 @@
-
-#define INPUT_CC
 
 #include "Input.hh"
-
-template <typename BitEnumType> struct InputKeymapEntry {
-    BitEnumType input;
-    int keycode1, keycode2;
-};
+#include "Config.hh"
 
 InputKeymapEntry<PlayerInputBit> INPUT_PLAYER_KEYMAP[] = {
-    {   INPUT_AIM_UP,       -CL_KEY_ENTER,      CL_KEY_UP       },
-    {   INPUT_AIM_DOWN,     -CL_KEY_ENTER,      CL_KEY_DOWN     },
-    {   INPUT_MOVE_LEFT,    -CL_KEY_ENTER,      CL_KEY_LEFT     },
-    {   INPUT_MOVE_RIGHT,   -CL_KEY_ENTER,      CL_KEY_RIGHT    },
-    {   INPUT_JUMP,         -CL_KEY_ENTER,      CL_KEY_RSHIFT   },
-    {   INPUT_DIG,          CL_KEY_LEFT,        CL_KEY_RIGHT    },
-    {   INPUT_SHOOT,        CL_KEY_RCONTROL,    0               },
-    {   INPUT_CHANGE_PREV,  CL_KEY_ENTER,       CL_KEY_LEFT     },
-    {   INPUT_CHANGE_NEXT,  CL_KEY_ENTER,       CL_KEY_RIGHT    },
-    {   INPUT_ROPE,         CL_KEY_ENTER,       CL_KEY_RSHIFT   },
-    {   INPUT_UNROPE,       -CL_KEY_ENTER,      CL_KEY_RSHIFT   },
-    {   INPUT_ROPE_UP,      CL_KEY_ENTER,       CL_KEY_UP       },
-    {   INPUT_ROPE_DOWN,    CL_KEY_ENTER,       CL_KEY_DOWN     },
-    {   INPUT_SUICIDE,      CL_KEY_LCONTROL,    CL_KEY_K        },
-    {   INPUT_NONE,         0,                  0               }
+    {   INPUT_AIM_UP,       INPUT_FLAG_REPEAT,      { -CL_KEY_ENTER,    CL_KEY_UP       } },
+    {   INPUT_AIM_DOWN,     INPUT_FLAG_REPEAT,      { -CL_KEY_ENTER,    CL_KEY_DOWN     } },
+    {   INPUT_MOVE_LEFT,    INPUT_FLAG_REPEAT,      { -CL_KEY_ENTER,    CL_KEY_LEFT     } },
+    {   INPUT_MOVE_RIGHT,   INPUT_FLAG_REPEAT,      { -CL_KEY_ENTER,    CL_KEY_RIGHT    } },
+    {   INPUT_JUMP,         INPUT_FLAG_NOREPEAT,    { -CL_KEY_ENTER,    CL_KEY_RSHIFT   } },
+    {   INPUT_DIG,          INPUT_FLAG_NOREPEAT,    { CL_KEY_LEFT,      CL_KEY_RIGHT    } },
+    {   INPUT_SHOOT,        INPUT_FLAG_REPEAT,      { CL_KEY_RCONTROL,  0               } },
+    {   INPUT_CHANGE_PREV,  INPUT_FLAG_NOREPEAT,    { CL_KEY_ENTER,     CL_KEY_LEFT     } },
+    {   INPUT_CHANGE_NEXT,  INPUT_FLAG_NOREPEAT,    { CL_KEY_ENTER,     CL_KEY_RIGHT    } },
+    {   INPUT_ROPE,         INPUT_FLAG_NOREPEAT,    { CL_KEY_ENTER,     CL_KEY_RSHIFT   } },
+    {   INPUT_UNROPE,       INPUT_FLAG_NOREPEAT,    { -CL_KEY_ENTER,    CL_KEY_RSHIFT   } },
+    {   INPUT_ROPE_UP,      INPUT_FLAG_REPEAT,      { CL_KEY_ENTER,     CL_KEY_UP       } },
+    {   INPUT_ROPE_DOWN,    INPUT_FLAG_REPEAT,      { CL_KEY_ENTER,     CL_KEY_DOWN     } },
+    {   INPUT_SUICIDE,      INPUT_FLAG_NOREPEAT,    { CL_KEY_LCONTROL,  CL_KEY_K        } },
+    {   INPUT_NONE,         0,                      { 0,                0               } }
 };
 
 InputKeymapEntry<GuiInputBit> INPUT_GUI_KEYMAP[] = {
-    {   GUI_INPUT_QUIT,             CL_KEY_ESCAPE,          0   },
-    {   GUI_INPUT_DISPLAY_WEAPON,   CL_KEY_ENTER,           0   },
-    {   GUI_INPUT_DEBUG_PLAYER,     CL_KEY_I,               0   },
+    {   GUI_INPUT_QUIT,             0,              { CL_KEY_ESCAPE,    0               } },
+    {   GUI_INPUT_DISPLAY_WEAPON,   0,              { CL_KEY_ENTER,     0               } },
+    {   GUI_INPUT_DEBUG_PLAYER,     0,              { CL_KEY_I,         0               } },
+    {   GUI_INPUT_NONE,             0,              { 0,                0,              } }
 };
 
-Input::Input (CL_InputDevice &keyboard) :
-    keyboard(keyboard)
+/*
+ * InputKeyRepeatEntry
+ */
+template <typename BitEnumType> 
+InputKeyRepeatEntry<BitEnumType>::InputKeyRepeatEntry (BitEnumType value, TimeMS expire) : 
+    value(value), expire(expire) 
 {
 
 }
+
+template <typename BitEnumType> 
+bool InputKeyRepeatEntry<BitEnumType>::operator< (const struct InputKeyRepeatEntry &other) {
+    return other.expire > expire;
+}
+
+template <typename BitEnumType> 
+bool InputKeyRepeatEntry<BitEnumType>::updateExpired (TimeMS dt) {
+    expire -= dt;
+
+    return (expire <= 0);
+}
+
+/*
+ * InputKeyRepeatQueue
+ */
+template <typename BitEnumType> 
+InputKeyRepeatQueue<BitEnumType>::InputKeyRepeatQueue (TimeMS expire) :
+    expire(expire)
+{
+
+}
+
+template <typename BitEnumType> 
+void InputKeyRepeatQueue<BitEnumType>::push (BitEnumType bit) {
+    list.push_back(InputKeyRepeatEntry<BitEnumType>(bit, expire));
+}
+
+template <typename BitEnumType> 
+bool InputKeyRepeatQueue<BitEnumType>::find (BitEnumType bit) {
+    for (list_iterator it = list.begin(); it != list.end(); it++) {
+        if (it->value == bit)
+            return true;
+    }
+
+    return false;
+}
+
+template <typename BitEnumType> 
+void InputKeyRepeatQueue<BitEnumType>::update (TimeMS dt) {
+    list_iterator it = list.begin();
     
-bool Input::checkKeycode (int keycode) {
+    // go through each entry, updateExpired and remove if expired
+    while (it != list.end()) {
+        if (it->updateExpired(dt))
+            it = list.erase(it);
+        else
+            it++;
+    }
+}
+
+/*
+ * InputHandler
+ */
+template <typename BitEnumType, typename BitMaskType> 
+InputHandler<BitEnumType, BitMaskType>::InputHandler (CL_InputDevice &keyboard, InputKeymapEntry<BitEnumType> *keymap, TimeMS keyrepeat_expire) :
+    keyboard(keyboard), 
+    keymap(keymap), 
+    value(0), 
+    dt(0),
+    queue(keyrepeat_expire)
+{
+
+}
+
+template <typename BitEnumType, typename BitMaskType> 
+bool InputHandler<BitEnumType, BitMaskType>::checkKeycode (int keycode) {
     if (keycode > 0)
         return keyboard.get_keycode(keycode);
 
@@ -48,23 +112,71 @@
     else // == 0
         return true;
 }
-
-template <typename BitEnumType, typename BitMaskType> BitMaskType Input::buildMask (InputKeymapEntry<BitEnumType> *keymap) {
-    BitMaskType input_mask = 0;
+        
+template <typename BitEnumType, typename BitMaskType> 
+void InputHandler<BitEnumType, BitMaskType>::readValue (BitMaskType &mask, TimeMS &dt) {
+    // copy to args
+    mask = this->value;
+    dt = this->dt;
 
-    for (InputKeymapEntry<BitEnumType> *e = keymap; (e->keycode1 || e->keycode2) && e->input; e++) {
-        if (checkKeycode(e->keycode1) && checkKeycode(e->keycode2))
-            input_mask |= e->input;
-    }
-    
-    return input_mask;
+    this->value = 0;
+    this->dt = 0;
 }
 
-PlayerInput Input::readPlayerInput (void) {
-    return buildMask<PlayerInputBit, PlayerInput>(INPUT_PLAYER_KEYMAP);
+template <typename BitEnumType, typename BitMaskType> 
+void InputHandler<BitEnumType, BitMaskType>::update (TimeMS dt) {
+    // update the key-repeat queue
+    queue.update(dt);
+
+    // then go through the keymap
+    for (InputKeymapEntry<BitEnumType> *e = keymap; e->input != 0; e++) {
+        // check if we've got the correct keycodes
+        if (checkKeycode(e->keycodes[0]) && checkKeycode(e->keycodes[1])) {
+            // should we do keyrepeats?
+            if (e->flags & INPUT_FLAG_NOREPEAT) {
+                // if it's in the queue, ignore, else add it
+                if (queue.find(e->input))
+                    continue;
+                else
+                    queue.push(e->input);
+            }
+
+            // set bit in mask
+            this->value |= e->input;
+        }
+    }
+
+    // then increment our dt
+    this->dt += dt;
+}
+
+/**
+ * Input
+ */
+Input::Input (CL_InputDevice &keyboard) :
+    keyboard(keyboard),
+    update_timer(INPUT_POLL_INTERVAL),
+    player(keyboard, INPUT_PLAYER_KEYMAP, INPUT_REPEAT_DELAY),
+    gui(keyboard, INPUT_GUI_KEYMAP, INPUT_REPEAT_DELAY)
+{
+    // connect timer 
+    slots.connect(update_timer.sig_tick(), &player, &InputHandler<PlayerInputBit, PlayerInput>::update);
+    slots.connect(update_timer.sig_tick(), &gui, &InputHandler<GuiInputBit, GuiInput>::update);
+
+    // enable timer
+    update_timer.start();
+}
+
+void Input::readPlayerInput (PlayerInput &mask, TimeMS &dt) {
+    player.readValue(mask, dt);
 }
 
 GuiInput Input::readGuiInput (void) {
-    return buildMask<GuiInputBit, GuiInput>(INPUT_GUI_KEYMAP);
+    GuiInput mask;
+    TimeMS dt;
+
+    gui.readValue(mask, dt);
+
+    return mask;
 }
 
--- a/src/Input.hh	Mon Dec 08 21:15:52 2008 +0000
+++ b/src/Input.hh	Mon Dec 08 21:18:08 2008 +0000
@@ -1,13 +1,25 @@
 #ifndef INPUT_HH
 #define INPUT_HH
 
-#include <stdint.h>
+#include "Timer.hh"
+#include "Types.hh"
+
 #include <ClanLib/Display/input_device.h>
 #include <ClanLib/Display/keys.h>
+#include <queue>
 
 // const TimeMS INPUT_INTERVAL_MS = 20;
 
 /**
+ * Flags to control input behaviour
+ */
+enum InputFlagBits {
+    /** Default */
+    INPUT_FLAG_REPEAT   = 0x0000,
+    INPUT_FLAG_NOREPEAT = 0x0001,
+};
+
+/**
  * The bits used in the PlayerInput bitmask, each represents a separate action handled by LocalPlayer::handleInput.
  *
  * @see LocalPlayer::handleInput
@@ -38,12 +50,19 @@
  * The bits used in the GuiInput bitmask, each represents something handled locally by Graphics.
  */
 enum GuiInputBit {
+    GUI_INPUT_NONE              = 0x0000,
+
     GUI_INPUT_QUIT              = 0x0001,
     GUI_INPUT_DISPLAY_WEAPON    = 0x0002,
     GUI_INPUT_DEBUG_PLAYER      = 0x0004,
 };
 
 /**
+ * Bitmask of InputFlagBits
+ */
+typedef uint8_t InputFlags;
+
+/**
  * Bitmask of PlayerInputBits
  *
  * @see PlayerInputBit
@@ -58,29 +77,191 @@
 typedef uint16_t GuiInput;
 
 /**
- * Keymap definition used in Input.cc
+ * Keymap definition struct
  */
-template <typename BitEnumType> struct InputKeymapEntry;
+template <typename BitEnumType> struct InputKeymapEntry {
+    /**
+     * The input bit to set if present
+     */
+    BitEnumType input;
+
+    /**
+     * Flags to use
+     *
+     * @see InputFlagBits
+     */
+    InputFlags flags;
+
+    /**
+     * Up to two keycodes to check
+     */
+    int keycodes[2];
+};
+
+
+/**
+ * A InputKeyRepeatQueue entry, this contains the input bit value itself, and then the remaining expire time
+ */
+template <typename BitEnumType> struct InputKeyRepeatEntry {
+        BitEnumType value;
+        TimeMS expire;
+
+    public:
+        InputKeyRepeatEntry (BitEnumType value, TimeMS expire);
+        
+        /**
+         * Since priority_queue always gives the greatest item, the one with the longest expire is the least item
+         */
+        bool operator< (const struct InputKeyRepeatEntry &other);
+        
+        /**
+         * Decrements expire, returning true if it has now expired, false otherwise
+         */
+        bool updateExpired (TimeMS dt);
+};
+
+/**
+ * A InputKeyRepeatQueue maintains a list of InputKeyRepeatEntry's, lets you add new input values, find old ones,
+ * and update the list
+ */
+template <typename BitEnumType> class InputKeyRepeatQueue {
+    private:
+        TimeMS expire;
+
+        typedef InputKeyRepeatEntry<BitEnumType> entry_type;
+
+        std::list<entry_type> list;
+
+        typedef typename std::list<entry_type>::iterator list_iterator;
+
+    public:
+        /**
+         * Constructs this queue to contain entries with the given expiry time
+         */
+        InputKeyRepeatQueue (TimeMS expire);
+        
+        /**
+         * Push a new input bit onto the queue
+         */
+        void push (BitEnumType bit);
+
+        /**
+         * Checks if the given input is in the queue
+         */
+        bool find (BitEnumType bit);
+        
+        /**
+         * Updates the list, removing expired items
+         */
+        void update (TimeMS dt);
+};
+
+/**
+ * An InputHandler uses an InputKeymapEntry to maintain a BitMaskType of current inputs
+ */
+template <typename BitEnumType, typename BitMaskType> class InputHandler {
+    private:
+        /**
+         * The keyboard that we read input from
+         */
+        CL_InputDevice &keyboard;
+        
+        /**
+         * The keymap that we use
+         */
+        InputKeymapEntry<BitEnumType> *keymap;
+        
+        /**
+         * The current bitmask value
+         */
+        BitMaskType value;
+
+        /**
+         * How long the bitmask was held...
+         */
+        TimeMS dt;
+        
+        /**
+         * The KeyRepeatQueue
+         */
+        InputKeyRepeatQueue<BitEnumType> queue;
+    
+    public:
+        /**
+         * Constructs the InputHandler using the given keyboard, keymap and key-repeat expire time
+         */
+        InputHandler (CL_InputDevice &keyboard, InputKeymapEntry<BitEnumType> *keymap, TimeMS keyrepeat_expire);
+    
+    private: 
+        /**
+         * Returns true if the keycode is valid, false if not.
+         *
+         * A positive keycode requires that the keycode be active, a negative keycode that the keycode be inactive,
+         * and a zero keycode always returns true.
+         *
+         * @param keycode A positive keycode to check that it's set, negative keycode to check that it's not set, or zero
+         * @returns bool true if positive+set/negavtive+notset/zero, false otherwise
+         */
+        bool checkKeycode (int keycode);
+    
+    public:
+        /**
+         * Updates the keyrepeat queue
+         */
+        void update (TimeMS dt);
+
+        /**
+         * Reads the current input mask value and length into mask and dt, and reset ours to zero
+         *
+         * @param mask our BitMaskType value is returned using this
+         * @param dt how long the input was held for
+         */
+        void readValue (BitMaskType &mask, TimeMS &dt);
+
+};
 
 /**
  * Handles reading input from a keyboard and mapping it to PlayerInput/GuiInput bitmasks
  */
 class Input {
     protected:
+        /**
+         * The keyboard device that we use
+         */
         CL_InputDevice &keyboard;
 
+        /**
+         * Our update timer
+         */
+        Timer update_timer;
+
+        CL_SlotContainer slots;
+        
+        /**
+         * Our PlayerInput
+         */
+        InputHandler<PlayerInputBit, PlayerInput> player;
+        
+        /**
+         * Our GuiInput
+         */
+        InputHandler<GuiInputBit, GuiInput> gui;
+
     public:
+        /**
+         * Build the input handler using the given keyboard and the default keymaps
+         */
         Input (CL_InputDevice &keyboard);
-    
-    private:
-        bool checkKeycode (int keycode);
-        template <typename BitEnumType, typename BitMaskType> BitMaskType buildMask (InputKeymapEntry<BitEnumType> *keymap);
 
     public:
-        /*
-         * Reads the keyboard to determine current state of the Player/Gui input
+        /**
+         * Reads the current PlayerInput value via mask, and the length of the input via dt
          */
-        PlayerInput readPlayerInput (void);
+        void readPlayerInput (PlayerInput &mask, TimeMS &dt);
+
+        /**
+         * Reads the current GuiInput mask and returns it
+         */
         GuiInput readGuiInput (void);
 };
 
--- a/src/Projectile.cc	Mon Dec 08 21:15:52 2008 +0000
+++ b/src/Projectile.cc	Mon Dec 08 21:18:08 2008 +0000
@@ -80,13 +80,14 @@
 
     if (visible) {
         PixelCoordinate pos = getCoordinate() - camera;
+        // XXX: scale
         PixelDimension radius = (unsigned int) weapon->getRadius();
 
         CL_Quad projectile(
-                pos.x,                      pos.y - weapon->getRadius(),
-                pos.x + weapon->getRadius(), pos.y,
-                pos.x,                      pos.y + weapon->getRadius(),
-                pos.x - weapon->getRadius(),             pos.y
+                pos.x,              pos.y - radius,
+                pos.x + radius,     pos.y,
+                pos.x,              pos.y + radius,
+                pos.x - radius,     pos.y
         );
         
         gc->fill_quad(projectile, CL_Color::green);
--- a/src/Rope.cc	Mon Dec 08 21:15:52 2008 +0000
+++ b/src/Rope.cc	Mon Dec 08 21:18:08 2008 +0000
@@ -6,7 +6,10 @@
 #include <stdexcept>
 
 Rope::Rope(Player &player) : 
-    PhysicsObject(player.state.world, ROPE_MASS, Vector(0,0), Vector(0,0), ROPE, 0.00, false), player(player), state(ROPE_FOLDED), pivotObject(NULL) 
+    PhysicsObject(player.state.world, ROPE_MASS, Vector(0,0), Vector(0,0), ROPE, 0.00, false), 
+    player(player), 
+    pivotObject(NULL),
+    state(ROPE_FOLDED) 
 {
     // XXX: better shape
     std::vector<Vector> shape(4);
@@ -38,7 +41,6 @@
 }
 
 void Rope::onCollision (Vector collisionPoint, PhysicsObject *other) {
-
     if (other != NULL) {
         if (other->getType() == PLAYER) {
             Player *target = dynamic_cast<Player*>(other);
--- a/src/Types.hh	Mon Dec 08 21:15:52 2008 +0000
+++ b/src/Types.hh	Mon Dec 08 21:18:08 2008 +0000
@@ -32,11 +32,11 @@
 /**
  * A time interval, measured in real milliseconds
  */
-typedef unsigned long TimeMS;
+typedef signed long TimeMS;
 
 /**
  * Abstract tick count, defined as a number of Timer::interval's against real time
  */
-typedef uint32_t TickCount;
+typedef int32_t TickCount;
 
 #endif