From 3a26219cb82ab54acc5645fc5a0d69650d202799 Mon Sep 17 00:00:00 2001 From: Nicolay Korslund Date: Mon, 7 Jun 2010 14:48:08 +0200 Subject: [PATCH] Created event dispatcher and function binder (for input system) --- input/dispatch_map.hpp | 73 +++++++++++++++ input/event_dispatcher.hpp | 78 +++++++++++++++ input/func_binder.hpp | 99 ++++++++++++++++++++ input/listener.hpp | 2 - input/tests/Makefile | 12 ++- input/tests/dispatch_map_test.cpp | 54 +++++++++++ input/tests/event_dispatcher_test.cpp | 54 +++++++++++ input/tests/funcbind_test.cpp | 77 ++++----------- input/tests/output/dispatch_map_test.out | 18 ++++ input/tests/output/event_dispatcher_test.out | 30 ++++++ input/tests/output/funcbind_test.out | 30 +++--- 11 files changed, 448 insertions(+), 79 deletions(-) create mode 100644 input/dispatch_map.hpp create mode 100644 input/event_dispatcher.hpp create mode 100644 input/func_binder.hpp create mode 100644 input/tests/dispatch_map_test.cpp create mode 100644 input/tests/event_dispatcher_test.cpp create mode 100644 input/tests/output/dispatch_map_test.out create mode 100644 input/tests/output/event_dispatcher_test.out diff --git a/input/dispatch_map.hpp b/input/dispatch_map.hpp new file mode 100644 index 000000000..6e2d8dbc8 --- /dev/null +++ b/input/dispatch_map.hpp @@ -0,0 +1,73 @@ +#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) + { + 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) + { + assert(isBound(in)); + const OutList &out = map[in]; + assert(!out.empty()); + return out; + } +}; + +} +#endif diff --git a/input/event_dispatcher.hpp b/input/event_dispatcher.hpp new file mode 100644 index 000000000..7c0b328e6 --- /dev/null +++ b/input/event_dispatcher.hpp @@ -0,0 +1,78 @@ +#ifndef _INPUT_EVENT_DISPATCHER_H +#define _INPUT_EVENT_DISPATCHER_H + +#include "dispatch_map.hpp" +#include + +namespace Input { + + /** + The EventDispatcher translates an input event (as given by an + identifying index and an optional void pointer) into an output + event function call. + */ + class EventDispatcher + { + DispatchMap map; + + /* + The event callback function that is called for all events. The + first parameter is the input event. The second parameter is the + resolved output event. The third is an optional user-defined + parameter passed to call(). + */ + typedef boost::function EventCallback; + + EventCallback callback; + + // Carpal-tunnel prevention + typedef DispatchMap::OutList _OL; + + public: + + /// Create an event binding connection + void bind(int in, int out) + { map.bind(in,out); } + + /// Dissolve an event binding connection + void unbind(int in, int out) + { map.unbind(in, out); } + + /// Check if a given input is bound to anything + bool isBound(int in) + { return map.isBound(in); } + + /// Register the callback you want to use to handle events. + void setCallback(EventCallback cb) + { callback = cb; } + + /** + Instigate an event. + + This will look up the input event number (first parameter), and + call the event callback for each output number associated with + (bound to) that input. + + The optional second paramter is also passed to the callback. + + If no output is bound to the given event number, the callback + is never called. + */ + void call(int event, void *p = NULL) + { + // You have to set the callback before using call(). + assert(!callback.empty()); + + // Not bound? Exit. + if(!isBound(event)) return; + + // Dispatch to all events. + const _OL &list = map.getList(event); + for(_OL::const_iterator it = list.begin(); + it != list.end(); it++) + callback(event, *it, p); + } + }; + +} +#endif diff --git a/input/func_binder.hpp b/input/func_binder.hpp new file mode 100644 index 000000000..036d4f8b3 --- /dev/null +++ b/input/func_binder.hpp @@ -0,0 +1,99 @@ +#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 < 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 < bindings.size()); + bindings[index] = FuncBinding(); + } + + /** + Call a specific action. Takes an optional parameter that is + passed to the action. + */ + void call(int index, void *p=NULL) + { + assert(index >= 0 && index < bindings.size()); + FuncBinding &fb = bindings[index]; + if(fb.action) fb.action(index, p); + } + + /// Check if a given index is bound to anything + bool isBound(int index) + { + assert(index >= 0 && index < bindings.size()); + return !bindings[index].action.empty(); + } + + /// Return the name associated with an action (empty if not bound) + const std::string &getName(int index) + { + assert(index >= 0 && index < bindings.size()); + return bindings[index].name; + } +}; + +} +#endif diff --git a/input/listener.hpp b/input/listener.hpp index 896c2b56a..4a65249c7 100644 --- a/input/listener.hpp +++ b/input/listener.hpp @@ -7,8 +7,6 @@ #include #include -//#include - namespace Input { struct ExitListener : Ogre::FrameListener, diff --git a/input/tests/Makefile b/input/tests/Makefile index 5246db9b5..0211fc764 100644 --- a/input/tests/Makefile +++ b/input/tests/Makefile @@ -1,9 +1,15 @@ GCC=g++ -all: funcbind_test +all: funcbind_test dispatch_map_test event_dispatcher_test -funcbind_test: funcbind_test.cpp - $(GCC) $^ -o $@ +funcbind_test: funcbind_test.cpp ../func_binder.hpp + $(GCC) $< -o $@ + +dispatch_map_test: dispatch_map_test.cpp ../dispatch_map.hpp + $(GCC) $< -o $@ + +event_dispatcher_test: event_dispatcher_test.cpp ../event_dispatcher.hpp + $(GCC) $< -o $@ 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/event_dispatcher_test.cpp b/input/tests/event_dispatcher_test.cpp new file mode 100644 index 000000000..181f9fad6 --- /dev/null +++ b/input/tests/event_dispatcher_test.cpp @@ -0,0 +1,54 @@ +#include +using namespace std; + +#include "../event_dispatcher.hpp" + +using namespace Input; + +void callback(int in, int out, void *p) +{ + cout << " Got event: in=" << in << " out=" << out << endl; +} + +EventDispatcher dsp; + +void callAll() +{ + cout << "\nDuty calls:\n"; + for(int i=1; i<5; i++) + { + cout << " Calling event " << i << ":\n"; + dsp.call(i); + } +} + +int main() +{ + cout << "Testing the event dispatcher\n"; + + dsp.setCallback(&callback); + + callAll(); + + dsp.bind(2,1); + dsp.bind(1,10); + dsp.bind(14,-12); + dsp.bind(2,-137); + + callAll(); + + dsp.unbind(1,8); + dsp.unbind(1,10); + dsp.unbind(2,-137); + dsp.unbind(2,1); + + callAll(); + + dsp.bind(3, 19); + dsp.bind(4, 18); + dsp.bind(4, 18); + + callAll(); + + return 0; +} diff --git a/input/tests/funcbind_test.cpp b/input/tests/funcbind_test.cpp index 178da9d32..636c590ca 100644 --- a/input/tests/funcbind_test.cpp +++ b/input/tests/funcbind_test.cpp @@ -1,84 +1,47 @@ #include using namespace std; -#include -#include -#include -#include +#include "../func_binder.hpp" -typedef boost::function Action; - -struct FuncBind +void f1(int i, void *p) { - std::string name; - Action action; -}; + cout << " F1 i=" << i << endl; -class Binder -{ - std::vector bindings; - -public: - /** - Initialize the struct by telling it how many functions you have - to bind. The largest index you intend to use should be number-1. - */ - Binder(int number) : bindings(number) {} - - void bind(int index, Action action, const std::string &name="") - { - assert(index >= 0 && index < bindings.size()); - FuncBind &fb = bindings[index]; - fb.action = action; - fb.name = name; - } - - void unbind(int index) - { - assert(index >= 0 && index < bindings.size()); - FuncBind &fb = bindings[index]; - fb = FuncBind(); - } - - void call(int index) - { - assert(index >= 0 && index < bindings.size()); - FuncBind &fb = bindings[index]; - if(fb.action) - { - cout << "Calling '" << fb.name << "'\n"; - fb.action(); - } - else - cout << "No function\n"; - } -}; - -void f1() -{ - cout << "In f1()\n"; + if(p) + cout << " Got a nice gift: " + << *((float*)p) << endl; } -void f2() +void f2(int i, void *p) { - cout << "In f2()\n"; + cout << " F2 i=" << i << endl; } +using namespace Input; + int main() { cout << "This will test the function binding system\n"; - Binder bnd(5); + 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 << "\nCalling " << i << endl; + 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/event_dispatcher_test.out b/input/tests/output/event_dispatcher_test.out new file mode 100644 index 000000000..f86aa2830 --- /dev/null +++ b/input/tests/output/event_dispatcher_test.out @@ -0,0 +1,30 @@ +Testing the event dispatcher + +Duty calls: + Calling event 1: + Calling event 2: + Calling event 3: + Calling event 4: + +Duty calls: + Calling event 1: + Got event: in=1 out=10 + Calling event 2: + Got event: in=2 out=-137 + Got event: in=2 out=1 + Calling event 3: + Calling event 4: + +Duty calls: + Calling event 1: + Calling event 2: + Calling event 3: + Calling event 4: + +Duty calls: + Calling event 1: + Calling event 2: + Calling event 3: + Got event: in=3 out=19 + Calling event 4: + Got event: in=4 out=18 diff --git a/input/tests/output/funcbind_test.out b/input/tests/output/funcbind_test.out index 21d5a7f44..862c5c972 100644 --- a/input/tests/output/funcbind_test.out +++ b/input/tests/output/funcbind_test.out @@ -1,19 +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 0 -Calling 'This is action 1' -In f1() - -Calling 1 -Calling '' -In f2() - -Calling 2 -Calling 'This is action 3' -In f1() - -Calling 3 -No function - -Calling 4 -No function +Calling with parameter: + F1 i=0 + Got a nice gift: 3.1415