AceRoutine  0.2
A low-memory, fast-switching, cooperative multitasking library using stackless coroutines on Arduino platforms.
Classes | Macros
Coroutine.h File Reference

All coroutines are instances of the Coroutine base class. More...

#include <stdint.h>
#include <Print.h>
#include "Flash.h"
#include "FCString.h"
Include dependency graph for Coroutine.h:
This graph shows which files directly or indirectly include this file:

Go to the source code of this file.

Classes

class  ace_routine::Coroutine
 Base class of all coroutines. More...
 

Macros

#define COROUTINE(...)   GET_COROUTINE(__VA_ARGS__, COROUTINE2, COROUTINE1)(__VA_ARGS__)
 Create a Coroutine instance named 'name'. More...
 
#define GET_COROUTINE(_1, _2, NAME, ...)   NAME
 
#define COROUTINE1(name)
 
#define COROUTINE2(className, name)
 
#define EXTERN_COROUTINE(...)
 Create an extern reference to a coroutine that is defined in another .cpp file. More...
 
#define GET_EXTERN_COROUTINE(_1, _2, NAME, ...)   NAME
 
#define EXTERN_COROUTINE1(name)
 
#define EXTERN_COROUTINE2(className, name)
 
#define COROUTINE_BEGIN()
 Mark the beginning of a coroutine. More...
 
#define COROUTINE_LOOP()
 Mark the beginning of a coroutine loop. More...
 
#define COROUTINE_YIELD_INTERNAL()
 
#define COROUTINE_YIELD()
 Yield execution to another coroutine. More...
 
#define COROUTINE_AWAIT(condition)
 Yield until condition is true, then execution continues. More...
 
#define COROUTINE_DELAY(delayMillis)
 Yield for delayMillis. More...
 
#define COROUTINE_DELAY_SECONDS(loopCounter, delaySeconds)
 Delay for delaySeconds. More...
 
#define COROUTINE_END()
 Mark the end of a coroutine. More...
 

Detailed Description

All coroutines are instances of the Coroutine base class.

The COROUTINE() macro creates these instances, and registers them to automatically run when CoroutineScheduler::loop() is called.

Various macros use macro overloading to implement a 1-argument and a 2-argument version. See https://stackoverflow.com/questions/11761703 to description of how that works.

The computed goto is a GCC extension: https://gcc.gnu.org/onlinedocs/gcc/Labels-as-Values.html The noinline and noclone attributes make sure that label pointers are always the same. I'm not 100% sure they are needed here, but they don't seem to hurt.

Definition in file Coroutine.h.

Macro Definition Documentation

◆ COROUTINE

#define COROUTINE (   ...)    GET_COROUTINE(__VA_ARGS__, COROUTINE2, COROUTINE1)(__VA_ARGS__)

Create a Coroutine instance named 'name'.

Two forms are supported

The 1-argument form uses the Coroutine class as the base class of the coroutine. The 2-argument form uses the user-provided className which must be a subclass of Coroutine.

The code in {} following this macro becomes the body of the Coroutine::runCoroutine() method.

Definition at line 64 of file Coroutine.h.

◆ COROUTINE1

#define COROUTINE1 (   name)
Value:
struct Coroutine_##name : ace_routine::Coroutine { \
Coroutine_##name(); \
virtual int runCoroutine() override \
__attribute__((__noinline__,__noclone__)); \
} name; \
Coroutine_##name :: Coroutine_##name() { \
setupCoroutine(ACE_ROUTINE_F(#name)); \
} \
int Coroutine_##name :: runCoroutine()
virtual int runCoroutine()=0
The body of the coroutine.
Base class of all coroutines.
Definition: Coroutine.h:271

Definition at line 69 of file Coroutine.h.

◆ COROUTINE2

#define COROUTINE2 (   className,
  name 
)
Value:
struct className##_##name : className { \
className##_##name(); \
virtual int runCoroutine() override \
__attribute__((__noinline__,__noclone__)); \
} name; \
className##_##name :: className##_##name() { \
setupCoroutine(ACE_ROUTINE_F(#name)); \
} \
int className##_##name :: runCoroutine()

Definition at line 80 of file Coroutine.h.

◆ COROUTINE_AWAIT

#define COROUTINE_AWAIT (   condition)
Value:
do { \
while (!(condition)) { \
setYielding(); \
COROUTINE_YIELD_INTERNAL(); \
} \
setRunning(); \
} while (false)

Yield until condition is true, then execution continues.

This is functionally equivalent to:

while (!condition) COROUTINE_YIELD();

but potentially slightly more efficient.

Definition at line 162 of file Coroutine.h.

◆ COROUTINE_BEGIN

#define COROUTINE_BEGIN ( )
Value:
void* p = getJump(); \
if (p != nullptr) { \
goto *p; \
}

Mark the beginning of a coroutine.

Definition at line 122 of file Coroutine.h.

◆ COROUTINE_DELAY

#define COROUTINE_DELAY (   delayMillis)
Value:
do { \
setDelay(delayMillis); \
while (!isDelayExpired()) { \
setDelaying(); \
COROUTINE_YIELD_INTERNAL(); \
} \
setRunning(); \
} while (false)

Yield for delayMillis.

A delayMillis of 0 is functionally equivalent to COROUTINE_YIELD(). To save memory, the delayMillis is stored as a uint16_t but the actual maximum is limited to 32767 millliseconds. See setDelay() for the reason for this limitation.

If you need to wait for longer than that, use a for-loop to call COROUTINE_DELAY() as many times as necessary.

This could have been implemented using COROUTINE_AWAIT() but this macro matches the global delay(millis) function already provided by the Arduino API. Also having a separate kStatusDelaying state allows the CoroutineScheduler to be slightly more efficient by avoiding the call to Coroutine::runCoroutine() if the delay has not expired.

Definition at line 186 of file Coroutine.h.

◆ COROUTINE_DELAY_SECONDS

#define COROUTINE_DELAY_SECONDS (   loopCounter,
  delaySeconds 
)
Value:
do { \
loopCounter = delaySeconds; \
while (loopCounter-- > 0) { \
COROUTINE_DELAY(1000); \
} \
} while (false)

