AceRoutine  0.3
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 "Flash.h" // ACE_ROUTINE_F()
31 #include "FCString.h"
32 
33 class AceRoutineTest_statusStrings;
34 
66 #define COROUTINE(...) \
67  GET_COROUTINE(__VA_ARGS__, COROUTINE2, COROUTINE1)(__VA_ARGS__)
68 
69 #define GET_COROUTINE(_1, _2, NAME, ...) NAME
70 
71 #define COROUTINE1(name) \
72 struct Coroutine_##name : ace_routine::Coroutine { \
73  Coroutine_##name(); \
74  int runCoroutine() override \
75  __attribute__((__noinline__,__noclone__)); \
76 } name; \
77 Coroutine_##name :: Coroutine_##name() { \
78  setupCoroutine(ACE_ROUTINE_F(#name)); \
79 } \
80 int Coroutine_##name :: runCoroutine()
81 
82 #define COROUTINE2(className, name) \
83 struct className##_##name : className { \
84  className##_##name(); \
85  int runCoroutine() override \
86  __attribute__((__noinline__,__noclone__)); \
87 } name; \
88 className##_##name :: className##_##name() { \
89  setupCoroutine(ACE_ROUTINE_F(#name)); \
90 } \
91 int className##_##name :: runCoroutine()
92 
101 #define EXTERN_COROUTINE(...) \
102  GET_EXTERN_COROUTINE(\
103  __VA_ARGS__, EXTERN_COROUTINE2, EXTERN_COROUTINE1)(__VA_ARGS__)
104 
105 #define GET_EXTERN_COROUTINE(_1, _2, NAME, ...) NAME
106 
107 #define EXTERN_COROUTINE1(name) \
108 struct Coroutine_##name : ace_routine::Coroutine { \
109  Coroutine_##name(); \
110  int runCoroutine() override \
111  __attribute__((__noinline__,__noclone__)); \
112 }; \
113 extern Coroutine_##name name
114 
115 #define EXTERN_COROUTINE2(className, name) \
116 struct className##_##name : className { \
117  className##_##name(); \
118  int runCoroutine() override \
119  __attribute__((__noinline__,__noclone__)); \
120 }; \
121 extern className##_##name name
122 
124 #define COROUTINE_BEGIN() \
125  void* p = getJump(); \
126  if (p != nullptr) { \
127  goto *p; \
128  }
129 
134 #define COROUTINE_LOOP() \
135  COROUTINE_BEGIN(); \
136  while (true) \
137 
138 #define COROUTINE_YIELD_INTERNAL() \
139  do { \
140  __label__ jumpLabel; \
141  setJump(&& jumpLabel); \
142  return 0; \
143  jumpLabel: ; \
144  } while (false)
145 
147 #define COROUTINE_YIELD() \
148  do { \
149  setYielding(); \
150  COROUTINE_YIELD_INTERNAL(); \
151  setRunning(); \
152  } while (false)
153 
164 #define COROUTINE_AWAIT(condition) \
165  do { \
166  setYielding(); \
167  do { \
168  COROUTINE_YIELD_INTERNAL(); \
169  } while (!(condition)); \
170  setRunning(); \
171  } while (false)
172 
189 #define COROUTINE_DELAY(delayMillis) \
190  do { \
191  setDelayMillis(delayMillis); \
192  setDelaying(); \
193  do { \
194  COROUTINE_YIELD_INTERNAL(); \
195  } while (!isDelayExpired()); \
196  setRunning(); \
197  } while (false)
198 
200 #define COROUTINE_DELAY_MICROS(delayMicros) \
201  do { \
202  setDelayMicros(delayMicros); \
203  setDelaying(); \
204  do { \
205  COROUTINE_YIELD_INTERNAL(); \
206  } while (!isDelayExpired()); \
207  setRunning(); \
208  } while (false)
209 
211 #define COROUTINE_DELAY_SECONDS(delaySeconds) \
212  do { \
213  setDelaySeconds(delaySeconds); \
214  setDelaying(); \
215  do { \
216  COROUTINE_YIELD_INTERNAL(); \
217  } while (!isDelayExpired()); \
218  setRunning(); \
219  } while (false)
220 
225 #define COROUTINE_END() \
226  do { \
227  __label__ jumpLabel; \
228  setEnding(); \
229  setJump(&& jumpLabel); \
230  jumpLabel: ; \
231  return 0; \
232  } while (false)
233 
234 namespace ace_routine {
235 
236 namespace internal {
243  inline unsigned long udiv1000(unsigned long n) {
244  // Use binomial expansion of 1/(1-x).
245  // 1/1000 = 1/(1024 - 24)
246  // = (1/2^10) * (1 / (1 - 3/2^7))
247  // = (1/2^10) * (1 + 3/2^7 + 9/2^14 + 27/2^21 + ...)
248  // = (1/2^10 + 3/2^17 + 9/2^24 + 27/2^31 + ...)
249  unsigned long x = (n >> 8);
250  unsigned long y = (x >> 8);
251  unsigned long z = (y >> 8);
252  return (x >> 2) + 3 * (y >> 1) + 9 * z;
253  }
254 }
255 
260 class Coroutine {
261  friend class CoroutineScheduler;
262  friend class ::AceRoutineTest_statusStrings;
263 
264  public:
270  static Coroutine** getRoot();
271 
277  Coroutine** getNext() { return &mNext; }
278 
280  const FCString& getName() const { return mName; }
281 
292  virtual int runCoroutine() = 0;
293 
298  virtual unsigned long coroutineMillis() const;
299 
304  virtual unsigned long coroutineMicros() const;
305 
312  virtual unsigned long coroutineSeconds() const;
313 
324  void suspend() {
325  if (isDone()) return;
326  mStatus = kStatusSuspended;
327  }
328 
335  void resume();
336 
338  bool isDelayExpired() {
339  switch (mDelayType) {
340  default:
341  case kDelayTypeMillis: {
342  uint16_t elapsedMillis = coroutineMillis() - mDelayStart;
343  return elapsedMillis >= mDelayDuration;
344  }
345  case kDelayTypeMicros: {
346  uint16_t elapsedMicros = coroutineMicros() - mDelayStart;
347  return elapsedMicros >= mDelayDuration;
348  }
349  case kDelayTypeSeconds: {
350  uint16_t elapsedSeconds = coroutineSeconds() - mDelayStart;
351  return elapsedSeconds >= mDelayDuration;
352  }
353  }
354  }
355 
357  bool isSuspended() const { return mStatus == kStatusSuspended; }
358 
360  bool isYielding() const { return mStatus == kStatusYielding; }
361 
363  bool isDelaying() const { return mStatus == kStatusDelaying; }
364 
366  bool isRunning() const { return mStatus == kStatusRunning; }
367 
373  bool isEnding() const { return mStatus == kStatusEnding; }
374 
381  bool isTerminated() const { return mStatus == kStatusTerminated; }
382 
388  bool isDone() const {
389  return mStatus == kStatusEnding || mStatus == kStatusTerminated;
390  }
391 
406  void setupCoroutine(const char* name) {
407  mName = FCString(name);
408  mStatus = kStatusYielding;
409  insertSorted();
410  }
411 
425  void setupCoroutine(const __FlashStringHelper* name) {
426  mName = FCString(name);
427  mStatus = kStatusYielding;
428  insertSorted();
429  }
430 
431  protected:
462  typedef uint8_t Status;
463 
470  static const Status kStatusSuspended = 0;
471 
473  static const Status kStatusYielding = 1;
474 
476  static const Status kStatusDelaying = 2;
477 
479  static const Status kStatusRunning = 3;
480 
482  static const Status kStatusEnding = 4;
483 
485  static const Status kStatusTerminated = 5;
486 
488  static const uint8_t kDelayTypeMillis = 0;
489 
491  static const uint8_t kDelayTypeMicros = 1;
492 
494  static const uint8_t kDelayTypeSeconds = 2;
495 
507 
509  virtual ~Coroutine() {}
510 
512  Status getStatus() const { return mStatus; }
513 
515  void statusPrintTo(Print& printer) {
516  printer.print(sStatusStrings[mStatus]);
517  }
518 
523  void setJump(void* jumpPoint) { mJumpPoint = jumpPoint; }
524 
529  void* getJump() const { return mJumpPoint; }
530 
532  void setRunning() { mStatus = kStatusRunning; }
533 
535  void setYielding() { mStatus = kStatusYielding; }
536 
538  void setDelaying() { mStatus = kStatusDelaying; }
539 
553  void setDelayMillis(uint16_t delayMillis) {
554  mDelayType = kDelayTypeMillis;
555  mDelayStart = coroutineMillis();
556  mDelayDuration = (delayMillis >= UINT16_MAX / 2)
557  ? UINT16_MAX / 2
558  : delayMillis;
559  }
560 
565  void setDelayMicros(uint16_t delayMicros) {
566  mDelayType = kDelayTypeMicros;
567  mDelayStart = coroutineMicros();
568  mDelayDuration = (delayMicros >= UINT16_MAX / 2)
569  ? UINT16_MAX / 2
570  : delayMicros;
571  }
572 
577  void setDelaySeconds(uint16_t delaySeconds) {
578  mDelayType = kDelayTypeSeconds;
579  mDelayStart = coroutineSeconds();
580  mDelayDuration = (delaySeconds >= UINT16_MAX / 2)
581  ? UINT16_MAX / 2
582  : delaySeconds;
583  }
584 
586  void setEnding() { mStatus = kStatusEnding; }
587 
592  void setTerminated() { mStatus = kStatusTerminated; }
593 
594  private:
595  // Disable copy-constructor and assignment operator
596  Coroutine(const Coroutine&) = delete;
597  Coroutine& operator=(const Coroutine&) = delete;
598 
600  static const __FlashStringHelper* const sStatusStrings[];
601 
614  void insertSorted();
615 
616  FCString mName;
617  Coroutine* mNext = nullptr;
618  void* mJumpPoint = nullptr;
619  Status mStatus = kStatusSuspended;
620  uint8_t mDelayType;
621  uint16_t mDelayStart; // millis or micros
622  uint16_t mDelayDuration; // millis or micros
623 };
624 
625 }
626 
627 #endif
Coroutine()
Constructor.
Definition: Coroutine.h:506
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:406
void setDelaySeconds(uint16_t delaySeconds)
Configure the delay timer for delaySeconds.
Definition: Coroutine.h:577
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:592
void setJump(void *jumpPoint)
Pointer to label where execute will start on the next call to runCoroutine().
Definition: Coroutine.h:523
void setDelayMillis(uint16_t delayMillis)
Configure the delay timer for delayMillis.
Definition: Coroutine.h:553
void * getJump() const
Pointer to label where execute will start on the next call to runCoroutine().
Definition: Coroutine.h:529
bool isDone() const
The coroutine is either Ending or Terminated.
Definition: Coroutine.h:388
const FCString & getName() const
Human-readable name of the coroutine.
Definition: Coroutine.h:280
bool isTerminated() const
The coroutine was terminated by the scheduler with a call to setTerminated().
Definition: Coroutine.h:381
bool isDelaying() const
The coroutine returned using COROUTINE_DELAY().
Definition: Coroutine.h:363
bool isEnding() const
The coroutine returned using COROUTINE_END().
Definition: Coroutine.h:373
void setupCoroutine(const __FlashStringHelper *name)
Same as setupCoroutine(const char*) except using flash string type.
Definition: Coroutine.h:425
Status getStatus() const
Return the status of the coroutine.
Definition: Coroutine.h:512
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:462
A union of (const char*) and (const __FlashStringHelper*) with a discriminator.
Definition: FCString.h:53
unsigned long udiv1000(unsigned long n)
Approximate division by 1000.
Definition: Coroutine.h:243
void setYielding()
Set the kStatusDelaying state.
Definition: Coroutine.h:535
bool isRunning() const
The coroutine is currently running.
Definition: Coroutine.h:366
virtual ~Coroutine()
Destructor.
Definition: Coroutine.h:509
bool isYielding() const
The coroutine returned using COROUTINE_YIELD().
Definition: Coroutine.h:360
void setRunning()
Set the kStatusRunning state.
Definition: Coroutine.h:532
void statusPrintTo(Print &printer)
Print the human-readable string of the Status.
Definition: Coroutine.h:515
void setEnding()
Set the kStatusEnding state.
Definition: Coroutine.h:586
bool isSuspended() const
The coroutine was suspended with a call to suspend().
Definition: Coroutine.h:357
bool isDelayExpired()
Check if delay time is over.
Definition: Coroutine.h:338
Coroutine ** getNext()
Return the next pointer as a pointer to the pointer, similar to getRoot().
Definition: Coroutine.h:277
Base class of all coroutines.
Definition: Coroutine.h:260
Various macros to smooth over the differences among the various platforms with regards to their suppo...
void setDelaying()
Set the kStatusDelaying state.
Definition: Coroutine.h:538
void setDelayMicros(uint16_t delayMicros)
Configure the delay timer for delayMicros.
Definition: Coroutine.h:565
void suspend()
Suspend the coroutine at the next scheduler iteration.
Definition: Coroutine.h:324