256 lines
6.7 KiB
C++
256 lines
6.7 KiB
C++
|
/**
|
||
|
* Class definition for a MicroBitMessageBus.
|
||
|
*
|
||
|
* The MicroBitMessageBus handles all messages passed between components.
|
||
|
*/
|
||
|
|
||
|
#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 = (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.
|
||
|
* Create a new Message Bus.
|
||
|
*/
|
||
|
MicroBitMessageBus::MicroBitMessageBus()
|
||
|
{
|
||
|
this->listeners = NULL;
|
||
|
this->seq = 0;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Invokes a callback on a given MicroBitListener
|
||
|
*
|
||
|
* Internal wrapper function, used to enable
|
||
|
* parameterized callbacks through the fiber scheduler.
|
||
|
*/
|
||
|
void async_callback(void *param)
|
||
|
{
|
||
|
MicroBitListener *listener = (MicroBitListener *)param;
|
||
|
if (listener->cb_arg != NULL)
|
||
|
((void (*)(MicroBitEvent, void*))listener->cb)(listener->evt, listener->cb_arg);
|
||
|
else
|
||
|
((void (*)(MicroBitEvent))listener->cb)(listener->evt);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Send the given event to all regstered recipients.
|
||
|
*
|
||
|
* @param The event to send. This structure is assumed to be heap allocated, and will
|
||
|
* be automatically freed once all recipients have been notified.
|
||
|
*/
|
||
|
void MicroBitMessageBus::send(MicroBitEvent &evt)
|
||
|
{
|
||
|
this->send(evt, NULL);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Send the given event to all regstered recipients, using a cached entry to minimize lookups.
|
||
|
* This is particularly useful for optimizing sensors that frequently send to the same channel.
|
||
|
*
|
||
|
* @param evt The event to send. This structure is assumed to be heap allocated, and will
|
||
|
* be automatically freed once all recipients have been notified.
|
||
|
* @param c Cache entry to reduce lookups for commonly used channels.
|
||
|
*
|
||
|
* TODO: For now, this is unbuffered. We should consider scheduling events here, and operating
|
||
|
* a different thread that empties the queue. This would perhaps provide greater opportunities
|
||
|
* for aggregation.
|
||
|
*
|
||
|
* Example:
|
||
|
* @code
|
||
|
* MicroBitEvent evt(id,MICROBIT_BUTTON_EVT_DOWN,ticks,NULL);
|
||
|
* evt.fire();
|
||
|
* //OR YOU CAN DO THIS...
|
||
|
* MicroBitEvent evt(id,MICROBIT_BUTTON_EVT_DOWN,ticks,NULL,true);
|
||
|
* @endcode
|
||
|
*
|
||
|
* @note THIS IS NOW WRAPPED BY THE MicroBitEvent CLASS FOR CONVENIENCE...
|
||
|
*/
|
||
|
void MicroBitMessageBus::send(MicroBitEvent &evt, MicroBitMessageBusCache *c)
|
||
|
{
|
||
|
MicroBitListener *l;
|
||
|
MicroBitListener *start;
|
||
|
|
||
|
// Find the start of the sublist where we'll send this event.
|
||
|
// Ideally, we'll have a valid, cached entry. Use it if we do.
|
||
|
if ( c != NULL && c->seq == this->seq)
|
||
|
{
|
||
|
l = c->ptr;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
l = listeners;
|
||
|
while (l != NULL && l->id != evt.source)
|
||
|
l = l->next;
|
||
|
}
|
||
|
|
||
|
start = l;
|
||
|
|
||
|
// Now, send the event to all listeners registered for this event.
|
||
|
while (l != NULL && l->id == evt.source)
|
||
|
{
|
||
|
if(l->value == MICROBIT_EVT_ANY || l->value == evt.value)
|
||
|
{
|
||
|
l->evt = evt;
|
||
|
create_fiber(async_callback, (void *)l);
|
||
|
}
|
||
|
|
||
|
l = l->next;
|
||
|
}
|
||
|
|
||
|
// Next, send to any listeners registered for ALL event sources.
|
||
|
l = listeners;
|
||
|
while (l != NULL && l->id == MICROBIT_ID_ANY)
|
||
|
{
|
||
|
l->evt = evt;
|
||
|
create_fiber(async_callback, (void *)l);
|
||
|
|
||
|
l = l->next;
|
||
|
}
|
||
|
|
||
|
// If we were given a cached entry that's now invalid, update it.
|
||
|
if ( c != NULL && c->seq != this->seq)
|
||
|
{
|
||
|
c->ptr = start;
|
||
|
c->seq = this->seq;
|
||
|
}
|
||
|
|
||
|
// Wake up any fibers that are blocked on this event
|
||
|
if (uBit.flags & MICROBIT_FLAG_SCHEDULER_RUNNING)
|
||
|
scheduler_event(evt);
|
||
|
|
||
|
|
||
|
// Finally, see if this event needs to be propogated through our BLE interface
|
||
|
if (uBit.ble_event_service)
|
||
|
uBit.ble_event_service->onMicroBitEvent(evt);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Register a listener function.
|
||
|
*
|
||
|
* @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_VALUE_ANY to receive events of any value.
|
||
|
*
|
||
|
* @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)
|
||
|
* {
|
||
|
* //do something
|
||
|
* }
|
||
|
* uBit.MessageBus.listen(MICROBIT_ID_BUTTON_B, MICROBIT_BUTTON_EVT_CLICK, onButtonBClick); // call function when ever a click event is detected.
|
||
|
* @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)) {
|
||
|
this->listen(id, value, (void*) handler, NULL);
|
||
|
}
|
||
|
|
||
|
void MicroBitMessageBus::listen(int id, int value, void* handler, void* arg)
|
||
|
{
|
||
|
//handler can't be NULL!
|
||
|
if (handler == NULL)
|
||
|
return;
|
||
|
|
||
|
MicroBitListener *l, *p;
|
||
|
|
||
|
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;
|
||
|
}
|
||
|
|
||
|
MicroBitListener *newListener = new MicroBitListener(id, value, handler, arg);
|
||
|
|
||
|
//if listeners is null - we can automatically add this listener to the list at the beginning...
|
||
|
if (listeners == NULL)
|
||
|
{
|
||
|
listeners = newListener;
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// Maintain an ordered list of listeners.
|
||
|
// 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.
|
||
|
p = listeners;
|
||
|
l = listeners;
|
||
|
|
||
|
while (l != NULL && l->id < id)
|
||
|
{
|
||
|
p = l;
|
||
|
l = l->next;
|
||
|
}
|
||
|
|
||
|
while (l != NULL && l->id == id && l->value < value)
|
||
|
{
|
||
|
p = l;
|
||
|
l = l->next;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
//add at front of list
|
||
|
if (p == listeners && (id < p->id || (p->id == id && p->value > value)))
|
||
|
{
|
||
|
newListener->next = p;
|
||
|
|
||
|
//this new listener is now the front!
|
||
|
listeners = newListener;
|
||
|
}
|
||
|
//add after p
|
||
|
else
|
||
|
{
|
||
|
newListener->next = p->next;
|
||
|
p->next = newListener;
|
||
|
}
|
||
|
|
||
|
// Increase our sequence number and we're done.
|
||
|
// This will lazily invalidate any cached entries to the listener list.
|
||
|
this->seq++;
|
||
|
}
|
||
|
|