microbit: Updated MicroBitMessageBus to use fork_on_block functionality

Updates the MicroBitMessageBus implementation to use fork_on_block() calls rather
than create_fiber() calls when servicing events. This provides greater optimisation
for non-blocking event handlers.

More specifically, this update:

 - Replaces calls to create_fiber() ith fork_on_block during event send operations.
 - Adds a queue of events, to ensure events generated from interrupt context are not serviced in interrupt context.
 - Registers a listener with the idle process to empty the event queue.
This commit is contained in:
Joe Finney 2015-08-19 23:35:45 +01:00
parent f4b8a1a272
commit 58d24ab0a8
3 changed files with 130 additions and 7 deletions

View file

@ -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

View file

@ -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();

View file

@ -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;
}