AceRoutine  1.3.1
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 <AceCommon.h> // FCString
31 #include "ClockInterface.h"
32 
33 class AceRoutineTest_statusStrings;
34 class SuspendTest_suspendAndResume;
35 
54 // https://stackoverflow.com/questions/295120
56 #if defined(__GNUC__) || defined(__clang__)
57  #define ACE_ROUTINE_DEPRECATED __attribute__((deprecated))
58 #elif defined(_MSC_VER)
59  #define ACE_ROUTINE_DEPRECATED __declspec(deprecated)
60 #else
61  #pragma message("WARNING: Implement ACE_ROUTINE_DEPRECATED for this compiler")
62  #define ACE_ROUTINE_DEPRECATED
63 #endif
64 
78 #define COROUTINE(...) \
79  GET_COROUTINE(__VA_ARGS__, COROUTINE2, COROUTINE1)(__VA_ARGS__)
80 
82 #define GET_COROUTINE(_1, _2, NAME, ...) NAME
83 
85 #define COROUTINE1(name) \
86 struct Coroutine_##name : ace_routine::Coroutine { \
87  Coroutine_##name(); \
88  int runCoroutine() override; \
89 } name; \
90 Coroutine_##name :: Coroutine_##name() { \
91 } \
92 int Coroutine_##name :: runCoroutine()
93 
95 #define COROUTINE2(className, name) \
96 struct className##_##name : className { \
97  className##_##name(); \
98  int runCoroutine() override; \
99 } name; \
100 className##_##name :: className##_##name() { \
101 } \
102 int className##_##name :: runCoroutine()
103 
112 #define EXTERN_COROUTINE(...) \
113  GET_EXTERN_COROUTINE(\
114  __VA_ARGS__, EXTERN_COROUTINE2, EXTERN_COROUTINE1)(__VA_ARGS__)
115 
119 #define GET_EXTERN_COROUTINE(_1, _2, NAME, ...) NAME
120 
122 #define EXTERN_COROUTINE1(name) \
123 struct Coroutine_##name : ace_routine::Coroutine { \
124  Coroutine_##name(); \
125  int runCoroutine() override; \
126 }; \
127 extern Coroutine_##name name
128 
130 #define EXTERN_COROUTINE2(className, name) \
131 struct className##_##name : className { \
132  className##_##name(); \
133  int runCoroutine() override; \
134 }; \
135 extern className##_##name name
136 
138 #define COROUTINE_BEGIN() \
139  void* p = this->getJump(); \
140  if (p != nullptr) { \
141  goto *p; \
142  }
143 
148 #define COROUTINE_LOOP() \
149  COROUTINE_BEGIN(); \
150  while (true) \
151 
152 
156 #define COROUTINE_YIELD_INTERNAL() \
157  do { \
158  __label__ jumpLabel; \
159  this->setJump(&& jumpLabel); \
160  return 0; \
161  jumpLabel: ; \
162  } while (false)
163 
165 #define COROUTINE_YIELD() \
166  do { \
167  this->setYielding(); \
168  COROUTINE_YIELD_INTERNAL(); \
169  this->setRunning(); \
170  } while (false)
171 
182 #define COROUTINE_AWAIT(condition) \
183  do { \
184  this->setYielding(); \
185  do { \
186  COROUTINE_YIELD_INTERNAL(); \
187  } while (!(condition)); \
188  this->setRunning(); \
189  } while (false)
190 
206 #define COROUTINE_DELAY(delayMillis) \
207  do { \
208  this->setDelayMillis(delayMillis); \
209  this->setDelaying(); \
210  do { \
211  COROUTINE_YIELD_INTERNAL(); \
212  } while (!this->isDelayExpired()); \
213  this->setRunning(); \
214  } while (false)
215 
217 #define COROUTINE_DELAY_MICROS(delayMicros) \
218  do { \
219  this->setDelayMicros(delayMicros); \
220  this->setDelaying(); \
221  do { \
222  COROUTINE_YIELD_INTERNAL(); \
223  } while (!this->isDelayMicrosExpired()); \
224  this->setRunning(); \
225  } while (false)
226 
242 #define COROUTINE_DELAY_SECONDS(delaySeconds) \
243  do { \
244  this->setDelaySeconds(delaySeconds); \
245  this->setDelaying(); \
246  do { \
247  COROUTINE_YIELD_INTERNAL(); \
248  } while (!this->isDelaySecondsExpired()); \
249  this->setRunning(); \
250  } while (false)
251 
256 #define COROUTINE_END() \
257  do { \
258  __label__ jumpLabel; \
259  this->setEnding(); \
260  this->setJump(&& jumpLabel); \
261  jumpLabel: ; \
262  return 0; \
263  } while (false)
264 
265 namespace ace_routine {
266 
268 extern const __FlashStringHelper* const sStatusStrings[];
269 
270 // Forward declaration of CoroutineSchedulerTemplate<T>
271 template <typename T> class CoroutineSchedulerTemplate;
272 
277 template <typename T_CLOCK>
279  friend class CoroutineSchedulerTemplate<CoroutineTemplate<T_CLOCK>>;
280  friend class ::AceRoutineTest_statusStrings;
281  friend class ::SuspendTest_suspendAndResume;
282 
283  public:
294  virtual int runCoroutine() = 0;
295 
306  void suspend() {
307  if (isDone()) return;
309  }
310 
317  void resume() {
318  if (mStatus != kStatusSuspended) return;
319 
320  // We lost the original state of the coroutine when suspend() was called
321  // but the coroutine will automatically go back into the original state
322  // when Coroutine::runCoroutine() is called because COROUTINE_YIELD(),
323  // COROUTINE_DELAY() and COROUTINE_AWAIT() are written to restore their
324  // status.
326  }
327 
341  void reset() {
343  mJumpPoint = nullptr;
344  }
345 
347  bool isDelayExpired() const {
348  uint16_t nowMillis = coroutineMillis();
349  uint16_t elapsed = nowMillis - mDelayStart;
350  return elapsed >= mDelayDuration;
351  }
352 
354  bool isDelayMicrosExpired() const {
355  uint16_t nowMicros = coroutineMicros();
356  uint16_t elapsed = nowMicros - mDelayStart;
357  return elapsed >= mDelayDuration;
358  }
359 
361  bool isDelaySecondsExpired() const {
362  uint16_t nowSeconds = coroutineSeconds();
363  uint16_t elapsed = nowSeconds - mDelayStart;
364  return elapsed >= mDelayDuration;
365  }
366 
368  bool isSuspended() const { return mStatus == kStatusSuspended; }
369 
371  bool isYielding() const { return mStatus == kStatusYielding; }
372 
374  bool isDelaying() const { return mStatus == kStatusDelaying; }
375 
377  bool isRunning() const { return mStatus == kStatusRunning; }
378 
384  bool isEnding() const { return mStatus == kStatusEnding; }
385 
392  bool isTerminated() const { return mStatus == kStatusTerminated; }
393 
399  bool isDone() const {
401  }
402 
409  void setupCoroutine(const char* /*name*/) ACE_ROUTINE_DEPRECATED {}
410 
417  void setupCoroutine(const __FlashStringHelper* /*name*/)
419 
420  protected:
451  typedef uint8_t Status;
452 
459  static const Status kStatusSuspended = 0;
460 
462  static const Status kStatusYielding = 1;
463 
465  static const Status kStatusDelaying = 2;
466 
468  static const Status kStatusRunning = 3;
469 
471  static const Status kStatusEnding = 4;
472 
474  static const Status kStatusTerminated = 5;
475 
478  insertAtRoot();
479  }
480 
493  ~CoroutineTemplate() = default;
494 
496  Status getStatus() const { return mStatus; }
497 
499  void statusPrintTo(Print& printer) {
500  printer.print(sStatusStrings[mStatus]);
501  }
502 
507  void setJump(void* jumpPoint) { mJumpPoint = jumpPoint; }
508 
513  void* getJump() const { return mJumpPoint; }
514 
517 
520 
523 
526 
532 
546  void setDelayMillis(uint16_t delayMillis) {
548 
549  // If delayMillis is a compile-time constant, the compiler seems to
550  // completely optimize away this bounds checking code.
551  mDelayDuration = (delayMillis >= UINT16_MAX / 2)
552  ? UINT16_MAX / 2
553  : delayMillis;
554  }
555 
560  void setDelayMicros(uint16_t delayMicros) {
562 
563  // If delayMicros is a compile-time constant, the compiler seems to
564  // completely optimize away this bounds checking code.
565  mDelayDuration = (delayMicros >= UINT16_MAX / 2)
566  ? UINT16_MAX / 2
567  : delayMicros;
568  }
569 
574  void setDelaySeconds(uint16_t delaySeconds) {
576 
577  // If delaySeconds is a compile-time constant, the compiler seems to
578  // completely optimize away this bounds checking code.
579  mDelayDuration = (delaySeconds >= UINT16_MAX / 2)
580  ? UINT16_MAX / 2
581  : delaySeconds;
582  }
583 
589  static unsigned long coroutineMillis() {
590  return T_CLOCK::millis();
591  }
592 
598  static unsigned long coroutineMicros() {
599  return T_CLOCK::micros();
600  }
601 
608  static unsigned long coroutineSeconds() {
609  return T_CLOCK::seconds();
610  }
611 
612  private:
613  // Disable copy-constructor and assignment operator
614  CoroutineTemplate(const CoroutineTemplate&) = delete;
615  CoroutineTemplate& operator=(const CoroutineTemplate&) = delete;
616 
622  static CoroutineTemplate** getRoot() {
623  // Use a static variable inside a function to solve the static
624  // initialization ordering problem.
625  static CoroutineTemplate* root;
626  return &root;
627  }
628 
635  CoroutineTemplate** getNext() { return &mNext; }
636 
643  void insertAtRoot() {
644  CoroutineTemplate** root = getRoot();
645  mNext = *root;
646  *root = this;
647  }
648 
649  protected:
652 
654  void* mJumpPoint = nullptr;
655 
658 
664  uint16_t mDelayStart;
665 
671  uint16_t mDelayDuration;
672 };
673 
681 
682 }
683 
684 #endif
ace_routine::CoroutineTemplate::isYielding
bool isYielding() const
The coroutine returned using COROUTINE_YIELD().
Definition: Coroutine.h:371
ace_routine::CoroutineTemplate::coroutineMillis
static unsigned long coroutineMillis()
Returns the current millisecond clock.
Definition: Coroutine.h:589
ace_routine::CoroutineTemplate::kStatusYielding
static const Status kStatusYielding
Coroutine returned using the COROUTINE_YIELD() statement.
Definition: Coroutine.h:462
ace_routine::CoroutineTemplate::kStatusEnding
static const Status kStatusEnding
Coroutine executed the COROUTINE_END() statement.
Definition: Coroutine.h:471
ace_routine::CoroutineTemplate::mDelayStart
uint16_t mDelayStart
Start time provided by COROUTINE_DELAY(), COROUTINE_DELAY_MICROS(), or COROUTINE_DELAY_SECONDS().
Definition: Coroutine.h:664
ace_routine::CoroutineTemplate::mNext
CoroutineTemplate * mNext
Pointer to the next coroutine in a singly-linked list.
Definition: Coroutine.h:651
ace_routine::CoroutineTemplate::Status
uint8_t Status
The execution status of the coroutine, corresponding to the COROUTINE_YIELD(), COROUTINE_DELAY(),...
Definition: Coroutine.h:451
ace_routine::CoroutineTemplate::CoroutineTemplate
CoroutineTemplate()
Constructor.
Definition: Coroutine.h:477
ace_routine::CoroutineTemplate::setupCoroutine
void setupCoroutine(const __FlashStringHelper *) ACE_ROUTINE_DEPRECATED
Deprecated method that does nothing.
Definition: Coroutine.h:417
ace_routine::CoroutineTemplate::isTerminated
bool isTerminated() const
The coroutine was terminated by the scheduler with a call to setTerminated().
Definition: Coroutine.h:392
ace_routine::CoroutineTemplate::suspend
void suspend()
Suspend the coroutine at the next scheduler iteration.
Definition: Coroutine.h:306
ace_routine::CoroutineTemplate::setYielding
void setYielding()
Set the kStatusDelaying state.
Definition: Coroutine.h:519
ace_routine::CoroutineTemplate::setDelaySeconds
void setDelaySeconds(uint16_t delaySeconds)
Configure the delay timer for delaySeconds.
Definition: Coroutine.h:574
ace_routine::CoroutineTemplate::~CoroutineTemplate
~CoroutineTemplate()=default
Destructor.
ace_routine::CoroutineTemplate::coroutineMicros
static unsigned long coroutineMicros()
Returns the current microseconds clock.
Definition: Coroutine.h:598
ace_routine::CoroutineTemplate::kStatusSuspended
static const Status kStatusSuspended
Coroutine has been suspended using suspend() and the scheduler should remove it from the queue upon t...
Definition: Coroutine.h:459
ace_routine::CoroutineTemplate::reset
void reset()
Reset the coroutine to its initial state.
Definition: Coroutine.h:341
ace_routine::CoroutineTemplate::resume
void resume()
Add a Suspended coroutine into the head of the scheduler linked list, and change the state to Yieldin...
Definition: Coroutine.h:317
ace_routine::CoroutineTemplate::setJump
void setJump(void *jumpPoint)
Pointer to label where execute will start on the next call to runCoroutine().
Definition: Coroutine.h:507
ace_routine::CoroutineTemplate::setDelayMillis
void setDelayMillis(uint16_t delayMillis)
Configure the delay timer for delayMillis.
Definition: Coroutine.h:546
ace_routine::CoroutineTemplate::kStatusRunning
static const Status kStatusRunning
Coroutine is currenly running.
Definition: Coroutine.h:468
ace_routine::CoroutineTemplate
Base class of all coroutines.
Definition: Coroutine.h:278
ace_routine::CoroutineTemplate::isRunning
bool isRunning() const
The coroutine is currently running.
Definition: Coroutine.h:377
ace_routine::CoroutineTemplate::kStatusDelaying
static const Status kStatusDelaying
Coroutine returned using the COROUTINE_DELAY() statement.
Definition: Coroutine.h:465
ace_routine::CoroutineTemplate::setTerminated
void setTerminated()
Set status to indicate that the Coroutine has been removed from the Scheduler queue.
Definition: Coroutine.h:531
ace_routine::CoroutineTemplate::getStatus
Status getStatus() const
Return the status of the coroutine.
Definition: Coroutine.h:496
ace_routine::CoroutineTemplate::runCoroutine
virtual int runCoroutine()=0
The body of the coroutine.
ace_routine::CoroutineTemplate::setRunning
void setRunning()
Set the kStatusRunning state.
Definition: Coroutine.h:516
ace_routine::CoroutineTemplate::kStatusTerminated
static const Status kStatusTerminated
Coroutine has ended and no longer in the scheduler queue.
Definition: Coroutine.h:474
ace_routine::CoroutineTemplate::isSuspended
bool isSuspended() const
The coroutine was suspended with a call to suspend().
Definition: Coroutine.h:368
ace_routine::CoroutineTemplate::isDelaySecondsExpired
bool isDelaySecondsExpired() const
Check if delay seconds time is over.
Definition: Coroutine.h:361
ace_routine::CoroutineTemplate::setDelaying
void setDelaying()
Set the kStatusDelaying state.
Definition: Coroutine.h:522
ace_routine::CoroutineSchedulerTemplate
Class that manages instances of the Coroutine class, and executes them in a round-robin fashion.
Definition: Coroutine.h:271
ace_routine::CoroutineTemplate::isDone
bool isDone() const
The coroutine is either Ending or Terminated.
Definition: Coroutine.h:399
ace_routine::CoroutineTemplate::mStatus
Status mStatus
Run-state of the coroutine.
Definition: Coroutine.h:657
ace_routine::CoroutineTemplate::mDelayDuration
uint16_t mDelayDuration
Delay time specified by COROUTINE_DELAY(), COROUTINE_DELAY_MICROS() or, COROUTINE_DELAY_SECONDS().
Definition: Coroutine.h:671
ace_routine::CoroutineTemplate::setupCoroutine
void setupCoroutine(const char *) ACE_ROUTINE_DEPRECATED
Deprecated method that does nothing.
Definition: Coroutine.h:409
ace_routine::CoroutineTemplate::isDelayExpired
bool isDelayExpired() const
Check if delay millis time is over.
Definition: Coroutine.h:347
ace_routine::CoroutineTemplate::mJumpPoint
void * mJumpPoint
Address of the label used by the computed-goto.
Definition: Coroutine.h:654
ace_routine::CoroutineTemplate::isDelayMicrosExpired
bool isDelayMicrosExpired() const
Check if delay micros time is over.
Definition: Coroutine.h:354
ace_routine::CoroutineTemplate::isDelaying
bool isDelaying() const
The coroutine returned using COROUTINE_DELAY().
Definition: Coroutine.h:374
ace_routine::CoroutineTemplate::isEnding
bool isEnding() const
The coroutine returned using COROUTINE_END().
Definition: Coroutine.h:384
ace_routine::CoroutineTemplate::coroutineSeconds
static unsigned long coroutineSeconds()
Returns the current clock in unit of seconds, truncated to the lower 16-bits.
Definition: Coroutine.h:608
ace_routine::CoroutineTemplate::getJump
void * getJump() const
Pointer to label where execute will start on the next call to runCoroutine().
Definition: Coroutine.h:513
ace_routine::CoroutineTemplate::setEnding
void setEnding()
Set the kStatusEnding state.
Definition: Coroutine.h:525
ace_routine::CoroutineTemplate::statusPrintTo
void statusPrintTo(Print &printer)
Print the human-readable string of the Status.
Definition: Coroutine.h:499
ACE_ROUTINE_DEPRECATED
#define ACE_ROUTINE_DEPRECATED
Macro that indicates a deprecation.
Definition: Coroutine.h:62
ace_routine::CoroutineTemplate::setDelayMicros
void setDelayMicros(uint16_t delayMicros)
Configure the delay timer for delayMicros.
Definition: Coroutine.h:560