diff --git a/inc/MicroBitMessageBus.h b/inc/MicroBitMessageBus.h index 84060f5..7521bd8 100644 --- a/inc/MicroBitMessageBus.h +++ b/inc/MicroBitMessageBus.h @@ -2,6 +2,7 @@ #define MICROBIT_MESSAGE_BUS_H #include "mbed.h" +#include "MicroBitComponent.h" #include "MicroBitEvent.h" // Enumeration of core components. @@ -42,6 +43,18 @@ struct MicroBitMessageBusCache MicroBitListener *ptr; }; +struct MicroBitEventQueueItem +{ + MicroBitEvent evt; + MicroBitEventQueueItem *next; + + /** + * Constructor. + * Creates a new MicroBitEventQueueItem. + * @param evt The event that is to be queued. + */ + MicroBitEventQueueItem(MicroBitEvent &evt); +}; /** * Class definition for the MicroBitMessageBus. @@ -61,7 +74,7 @@ struct MicroBitMessageBusCache * 1) Maintain a low RAM footprint where possible * 2) Make few assumptions about the underlying platform, but allow optimizations where possible. */ -class MicroBitMessageBus +class MicroBitMessageBus : public MicroBitComponent { public: @@ -91,7 +104,7 @@ class MicroBitMessageBus /** * Send the given event to all regstered recipients, using a cached entry to minimize lookups. - * This is particularly useful for soptimizing ensors that frequently send to the same channel. + * 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. @@ -131,9 +144,17 @@ class MicroBitMessageBus private: - MicroBitListener *listeners; // Chain of active listeners. - int seq; // Sequence number. Used to invalidate cache entries. + 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(); + + virtual void idleTick(); + virtual int isIdleCallbackNeeded(); }; #endif diff --git a/source/MicroBit.cpp b/source/MicroBit.cpp index 6cb62e5..dffbf0e 100644 --- a/source/MicroBit.cpp +++ b/source/MicroBit.cpp @@ -84,6 +84,7 @@ void MicroBit::init() //add the compass and accelerometer to the idle array addIdleComponent(&uBit.accelerometer); addIdleComponent(&uBit.compass); + addIdleComponent(&uBit.MessageBus); // Seed our random number generator seedRandom(); diff --git a/source/MicroBitMessageBus.cpp b/source/MicroBitMessageBus.cpp index 7ddd91f..8db8c77 100644 --- a/source/MicroBitMessageBus.cpp +++ b/source/MicroBitMessageBus.cpp @@ -6,6 +6,16 @@ #include "MicroBit.h" +/** + * Constructor. + * Create a new MicroBitEventQueueItem. + * @param evt The event that is to be queued. + */ +MicroBitEventQueueItem::MicroBitEventQueueItem(MicroBitEvent &evt) +{ + this->evt = evt; + this->next = NULL; +} /** * Constructor. * Create a new Message Bus Listener. @@ -42,6 +52,8 @@ MicroBitListener::MicroBitListener(uint16_t id, uint16_t value, void* handler, v MicroBitMessageBus::MicroBitMessageBus() { this->listeners = NULL; + this->evt_queue_head = NULL; + this->evt_queue_tail = NULL; this->seq = 0; } @@ -60,6 +72,84 @@ void async_callback(void *param) ((void (*)(MicroBitEvent))listener->cb)(listener->evt); } + +/** + * Queue the given event for processing at a later time. + * Add the given event at the tail of our queue. + * + * @param The event to queue. + */ +void MicroBitMessageBus::queueEvent(MicroBitEvent &evt) +{ + MicroBitEventQueueItem *item = new MicroBitEventQueueItem(evt); + + __disable_irq(); + + if (evt_queue_tail == NULL) + evt_queue_head = evt_queue_tail = item; + else + evt_queue_tail->next = item; + + __enable_irq(); +} + +/** + * Extract the next event from the front of the event queue (if present). + * @return + * + * @param The event to queue. + */ +MicroBitEventQueueItem* MicroBitMessageBus::dequeueEvent() +{ + MicroBitEventQueueItem *item = NULL; + + __disable_irq(); + + if (evt_queue_head != NULL) + { + item = evt_queue_head; + evt_queue_head = item->next; + + if (evt_queue_head == NULL) + evt_queue_tail = NULL; + } + + __enable_irq(); + + return item; +} + +/** + * Periodic callback from MicroBit. + * Process at least one event from the event queue, if it is not empty. + */ +void MicroBitMessageBus::idleTick() +{ + MicroBitEventQueueItem *item = this->dequeueEvent(); + + // Whilst there are events to process, pull them off the queue and process them. + while (item != NULL) + { + // send the event. + this->send(item->evt); + + // Free the queue item. + delete item; + + // Pull the next event to process, if there is one. + item = this->dequeueEvent(); + } +} + +/** + * Indicates whether or not we have any background work to do. + * @ return 1 if there are any events waitingto be processed, 0 otherwise. + */ +int MicroBitMessageBus::isIdleCallbackNeeded() +{ + return !(evt_queue_head == NULL); +} + /** * Send the given event to all regstered recipients. * @@ -72,7 +162,7 @@ void MicroBitMessageBus::send(MicroBitEvent &evt) } /** - * Send the given event to all regstered recipients, using a cached entry to minimize lookups. + * Send the given event to all registered 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 @@ -98,6 +188,17 @@ void MicroBitMessageBus::send(MicroBitEvent &evt, MicroBitMessageBusCache *c) MicroBitListener *l; MicroBitListener *start; + // Firstly, determine if we're operating in interrupt context... + // If so, we queue processing of the event until we're scheduled in normal thread context. + // We do this to avoid executing event handler code in IRQ context, which may bring + // hidden race condition to kids code. + + if (inInterruptContext()) + { + this->queueEvent(evt); + return; + } + // 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) @@ -119,7 +220,7 @@ void MicroBitMessageBus::send(MicroBitEvent &evt, MicroBitMessageBusCache *c) if(l->value == MICROBIT_EVT_ANY || l->value == evt.value) { l->evt = evt; - create_fiber(async_callback, (void *)l); + fork_on_block(async_callback, (void *)l); } l = l->next; @@ -130,7 +231,7 @@ void MicroBitMessageBus::send(MicroBitEvent &evt, MicroBitMessageBusCache *c) while (l != NULL && l->id == MICROBIT_ID_ANY) { l->evt = evt; - create_fiber(async_callback, (void *)l); + fork_on_block(async_callback, (void *)l); l = l->next; }