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.master
parent
f4b8a1a272
commit
58d24ab0a8
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue