AceRoutine  1.4.2
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 "ClockInterface.h"
31 
32 class AceRoutineTest_statusStrings;
33 class SuspendTest_suspendAndResume;
34 
53 // https://stackoverflow.com/questions/295120
55 #if defined(__GNUC__) || defined(__clang__)
56  #define ACE_ROUTINE_DEPRECATED __attribute__((deprecated))
57 #elif defined(_MSC_VER)
58  #define ACE_ROUTINE_DEPRECATED __declspec(deprecated)
59 #else
60  #pragma message("WARNING: Implement ACE_ROUTINE_DEPRECATED for this compiler")
61  #define ACE_ROUTINE_DEPRECATED
62 #endif
63 
77 #define COROUTINE(...) \
78  GET_COROUTINE(__VA_ARGS__, COROUTINE2, COROUTINE1)(__VA_ARGS__)
79 
81 #define GET_COROUTINE(_1, _2, NAME, ...) NAME
82 
84 #define COROUTINE1(name) \
85 struct Coroutine_##name : ace_routine::Coroutine { \
86  Coroutine_##name(); \
87  int runCoroutine() override; \
88 } name; \
89 Coroutine_##name :: Coroutine_##name() { \
90 } \
91 int Coroutine_##name :: runCoroutine()
92 
94 #define COROUTINE2(className, name) \
95 struct className##_##name : className { \
96  className##_##name(); \
97  int runCoroutine() override; \
98 } name; \
99 className##_##name :: className##_##name() { \
100 } \
101 int className##_##name :: runCoroutine()
102 
111 #define EXTERN_COROUTINE(...) \
112  GET_EXTERN_COROUTINE(\
113  __VA_ARGS__, EXTERN_COROUTINE2, EXTERN_COROUTINE1)(__VA_ARGS__)
114 
118 #define GET_EXTERN_COROUTINE(_1, _2, NAME, ...) NAME
119 
121 #define EXTERN_COROUTINE1(name) \
122 struct Coroutine_##name : ace_routine::Coroutine { \
123  Coroutine_##name(); \
124  int runCoroutine() override; \
125 }; \
126 extern Coroutine_##name name
127 
129 #define EXTERN_COROUTINE2(className, name) \
130 struct className##_##name : className { \
131  className##_##name(); \
132  int runCoroutine() override; \
133 }; \
134 extern className##_##name name
135 
137 #define COROUTINE_BEGIN() \
138  void* p = this->getJump(); \
139  if (p != nullptr) { \
140  goto *p; \
141  }
142 
147 #define COROUTINE_LOOP() \
148  COROUTINE_BEGIN(); \
149  while (true) \
150 
151 
155 #define COROUTINE_YIELD_INTERNAL() \
156  do { \
157  __label__ jumpLabel; \
158  this->setJump(&& jumpLabel); \
159  return 0; \
160  jumpLabel: ; \
161  } while (false)
162 
164 #define COROUTINE_YIELD() \
165  do { \
166  this->setYielding(); \
167  COROUTINE_YIELD_INTERNAL(); \
168  this->setRunning(); \
169  } while (false)
170 
181 #define COROUTINE_AWAIT(condition) \
182  do { \
183  this->setYielding(); \
184  do { \
185  COROUTINE_YIELD_INTERNAL(); \
186  } while (!(condition)); \
187  this->setRunning(); \
188  } while (false)
189 
205 #define COROUTINE_DELAY(delayMillis) \
206  do { \
207  this->setDelayMillis(delayMillis); \
208  this->setDelaying(); \
209  do { \
210  COROUTINE_YIELD_INTERNAL(); \
211  } while (!this->isDelayExpired()); \
212  this->setRunning(); \
213  } while (false)
214 
216 #define COROUTINE_DELAY_MICROS(delayMicros) \
217  do { \
218  this->setDelayMicros(delayMicros); \
219  this->setDelaying(); \
220  do { \
221  COROUTINE_YIELD_INTERNAL(); \
222  } while (!this->isDelayMicrosExpired()); \
223  this->setRunning(); \
224  } while (false)
225 
241 #define COROUTINE_DELAY_SECONDS(delaySeconds) \
242  do { \
243  this->setDelaySeconds(delaySeconds); \
244  this->setDelaying(); \
245  do { \
246  COROUTINE_YIELD_INTERNAL(); \
247  } while (!this->isDelaySecondsExpired()); \
248  this->setRunning(); \
249  } while (false)
250 
255 #define COROUTINE_END() \
256  do { \
257  __label__ jumpLabel; \
258  this->setEnding(); \
259  this->setJump(&& jumpLabel); \
260  jumpLabel: ; \
261  return 0; \
262  } while (false)
263 
264 namespace ace_routine {
265 
267 extern const __FlashStringHelper* const sStatusStrings[];
268 
269 // Forward declaration of CoroutineSchedulerTemplate<T>
270 template <typename T> class CoroutineSchedulerTemplate;
271 
276 template <typename T_CLOCK>
278  friend class CoroutineSchedulerTemplate<CoroutineTemplate<T_CLOCK>>;
279  friend class ::AceRoutineTest_statusStrings;
280  friend class ::SuspendTest_suspendAndResume;
281 
282  public:
293  virtual int runCoroutine() = 0;
294 
309  virtual void setupCoroutine() {}
310 
321  void suspend() {
322  if (isDone()) return;
324  }
325 
332  void resume() {
333  if (mStatus != kStatusSuspended) return;
334 
335  // We lost the original state of the coroutine when suspend() was called
336  // but the coroutine will automatically go back into the original state
337  // when Coroutine::runCoroutine() is called because COROUTINE_YIELD(),
338  // COROUTINE_DELAY() and COROUTINE_AWAIT() are written to restore their
339  // status.
341  }
342 
356  void reset() {
358  mJumpPoint = nullptr;
359  }
360 
362  bool isDelayExpired() const {
363  uint16_t nowMillis = coroutineMillis();
364  uint16_t elapsed = nowMillis - mDelayStart;
365  return elapsed >= mDelayDuration;
366  }
367 
369  bool isDelayMicrosExpired() const {
370  uint16_t nowMicros = coroutineMicros();
371  uint16_t elapsed = nowMicros - mDelayStart;
372  return elapsed >= mDelayDuration;
373  }
374 
376  bool isDelaySecondsExpired() const {
377  uint16_t nowSeconds = coroutineSeconds();
378  uint16_t elapsed = nowSeconds - mDelayStart;
379  return elapsed >= mDelayDuration;
380  }
381 
383  bool isSuspended() const { return mStatus == kStatusSuspended; }
384 
386  bool isYielding() const { return mStatus == kStatusYielding; }
387 
389  bool isDelaying() const { return mStatus == kStatusDelaying; }
390 
392  bool isRunning() const { return mStatus == kStatusRunning; }
393 
399  bool isEnding() const { return mStatus == kStatusEnding; }
400 
407  bool isTerminated() const { return mStatus == kStatusTerminated; }
408 
414  bool isDone() const {
416  }
417 
424  void setupCoroutine(const char* /*name*/) ACE_ROUTINE_DEPRECATED {}
425 
432  void setupCoroutine(const __FlashStringHelper* /*name*/)
434 
435  protected:
466  typedef uint8_t Status;
467 
474  static const Status kStatusSuspended = 0;
475 
477  static const Status kStatusYielding = 1;
478 
480  static const Status kStatusDelaying = 2;
481 
483  static const Status kStatusRunning = 3;
484 
486  static const Status kStatusEnding = 4;
487 
489  static const Status kStatusTerminated = 5;
490 
493  insertAtRoot();
494  }
495 
508  ~CoroutineTemplate() = default;
509 
511  Status getStatus() const { return mStatus; }
512 
514  void statusPrintTo(Print& printer) {
515  printer.print(sStatusStrings[mStatus]);
516  }
517 
522  void setJump(void* jumpPoint) { mJumpPoint = jumpPoint; }
523 
528  void* getJump() const { return mJumpPoint; }
529 
532 
535 
538 
541 
547 
561  void setDelayMillis(uint16_t delayMillis) {
563 
564  // If delayMillis is a compile-time constant, the compiler seems to
565  // completely optimize away this bounds checking code.
566  mDelayDuration = (delayMillis >= UINT16_MAX / 2)
567  ? UINT16_MAX / 2
568  : delayMillis;
569  }
570 
575  void setDelayMicros(uint16_t delayMicros) {
577 
578  // If delayMicros is a compile-time constant, the compiler seems to
579  // completely optimize away this bounds checking code.
580  mDelayDuration = (delayMicros >= UINT16_MAX / 2)
581  ? UINT16_MAX / 2
582  : delayMicros;
583  }
584 
589  void setDelaySeconds(uint16_t delaySeconds) {
591 
592  // If delaySeconds is a compile-time constant, the compiler seems to
593  // completely optimize away this bounds checking code.
594  mDelayDuration = (delaySeconds >= UINT16_MAX / 2)
595  ? UINT16_MAX / 2
596  : delaySeconds;
597  }
598 
604  static unsigned long coroutineMillis() {
605  return T_CLOCK::millis();
606  }
607 
613  static unsigned long coroutineMicros() {
614  return T_CLOCK::micros();
615  }
616 
623  static unsigned long coroutineSeconds() {
624  return T_CLOCK::seconds();
625  }
626 
627  private:
628  // Disable copy-constructor and assignment operator
629  CoroutineTemplate(const CoroutineTemplate&) = delete;
630  CoroutineTemplate& operator=(const CoroutineTemplate&) = delete;
631 
637  static CoroutineTemplate** getRoot() {
638  // Use a static variable inside a function to solve the static
639  // initialization ordering problem.
640  static CoroutineTemplate* root;
641  return &root;
642  }
643 
650  CoroutineTemplate** getNext() { return &mNext; }
651 
658  void insertAtRoot() {
659  CoroutineTemplate** root = getRoot();
660  mNext = *root;
661  *root = this;
662  }
663 
664  protected:
667 
669  void* mJumpPoint = nullptr;
670 
673 
679  uint16_t mDelayStart;
680 
686  uint16_t mDelayDuration;
687 };
688 
696 
697 }
698 
699 #endif
ace_routine::CoroutineTemplate::isYielding
bool isYielding() const
The coroutine returned using COROUTINE_YIELD().
Definition: Coroutine.h:386
ace_routine::CoroutineTemplate::coroutineMillis
static unsigned long coroutineMillis()
Returns the current millisecond clock.
Definition: Coroutine.h:604
ace_routine::CoroutineTemplate::kStatusYielding
static const Status kStatusYielding
Coroutine returned using the COROUTINE_YIELD() statement.
Definition: Coroutine.h:477
ace_routine::CoroutineTemplate::kStatusEnding
static const Status kStatusEnding
Coroutine executed the COROUTINE_END() statement.
Definition: Coroutine.h:486
ace_routine::CoroutineTemplate::mDelayStart
uint16_t mDelayStart
Start time provided by COROUTINE_DELAY(), COROUTINE_DELAY_MICROS(), or COROUTINE_DELAY_SECONDS().
Definition: Coroutine.h:679
ace_routine::CoroutineTemplate::mNext
CoroutineTemplate * mNext
Pointer to the next coroutine in a singly-linked list.
Definition: Coroutine.h:666
ace_routine::CoroutineTemplate::Status
uint8_t Status
The execution status of the coroutine, corresponding to the COROUTINE_YIELD(), COROUTINE_DELAY(),...
Definition: Coroutine.h:466
ace_routine::CoroutineTemplate::CoroutineTemplate
CoroutineTemplate()
Constructor.
Definition: Coroutine.h:492
ace_routine::CoroutineTemplate::setupCoroutine
void setupCoroutine(const __FlashStringHelper *) ACE_ROUTINE_DEPRECATED
Deprecated method that does nothing.
Definition: Coroutine.h:432
ace_routine::CoroutineTemplate::isTerminated
bool isTerminated() const
The coroutine was terminated by the scheduler with a call to setTerminated().
Definition: Coroutine.h:407
ace_routine::CoroutineTemplate::suspend
void suspend()
Suspend the coroutine at the next scheduler iteration.
Definition: Coroutine.h:321
ace_routine::CoroutineTemplate::setYielding
void setYielding()
Set the kStatusDelaying state.
Definition: Coroutine.h:534
ace_routine::CoroutineTemplate::setDelaySeconds
void setDelaySeconds(uint16_t delaySeconds)
Configure the delay timer for delaySeconds.
Definition: Coroutine.h:589
ace_routine::CoroutineTemplate::~CoroutineTemplate
~CoroutineTemplate()=default
Destructor.
ace_routine::CoroutineTemplate::coroutineMicros
static unsigned long coroutineMicros()
Returns the current microseconds clock.
Definition: Coroutine.h:613
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:474
ace_routine::CoroutineTemplate::reset
void reset()
Reset the coroutine to its initial state.
Definition: Coroutine.h:356
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:332
ace_routine::CoroutineTemplate::setJump
void setJump(void *jumpPoint)
Pointer to label where execute will start on the next call to runCoroutine().
Definition: Coroutine.h:522
ace_routine::CoroutineTemplate::setDelayMillis
void setDelayMillis(uint16_t delayMillis)
Configure the delay timer for delayMillis.
Definition: Coroutine.h:561
ace_routine::CoroutineTemplate::kStatusRunning
static const Status kStatusRunning
Coroutine is currenly running.
Definition: Coroutine.h:483
ace_routine::CoroutineTemplate
Base class of all coroutines.
Definition: Coroutine.h:277
ace_routine::CoroutineTemplate::isRunning
bool isRunning() const
The coroutine is currently running.
Definition: Coroutine.h:392
ace_routine::CoroutineTemplate::kStatusDelaying
static const Status kStatusDelaying
Coroutine returned using the COROUTINE_DELAY() statement.
Definition: Coroutine.h:480
ace_routine::CoroutineTemplate::setTerminated
void setTerminated()
Set status to indicate that the Coroutine has been removed from the Scheduler queue.
Definition: Coroutine.h:546
ace_routine::CoroutineTemplate::getStatus
Status getStatus() const
Return the status of the coroutine.
Definition: Coroutine.h:511
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:531
ace_routine::CoroutineTemplate::kStatusTerminated
static const Status kStatusTerminated
Coroutine has ended and no longer in the scheduler queue.
Definition: Coroutine.h:489
ace_routine::CoroutineTemplate::isSuspended
bool isSuspended() const
The coroutine was suspended with a call to suspend().
Definition: Coroutine.h:383
ace_routine::CoroutineTemplate::isDelaySecondsExpired
bool isDelaySecondsExpired() const
Check if delay seconds time is over.
Definition: Coroutine.h:376
ace_routine::CoroutineTemplate::setDelaying
void setDelaying()
Set the kStatusDelaying state.
Definition: Coroutine.h:537
ace_routine::CoroutineSchedulerTemplate
Class that manages instances of the Coroutine class, and executes them in a round-robin fashion.
Definition: Coroutine.h:270
ace_routine::CoroutineTemplate::isDone
bool isDone() const
The coroutine is either Ending or Terminated.
Definition: Coroutine.h:414
ace_routine::CoroutineTemplate::mStatus
Status mStatus
Run-state of the coroutine.
Definition: Coroutine.h:672
ace_routine::CoroutineTemplate::mDelayDuration
uint16_t mDelayDuration
Delay time specified by COROUTINE_DELAY(), COROUTINE_DELAY_MICROS() or, COROUTINE_DELAY_SECONDS().
Definition: Coroutine.h:686
ace_routine::CoroutineTemplate::setupCoroutine
void setupCoroutine(const char *) ACE_ROUTINE_DEPRECATED
Deprecated method that does nothing.
Definition: Coroutine.h:424
ace_routine::CoroutineTemplate::isDelayExpired
bool isDelayExpired() const
Check if delay millis time is over.
Definition: Coroutine.h:362
ace_routine::CoroutineTemplate::mJumpPoint
void * mJumpPoint
Address of the label used by the computed-goto.
Definition: Coroutine.h:669
ace_routine::CoroutineTemplate::isDelayMicrosExpired
bool isDelayMicrosExpired() const
Check if delay micros time is over.
Definition: Coroutine.h:369
ace_routine::CoroutineTemplate::isDelaying
bool isDelaying() const
The coroutine returned using COROUTINE_DELAY().
Definition: Coroutine.h:389
ace_routine::CoroutineTemplate::isEnding
bool isEnding() const
The coroutine returned using COROUTINE_END().
Definition: Coroutine.h:399
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:623
ace_routine::CoroutineTemplate::getJump
void * getJump() const
Pointer to label where execute will start on the next call to runCoroutine().
Definition: Coroutine.h:528
ace_routine::CoroutineTemplate::setEnding
void setEnding()
Set the kStatusEnding state.
Definition: Coroutine.h:540
ace_routine::CoroutineTemplate::statusPrintTo
void statusPrintTo(Print &printer)
Print the human-readable string of the Status.
Definition: Coroutine.h:514
ACE_ROUTINE_DEPRECATED
#define ACE_ROUTINE_DEPRECATED
Macro that indicates a deprecation.
Definition: Coroutine.h:61
ace_routine::CoroutineTemplate::setupCoroutine
virtual void setupCoroutine()
Perform coroutine initialization.
Definition: Coroutine.h:309
ace_routine::CoroutineTemplate::setDelayMicros
void setDelayMicros(uint16_t delayMicros)
Configure the delay timer for delayMicros.
Definition: Coroutine.h:575