From 5d73b47cc06803f416f1da969ac0450e80ff5199 Mon Sep 17 00:00:00 2001 From: Nicolay Korslund Date: Sun, 4 Jul 2010 14:28:51 +0200 Subject: [PATCH] Added input component --- .gitignore | 1 + .gitmodules | 3 + input/dispatch_map.hpp | 75 ++++++++++++++++ input/dispatcher.hpp | 49 +++++++++++ input/func_binder.hpp | 104 +++++++++++++++++++++++ input/poller.hpp | 46 ++++++++++ input/tests/Makefile | 18 ++++ input/tests/dispatch_map_test.cpp | 54 ++++++++++++ input/tests/funcbind_test.cpp | 47 ++++++++++ input/tests/output/dispatch_map_test.out | 18 ++++ input/tests/output/funcbind_test.out | 15 ++++ input/tests/output/sdl_binder_test.out | 4 + input/tests/output/sdl_driver_test.out | 4 + input/tests/sdl_binder_test.cpp | 71 ++++++++++++++++ input/tests/sdl_driver_test.cpp | 31 +++++++ input/tests/test.sh | 18 ++++ mangle | 1 + testall.sh | 11 +++ 18 files changed, 570 insertions(+) create mode 100644 .gitmodules create mode 100644 input/dispatch_map.hpp create mode 100644 input/dispatcher.hpp create mode 100644 input/func_binder.hpp create mode 100644 input/poller.hpp create mode 100644 input/tests/Makefile create mode 100644 input/tests/dispatch_map_test.cpp create mode 100644 input/tests/funcbind_test.cpp create mode 100644 input/tests/output/dispatch_map_test.out create mode 100644 input/tests/output/funcbind_test.out create mode 100644 input/tests/output/sdl_binder_test.out create mode 100644 input/tests/output/sdl_driver_test.out create mode 100644 input/tests/sdl_binder_test.cpp create mode 100644 input/tests/sdl_driver_test.cpp create mode 100755 input/tests/test.sh create mode 160000 mangle create mode 100755 testall.sh diff --git a/.gitignore b/.gitignore index d6ff91a9e..23a5e931b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ *~ *.o +*_test diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 000000000..0a9a9121f --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "mangle"] + path = mangle + url = git://github.com/korslund/mangle diff --git a/input/dispatch_map.hpp b/input/dispatch_map.hpp new file mode 100644 index 000000000..356660e75 --- /dev/null +++ b/input/dispatch_map.hpp @@ -0,0 +1,75 @@ +#ifndef _INPUT_DISPATCHMAP_H +#define _INPUT_DISPATCHMAP_H + +#include +#include +#include + +namespace Input { + +/** + DispatchMap is a simple connection system that connects incomming + signals with outgoing signals. + + The signals can be connected one-to-one, many-to-one, one-to-many + or many-to-many. + + The dispatch map is completely system agnostic. It is a pure data + structure and all signals are just integer indices. It does not + delegate any actions, but used together with Dispatcher it can be + used to build an event system. + */ +struct DispatchMap +{ + typedef std::set OutList; + typedef std::map InMap; + + typedef OutList::iterator Oit; + typedef InMap::iterator Iit; + + InMap map; + + void bind(int in, int out) + { + map[in].insert(out); + } + + void unbind(int in, int out) + { + Iit it = map.find(in); + if(it != map.end()) + { + it->second.erase(out); + + // If there are no more elements, then remove the entire list + if(it->second.empty()) + map.erase(it); + } + } + + /// Check if a given input is bound to anything + bool isBound(int in) const + { + return map.find(in) != map.end(); + } + + /** + Get the list of outputs bound to the given input. Only call this + on inputs that you know are bound to something. + + The returned set is only intended for immediate iteration. Do not + store references to it. + */ + const OutList &getList(int in) const + { + assert(isBound(in)); + InMap::const_iterator it = map.find(in); + assert(it != map.end()); + const OutList &out = it->second; + assert(!out.empty()); + return out; + } +}; + +} +#endif diff --git a/input/dispatcher.hpp b/input/dispatcher.hpp new file mode 100644 index 000000000..107211a49 --- /dev/null +++ b/input/dispatcher.hpp @@ -0,0 +1,49 @@ +#ifndef _INPUT_DISPATCHER_H +#define _INPUT_DISPATCHER_H + +#include "dispatch_map.hpp" +#include "func_binder.hpp" +#include + +namespace Input { + +struct Dispatcher : Mangle::Input::Event +{ + DispatchMap map; + FuncBinder funcs; + + /** + Constructor. Takes the number of actions and passes it to + FuncBinder. + */ + Dispatcher(int actions) : funcs(actions) {} + + void bind(int action, int key) { map.bind(key, action); } + void unbind(int action, int key) { map.unbind(key, action); } + bool isBound(int key) const { return map.isBound(key); } + + /** + Instigate an event. It is translated through the dispatch map and + sent to the function bindings. + */ + typedef DispatchMap::OutList _O; + void event(Type type, int index, const void* p) + { + // No bindings, nothing happens + if(!isBound(index)) + return; + + // Only treat key-down events for now + if(type != EV_KeyDown) + return; + + // Get the mapped actions and execute them + const _O &list = map.getList(index); + _O::const_iterator it; + for(it = list.begin(); it != list.end(); it++) + funcs.call(*it, p); + } +}; + +} +#endif diff --git a/input/func_binder.hpp b/input/func_binder.hpp new file mode 100644 index 000000000..20119b07b --- /dev/null +++ b/input/func_binder.hpp @@ -0,0 +1,104 @@ +#ifndef _INPUT_FUNCBINDER_H +#define _INPUT_FUNCBINDER_H + +#include +#include +#include +#include + +namespace Input { + +/** + An Action defines the user defined action corresponding to a + binding. + + The first parameter is the action index that invoked this call. You + can assign the same function to multiple actions, and this can help + you keep track of which action was invoked. + + The second parameter is an optional user-defined parameter, + represented by a void pointer. In many cases it is practical to + point this to temporaries (stack values), so make sure not to store + permanent references to it unless you've planning for this on the + calling side as well. + */ +typedef boost::function Action; + +/** + The FuncBinder is a simple struct that binds user-defined indices + to functions. It is useful for binding eg. keyboard events to + specific actions in your program, but can potentially have many + other uses as well. + */ +class FuncBinder +{ + struct FuncBinding + { + std::string name; + Action action; + }; + + std::vector bindings; + +public: + /** + Constructor. Initialize the struct by telling it how many action + indices you intend to bind. + + The indices you use should be 0 <= i < number. + */ + FuncBinder(int number) : bindings(number) {} + + /** + Bind an action to an index. + */ + void bind(int index, Action action, const std::string &name="") + { + assert(index >= 0 && index < (int)bindings.size()); + + FuncBinding &fb = bindings[index]; + fb.action = action; + fb.name = name; + } + + /** + Unbind an index, reverting a previous bind(). + */ + void unbind(int index) + { + assert(index >= 0 && index < (int)bindings.size()); + + bindings[index] = FuncBinding(); + } + + /** + Call a specific action. Takes an optional parameter that is + passed to the action. + */ + void call(int index, const void *p=NULL) const + { + assert(index >= 0 && index < (int)bindings.size()); + + const FuncBinding &fb = bindings[index]; + if(fb.action) fb.action(index, p); + } + + /// Check if a given index is bound to anything + bool isBound(int index) const + { + assert(index >= 0 && index < (int)bindings.size()); + + return !bindings[index].action.empty(); + } + + /// Return the name associated with an action (empty if not bound) + const std::string &getName(int index) const + { + assert(index >= 0 && index < (int)bindings.size()); + + return bindings[index].name; + } +}; + +} +#endif diff --git a/input/poller.hpp b/input/poller.hpp new file mode 100644 index 000000000..794f79b74 --- /dev/null +++ b/input/poller.hpp @@ -0,0 +1,46 @@ +#ifndef _INPUT_POLLER_H +#define _INPUT_POLLER_H + +#include "dispatch_map.hpp" +#include + +namespace Input { + +/** The poller is used to check (poll) for keys rather than waiting + for events. */ +struct Poller +{ + DispatchMap map; + Mangle::Input::Driver &input; + + Poller(Mangle::Input::Driver &drv) + : input(drv) {} + + /** Bind or unbind a given action with a key. The action is the first + parameter, the key is the second. + */ + void bind(int in, int out) { map.bind(in, out); } + void unbind(int in, int out) { map.unbind(in, out); } + bool isBound(int in) const { return map.isBound(in); } + + /// Check whether a given action button is currently pressed. + typedef DispatchMap::OutList _O; + bool isDown(int index) const + { + // No bindings, no action + if(!isBound(index)) + return false; + + // Get all the keys bound to this action, and check them. + const _O &list = map.getList(index); + _O::const_iterator it; + for(it = list.begin(); it != list.end(); it++) + // If there's any match, we're good to go. + if(input.isDown(*it)) return true; + + return false; + } +}; + +} +#endif diff --git a/input/tests/Makefile b/input/tests/Makefile new file mode 100644 index 000000000..91a0b2663 --- /dev/null +++ b/input/tests/Makefile @@ -0,0 +1,18 @@ +GCC=g++ + +all: funcbind_test dispatch_map_test sdl_driver_test sdl_binder_test + +funcbind_test: funcbind_test.cpp ../func_binder.hpp + $(GCC) $< -o $@ + +dispatch_map_test: dispatch_map_test.cpp ../dispatch_map.hpp + $(GCC) $< -o $@ + +sdl_driver_test: sdl_driver_test.cpp + $(GCC) $< ../../mangle/input/servers/sdl_driver.cpp -o $@ -I/usr/include/SDL/ -lSDL -I../../ + +sdl_binder_test: sdl_binder_test.cpp + $(GCC) $< ../../mangle/input/servers/sdl_driver.cpp -o $@ -I/usr/include/SDL/ -lSDL -I../../ + +clean: + rm *_test diff --git a/input/tests/dispatch_map_test.cpp b/input/tests/dispatch_map_test.cpp new file mode 100644 index 000000000..5cc7e1ef2 --- /dev/null +++ b/input/tests/dispatch_map_test.cpp @@ -0,0 +1,54 @@ +#include +using namespace std; + +#include "../dispatch_map.hpp" + +using namespace Input; + +typedef DispatchMap::OutList OutList; +typedef OutList::const_iterator Cit; + +void showList(const DispatchMap::OutList &out) +{ + for(Cit it = out.begin(); + it != out.end(); it++) + { + cout << " " << *it << endl; + } +} + +void showAll(DispatchMap &map) +{ + cout << "\nPrinting everything:\n"; + for(DispatchMap::Iit it = map.map.begin(); + it != map.map.end(); it++) + { + cout << it->first << ":\n"; + showList(map.getList(it->first)); + } +} + +int main() +{ + cout << "Testing the dispatch map\n"; + + DispatchMap dsp; + + dsp.bind(1,9); + dsp.bind(2,-5); + dsp.bind(2,9); + dsp.bind(3,10); + dsp.bind(3,12); + dsp.bind(3,10); + + showAll(dsp); + + dsp.unbind(1,9); + dsp.unbind(5,8); + dsp.unbind(3,11); + dsp.unbind(3,12); + dsp.unbind(3,12); + + showAll(dsp); + return 0; +} diff --git a/input/tests/funcbind_test.cpp b/input/tests/funcbind_test.cpp new file mode 100644 index 000000000..4716f8b81 --- /dev/null +++ b/input/tests/funcbind_test.cpp @@ -0,0 +1,47 @@ +#include +using namespace std; + +#include "../func_binder.hpp" + +void f1(int i, const void *p) +{ + cout << " F1 i=" << i << endl; + + if(p) + cout << " Got a nice gift: " + << *((const float*)p) << endl; +} + +void f2(int i, const void *p) +{ + cout << " F2 i=" << i << endl; +} + +using namespace Input; + +int main() +{ + cout << "This will test the function binding system\n"; + + FuncBinder bnd(5); + + bnd.bind(0, &f1, "This is action 1"); + bnd.bind(1, &f2); + bnd.bind(2, &f1, "This is action 3"); + bnd.bind(3, &f2, "This is action 4"); + + bnd.unbind(2); + + for(int i=0; i<5; i++) + { + cout << "Calling " << i << ": '" << bnd.getName(i) << "'\n"; + bnd.call(i); + if(!bnd.isBound(i)) cout << " (not bound)\n"; + } + + cout << "\nCalling with parameter:\n"; + float f = 3.1415; + bnd.call(0, &f); + + return 0; +} diff --git a/input/tests/output/dispatch_map_test.out b/input/tests/output/dispatch_map_test.out new file mode 100644 index 000000000..01aa9d9d9 --- /dev/null +++ b/input/tests/output/dispatch_map_test.out @@ -0,0 +1,18 @@ +Testing the dispatch map + +Printing everything: +1: + 9 +2: + -5 + 9 +3: + 10 + 12 + +Printing everything: +2: + -5 + 9 +3: + 10 diff --git a/input/tests/output/funcbind_test.out b/input/tests/output/funcbind_test.out new file mode 100644 index 000000000..862c5c972 --- /dev/null +++ b/input/tests/output/funcbind_test.out @@ -0,0 +1,15 @@ +This will test the function binding system +Calling 0: 'This is action 1' + F1 i=0 +Calling 1: '' + F2 i=1 +Calling 2: '' + (not bound) +Calling 3: 'This is action 4' + F2 i=3 +Calling 4: '' + (not bound) + +Calling with parameter: + F1 i=0 + Got a nice gift: 3.1415 diff --git a/input/tests/output/sdl_binder_test.out b/input/tests/output/sdl_binder_test.out new file mode 100644 index 000000000..fd4eb90e3 --- /dev/null +++ b/input/tests/output/sdl_binder_test.out @@ -0,0 +1,4 @@ +Hold the Q key to quit: +You are running in script mode, aborting. Run this test with a parameter (any at all) to test the input loop properly + +Bye bye! diff --git a/input/tests/output/sdl_driver_test.out b/input/tests/output/sdl_driver_test.out new file mode 100644 index 000000000..fd4eb90e3 --- /dev/null +++ b/input/tests/output/sdl_driver_test.out @@ -0,0 +1,4 @@ +Hold the Q key to quit: +You are running in script mode, aborting. Run this test with a parameter (any at all) to test the input loop properly + +Bye bye! diff --git a/input/tests/sdl_binder_test.cpp b/input/tests/sdl_binder_test.cpp new file mode 100644 index 000000000..715e797bb --- /dev/null +++ b/input/tests/sdl_binder_test.cpp @@ -0,0 +1,71 @@ +#include +#include +#include +#include "../dispatcher.hpp" +#include "../poller.hpp" + +using namespace std; +using namespace Mangle::Input; +using namespace Input; + +enum Actions + { + A_Quit, + A_Left, + A_Right, + + A_LAST + }; + +bool quit=false; + +void doExit(int,const void*) +{ + quit = true; +} + +void goLeft(int,const void*) +{ + cout << "Going left\n"; +} + +int main(int argc, char** argv) +{ + SDL_Init(SDL_INIT_VIDEO); + SDL_SetVideoMode(640, 480, 0, SDL_SWSURFACE); + SDLDriver input; + Dispatcher disp(A_LAST); + Poller poll(input); + + input.setEvent(&disp); + + disp.funcs.bind(A_Quit, &doExit); + disp.funcs.bind(A_Left, &goLeft); + + disp.bind(A_Quit, SDLK_q); + disp.bind(A_Left, SDLK_a); + disp.bind(A_Left, SDLK_LEFT); + + poll.bind(A_Right, SDLK_d); + poll.bind(A_Right, SDLK_RIGHT); + + cout << "Hold the Q key to quit:\n"; + //input->setEvent(&mycb); + while(!quit) + { + input.capture(); + if(poll.isDown(A_Right)) + cout << "We're going right!\n"; + SDL_Delay(20); + + if(argc == 1) + { + cout << "You are running in script mode, aborting. Run this test with a parameter (any at all) to test the input loop properly\n"; + break; + } + } + cout << "\nBye bye!\n"; + + SDL_Quit(); + return 0; +} diff --git a/input/tests/sdl_driver_test.cpp b/input/tests/sdl_driver_test.cpp new file mode 100644 index 000000000..1771bcfe4 --- /dev/null +++ b/input/tests/sdl_driver_test.cpp @@ -0,0 +1,31 @@ +#include +#include +#include + +using namespace std; +using namespace Mangle::Input; + +int main(int argc, char** argv) +{ + SDL_Init(SDL_INIT_VIDEO); + SDL_SetVideoMode(640, 480, 0, SDL_SWSURFACE); + SDLDriver input; + + cout << "Hold the Q key to quit:\n"; + //input->setEvent(&mycb); + while(!input.isDown(SDLK_q)) + { + input.capture(); + SDL_Delay(20); + + if(argc == 1) + { + cout << "You are running in script mode, aborting. Run this test with a parameter (any at all) to test the input loop properly\n"; + break; + } + } + cout << "\nBye bye!\n"; + + SDL_Quit(); + return 0; +} diff --git a/input/tests/test.sh b/input/tests/test.sh new file mode 100755 index 000000000..2d07708ad --- /dev/null +++ b/input/tests/test.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +make || exit + +mkdir -p output + +PROGS=*_test + +for a in $PROGS; do + if [ -f "output/$a.out" ]; then + echo "Running $a:" + ./$a | diff output/$a.out - + else + echo "Creating $a.out" + ./$a > "output/$a.out" + git add "output/$a.out" + fi +done diff --git a/mangle b/mangle new file mode 160000 index 000000000..7583fc3f1 --- /dev/null +++ b/mangle @@ -0,0 +1 @@ +Subproject commit 7583fc3f1bfa6d0fde56c925da959a5e9e50031a diff --git a/testall.sh b/testall.sh new file mode 100755 index 000000000..097fdabd5 --- /dev/null +++ b/testall.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +function run() +{ + echo "TESTING $1" + cd "$1/tests/" + ./test.sh + cd ../../ +} + +run input