Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce optional javascript semantics for event handling #418

Open
wants to merge 36 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
9dedea4
Introduce optional javascript semantics for event handling
finneyj Sep 27, 2018
69bf72e
Update string print() method to avoid unecessary delays
finneyj Feb 27, 2019
b1cb519
Simplify implementation of fiber_wake_on_event()
finneyj Feb 27, 2019
6112d74
MicroBitMessageBus::process() updated to pass events by value
finneyj Feb 27, 2019
ca43746
Ensure MicroBitDisplay::waitForFreeDisplay() is free of race conditions
finneyj Feb 27, 2019
31fea82
Change Fiber lists to be singly-linked
finneyj May 3, 2019
60d423e
Ignore .vscode metadata
finneyj May 3, 2019
1c1426f
Introduce FiberTable stucture
finneyj May 3, 2019
461fb33
Integrate optional fiber meta-data contributed in #424
finneyj May 7, 2019
07f07fb
Ensure Fiber stack allocation happens in the context of that Fiber
finneyj May 7, 2019
b71334e
Add user define limit to size of FiberPool
finneyj May 7, 2019
b9816a4
Reintroduce validation test in fiber_sleep()
finneyj May 7, 2019
226bf9c
Replace FiberTable with a list of all fibers to save memory
finneyj May 8, 2019
92c6cc6
Allow overriding of the heap allocator and getting heap sizes
finneyj May 8, 2019
7413352
Introduce optional javascript semantics for event handling
finneyj Sep 27, 2018
c9c98be
Update string print() method to avoid unecessary delays
finneyj Feb 27, 2019
b28dba4
MicroBitMessageBus::process() updated to pass events by value
finneyj Feb 27, 2019
77d679c
Ensure MicroBitDisplay::waitForFreeDisplay() is free of race conditions
finneyj Feb 27, 2019
c04b1fd
Merge branch 'js-event-semantics' of https://www.github.com/lancaster…
finneyj May 8, 2019
8075eab
.gitignore vscode metadata
finneyj Mar 24, 2020
25e1e10
Add Fiber->user_data and list_fiber()
mmoskal Jan 20, 2019
92d0cee
Allow overriding of the heap allocator and getting heap sizes
mmoskal Jan 20, 2019
415e5eb
Compilation fixes
mmoskal Jan 20, 2019
15ebed2
Add fiber_user_data to yotta mappings; fix some errors
mmoskal Jan 20, 2019
d823d4b
Rename list pointers in MicroBitFiber.h
finneyj Mar 25, 2020
c42776c
Merge branch 'pxtgc' into js-event-semantics
finneyj Mar 25, 2020
219feb8
Remove list_fibers API
finneyj Mar 25, 2020
3ed3674
Add CONFIG flag to indicate use of get_fiber_list() API
finneyj Mar 25, 2020
76538ef
Re-enumerate component ID values to align with CODAL
finneyj Mar 25, 2020
f96a390
Use schedule() not fiber_sleep(0) in MicroBitSerial.cpp (Fix #461)
finneyj Apr 1, 2020
dd339a7
Honour delay parameter in MicroBitDisplay::print(MicroBitImage)
finneyj Apr 1, 2020
0fd4a80
Correctly initialise animation timer for printChar() Fix #463
finneyj Apr 21, 2020
3bccdd6
Ensure consistent behaviour of MicroBitDisplay::print() operations
finneyj Apr 21, 2020
673228f
Align print() semantics with previous versions
finneyj Apr 24, 2020
2443beb
Align MICROBIT_PIN_EVENT_ON_* with codal
mmoskal Jun 8, 2020
e5b6741
Align allocateNotifyEvent() API with codal
finneyj Jul 31, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions inc/core/MicroBitConfig.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
//
Expand Down
4 changes: 2 additions & 2 deletions inc/core/MicroBitListener.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -67,7 +67,7 @@ struct MicroBitListener

MicroBitEvent evt;
MicroBitEventQueueItem *evt_queue;

MicroBitLock lock;
MicroBitListener *next;

/**
Expand Down
66 changes: 66 additions & 0 deletions inc/core/MicroBitLock.h
Original file line number Diff line number Diff line change
@@ -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
6 changes: 4 additions & 2 deletions inc/drivers/MicroBitMessageBus.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand Down Expand Up @@ -105,7 +108,7 @@ class MicroBitMessageBus : public EventModel, public MicroBitComponent
* @note It is recommended that all external code uses the send() function instead of this function,
* or the constructors provided by MicrobitEvent.
*/
int process(MicroBitEvent &evt, bool urgent = false);
int process(MicroBitEvent evt, bool urgent = false);
Copy link
Contributor

@jamesadevine jamesadevine Feb 27, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@finneyj what's the overhead implications of this change?


/**
* Returns the microBitListener with the given position in our list.
Expand Down Expand Up @@ -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.

/**
Expand Down
4 changes: 4 additions & 0 deletions inc/platform/yotta_cfg_mappings.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
86 changes: 81 additions & 5 deletions source/core/MicroBitFiber.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -428,12 +428,7 @@ int fiber_wake_on_event(uint16_t id, uint16_t value)
// 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;
dequeue_fiber(f);
queue_fiber(f, &runQueue);
schedule();
}
}

// Encode the event data in the context field. It's handy having a 32 bit core. :-)
Expand Down Expand Up @@ -942,3 +937,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;
}
11 changes: 5 additions & 6 deletions source/drivers/MicroBitDisplay.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -354,8 +354,7 @@ void MicroBitDisplay::updateScrollText()
void MicroBitDisplay::updatePrintText()
{
image.print(printingChar < printingText.length() ? printingText.charAt(printingChar) : ' ',0,0);

if (printingChar > printingText.length())
if (printingChar >= printingText.length())
{
animationMode = ANIMATION_MODE_NONE;

Expand Down Expand Up @@ -448,7 +447,7 @@ void MicroBitDisplay::stopAnimation()
void MicroBitDisplay::waitForFreeDisplay()
{
// If there's an ongoing animation, wait for our turn to display.
if (animationMode != ANIMATION_MODE_NONE && animationMode != ANIMATION_MODE_STOPPED)
while (animationMode != ANIMATION_MODE_NONE && animationMode != ANIMATION_MODE_STOPPED)
fiber_wait_for_event(MICROBIT_ID_NOTIFY, MICROBIT_DISPLAY_EVT_FREE);
}

Expand Down Expand Up @@ -492,7 +491,7 @@ int MicroBitDisplay::printCharAsync(char c, int delay)
if (delay > 0)
{
animationDelay = delay;
animationTick = 0;
animationTick = delay-1;
animationMode = ANIMATION_MODE_PRINT_CHARACTER;
}
}
Expand Down Expand Up @@ -533,7 +532,7 @@ int MicroBitDisplay::printAsync(ManagedString s, int delay)
printingChar = 0;
printingText = s;
animationDelay = delay;
animationTick = 0;
animationTick = delay-1;

animationMode = ANIMATION_MODE_PRINT_TEXT;
}
Expand Down Expand Up @@ -576,7 +575,7 @@ int MicroBitDisplay::printAsync(MicroBitImage i, int x, int y, int alpha, int de
if(delay > 0)
{
animationDelay = delay;
animationTick = 0;
animationTick = delay-1;
animationMode = ANIMATION_MODE_PRINT_CHARACTER;
}
}
Expand Down
31 changes: 27 additions & 4 deletions source/drivers/MicroBitMessageBus.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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
}

/**
Expand Down Expand Up @@ -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.
*
Expand All @@ -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;
Expand Down Expand Up @@ -337,7 +358,7 @@ int MicroBitMessageBus::send(MicroBitEvent evt)
* @note It is recommended that all external code uses the send() function instead of this function,
* or the constructors provided by MicrobitEvent.
*/
int MicroBitMessageBus::process(MicroBitEvent &evt, bool urgent)
int MicroBitMessageBus::process(MicroBitEvent evt, bool urgent)
{
MicroBitListener *l;
int complete = 1;
Expand Down Expand Up @@ -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
{
Expand Down