AceRoutine  1.0
A low-memory, fast-switching, cooperative multitasking library using stackless coroutines on Arduino platforms.
Coroutine.h
Go to the documentation of this file.
1 /*
2 MIT License
3 
4 Copyright (c) 2018 Brian T. Park
5 
6 Permission is hereby granted, free of charge, to any person obtaining a copy
7 of this software and associated documentation files (the "Software"), to deal
8 in the Software without restriction, including without limitation the rights
9 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 copies of the Software, and to permit persons to whom the Software is
11 furnished to do so, subject to the following conditions:
12 
13 The above copyright notice and this permission notice shall be included in all
14 copies or substantial portions of the Software.
15 
16 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 SOFTWARE.
23 */
24 
25 #ifndef ACE_ROUTINE_COROUTINE_H
26 #define ACE_ROUTINE_COROUTINE_H
27 
28 #include <stdint.h> // UINT16_MAX
29 #include <Print.h> // Print
30 #include "FCString.h"
31 
32 class AceRoutineTest_statusStrings;
33 
65 #define COROUTINE(...) \
66  GET_COROUTINE(__VA_ARGS__, COROUTINE2, COROUTINE1)(__VA_ARGS__)
67 
68 #define GET_COROUTINE(_1, _2, NAME, ...) NAME
69 
70 #define COROUTINE1(name) \
71 struct Coroutine_##name : ace_routine::Coroutine { \
72  Coroutine_##name(); \
73  int runCoroutine() override; \
74 } name; \
75 Coroutine_##name :: Coroutine_##name() { \
76  setupCoroutine(F(#name)); \
77 } \
78 int Coroutine_##name :: runCoroutine()
79 
80 #define COROUTINE2(className, name) \
81 struct className##_##name : className { \
82  className##_##name(); \
83  int runCoroutine() override; \
84 } name; \
85 className##_##name :: className##_##name() { \
86  setupCoroutine(F(#name)); \
87 } \
88 int className##_##name :: runCoroutine()
89 
98 #define EXTERN_COROUTINE(...) \
99  GET_EXTERN_COROUTINE(\
100  __VA_ARGS__, EXTERN_COROUTINE2, EXTERN_COROUTINE1)(__VA_ARGS__)
101 
102 #define GET_EXTERN_COROUTINE(_1, _2, NAME, ...) NAME
103 
104 #define EXTERN_COROUTINE1(name) \
105 struct Coroutine_##name : ace_routine::Coroutine { \
106  Coroutine_##name(); \
107  int runCoroutine() override; \
108 }; \
109 extern Coroutine_##name name
110 
111 #define EXTERN_COROUTINE2(className, name) \
112 struct className##_##name : className { \
113  className##_##name(); \
114  int runCoroutine() override; \
115 }; \
116 extern className##_##name name
117 
119 #define COROUTINE_BEGIN() \
120  void* p = getJump(); \
121  if (p != nullptr) { \
122  goto *p; \
123  }
124 
129 #define COROUTINE_LOOP() \
130  COROUTINE_BEGIN(); \
131  while (true) \
132 
133 #define COROUTINE_YIELD_INTERNAL() \
134  do { \
135  __label__ jumpLabel; \
136  setJump(&& jumpLabel); \
137  return 0; \
138  jumpLabel: ; \
139  } while (false)
140 
142 #define COROUTINE_YIELD() \
143  do { \
144  setYielding(); \
145  COROUTINE_YIELD_INTERNAL(); \
146  setRunning(); \
147  } while (false)
148 
159 #define COROUTINE_AWAIT(condition) \
160  do { \
161  setYielding(); \
162  do { \
163  COROUTINE_YIELD_INTERNAL(); \
164  } while (!(condition)); \
165  setRunning(); \
166  } while (false)
167 
184 #define COROUTINE_DELAY(delayMillis) \
185  do { \
186  setDelayMillis(delayMillis); \
187  setDelaying(); \
188  do { \
189  COROUTINE_YIELD_INTERNAL(); \
190  } while (!isDelayExpired()); \
191  setRunning(); \
192  } while (false)
193 
195 #define COROUTINE_DELAY_MICROS(delayMicros) \
196  do { \
197  setDelayMicros(delayMicros); \
198  setDelaying(); \
199  do { \
200  COROUTINE_YIELD_INTERNAL(); \
201  } while (!isDelayExpired()); \
202  setRunning(); \
203  } while (false)
204 
206 #define COROUTINE_DELAY_SECONDS(delaySeconds) \
207  do { \
208  setDelaySeconds(delaySeconds); \
209  setDelaying(); \
210  do { \
211  COROUTINE_YIELD_INTERNAL(); \
212  } while (!isDelayExpired()); \
213  setRunning(); \
214  } while (false)
215 
220 #define COROUTINE_END() \
221  do { \
222  __label__ jumpLabel; \
223  setEnding(); \
224  setJump(&& jumpLabel); \
225  jumpLabel: ; \
226  return 0; \
227  } while (false)
228 
229 namespace ace_routine {
230 
231 namespace internal {
238  inline unsigned long udiv1000(unsigned long n) {
239  // Use binomial expansion of 1/(1-x).
240  // 1/1000 = 1/(1024 - 24)
241  // = (1/2^10) * (1 / (1 - 3/2^7))
242  // = (1/2^10) * (1 + 3/2^7 + 9/2^14 + 27/2^21 + ...)
243  // = (1/2^10 + 3/2^17 + 9/2^24 + 27/2^31 + ...)
244  unsigned long x = (n >> 8);
245  unsigned long y = (x >> 8);
246  unsigned long z = (y >> 8);
247  return (x >> 2) + 3 * (y >> 1) + 9 * z;
248  }
249 }
250 
255 class Coroutine {
256  friend class CoroutineScheduler;
257  friend class ::AceRoutineTest_statusStrings;
258 
259  public:
265  static Coroutine** getRoot();
266 
272  Coroutine** getNext() { return &mNext; }
273 
275  const FCString& getName() const { return mName; }
276 
287  virtual int runCoroutine() = 0;
288 
293  virtual unsigned long coroutineMillis() const;
294 
299  virtual unsigned long coroutineMicros() const;
300 
307  virtual unsigned long coroutineSeconds() const;
308 
319  void suspend() {
320  if (isDone()) return;
321  mStatus = kStatusSuspended;
322  }
323 
330  void resume();
331 
333  bool isDelayExpired() {
334  switch (mDelayType) {
335  case kDelayTypeMillis: {
336  uint16_t elapsedMillis = coroutineMillis() - mDelayStart;
337  return elapsedMillis >= mDelayDuration;
338  }
339  case kDelayTypeMicros: {
340  uint16_t elapsedMicros = coroutineMicros() - mDelayStart;
341  return elapsedMicros >= mDelayDuration;
342  }
343  case kDelayTypeSeconds: {
344  uint16_t elapsedSeconds = coroutineSeconds() - mDelayStart;
345  return elapsedSeconds >= mDelayDuration;
346  }
347  default:
348  // This should never happen.
349  return true;
350  }
351  }
352 
354  bool isSuspended() const { return mStatus == kStatusSuspended; }
355 
357  bool isYielding() const { return mStatus == kStatusYielding; }
358 
360  bool isDelaying() const { return mStatus == kStatusDelaying; }
361 
363  bool isRunning() const { return mStatus == kStatusRunning; }
364 
370  bool isEnding() const { return mStatus == kStatusEnding; }
371 
378  bool isTerminated() const { return mStatus == kStatusTerminated; }
379 
385  bool isDone() const {
386  return mStatus == kStatusEnding || mStatus == kStatusTerminated;
387  }
388 
403  void setupCoroutine(const char* name) {
404  mName = FCString(name);
405  mStatus = kStatusYielding;
406  insertSorted();
407  }
408 
422  void setupCoroutine(const __FlashStringHelper* name) {
423  mName = FCString(name);
424  mStatus = kStatusYielding;
425  insertSorted();
426  }
427 
428  protected:
459  typedef uint8_t Status;
460 
467  static const Status kStatusSuspended = 0;
468 
470  static const Status kStatusYielding = 1;
471 
473  static const Status kStatusDelaying = 2;
474 
476  static const Status kStatusRunning = 3;
477 
479  static const Status kStatusEnding = 4;
480 
482  static const Status kStatusTerminated = 5;
483 
485  static const uint8_t kDelayTypeMillis = 0;
486 
488  static const uint8_t kDelayTypeMicros = 1;
489 
491  static const uint8_t kDelayTypeSeconds = 2;
492 
504 
506  virtual ~Coroutine() {}
507 
509  Status getStatus() const { return mStatus; }
510 
512  void statusPrintTo(Print& printer) {
513  printer.print(sStatusStrings[mStatus]);
514  }
515 
520  void setJump(void* jumpPoint) { mJumpPoint = jumpPoint; }
521 
526  void* getJump() const { return mJumpPoint; }
527 
529  void setRunning() { mStatus = kStatusRunning; }
530 
532  void setYielding() { mStatus = kStatusYielding; }
533 
535  void setDelaying() { mStatus = kStatusDelaying; }
536 
538  void setEnding() { mStatus = kStatusEnding; }
539 
544  void setTerminated() { mStatus = kStatusTerminated; }
545 
559  void setDelayMillis(uint16_t delayMillis) {
560  mDelayType = kDelayTypeMillis;
561  mDelayStart = coroutineMillis();
562  mDelayDuration = (delayMillis >= UINT16_MAX / 2)
563  ? UINT16_MAX / 2
564  : delayMillis;
565  }
566 
571  void setDelayMicros(uint16_t delayMicros) {
572  mDelayType = kDelayTypeMicros;
573  mDelayStart = coroutineMicros();
574  mDelayDuration = (delayMicros >= UINT16_MAX / 2)
575  ? UINT16_MAX / 2
576  : delayMicros;
577  }
578 
583  void setDelaySeconds(uint16_t delaySeconds) {
584  mDelayType = kDelayTypeSeconds;
585  mDelayStart = coroutineSeconds();
586  mDelayDuration = (delaySeconds >= UINT16_MAX / 2)
587  ? UINT16_MAX / 2
588  : delaySeconds;
589  }
590 
591  private:
592  // Disable copy-constructor and assignment operator
593  Coroutine(const Coroutine&) = delete;
594  Coroutine& operator=(const Coroutine&) = delete;
595 
597  static const __FlashStringHelper* const sStatusStrings[];
598 
611  void insertSorted();
612 
613  FCString mName;
614  Coroutine* mNext = nullptr;
615  void* mJumpPoint = nullptr;
616  Status mStatus = kStatusSuspended;
617  uint8_t mDelayType;
618  uint16_t mDelayStart; // millis or micros
619  uint16_t mDelayDuration; // millis or micros
620 };
621 
622 }
623 
624 #endif
Coroutine()
Constructor.
Definition: Coroutine.h:503
void setupCoroutine(const char *name)
Initialize the coroutine for the CoroutineScheduler, set it to Yielding state, and add it to the link...
Definition: Coroutine.h:403
void setDelaySeconds(uint16_t delaySeconds)
Configure the delay timer for delaySeconds.
Definition: Coroutine.h:583
Class that manages instances of the Coroutine class, and executes them in a round-robin fashion...
void setTerminated()
Set status to indicate that the Coroutine has been removed from the Scheduler queue.
Definition: Coroutine.h:544
void setJump(void *jumpPoint)
Pointer to label where execute will start on the next call to runCoroutine().
Definition: Coroutine.h:520
void setDelayMillis(uint16_t delayMillis)
Configure the delay timer for delayMillis.
Definition: Coroutine.h:559
void * getJump() const
Pointer to label where execute will start on the next call to runCoroutine().
Definition: Coroutine.h:526
bool isDone() const
The coroutine is either Ending or Terminated.
Definition: Coroutine.h:385
const FCString & getName() const
Human-readable name of the coroutine.
Definition: Coroutine.h:275
bool isTerminated() const
The coroutine was terminated by the scheduler with a call to setTerminated().
Definition: Coroutine.h:378
bool isDelaying() const
The coroutine returned using COROUTINE_DELAY().
Definition: Coroutine.h:360
bool isEnding() const
The coroutine returned using COROUTINE_END().
Definition: Coroutine.h:370
void setupCoroutine(const __FlashStringHelper *name)
Same as setupCoroutine(const char*) except using flash string type.
Definition: Coroutine.h:422
Status getStatus() const
Return the status of the coroutine.
Definition: Coroutine.h:509
uint8_t Status
The execution status of the coroutine, corresponding to the COROUTINE_YIELD(), COROUTINE_DELAY(), COROUTINE_AWAIT() and COROUTINE_END() macros.
Definition: Coroutine.h:459
A union of (const char*) and (const __FlashStringHelper*) with a discriminator.
Definition: FCString.h:46
unsigned long udiv1000(unsigned long n)
Approximate division by 1000.
Definition: Coroutine.h:238
void setYielding()
Set the kStatusDelaying state.
Definition: Coroutine.h:532
bool isRunning() const
The coroutine is currently running.
Definition: Coroutine.h:363
virtual ~Coroutine()
Destructor.
Definition: Coroutine.h:506
bool isYielding() const
The coroutine returned using COROUTINE_YIELD().
Definition: Coroutine.h:357
void setRunning()
Set the kStatusRunning state.
Definition: Coroutine.h:529
void statusPrintTo(Print &printer)
Print the human-readable string of the Status.
Definition: Coroutine.h:512
void setEnding()
Set the kStatusEnding state.
Definition: Coroutine.h:538
bool isSuspended() const
The coroutine was suspended with a call to suspend().
Definition: Coroutine.h:354
bool isDelayExpired()
Check if delay time is over.
Definition: Coroutine.h:333
Coroutine ** getNext()
Return the next pointer as a pointer to the pointer, similar to getRoot().
Definition: Coroutine.h:272
Base class of all coroutines.
Definition: Coroutine.h:255
void setDelaying()
Set the kStatusDelaying state.
Definition: Coroutine.h:535
void setDelayMicros(uint16_t delayMicros)
Configure the delay timer for delayMicros.
Definition: Coroutine.h:571
void suspend()
Suspend the coroutine at the next scheduler iteration.
Definition: Coroutine.h:319