diff --git a/inc/MemberFunctionCallback.h b/inc/MemberFunctionCallback.h new file mode 100644 index 0000000..b2ada58 --- /dev/null +++ b/inc/MemberFunctionCallback.h @@ -0,0 +1,77 @@ +#ifndef MEMBER_FUNCTION_CALLBACK_H +#define MEMBER_FUNCTION_CALLBACK_H + +#include "mbed.h" +#include "MicroBitEvent.h" + +/** + * Class definition for a MemberFunctionCallback. + * + * C++ member functions (also known as methods) have a more complex + * representation than normal C functions. This allows a referene to + * a C++ member function to be stored then called at a later date. + * + * This class is used extensively by the MicroBitMessageBus to deliver + * events to C++ methods. + */ +class MemberFunctionCallback +{ + private: + void* object; + uint32_t method[4]; + void (*invoke)(void *object, uint32_t *method, MicroBitEvent e); + template static void methodCall(void* object, uint32_t*method, MicroBitEvent e); + + public: + + /** + * Constructor. Creates a MemberFunctionCallback based on a pointer to given method. + * @param object The object the callback method should be invooked on. + * @param method The method to invoke. + */ + template MemberFunctionCallback(T* object, void (T::*method)(MicroBitEvent e)); + + /** + * Comparison of two MemberFunctionCallback objects. + * @return TRUE if the given MemberFunctionCallback is equivalent to this one. FALSE otherwise. + */ + bool operator==(const MemberFunctionCallback &mfc); + + /** + * Calls the method reference held by this MemberFunctionCallback. + * @param e The event to deliver to the method + */ + void fire(MicroBitEvent e); +}; + +/** + * Constructor. Creates a representation of a pointer to a C++ member function (method). + * @param object The object the callback method should be invooked on. + * @param method The method to invoke. + */ +template +MemberFunctionCallback::MemberFunctionCallback(T* object, void (T::*method)(MicroBitEvent e)) +{ + this->object = object; + memclr(this->method, sizeof(method)); + memcpy(this->method, &method, sizeof(method)); + invoke = &MemberFunctionCallback::methodCall; +} + +/** + * Template to create static methods capable of invoking a C++ member function (method) + * based on the given paramters. + */ +template +void MemberFunctionCallback::methodCall(void *object, uint32_t *method, MicroBitEvent e) +{ + T* o = (T*)object; + void (T::*m)(MicroBitEvent); + memcpy(&m, method, sizeof(m)); + + (o->*m)(e); +} + +#endif + + diff --git a/inc/MicroBit.h b/inc/MicroBit.h index ca85a29..e48152b 100644 --- a/inc/MicroBit.h +++ b/inc/MicroBit.h @@ -4,10 +4,6 @@ #include "mbed.h" #include "MicroBitConfig.h" #include "MicroBitPanic.h" - -#include "ble/BLE.h" -#include "ble/services/DeviceInformationService.h" - #include "ErrorNo.h" #include "MicroBitHeapAllocator.h" @@ -15,9 +11,10 @@ #include "MicroBitFiber.h" #include "ManagedType.h" #include "ManagedString.h" + +#include "MicroBitEvent.h" #include "MicroBitFont.h" #include "MicroBitImage.h" -#include "MicroBitEvent.h" #include "MicroBitMessageBus.h" #include "DynamicPwm.h" #include "MicroBitComponent.h" @@ -31,6 +28,8 @@ #include "MicroBitCompass.h" #include "MicroBitAccelerometer.h" +#include "ble/BLE.h" +#include "ble/services/DeviceInformationService.h" #include "MicroBitDFUService.h" #include "MicroBitEventService.h" #include "ExternalEvents.h" diff --git a/inc/MicroBitListener.h b/inc/MicroBitListener.h new file mode 100644 index 0000000..7f60cfd --- /dev/null +++ b/inc/MicroBitListener.h @@ -0,0 +1,81 @@ +#ifndef MICROBIT_LISTENER_H +#define MICROBIT_LISTENER_H + +#include "mbed.h" +#include "MicroBitEvent.h" + +// MessageBusListener flags... +#define MESSAGE_BUS_LISTENER_PARAMETERISED 0x0001 +#define MESSAGE_BUS_LISTENER_METHOD 0x0002 +#define MESSAGE_BUS_LISTENER_REENTRANT 0x0004 +#define MESSAGE_BUS_LISTENER_BUSY 0x0008 + +struct MicroBitListener +{ + uint16_t id; // The ID of the component that this listener is interested in. + uint16_t value; // Value this listener is interested in receiving. + uint16_t flags; // Status and configuration options codes for this listener. + + union + { + void (*cb)(MicroBitEvent); + void (*cb_param)(MicroBitEvent, void *); + MemberFunctionCallback *cb_method; + }; + + void* cb_arg; // Optional argument to be passed to the caller. + + MicroBitEvent evt; + + MicroBitListener *next; + + /** + * Constructor. + * Create a new Message Bus Listener. + * @param id The ID of the component you want to listen to. + * @param value The event ID you would like to listen to from that component + * @param handler A function pointer to call when the event is detected. + */ + MicroBitListener(uint16_t id, uint16_t value, void (*handler)(MicroBitEvent)); + + /** + * Alternative constructor where we register a value to be passed to the + * callback. + */ + MicroBitListener(uint16_t id, uint16_t value, void (*handler)(MicroBitEvent, void *), void* arg); + + /** + * Constructor. + * Create a new Message Bus Listener, with a callback to a c++ member function. + * @param id The ID of the component you want to listen to. + * @param value The event ID you would like to listen to from that component. + * @param object The C++ object on which to call the event handler. + * @param object The method within the C++ object to call. + */ + template + MicroBitListener(uint16_t id, uint16_t value, T* object, void (T::*method)(MicroBitEvent)); +}; + +/** + * Constructor. + * Create a new Message Bus Listener, with a callback to a c++ member function. + * @param id The ID of the component you want to listen to. + * @param value The event ID you would like to listen to from that component. + * @param object The C++ object on which to call the event handler. + * @param object The method within the C++ object to call. + */ + +template +MicroBitListener::MicroBitListener(uint16_t id, uint16_t value, T* object, void (T::*method)(MicroBitEvent)) +{ + this->id = id; + this->value = value; + this->cb_method = new MemberFunctionCallback(object, method); + this->cb_arg = NULL; + this->flags = MESSAGE_BUS_LISTENER_METHOD; + this->next = NULL; +} + +#endif + + diff --git a/inc/MicroBitMessageBus.h b/inc/MicroBitMessageBus.h index 2106c7a..ca3069d 100644 --- a/inc/MicroBitMessageBus.h +++ b/inc/MicroBitMessageBus.h @@ -2,6 +2,8 @@ #define MICROBIT_MESSAGE_BUS_H #include "mbed.h" +#include "MemberFunctionCallback.h" +#include "MicroBitListener.h" #include "MicroBitComponent.h" #include "MicroBitEvent.h" @@ -10,33 +12,9 @@ #define MICROBIT_ID_ANY 0 #define MICROBIT_EVT_ANY 0 -struct MicroBitListener -{ - uint16_t id; // The ID of the component that this listener is interested in. - uint16_t value; // Value this listener is interested in receiving. - void* cb; // Callback function associated with this listener. Either (*cb)(MicroBitEvent) or (*cb)(MicroBitEvent, void*) depending on whether cb_arg is NULL. - void* cb_arg; // Argument to be passed to the caller. This is assumed to be a pointer, so passing in NULL means that the function doesn't take an argument. - MicroBitEvent evt; - - MicroBitListener *next; - - /** - * Constructor. - * Create a new Message Bus Listener. - * @param id The ID of the component you want to listen to. - * @param value The event ID you would like to listen to from that component - * @param handler A function pointer to call when the event is detected. - */ - MicroBitListener(uint16_t id, uint16_t value, void (*handler)(MicroBitEvent)); - - /** - * Alternative constructor where we register a value to be passed to the - * callback. If arg == NULL, the function takes no extra arguemnt. - * Otherwise, the function is understood to take an extra argument. - */ - MicroBitListener(uint16_t id, uint16_t value, void *handler, void* arg); -}; - +/** + * Enclosing class to hold a chain of events. + */ struct MicroBitEventQueueItem { MicroBitEvent evt; @@ -50,6 +28,7 @@ struct MicroBitEventQueueItem MicroBitEventQueueItem(MicroBitEvent evt); }; + /** * Class definition for the MicroBitMessageBus. * @@ -136,14 +115,28 @@ class MicroBitMessageBus : public MicroBitComponent */ void listen(int id, int value, void (*handler)(MicroBitEvent, void*), void* arg); + /** + * Register a listener function. + * + * As above, but allows callbacks into member functions within a C++ object. + * This one is a bit more complex, but hey, that's C++ for you! + */ + template + void listen(uint16_t id, uint16_t value, T* object, void (T::*handler)(MicroBitEvent)); + private: - + + /** + * Add the given MicroBitListener to the list of event handlers, unconditionally. + * @param listener The MicroBitListener to validate. + * @return 1 if the listener is valid, 0 otherwise. + */ + int add(MicroBitListener *newListener); + MicroBitListener *listeners; // Chain of active listeners. MicroBitEventQueueItem *evt_queue_head; // Head of queued events to be processed. MicroBitEventQueueItem *evt_queue_tail; // Tail of queued events to be processed. - int seq; // Sequence number. Used to invalidate cache entries. - void listen(int id, int value, void* handler, void* arg); void queueEvent(MicroBitEvent &evt); MicroBitEventQueueItem* dequeueEvent(); @@ -151,6 +144,35 @@ class MicroBitMessageBus : public MicroBitComponent virtual int isIdleCallbackNeeded(); }; +/** + * A registrationt function to allow C++ member funcitons (methods) to be registered as an event + * listener. + * + * @param id The source of messages to listen for. Events sent from any other IDs will be filtered. + * Use MICROBIT_ID_ANY to receive events from all components. + * + * @param value The value of messages to listen for. Events with any other values will be filtered. + * Use MICROBIT_EVT_ANY to receive events of any value. + * + * @param object The object on which the method should be invoked. + * @param hander The method to call when an event is received. + */ +template +void MicroBitMessageBus::listen(uint16_t id, uint16_t value, T* object, void (T::*handler)(MicroBitEvent)) +{ + if (object == NULL || handler == NULL) + return; + + MicroBitListener *newListener = new MicroBitListener(id, value, object, handler); + + if(!add(newListener)) + { + delete newListener->cb_method; + delete newListener; + return; + } +} + #endif diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt index e23a9bc..abb249c 100755 --- a/source/CMakeLists.txt +++ b/source/CMakeLists.txt @@ -28,6 +28,8 @@ set(YOTTA_AUTO_MICROBIT-DAL_CPP_FILES "MicroBitPin.cpp" "MicroBitSerial.cpp" "MicroBitHeapAllocator.cpp" + "MicroBitListener.cpp" + "MemberFunctionCallback.cpp" ) if (YOTTA_CFG_MICROBIT_CONFIGFILE) diff --git a/source/MemberFunctionCallback.cpp b/source/MemberFunctionCallback.cpp new file mode 100644 index 0000000..d594165 --- /dev/null +++ b/source/MemberFunctionCallback.cpp @@ -0,0 +1,32 @@ +/** + * Class definition for a MemberFunctionCallback. + * + * C++ member functions (also known as methods) have a more complex + * representation than normal C functions. This allows a referene to + * a C++ member function to be stored then called at a later date. + * + * This class is used extensively by the MicroBitMessageBus to deliver + * events to C++ methods. + */ + +#include "MicroBit.h" + +/** + * Calls the method reference held by this MemberFunctionCallback. + * @param e The event to deliver to the method + */ +void MemberFunctionCallback::fire(MicroBitEvent e) +{ + invoke(object, method, e); +} + +/** + * Comparison of two MemberFunctionCallback objects. + * @return TRUE if the given MemberFunctionCallback is equivalent to this one. FALSE otherwise. + */ +bool MemberFunctionCallback::operator==(const MemberFunctionCallback &mfc) +{ + return (object == mfc.object && memcmp(method,mfc.method,sizeof(method))==0); +} + + diff --git a/source/MicroBitListener.cpp b/source/MicroBitListener.cpp new file mode 100644 index 0000000..933ab55 --- /dev/null +++ b/source/MicroBitListener.cpp @@ -0,0 +1,45 @@ +/** + * Class definition for a MicroBitListener. + * + * MicroBitListener holds all the information related to a single event handler required + * to match and fire event handlers to incoming events. + */ + +#include "mbed.h" +#include "MicroBit.h" + +/** + * Constructor. + * Create a new Message Bus Listener. + * @param id The ID of the component you want to listen to. + * @param value The event ID you would like to listen to from that component + * @param handler A function pointer to call when the event is detected. + */ +MicroBitListener::MicroBitListener(uint16_t id, uint16_t value, void (*handler)(MicroBitEvent)) +{ + this->id = id; + this->value = value; + this->cb = handler; + this->cb_arg = NULL; + this->flags = 0; + this->next = NULL; +} + +/** + * Constructor. + * Create a new parameterised Message Bus Listener. + * @param id The ID of the component you want to listen to. + * @param value The event ID you would like to listen to from that component. + * @param handler A function pointer to call when the event is detected. + * @param arg An additional argument to pass to the event handler function. + */ +MicroBitListener::MicroBitListener(uint16_t id, uint16_t value, void (*handler)(MicroBitEvent, void *), void* arg) +{ + this->id = id; + this->value = value; + this->cb_param = handler; + this->cb_arg = arg; + this->flags = MESSAGE_BUS_LISTENER_PARAMETERISED; + this->next = NULL; +} + diff --git a/source/MicroBitMessageBus.cpp b/source/MicroBitMessageBus.cpp index d48fa5f..d59582c 100644 --- a/source/MicroBitMessageBus.cpp +++ b/source/MicroBitMessageBus.cpp @@ -16,34 +16,7 @@ MicroBitEventQueueItem::MicroBitEventQueueItem(MicroBitEvent evt) this->evt = evt; this->next = NULL; } -/** - * Constructor. - * Create a new Message Bus Listener. - * @param id The ID of the component you want to listen to. - * @param value The event ID you would like to listen to from that component - * @param handler A function pointer to call when the event is detected. - */ -MicroBitListener::MicroBitListener(uint16_t id, uint16_t value, void (*handler)(MicroBitEvent)) -{ - this->id = id; - this->value = value; - this->cb = (void*) handler; - this->cb_arg = NULL; - this->next = NULL; -} -/** - * A low-level, internal version of the constructor, where the handler's type is determined - * by the value of arg. (See MicroBitMessageBus.h). - */ -MicroBitListener::MicroBitListener(uint16_t id, uint16_t value, void* handler, void* arg) -{ - this->id = id; - this->value = value; - this->cb = handler; - this->cb_arg = arg; - this->next = NULL; -} /** * Constructor. @@ -65,10 +38,21 @@ MicroBitMessageBus::MicroBitMessageBus() void async_callback(void *param) { MicroBitListener *listener = (MicroBitListener *)param; - if (listener->cb_arg != NULL) - ((void (*)(MicroBitEvent, void*))listener->cb)(listener->evt, listener->cb_arg); + + // Determine the calling convention for the callback, and invoke... + // C++ is really bad at this! Especially as the ARM compiler is yet to support C++ 11 :-/ + + // Firstly, check for a method callback into an object. + if (listener->flags & MESSAGE_BUS_LISTENER_METHOD) + listener->cb_method->fire(listener->evt); + + // Now a parameterised C function + else if (listener->flags & MESSAGE_BUS_LISTENER_PARAMETERISED) + listener->cb_param(listener->evt, listener->cb_arg); + + // We must have a plain C function else - ((void (*)(MicroBitEvent))listener->cb)(listener->evt); + listener->cb(listener->evt); } @@ -243,8 +227,6 @@ void MicroBitMessageBus::process(MicroBitEvent evt) * * @param hander The function to call when an event is received. * - * TODO: We currently don't support C++ member functions as callbacks, which we should. - * * Example: * @code * void onButtonBClick(MicroBitEvent evt) @@ -255,75 +237,110 @@ void MicroBitMessageBus::process(MicroBitEvent evt) * @endcode */ -void MicroBitMessageBus::listen(int id, int value, void (*handler)(MicroBitEvent, void*), void* arg) { - this->listen(id, value, (void*) handler, arg); -} +void MicroBitMessageBus::listen(int id, int value, void (*handler)(MicroBitEvent)) +{ + if (handler == NULL) + return; + + MicroBitListener *newListener = new MicroBitListener(id, value, handler); -void MicroBitMessageBus::listen(int id, int value, void (*handler)(MicroBitEvent)) { - this->listen(id, value, (void*) handler, NULL); + if(!add(newListener)) + { + delete newListener; + return; + } } -void MicroBitMessageBus::listen(int id, int value, void* handler, void* arg) +void MicroBitMessageBus::listen(int id, int value, void (*handler)(MicroBitEvent, void*), void* arg) { - //handler can't be NULL! if (handler == NULL) return; + MicroBitListener *newListener = new MicroBitListener(id, value, handler, arg); + + if(!add(newListener)) + { + delete newListener; + return; + } +} + + +/** + * Add the given MicroBitListener to the list of event handlers, unconditionally. + * @param listener The MicroBitListener to validate. + * @return 1 if the listener is valid, 0 otherwise. + */ +int MicroBitMessageBus::add(MicroBitListener *newListener) +{ MicroBitListener *l, *p; + int methodCallback; + + //handler can't be NULL! + if (newListener == NULL) + return 0; + + methodCallback = newListener->flags & MESSAGE_BUS_LISTENER_METHOD; l = listeners; // Firstly, we treat a listener as an idempotent operation. Ensure we don't already have this handler // registered in a that will already capture these events. If we do, silently ignore. - while (l != NULL) - { - if (l->id == id && l->value == value && l->cb == handler) - return; - l = l->next; - } + // We always check the ID, VALUE and CB_METHOD fields. + // If we have a callback to a method, check the cb_method class. Otherwise, the cb function point is sufficient. + while (l != NULL) + { + if (l->id == newListener->id && l->value == newListener->value && (methodCallback ? *l->cb_method == *newListener->cb_method : l->cb == newListener->cb)) + return 0; - MicroBitListener *newListener = new MicroBitListener(id, value, handler, arg); + l = l->next; + } - //if listeners is null - we can automatically add this listener to the list at the beginning... + // We have a valid, new event handler. Add it to the list. + // if listeners is null - we can automatically add this listener to the list at the beginning... if (listeners == NULL) { listeners = newListener; - return; + return 1; } - // Maintain an ordered list of listeners. - // Chain is held stictly in increasing order of ID (first level), then value code (second level). + // We maintain an ordered list of listeners. + // The chain is held stictly in increasing order of ID (first level), then value code (second level). // Find the correct point in the chain for this event. - // Adding a listener is a rare occurance, so we just walk the list. + // Adding a listener is a rare occurance, so we just walk the list... + p = listeners; l = listeners; - while (l != NULL && l->id < id) + while (l != NULL && l->id < newListener->id) { p = l; l = l->next; } - while (l != NULL && l->id == id && l->value < value) + while (l != NULL && l->id == newListener->id && l->value < newListener->value) { p = l; l = l->next; } //add at front of list - if (p == listeners && (id < p->id || (p->id == id && p->value > value))) + if (p == listeners && (newListener->id < p->id || (p->id == newListener->id && p->value > newListener->value))) { newListener->next = p; //this new listener is now the front! listeners = newListener; } + //add after p else { newListener->next = p->next; p->next = newListener; } + + return 1; }