Skip to content

Commit 599ca4c

Browse files
authored
Merge pull request #22 from bxparks/develop
v1.2.1 - add DEVELOPER.md notes
2 parents b907fb5 + dd1274d commit 599ca4c

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+408
-149
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
# Changelog
22

33
* Unreleased
4+
* 1.2.1 (2020-11-12)
5+
* Add `DEVELOPER.md` notes to myself.
6+
* Add python script to generate the README.md in
7+
[examples/MemoryBenchmarks](examples/MemoryBenchmarks) to automatically
8+
regenerate the embedded ascii tables. Regenerate README.md for v1.2.
49
* 1.2 (2020-11-10)
510
* Fix an infinite loop in the internal singly-linked list of coroutines when
611
`resume()` is called immediately after `suspend()`, without waiting for

DEVELOPER.md

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
# Developer Notes
2+
3+
Information useful to developers of this library. In other words, things that
4+
even I have trouble remembering.
5+
6+
## Coroutine Linked List
7+
8+
The `COROUTINE()` macro creates an instance of `Coroutine` whose constructor
9+
calls `setupCoroutine(const char* name)`. The `setupCoroutine()` method inserts
10+
the coroutine instance into a singly linked list. This allows the
11+
`CoroutineScheduler` to iterate over the linked list and allow the coroutines to
12+
execute in a round-robin fashion.
13+
14+
It always takes me a few minutes to understand how I implemented the singly
15+
linked list, so here are some notes to myself.
16+
17+
Each instance of `Coroutine` contains an `mNext` variable that points to the
18+
next instance of `Coroutine`. The last instance has an `mNext` that contains a
19+
`nullptr`.
20+
21+
```
22+
getRoot() Coroutine Coroutine
23+
+--------+ +--------+ +--------+
24+
| | --->| | -->| | --> nullptr
25+
| | / | | / | | /
26+
| root------- | mNext------ | mNext-------
27+
+-^------+ +-^------+ +-^------+
28+
Coroutine | / /
29+
Scheduler | / /
30+
+---------+ | / /
31+
| | | / /
32+
|mCurrent------------------------------------
33+
+---------+
34+
```
35+
36+
The first element of the linked list is defined by the pointer stored in a
37+
static variable called `root` inside the `Coroutine::getRoot()` static function.
38+
Static variables inside functions are guaranteed to be initialized only once
39+
upon the first call to `getRoot()` and will be initialized to the default value
40+
of the type (`nullptr` in this case).
41+
42+
The `CoroutineScheduler` uses a variable called `mCurrent` to keep track of the
43+
`Coroutine` which is currently being handled. The important part is to realize
44+
that `mCurrent` does *not* hold a pointer to `Coroutine` (i.e. `Coroutine*`) but
45+
it holds a pointer to a pointer to `Coroutine` (i.e. `Coroutine**`). Similarly,
46+
`Coroutine::getRoot()` returns a *pointer* to the `root` (i.e. `Coroutine**`),
47+
not the value of `root` (i.e. `Coroutine*`).
48+
49+
Using the pointer to a pointer (i.e. `Coroutine**`) to represent the "current"
50+
coroutine has a number of advantages and simplifies the code that traverses the
51+
linked list.
52+
53+
* The root node can be treated the same as a regular `Coroutine` node.
54+
* See `CoroutineScheduler::listCoroutines()` to see this in action.
55+
* It allows new nodes to be inserted just after the "current" node.
56+
* The `mCurrent` points to the `mNext` of the previous node, so with just a
57+
single pointer, both the previous node and the current node can be
58+
modified. If the "current" node was represented by a `Coroutine*`, it
59+
would not be possible to update the previous node because this is a singly
60+
linked list.
61+
* See `Coroutine::insertAtRoot()` and `Coroutine::insertSorted()` to see how
62+
this work.
63+
* It also allows the "current" node to be deleted from the linked list, although
64+
this capability is not used in the library.

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ others (in my opinion of course):
5252
* uses the [computed goto](https://gcc.gnu.org/onlinedocs/gcc/Labels-as-Values.html)
5353
feature of the GCC compiler (also supported by Clang) to avoid the
5454
[Duff's Device](https://en.wikipedia.org/wiki/Duff%27s_device) hack
55-
* allows `switch` statemens in the coroutines
55+
* allows `switch` statements in the coroutines
5656
* C/C++ macros eliminate boilerplate code and make the code easy to read
5757
* the base `Coroutine` class is easy to subclass to add additional variables and
5858
functions
@@ -80,7 +80,7 @@ Arduino API (AVR, Teensy, ESP8266, ESP32, etc), and it provides a handful of
8080
additional macros that can reduce boilerplate code.
8181

8282

83-
**Version**: 1.2 (2020-11-10)
83+
**Version**: 1.2.1 (2020-11-12)
8484

8585
**Changelog**: [CHANGELOG.md](CHANGELOG.md)
8686

USER_GUIDE.md

Lines changed: 77 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
1-
# User Guide
1+
# AceRoutine User Guide
22

33
See the [README.md](README.md) for installation instructions and other
44
background information. This document describes how to use the library once it
55
is installed.
66

7-
**Version**: 1.2 (2020-11-10)
7+
**Version**: 1.2.1 (2020-11-12)
88

99
**Table of Contents**
1010

1111
* [Coroutine Setup](#Setup)
12-
* [Include Header and Namespace](#Include)
12+
* [Include Header and Namespace](#IncludeHeader)
1313
* [Macros](#Macros)
1414
* [Overall Structure](#OverallStructure)
1515
* [Coroutine Class](#CoroutineClass)
@@ -26,10 +26,7 @@ is installed.
2626
* [While Loops](#WhileLoops)
2727
* [Forever Loops](#ForeverLoops)
2828
* [Macros As Statements](#MacrosAsStatements)
29-
* [Coroutine Interactions](#CoroutineInteractions)
30-
* [No Nested Loop Macro](#NoNestedLoop)
31-
* [No Delegation to Regular Functions](#NoDelegation)
32-
* [Chaining Coroutines](#Chaining)
29+
* [Chaining Coroutines](#ChainingCoroutines)
3330
* [Running and Scheduling](#RunningAndScheduling)
3431
* [Manual Scheduling](#ManualScheduling)
3532
* [CoroutineScheduler](#CoroutineScheduler)
@@ -38,19 +35,22 @@ is installed.
3835
* [Reset Coroutine](#Reset)
3936
* [Coroutine States](#States)
4037
* [Customizing](#Customizing)
41-
* [Custom Coroutines](#Custom)
42-
* [Manual Coroutines](#Manual)
38+
* [Custom Coroutines](#CustomCoroutines)
39+
* [Manual Coroutines](#ManualCoroutines)
4340
* [Coroutine Communication](#Communication)
4441
* [Instance Variables](#InstanceVariables)
4542
* [Channels (Experimental)](#Channels)
4643
* [Miscellaneous](#Miscellaneous)
4744
* [External Coroutines](#External)
4845
* [Functors](#Functors)
46+
* [Bugs and Limitations](#BugsAndLimitations)
47+
* [No Nested LOOP Macro](#NoNestedLoop)
48+
* [No Delegation to Regular Functions](#NoDelegation)
4949

5050
<a name="Setup"></a>
5151
## Coroutine Setup
5252

53-
<a name="Include"></a>
53+
<a name="IncludeHeader"></a>
5454
### Include Header and Namespace
5555

5656
Only a single header file `AceRoutine.h` is required to use this library.
@@ -191,7 +191,7 @@ subclasses. There are 2 recommended ways of creating coroutines:
191191
* Manually subclassing the `Coroutine` class.
192192

193193
(The third option is useful mostly for unit testing purposes, and is explained
194-
later in the [Custom Coroutines](#Custom) section below.)
194+
later in the [Custom Coroutines](#CustomCoroutines) section below.)
195195

196196
**Using COROUTINE() Macro**
197197

@@ -260,7 +260,7 @@ Since we are creating 2 instances of the `MyCoroutine` class, we call the
260260
the constructor.
261261
262262
For more details on manual Coroutine instances, see the
263-
[Manual Coroutines](#Manual) section below.
263+
[Manual Coroutines](#ManualCoroutines) section below.
264264
265265
<a name="CoroutineBody"></a>
266266
## Coroutine Body
@@ -622,68 +622,14 @@ example, the following is allowed:
622622
...
623623
```
624624

625-
<a name="CoroutineInteractions"></a>
626-
## Coroutine Interactions
627-
628-
Here are some potentially surprising ways that certain macros or Coroutine
629-
features interact with each other inside the `runCoroutine()` method.
630-
631-
<a name="NoNestedLoop"></a>
632-
### No Nested Loop Macro
633-
634-
The `COROUTINE_LOOP()` macro cannot be nested. In other words, the following is
635-
**not** allowed:
636-
637-
```C++
638-
COROUTINE(routine) {
639-
COROUTINE_LOOP() {
640-
...
641-
if (condition) {
642-
COROUTINE_LOOP() { // <----- NOT ALLOWED
643-
...
644-
COROUTINE_YIELD();
645-
}
646-
}
647-
COROUTINE_YIELD();
648-
}
649-
}
650-
```
651-
652-
<a name="NoDelegation"></a>
653-
### No Delegation to Regular Functions
654-
655-
Coroutines macros inside the `runCoroutine()` **cannot** be delegated to another
656-
C/C++ function, even though this becomes tempting when the `runCoroutine()`
657-
implementation becomes complex. In other words, if you call another function
658-
from within the `runCoroutine()`, you cannot use the various `COROUTINE_XXX()`
659-
macros inside the delegated function. The macros were designed to trigger a
660-
compiler error in most cases, but this is not guaranteed:
661-
662-
```C++
663-
void doSomething() {
664-
...
665-
COROUTINE_YIELD(); // <--- ***compiler error***
666-
...
667-
}
668-
669-
COROUTINE(cannotUseNestedMacros) {
670-
COROUTINE_LOOP() {
671-
if (condition) {
672-
doSomething(); // <--- doesn't work
673-
} else {
674-
COROUTINE_YIELD();
675-
}
676-
}
677-
}
678-
```
679-
680-
<a name="Chaining"></a>
625+
<a name="ChainingCoroutines"></a>
681626
### Chaining Coroutines
682627

683628
Coroutines can be chained, in other words, the `runCoroutine()` of one coroutine
684629
*can* explicitly call the `runCoroutine()` of another coroutine.
685630

686-
I have found it useful to chain coroutines when using the **Manual Coroutines**
631+
I have found it useful to chain coroutines when using the [Manual
632+
Coroutines](#ManualCoroutines)
687633
described in the section below. The ability to chain coroutines allows us to
688634
implement a [Decorator
689635
Pattern](https://en.wikipedia.org/wiki/Decorator_pattern), also known as "a
@@ -784,6 +730,7 @@ in the global `loop()` method, like this:
784730
```C++
785731
void setup() {
786732
...
733+
myRoutine.setupCoroutine(F("myRoutine"));
787734
CoroutineScheduler::setup();
788735
}
789736

@@ -797,6 +744,11 @@ coroutines that are managed by the scheduler. Each call to
797744
`CoroutineScheduler::loop()` executes one coroutine in that list in a simple
798745
round-robin scheduling algorithm.
799746

747+
If you are manually subclassing the `Coroutine` class to create your own
748+
[Manual Coroutines](#ManualCoroutines), you must call the
749+
`Coroutine::setupCoroutine()` method in the global `setup()` so that the
750+
coroutine instance is added to the `CoroutineScheduler`.
751+
800752
Prior to v1.2, the initial ordering was sorted by the `Coroutine::getName()`.
801753
And calling `suspend()` would remove the coroutine from the internal list
802754
of coroutines, and `resume()` would add the the coroutine back into the list.
@@ -966,7 +918,7 @@ COROUTINE(doSomethingElse) {
966918
<a name="Customizing"></a>
967919
## Customizing
968920
969-
<a name="Custom"></a>
921+
<a name="CustomCoroutines"></a>
970922
### Custom Coroutines (Not Recommended)
971923
972924
All coroutines are instances of the `Coroutine` class, or one of its subclasses.
@@ -1003,7 +955,7 @@ where I have found this feature to be useful is in writing the
1003955
situation, I suspect that the **Manual Coroutines** section described in
1004956
below will be more useful and easier to understand.
1005957

1006-
<a name="Manual"></a>
958+
<a name="ManualCoroutines"></a>
1007959
### Manual Coroutines (Recommended)
1008960

1009961
A manual coroutine is a custom coroutine whose body of the coroutine (i.e
@@ -1083,6 +1035,8 @@ Some examples of manual coroutines:
10831035
[BlinkSlowFastRoutine](examples/BlinkSlowFastRoutine)
10841036
* [HelloManualCoroutine](examples/HelloManualCoroutine) which shows the same
10851037
functionality as [HelloCoroutine](examples/HelloCoroutine).
1038+
* [SoundManager](examples/SoundManager) which uses both the automatic coroutine
1039+
defined by `COROUTINE()` macro and an explicitly subclasses manual coroutine.
10861040

10871041
<a name="Communication"></a>
10881042
## Coroutine Communication
@@ -1393,3 +1347,55 @@ C++ allows the creation of objects that look syntactically like functions.
13931347
by defining the `operator()` method on the class. I have not defined this method
13941348
in the `Coroutine` class because I have not found a use-case for it. However, if
13951349
someone can demonstrate a compelling use-case, then I would be happy to add it.
1350+
1351+
<a name="BugsAndLimitations"></a>
1352+
## Bugs and Limitations
1353+
1354+
<a name="NoNestedLoop"></a>
1355+
### No Nested LOOP Macro
1356+
1357+
The `COROUTINE_LOOP()` macro cannot be nested. In other words, the following is
1358+
**not** allowed:
1359+
1360+
```C++
1361+
COROUTINE(routine) {
1362+
COROUTINE_LOOP() {
1363+
...
1364+
if (condition) {
1365+
COROUTINE_LOOP() { // <----- NOT ALLOWED
1366+
...
1367+
COROUTINE_YIELD();
1368+
}
1369+
}
1370+
COROUTINE_YIELD();
1371+
}
1372+
}
1373+
```
1374+
1375+
<a name="NoDelegation"></a>
1376+
### No Delegation to Regular Functions
1377+
1378+
Coroutines macros inside the `runCoroutine()` **cannot** be delegated to another
1379+
C/C++ function, even though this becomes tempting when the `runCoroutine()`
1380+
implementation becomes complex. In other words, if you call another function
1381+
from within the `runCoroutine()`, you cannot use the various `COROUTINE_XXX()`
1382+
macros inside the delegated function. The macros were designed to trigger a
1383+
compiler error in most cases, but this is not guaranteed:
1384+
1385+
```C++
1386+
void doSomething() {
1387+
...
1388+
COROUTINE_YIELD(); // <--- ***compiler error***
1389+
...
1390+
}
1391+
1392+
COROUTINE(cannotUseNestedMacros) {
1393+
COROUTINE_LOOP() {
1394+
if (condition) {
1395+
doSomething(); // <--- doesn't work
1396+
} else {
1397+
COROUTINE_YIELD();
1398+
}
1399+
}
1400+
}
1401+
```

docs/doxygen.cfg

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ PROJECT_NAME = "AceRoutine"
3838
# could be handy for archiving the generated documentation or if some version
3939
# control system is used.
4040

41-
PROJECT_NUMBER = 1.2
41+
PROJECT_NUMBER = 1.2.1
4242

4343
# Using the PROJECT_BRIEF tag one can provide an optional one line description
4444
# for a project that appears at the top of each page and should give viewer a

docs/html/AceRoutine_8h_source.html

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
<tr style="height: 56px;">
2323
<td id="projectalign" style="padding-left: 0.5em;">
2424
<div id="projectname">AceRoutine
25-
&#160;<span id="projectnumber">1.2</span>
25+
&#160;<span id="projectnumber">1.2.1</span>
2626
</div>
2727
<div id="projectbrief">A low-memory, fast-switching, cooperative multitasking library using stackless coroutines on Arduino platforms.</div>
2828
</td>
@@ -103,8 +103,8 @@
103103
<div class="line"><a name="l00041"></a><span class="lineno"> 41</span>&#160;<span class="preprocessor">#include &quot;ace_routine/Channel.h&quot;</span></div>
104104
<div class="line"><a name="l00042"></a><span class="lineno"> 42</span>&#160; </div>
105105
<div class="line"><a name="l00043"></a><span class="lineno"> 43</span>&#160;<span class="comment">// Version format: xxyyzz == &quot;xx.yy.zz&quot;</span></div>
106-
<div class="line"><a name="l00044"></a><span class="lineno"> 44</span>&#160;<span class="preprocessor">#define ACE_ROUTINE_VERSION 10200</span></div>
107-
<div class="line"><a name="l00045"></a><span class="lineno"> 45</span>&#160;<span class="preprocessor">#define ACE_ROUTINE_VERSION_STRING &quot;1.2&quot;</span></div>
106+
<div class="line"><a name="l00044"></a><span class="lineno"> 44</span>&#160;<span class="preprocessor">#define ACE_ROUTINE_VERSION 10201</span></div>
107+
<div class="line"><a name="l00045"></a><span class="lineno"> 45</span>&#160;<span class="preprocessor">#define ACE_ROUTINE_VERSION_STRING &quot;1.2.1&quot;</span></div>
108108
<div class="line"><a name="l00046"></a><span class="lineno"> 46</span>&#160; </div>
109109
<div class="line"><a name="l00047"></a><span class="lineno"> 47</span>&#160;<span class="preprocessor">#endif</span></div>
110110
</div><!-- fragment --></div><!-- contents -->

docs/html/Channel_8h_source.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
<tr style="height: 56px;">
2323
<td id="projectalign" style="padding-left: 0.5em;">
2424
<div id="projectname">AceRoutine
25-
&#160;<span id="projectnumber">1.2</span>
25+
&#160;<span id="projectnumber">1.2.1</span>
2626
</div>
2727
<div id="projectbrief">A low-memory, fast-switching, cooperative multitasking library using stackless coroutines on Arduino platforms.</div>
2828
</td>

docs/html/CoroutineScheduler_8cpp_source.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
<tr style="height: 56px;">
2323
<td id="projectalign" style="padding-left: 0.5em;">
2424
<div id="projectname">AceRoutine
25-
&#160;<span id="projectnumber">1.2</span>
25+
&#160;<span id="projectnumber">1.2.1</span>
2626
</div>
2727
<div id="projectbrief">A low-memory, fast-switching, cooperative multitasking library using stackless coroutines on Arduino platforms.</div>
2828
</td>

0 commit comments

Comments
 (0)