AceRoutine  1.4.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 
310  virtual void setupCoroutine() {}
311 
322  void suspend() {
323  if (isDone()) return;
325  }
326 
333  void resume() {
334  if (mStatus != kStatusSuspended) return;
335 
336  // We lost the original state of the coroutine when suspend() was called
337  // but the coroutine will automatically go back into the original state
338  // when Coroutine::runCoroutine() is called because COROUTINE_YIELD(),
339  // COROUTINE_DELAY() and COROUTINE_AWAIT() are written to restore their
340  // status.
342  }
343 
357  void reset() {
359  mJumpPoint = nullptr;
360  }
361 
363  bool isDelayExpired() const {
364  uint16_t nowMillis = coroutineMillis();
365  uint16_t elapsed = nowMillis - mDelayStart;
366  return elapsed >= mDelayDuration;
367  }
368 
370  bool isDelayMicrosExpired() const {
371  uint16_t nowMicros = coroutineMicros();
372  uint16_t elapsed = nowMicros - mDelayStart;
373  return elapsed >= mDelayDuration;
374  }
375 
377  bool isDelaySecondsExpired() const {
378  uint16_t nowSeconds = coroutineSeconds();
379  uint16_t elapsed = nowSeconds - mDelayStart;
380  return elapsed >= mDelayDuration;
381  }
382 
384  bool isSuspended() const { return mStatus == kStatusSuspended; }
385 
387  bool isYielding() const { return mStatus == kStatusYielding; }
388 
390  bool isDelaying() const { return mStatus == kStatusDelaying; }
391 
393  bool isRunning() const { return mStatus == kStatusRunning; }
394 
400  bool isEnding() const { return mStatus == kStatusEnding; }
401 
408  bool isTerminated() const { return mStatus == kStatusTerminated; }
409 
415  bool isDone() const {
417  }
418 
425  void setupCoroutine(const char* /*name*/) ACE_ROUTINE_DEPRECATED {}
426 
433  void setupCoroutine(const __FlashStringHelper* /*name*/)
435 
436  protected:
467  typedef uint8_t Status;
468 
475  static const Status kStatusSuspended = 0;
476 
478  static const Status kStatusYielding = 1;
479 
481  static const Status kStatusDelaying = 2;
482 
484  static const Status kStatusRunning = 3;
485 
487  static const Status kStatusEnding = 4;
488 
490  static const Status kStatusTerminated = 5;
491 
494  insertAtRoot();
495  }
496 
509  ~CoroutineTemplate() = default;
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 
533 
536 
539 
542 
548 
562  void setDelayMillis(uint16_t delayMillis) {
564 
565  // If delayMillis is a compile-time constant, the compiler seems to
566  // completely optimize away this bounds checking code.
567  mDelayDuration = (delayMillis >= UINT16_MAX / 2)
568  ? UINT16_MAX / 2
569  : delayMillis;
570  }
571 
576  void setDelayMicros(uint16_t delayMicros) {
578 
579  // If delayMicros is a compile-time constant, the compiler seems to
580  // completely optimize away this bounds checking code.
581  mDelayDuration = (delayMicros >= UINT16_MAX / 2)
582  ? UINT16_MAX / 2
583  : delayMicros;
584  }
585 
590  void setDelaySeconds(uint16_t delaySeconds) {
592 
593  // If delaySeconds is a compile-time constant, the compiler seems to
594  // completely optimize away this bounds checking code.
595  mDelayDuration = (delaySeconds >= UINT16_MAX / 2)
596  ? UINT16_MAX / 2
597  : delaySeconds;
598  }
599 
605  static unsigned long coroutineMillis() {
606  return T_CLOCK::millis();
607  }
608 
614  static unsigned long coroutineMicros() {
615  return T_CLOCK::micros();
616  }
617 
624  static unsigned long coroutineSeconds() {
625  return T_CLOCK::seconds();
626  }
627 
628  private:
629  // Disable copy-constructor and assignment operator
630  CoroutineTemplate(const CoroutineTemplate&) = delete;
631  CoroutineTemplate& operator=(const CoroutineTemplate&) = delete;
632 
638  static CoroutineTemplate** getRoot() {
639  // Use a static variable inside a function to solve the static
640  // initialization ordering problem.
641  static CoroutineTemplate* root;
642  return &root;
643  }
644 
651  CoroutineTemplate** getNext() { return &mNext; }
652 
659  void insertAtRoot() {
660  CoroutineTemplate** root = getRoot();
661  mNext = *root;
662  *root = this;
663  }
664 
665  protected:
668 
670  void* mJumpPoint = nullptr;
671 
674 
680  uint16_t mDelayStart;
681 
687  uint16_t mDelayDuration;
688 };
689 
697 
698 }
699 
700 #endif
ace_routine::CoroutineTemplate::isYielding
bool isYielding() const
The coroutine returned using COROUTINE_YIELD().
Definition: Coroutine.h:387
ace_routine::CoroutineTemplate::coroutineMillis
static unsigned long coroutineMillis()
Returns the current millisecond clock.
Definition: Coroutine.h:605
ace_routine::CoroutineTemplate::kStatusYielding
static const Status kStatusYielding
Coroutine returned using the COROUTINE_YIELD() statement.
Definition: Coroutine.h:478
ace_routine::CoroutineTemplate::kStatusEnding
static const Status kStatusEnding
Coroutine executed the COROUTINE_END() statement.
Definition: Coroutine.h:487
ace_routine::CoroutineTemplate::mDelayStart
uint16_t mDelayStart
Start time provided by COROUTINE_DELAY(), COROUTINE_DELAY_MICROS(), or COROUTINE_DELAY_SECONDS().
Definition: Coroutine.h:680
ace_routine::CoroutineTemplate::mNext
CoroutineTemplate * mNext
Pointer to the next coroutine in a singly-linked list.
Definition: Coroutine.h:667
ace_routine::CoroutineTemplate::Status
uint8_t Status
The execution status of the coroutine, corresponding to the COROUTINE_YIELD(), COROUTINE_DELAY(),...
Definition: Coroutine.h:467
ace_routine::CoroutineTemplate::CoroutineTemplate
CoroutineTemplate()
Constructor.
Definition: Coroutine.h:493
ace_routine::CoroutineTemplate::setupCoroutine
void setupCoroutine(const __FlashStringHelper *) ACE_ROUTINE_DEPRECATED
Deprecated method that does nothing.
Definition: Coroutine.h:433
ace_routine::CoroutineTemplate::isTerminated
bool isTerminated() const
The coroutine was terminated by the scheduler with a call to setTerminated().
Definition: Coroutine.h:408
ace_routine::CoroutineTemplate::suspend
void suspend()
Suspend the coroutine at the next scheduler iteration.
Definition: Coroutine.h:322
ace_routine::CoroutineTemplate::setYielding
void setYielding()
Set the kStatusDelaying state.
Definition: Coroutine.h:535
ace_routine::CoroutineTemplate::setDelaySeconds
void setDelaySeconds(uint16_t delaySeconds)
Configure the delay timer for delaySeconds.
Definition: Coroutine.h:590
ace_routine::CoroutineTemplate::~CoroutineTemplate
~CoroutineTemplate()=default
Destructor.
ace_routine::CoroutineTemplate::coroutineMicros
static unsigned long coroutineMicros()
Returns the current microseconds clock.
Definition: Coroutine.h:614
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:475
ace_routine::CoroutineTemplate::reset
void reset()
Reset the coroutine to its initial state.
Definition: Coroutine.h:357
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:333
ace_routine::CoroutineTemplate::setJump
void setJump(void *jumpPoint)
Pointer to label where execute will start on the next call to runCoroutine().
Definition: Coroutine.h:523
ace_routine::CoroutineTemplate::setDelayMillis
void setDelayMillis(uint16_t delayMillis)
Configure the delay timer for delayMillis.
Definition: Coroutine.h:562
ace_routine::CoroutineTemplate::kStatusRunning
static const Status kStatusRunning
Coroutine is currenly running.
Definition: Coroutine.h:484
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:393
ace_routine::CoroutineTemplate::kStatusDelaying
static const Status kStatusDelaying
Coroutine returned using the COROUTINE_DELAY() statement.
Definition: Coroutine.h:481
ace_routine::CoroutineTemplate::setTerminated
void setTerminated()
Set status to indicate that the Coroutine has been removed from the Scheduler queue.
Definition: Coroutine.h:547
ace_routine::CoroutineTemplate::getStatus
Status getStatus() const
Return the status of the coroutine.
Definition: Coroutine.h:512
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:532
ace_routine::CoroutineTemplate::kStatusTerminated
static const Status kStatusTerminated
Coroutine has ended and no longer in the scheduler queue.
Definition: Coroutine.h:490
ace_routine::CoroutineTemplate::isSuspended
bool isSuspended() const
The coroutine was suspended with a call to suspend().
Definition: Coroutine.h:384
ace_routine::CoroutineTemplate::isDelaySecondsExpired
bool isDelaySecondsExpired() const
Check if delay seconds time is over.
Definition: Coroutine.h:377
ace_routine::CoroutineTemplate::setDelaying
void setDelaying()
Set the kStatusDelaying state.
Definition: Coroutine.h:538
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:415
ace_routine::CoroutineTemplate::mStatus
Status mStatus
Run-state of the coroutine.
Definition: Coroutine.h:673
ace_routine::CoroutineTemplate::mDelayDuration
uint16_t mDelayDuration
Delay time specified by COROUTINE_DELAY(), COROUTINE_DELAY_MICROS() or, COROUTINE_DELAY_SECONDS().
Definition: Coroutine.h:687
ace_routine::CoroutineTemplate::setupCoroutine
void setupCoroutine(const char *) ACE_ROUTINE_DEPRECATED
Deprecated method that does nothing.
Definition: Coroutine.h:425
ace_routine::CoroutineTemplate::isDelayExpired
bool isDelayExpired() const
Check if delay millis time is over.
Definition: Coroutine.h:363
ace_routine::CoroutineTemplate::mJumpPoint
void * mJumpPoint
Address of the label used by the computed-goto.
Definition: Coroutine.h:670
ace_routine::CoroutineTemplate::isDelayMicrosExpired
bool isDelayMicrosExpired() const
Check if delay micros time is over.
Definition: Coroutine.h:370
ace_routine::CoroutineTemplate::isDelaying
bool isDelaying() const
The coroutine returned using COROUTINE_DELAY().
Definition: Coroutine.h:390
ace_routine::CoroutineTemplate::isEnding
bool isEnding() const
The coroutine returned using COROUTINE_END().
Definition: Coroutine.h:400
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:624
ace_routine::CoroutineTemplate::getJump
void * getJump() const
Pointer to label where execute will start on the next call to runCoroutine().
Definition: Coroutine.h:529
ace_routine::CoroutineTemplate::setEnding
void setEnding()
Set the kStatusEnding state.
Definition: Coroutine.h:541
ace_routine::CoroutineTemplate::statusPrintTo
void statusPrintTo(Print &printer)
Print the human-readable string of the Status.
Definition: Coroutine.h:515
ACE_ROUTINE_DEPRECATED
#define ACE_ROUTINE_DEPRECATED
Macro that indicates a deprecation.
Definition: Coroutine.h:62
ace_routine::CoroutineTemplate::setupCoroutine
virtual void setupCoroutine()
Perform coroutine initialization.
Definition: Coroutine.h:310
ace_routine::CoroutineTemplate::setDelayMicros
void setDelayMicros(uint16_t delayMicros)
Configure the delay timer for delayMicros.
Definition: Coroutine.h:576