Introduce optional javascript semantics for event handling
- new CONFIG option now allows the user to choose between ordered, serial processing of event handlers and parallel execution of events (JS semantics) OR parallel processing of event handlers and serial execution of events (legacy mciro:bit semantics) - Introduciton of a MicroBitLock primitive for mutual exclusion
This commit is contained in:
parent
3898c0ddc7
commit
9dedea4df2
|
@ -171,6 +171,21 @@ extern uint32_t __etext;
|
|||
#define MESSAGE_BUS_LISTENER_MAX_QUEUE_DEPTH 10
|
||||
#endif
|
||||
|
||||
//
|
||||
// Define MESSAGE_BUS concurrency behaviour.
|
||||
// Set to MESSAGE_BUS_CONCURRENT_LISTENERS to fire event handler
|
||||
// concurrently when a given event is raised, and process events sequentially as they arrive (default micro:bit semantics).
|
||||
// Set to MESSAGE_BUS_CONCURRENT_EVENTS to to fire event handlers sequentially for any given event, while still allowing
|
||||
// concurrent processing of events.
|
||||
//
|
||||
//
|
||||
// Permissable values are:
|
||||
// 0: MESSAGE_BUS_CONCURRENT_LISTENERS
|
||||
// 1: MESSAGE_BUS_CONCURRENT_EVENTS
|
||||
//
|
||||
#ifndef MESSAGE_BUS_CONCURRENCY_MODE
|
||||
#define MESSAGE_BUS_CONCURRENCY_MODE MESSAGE_BUS_CONCURRENT_LISTENERS
|
||||
#endif
|
||||
//
|
||||
// Core micro:bit services
|
||||
//
|
||||
|
|
|
@ -28,9 +28,9 @@ DEALINGS IN THE SOFTWARE.
|
|||
|
||||
#include "mbed.h"
|
||||
#include "MicroBitConfig.h"
|
||||
#include "MicroBitLock.h"
|
||||
#include "MicroBitEvent.h"
|
||||
#include "MemberFunctionCallback.h"
|
||||
#include "MicroBitConfig.h"
|
||||
|
||||
// MicroBitListener flags...
|
||||
#define MESSAGE_BUS_LISTENER_PARAMETERISED 0x0001
|
||||
|
@ -67,7 +67,7 @@ struct MicroBitListener
|
|||
|
||||
MicroBitEvent evt;
|
||||
MicroBitEventQueueItem *evt_queue;
|
||||
|
||||
MicroBitLock lock;
|
||||
MicroBitListener *next;
|
||||
|
||||
/**
|
||||
|
|
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016 British Broadcasting Corporation.
|
||||
This software is provided by Lancaster University by arrangement with the BBC.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a
|
||||
copy of this software and associated documentation files (the "Software"),
|
||||
to deal in the Software without restriction, including without limitation
|
||||
the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
and/or sell copies of the Software, and to permit persons to whom the
|
||||
Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
/**
|
||||
* A simple lock, mostly used for mutual exclusion.
|
||||
*/
|
||||
|
||||
#ifndef MICROBIT_LOCK_H
|
||||
#define MICROBIT_LOCK_H
|
||||
|
||||
#include "MicroBitConfig.h"
|
||||
|
||||
class Fiber;
|
||||
|
||||
class MicroBitLock
|
||||
{
|
||||
private:
|
||||
bool locked;
|
||||
Fiber *queue;
|
||||
|
||||
public:
|
||||
|
||||
/**
|
||||
* Create a new lock that can be used for mutual exclusion and condition synchronisation.
|
||||
*/
|
||||
MicroBitLock();
|
||||
|
||||
/**
|
||||
* Block the calling fiber until the lock is available
|
||||
**/
|
||||
void wait();
|
||||
|
||||
/**
|
||||
* Release the lock, and signal to one waiting fiber to continue
|
||||
*/
|
||||
void notify();
|
||||
|
||||
/**
|
||||
* Release the lock, and signal to all waiting fibers to continue
|
||||
*/
|
||||
void notifyAll();
|
||||
};
|
||||
|
||||
#endif
|
|
@ -33,6 +33,9 @@ DEALINGS IN THE SOFTWARE.
|
|||
#include "MicroBitListener.h"
|
||||
#include "EventModel.h"
|
||||
|
||||
#define MESSAGE_BUS_CONCURRENT_LISTENERS 0
|
||||
#define MESSAGE_BUS_CONCURRENT_EVENTS 1
|
||||
|
||||
/**
|
||||
* Class definition for the MicroBitMessageBus.
|
||||
*
|
||||
|
@ -144,7 +147,6 @@ class MicroBitMessageBus : public EventModel, public MicroBitComponent
|
|||
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.
|
||||
uint16_t nonce_val; // The last nonce issued.
|
||||
uint16_t queueLength; // The number of events currently waiting to be processed.
|
||||
|
||||
/**
|
||||
|
|
|
@ -171,4 +171,8 @@
|
|||
#define MICROBIT_BLE_DEVICE_INFORMATION_SERVICE YOTTA_CFG_MICROBIT_DAL_BLUETOOTH_DEVICE_INFO_SERVICE
|
||||
#endif
|
||||
|
||||
#ifdef YOTTA_CFG_MICROBIT_DAL_MESSAGE_BUS_CONCURRENCY_MODE
|
||||
#define MESSAGE_BUS_CONCURRENCY_MODE YOTTA_CFG_MICROBIT_DAL_MESSAGE_BUS_CONCURRENCY_MODE
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
|
|
@ -942,3 +942,84 @@ void idle_task()
|
|||
schedule();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new lock that can be used for mutual exclusion and condition synchronisation.
|
||||
*/
|
||||
MicroBitLock::MicroBitLock()
|
||||
{
|
||||
queue = NULL;
|
||||
locked = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Block the calling fiber until the lock is available
|
||||
**/
|
||||
void MicroBitLock::wait()
|
||||
{
|
||||
Fiber *f = currentFiber;
|
||||
|
||||
// If the scheduler is not running, then simply exit, as we're running monothreaded.
|
||||
if (!fiber_scheduler_running())
|
||||
return;
|
||||
|
||||
if (locked)
|
||||
{
|
||||
// wait() is a blocking call, so if we're in a fork on block context,
|
||||
// it's time to spawn a new fiber...
|
||||
if (currentFiber->flags & MICROBIT_FIBER_FLAG_FOB)
|
||||
{
|
||||
// Allocate a new fiber. This will come from the fiber pool if availiable,
|
||||
// else a new one will be allocated on the heap.
|
||||
forkedFiber = getFiberContext();
|
||||
|
||||
// If we're out of memory, there's nothing we can do.
|
||||
// keep running in the context of the current thread as a best effort.
|
||||
if (forkedFiber != NULL)
|
||||
f = forkedFiber;
|
||||
}
|
||||
|
||||
// Remove fiber from the run queue
|
||||
dequeue_fiber(f);
|
||||
|
||||
// Add fiber to the sleep queue. We maintain strict ordering here to reduce lookup times.
|
||||
queue_fiber(f, &queue);
|
||||
|
||||
// Finally, enter the scheduler.
|
||||
schedule();
|
||||
}
|
||||
|
||||
locked = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Release the lock, and signal to one waiting fiber to continue
|
||||
*/
|
||||
void MicroBitLock::notify()
|
||||
{
|
||||
Fiber *f = queue;
|
||||
|
||||
if (f)
|
||||
{
|
||||
dequeue_fiber(f);
|
||||
queue_fiber(f, &runQueue);
|
||||
}
|
||||
locked = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Release the lock, and signal to all waiting fibers to continue
|
||||
*/
|
||||
void MicroBitLock::notifyAll()
|
||||
{
|
||||
Fiber *f = queue;
|
||||
|
||||
while (f)
|
||||
{
|
||||
dequeue_fiber(f);
|
||||
queue_fiber(f, &runQueue);
|
||||
f = queue;
|
||||
}
|
||||
|
||||
locked = false;
|
||||
}
|
||||
|
|
|
@ -95,14 +95,19 @@ void async_callback(void *param)
|
|||
// Queue this event up for later, if that's how we've been configured.
|
||||
if (listener->flags & MESSAGE_BUS_LISTENER_QUEUE_IF_BUSY)
|
||||
{
|
||||
#if (MESSAGE_BUS_CONCURRENCY_MODE == MESSAGE_BUS_CONCURRENT_LISTENERS)
|
||||
listener->queue(listener->evt);
|
||||
return;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
// 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 :-/
|
||||
|
||||
#if (MESSAGE_BUS_CONCURRENCY_MODE == MESSAGE_BUS_CONCURRENT_EVENTS)
|
||||
listener->lock.wait();
|
||||
#endif
|
||||
// Record that we have a fiber going into this listener...
|
||||
listener->flags |= MESSAGE_BUS_LISTENER_BUSY;
|
||||
|
||||
|
@ -138,6 +143,10 @@ void async_callback(void *param)
|
|||
|
||||
// The fiber of exiting... clear our state.
|
||||
listener->flags &= ~MESSAGE_BUS_LISTENER_BUSY;
|
||||
|
||||
#if (MESSAGE_BUS_CONCURRENCY_MODE == MESSAGE_BUS_CONCURRENT_EVENTS)
|
||||
listener->lock.notify();
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -261,6 +270,13 @@ int MicroBitMessageBus::deleteMarkedListeners()
|
|||
return removed;
|
||||
}
|
||||
|
||||
MicroBitEvent last_event;
|
||||
void process_sequentially(void *param)
|
||||
{
|
||||
MicroBitMessageBus *m = (MicroBitMessageBus *)param;
|
||||
m->process(last_event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Periodic callback from MicroBit.
|
||||
*
|
||||
|
@ -278,7 +294,12 @@ void MicroBitMessageBus::idleTick()
|
|||
while (item)
|
||||
{
|
||||
// send the event to all standard event listeners.
|
||||
#if (MESSAGE_BUS_CONCURRENCY_MODE == MESSAGE_BUS_CONCURRENT_EVENTS)
|
||||
last_event = item->evt;
|
||||
invoke(process_sequentially,this);
|
||||
#else
|
||||
this->process(item->evt);
|
||||
#endif
|
||||
|
||||
// Free the queue item.
|
||||
delete item;
|
||||
|
@ -365,10 +386,12 @@ int MicroBitMessageBus::process(MicroBitEvent &evt, bool urgent)
|
|||
// Otherwise, we invoke it in a 'fork on block' context, that will automatically create a fiber
|
||||
// should the event handler attempt a blocking operation, but doesn't have the overhead
|
||||
// of creating a fiber needlessly. (cool huh?)
|
||||
if (l->flags & MESSAGE_BUS_LISTENER_NONBLOCKING || !fiber_scheduler_running())
|
||||
async_callback(l);
|
||||
else
|
||||
#if (MESSAGE_BUS_CONCURRENCY_MODE == MESSAGE_BUS_CONCURRENT_LISTENERS)
|
||||
if (!(l->flags & MESSAGE_BUS_LISTENER_NONBLOCKING) && fiber_scheduler_running())
|
||||
invoke(async_callback, l);
|
||||
else
|
||||
#endif
|
||||
async_callback(l);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
Loading…
Reference in New Issue