Arduino coroutines: Are they useful?

I have no idea if anyone has a use for this, but I thought I could put it here and see what people thought. Feedback is welcomed.

Coroutines, in this context, are functions which return results multiple times during their execution and allow other code to run interleaved. They allow cooperative multitasking. They keep state between calls and continue where they left off. Yielding is the process of leaving the function where you intend to resume later.

I think that coroutines are sometimes useful abstractions for things like parsers, especially where you may have several messages coming in from various places and want to parse each one, in parallel. This lead me to think that they may be useful on Arduino.

What I have here is a way to simulate coroutines in C++ (up to a point). I didn’t invent the method; it’s widely known. The key to it is that case statements within switch blocks can be further nested within other blocks. (I certainly don’t recommend you do this at work, if you want to keep your job.)

I’m sorry for the clumsy syntax, but I played around for a while and decided that this was a reasonable compromise, given the limitations of C++. I could have gone with something a lot simpler, which could only handle one instance of a particular coroutine. I know there are people who believe that C preprocessor macros are a sin, but in this case, I think they improve readability.

The listing for “coroutines.h”, to be put in your include path or as a tab within your sketch. See the example in the comment (I know it’s not a good motivating example. That’s where you guys come in.)

#ifndef INC_COROUTINES_H
#define INC_COROUTINES_H

/*
 * Coroutines are implemented as C++ functors. They are a shorthand for
 * writing explicit finite state machines.
 *
 * cResume will return false once all yields are done.
 * The return type of the coroutine must be default constructable.
 * cYield will not function correctly within a switch block.
 *
 * Example Usage: ( print 123456789 over serial )
 * -------------
 * #include "coroutines.h"
 *
 * defCoroutine( int, sequence ) {
 *   int i;
 *   cBegin;
 *     i = 0;
 *     for ( true )
 *       cYield( ++i );
 *   cEnd;
 * };
 *
 * sequence s1;
 * int n;
 *
 * void setup() {
 *   n = 0;
 *   Serial.begin(9600);
 * }
 *
 * void loop() {
 *   int x;
 *   while ( n < 9 && cResume( x, func ) )
 *     ++n;
 *     Serial.print( x );
 *     delay( 200 );
 * }
 */

#define defCoroutine(T, name) struct name : Coroutine<T>

#define cBegin virtual RetType operator()() { switch (_state) { case 0:;

#define cYield(x) do { _state = __LINE__; return x; \
                   case __LINE__:; } while (0)

#define cStop { _live = false; return _defRet; }

#define cResume(v,C) (v = C(), C.Live())

#define cEnd }; cStop; }

template<class T>
class Coroutine {
  protected:
    typedef T RetType;
    int _state;
    bool _live;
    RetType _defRet;
    Coroutine() : _state( 0 ), _live( true ) {}
  public:
    bool Live() { return _live; }
};

#endif

PS I don’t generally advocate unbalanced brackets in macros or the kind of abuse of C++ that is presented here, honest. I may have overdone it with the macros, in the name of implementation hiding.

1 Like