Delay for delaySeconds.

Maximum value is the maximum value of the loopCounter which can be any integer type. For example, if the loopCounter is a uint16_t, then the maximum delay is 65535 seconds, or 18h12m15s. This macro calls COROUTINE_DELAY() every 1000 milliseconds, so over the course of the entire delay, there may be some drift. (Some of this drift could be avoided by looping in 16000ms chunks, then looping the remainder in 1000ms chunks. Division of delaySeconds by 16 would be fast. But minimizing drift doesn't seem to be important for the use cases that I have in mind, so I haven't implemented this.)

The loopCounter needs to be supplied to the macro because AceRoutine coroutines are stackless so the loop cannot use the stack to keep count of the number of seconds. Instead it needs to be given a loop counter that retains information across multiple calls. That loop counter can be a member variable of the Coroutine object, or it can be a function-static variable.

The usage would be something like:

class MyCoroutine: public Coroutine {
public:
virtual int runCoroutine() override {
...
COROUTINE_DELAY_SECONDS(mDelayCounter, 1000);
...
}
private:
uint16_t mDelayCounter;
};
...

or

COROUTINE(myRoutine) {
...
static uint16_t delayCounter;
COROUTINE_DELAY_SECONDS(delayCounter, 1000);
...
}
}

Definition at line 242 of file Coroutine.h.

◆ COROUTINE_END

#define COROUTINE_END ( )
Value:
do { \
__label__ jumpLabel; \
setEnding(); \
setJump(&& jumpLabel); \
jumpLabel: ; \
return 0; \
} while (false)

Mark the end of a coroutine.

Subsequent calls to Coroutine::runCoroutine() will do nothing.

Definition at line 254 of file Coroutine.h.

◆ COROUTINE_LOOP

#define COROUTINE_LOOP ( )
Value:
while (true) \
#define COROUTINE_BEGIN()
Mark the beginning of a coroutine.
Definition: Coroutine.h:122

Mark the beginning of a coroutine loop.

Can be used instead of COROUTINE_BEGIN() at the beginning of a Coroutine.

Definition at line 132 of file Coroutine.h.

◆ COROUTINE_YIELD

#define COROUTINE_YIELD ( )
Value:
do { \
setYielding(); \
COROUTINE_YIELD_INTERNAL(); \
setRunning(); \
} while (false)

Yield execution to another coroutine.

Definition at line 145 of file Coroutine.h.

◆ COROUTINE_YIELD_INTERNAL

#define COROUTINE_YIELD_INTERNAL ( )
Value:
do { \
__label__ jumpLabel; \
setJump(&& jumpLabel); \
return 0; \
jumpLabel: ; \
} while (false)

Definition at line 136 of file Coroutine.h.

◆ EXTERN_COROUTINE

#define EXTERN_COROUTINE (   ...)
Value:
GET_EXTERN_COROUTINE(\
__VA_ARGS__, EXTERN_COROUTINE2, EXTERN_COROUTINE1)(__VA_ARGS__)

Create an extern reference to a coroutine that is defined in another .cpp file.

The extern reference is needed before it can be used. Two forms are supported:

Definition at line 99 of file Coroutine.h.

◆ EXTERN_COROUTINE1

#define EXTERN_COROUTINE1 (   name)
Value:
struct Coroutine_##name : ace_routine::Coroutine { \
Coroutine_##name(); \
virtual int runCoroutine() override \
__attribute__((__noinline__,__noclone__)); \
}; \
extern Coroutine_##name name
virtual int runCoroutine()=0
The body of the coroutine.
Base class of all coroutines.
Definition: Coroutine.h:271

Definition at line 105 of file Coroutine.h.

◆ EXTERN_COROUTINE2

#define EXTERN_COROUTINE2 (   className,
  name 
)
Value:
struct className##_##name : className { \
className##_##name(); \
virtual int runCoroutine() override \
__attribute__((__noinline__,__noclone__)); \
}; \
extern className##_##name name

Definition at line 113 of file Coroutine.h.