tin  1.5.9
coroutine.h
Go to the documentation of this file.
1 // Copyright (c) 2026 Tinverse LLC. All rights reserved.
2 // SPDX-License-Identifier: LicenseRef-Tinverse-Commercial
3 
18 
19 #pragma once
20 
21 #include <array>
22 #include <concepts>
23 #include <coroutine>
24 #include <cstddef>
25 #include <cstdint>
26 #include <tuple>
27 #include <type_traits>
28 #include <utility>
29 
30 #include "tsm/ticks.h"
31 
32 namespace tsm {
33 
34 class task_context;
35 
42 class task
43 {
44  public:
45  struct promise_type;
46  using handle_type = std::coroutine_handle<promise_type>;
47 
48  task() = default;
49  explicit task(handle_type handle) noexcept
50  : handle_(handle)
51  {
52  }
53 
54  task(task const&) = delete;
55  task& operator=(task const&) = delete;
56 
57  task(task&& other) noexcept
58  : handle_(other.handle_)
59  {
60  other.handle_ = {};
61  }
62 
63  task& operator=(task&& other) noexcept
64  {
65  if (this != &other) {
66  destroy();
67  handle_ = other.handle_;
68  other.handle_ = {};
69  }
70  return *this;
71  }
72 
74  {
75  destroy();
76  }
77 
78  [[nodiscard]] explicit operator bool() const noexcept
79  {
80  return handle_ != nullptr;
81  }
82 
83  [[nodiscard]] bool done() const noexcept
84  {
85  return handle_ == nullptr || handle_.done();
86  }
87 
88  [[nodiscard]] handle_type handle() const noexcept
89  {
90  return handle_;
91  }
92 
93  [[nodiscard]] bool resume();
94 
95  private:
96  void destroy() noexcept
97  {
98  if (handle_ != nullptr) {
99  handle_.destroy();
100  handle_ = {};
101  }
102  }
103 
104  handle_type handle_{};
105 };
106 
111 template<std::size_t Bytes>
113 {
114  public:
115  static_assert(Bytes > 0U,
116  "tsm: static_coroutine_arena requires non-zero storage");
117 
118  [[nodiscard]] void* allocate(std::size_t bytes,
119  std::size_t alignment) noexcept
120  {
121  const std::size_t aligned = align_up(used_, alignment);
122  if (aligned > Bytes || bytes > Bytes - aligned) {
123  return nullptr;
124  }
125  used_ = aligned + bytes;
126  return storage_.data() + aligned;
127  }
128 
129  [[nodiscard]] std::size_t used() const noexcept
130  {
131  return used_;
132  }
133  [[nodiscard]] std::size_t capacity() const noexcept
134  {
135  return Bytes;
136  }
137 
138  private:
139  static constexpr std::size_t align_up(std::size_t value,
140  std::size_t alignment) noexcept
141  {
142  return (value + alignment - 1U) & ~(alignment - 1U);
143  }
144 
145  alignas(std::max_align_t) std::array<unsigned char, Bytes> storage_{};
146  std::size_t used_{};
147 };
148 
149 enum class task_wait_kind : unsigned char
150 {
151  ready,
152  running,
153  sleeping,
154  predicate,
155  completed,
156  failed
157 };
158 
159 enum class task_status : unsigned char
160 {
161  not_started,
162  ready,
163  running,
164  waiting,
165  completed,
166  failed
167 };
168 
169 enum class task_failure_reason : unsigned char
170 {
171  none,
176  cancelled,
178 };
179 
180 enum class spawn_result : unsigned char
181 {
182  started,
183  no_slot,
187 };
188 
196 {
197  public:
198  using predicate_fn = bool (*)(void const*) noexcept;
199  using wake_fn = void (*)(void*, std::size_t) noexcept;
200  using sleep_fn = bool (*)(void*, std::size_t, tsm::tick_rep) noexcept;
201  using cancel_sleep_fn = void (*)(void*, std::size_t) noexcept;
202 
203  task_context() = default;
204 
206  std::size_t,
207  std::size_t,
208  std::size_t) = delete;
209 
210  template<std::size_t Bytes>
212  std::size_t task_id,
213  std::size_t instance_id,
214  std::size_t frame_limit,
215  void* scheduler,
216  wake_fn wake,
217  sleep_fn sleep,
218  cancel_sleep_fn cancel_sleep) noexcept
219  {
220  // Bind is separate from coroutine construction so the scheduler can
221  // prepare every slot before any task frame asks for storage.
222  allocate_ = [](void* object,
223  std::size_t bytes,
224  std::size_t alignment) noexcept -> void* {
225  return static_cast<static_coroutine_arena<Bytes>*>(object)
226  ->allocate(bytes, alignment);
227  };
228  arena_ = &arena;
229  task_id_ = task_id;
230  instance_id_ = instance_id;
231  frame_limit_ = frame_limit;
232  scheduler_ = scheduler;
233  wake_ = wake;
234  sleep_ = sleep;
235  cancel_sleep_ = cancel_sleep;
236  status_ = task_status::not_started;
237  }
238 
239  [[nodiscard]] void* allocate_frame(std::size_t bytes,
240  std::size_t alignment) noexcept
241  {
242  // Coroutine allocation is part of task admission. A frame that exceeds
243  // its declared budget fails the task.
244  if (bytes > frame_limit_) {
246  return nullptr;
247  }
248  if (allocate_ == nullptr) {
250  return nullptr;
251  }
252  void* storage = allocate_(arena_, bytes, alignment);
253  if (storage == nullptr) {
255  }
256  return storage;
257  }
258 
259  [[nodiscard]] std::size_t task_id() const noexcept
260  {
261  return task_id_;
262  }
263  [[nodiscard]] std::size_t instance_id() const noexcept
264  {
265  return instance_id_;
266  }
267  [[nodiscard]] tsm::tick_rep now() const noexcept
268  {
269  return now_;
270  }
271  [[nodiscard]] task_wait_kind wait_kind() const noexcept
272  {
273  return wait_kind_;
274  }
275  [[nodiscard]] task_status status() const noexcept
276  {
277  return status_;
278  }
279  [[nodiscard]] task_failure_reason failure_reason() const noexcept
280  {
281  return failure_reason_;
282  }
283  [[nodiscard]] std::uint32_t generation() const noexcept
284  {
285  return generation_;
286  }
287 
288  void set_now(tsm::tick_rep now) noexcept
289  {
290  now_ = now;
291  }
292 
293  void mark_ready() noexcept
294  {
295  if (status_ == task_status::completed ||
296  status_ == task_status::failed) {
297  return;
298  }
299  // A ready task is queued at most once. This keeps repeated signal/timer
300  // wakeups from consuming ready-queue capacity before the task resumes.
301  wait_kind_ = task_wait_kind::ready;
302  status_ = task_status::ready;
303  failure_reason_ = task_failure_reason::none;
304  predicate_ = nullptr;
305  predicate_object_ = nullptr;
306  request_wake();
307  }
308 
309  void prepare_start() noexcept
310  {
311  cancel_sleep();
312  ++generation_;
313  wait_kind_ = task_wait_kind::ready;
314  status_ = task_status::not_started;
315  failure_reason_ = task_failure_reason::none;
316  predicate_ = nullptr;
317  predicate_object_ = nullptr;
318  queued_ = false;
319  }
320 
321  void mark_running() noexcept
322  {
323  if (status_ != task_status::completed &&
324  status_ != task_status::failed) {
325  wait_kind_ = task_wait_kind::running;
326  status_ = task_status::running;
327  queued_ = false;
328  }
329  }
330 
332  {
333  if (ticks == 0U) {
334  mark_ready();
335  return;
336  }
337  wait_kind_ = task_wait_kind::sleeping;
338  status_ = task_status::waiting;
339  if (sleep_ == nullptr || !sleep_(scheduler_, task_id_, ticks)) {
341  }
342  }
343 
344  void cancel_sleep() noexcept
345  {
346  if (cancel_sleep_ != nullptr && scheduler_ != nullptr) {
347  cancel_sleep_(scheduler_, task_id_);
348  }
349  }
350 
351  template<typename Object, typename Predicate>
352  void wait_until(Object const& object, Predicate predicate) noexcept
353  {
354  predicate_object_ = &object;
355  predicate_ = predicate;
356  wait_kind_ = task_wait_kind::predicate;
357  status_ = task_status::waiting;
358  }
359 
360  [[nodiscard]] bool ready_to_resume() const noexcept
361  {
362  switch (wait_kind_) {
364  return true;
366  return false;
368  return false;
370  return predicate_ != nullptr && predicate_(predicate_object_);
373  return false;
374  }
375  return false;
376  }
377 
378  [[nodiscard]] bool predicate_ready() const noexcept
379  {
380  return wait_kind_ == task_wait_kind::predicate &&
381  predicate_ != nullptr && predicate_(predicate_object_);
382  }
383 
384  void mark_completed() noexcept
385  {
386  wait_kind_ = task_wait_kind::completed;
387  status_ = task_status::completed;
388  queued_ = false;
389  }
392  {
393  wait_kind_ = task_wait_kind::failed;
394  status_ = task_status::failed;
395  failure_reason_ = reason;
396  predicate_ = nullptr;
397  predicate_object_ = nullptr;
398  queued_ = false;
399  }
400 
401  void cancel() noexcept
402  {
403  cancel_sleep();
405  }
406 
407  void request_wake() noexcept
408  {
409  if (queued_ || wake_ == nullptr || scheduler_ == nullptr) {
410  return;
411  }
412  queued_ = true;
413  wake_(scheduler_, task_id_);
414  }
415 
416  void clear_queued() noexcept
417  {
418  queued_ = false;
419  }
420 
421  private:
422  using allocate_fn = void* (*)(void*, std::size_t, std::size_t) noexcept;
423 
424  void* arena_{};
425  void* scheduler_{};
426  allocate_fn allocate_{};
427  wake_fn wake_{};
428  sleep_fn sleep_{};
429  cancel_sleep_fn cancel_sleep_{};
430  predicate_fn predicate_{};
431  void const* predicate_object_{};
432  std::size_t task_id_{};
433  std::size_t instance_id_{};
434  std::size_t frame_limit_{};
435  tsm::tick_rep now_{};
436  std::uint32_t generation_{};
440  bool queued_{};
441 };
442 
443 namespace runtime::detail {
444 
445 inline task_context*
447 {
448  return nullptr;
449 }
450 
451 template<typename First, typename... Rest>
453 find_task_context(First& first, Rest&... rest) noexcept
454 {
455  if constexpr (std::is_same_v<std::remove_cvref_t<First>, task_context>) {
456  return &first;
457  } else {
458  return find_task_context(rest...);
459  }
460 }
461 
462 } // namespace runtime::detail
463 
465 {
466  promise_type() = default;
467 
468  template<typename First, typename... Args>
469  explicit promise_type(First& first, Args&... args) noexcept
470  : context_(runtime::detail::find_task_context(first, args...))
471  {
472  }
473 
475  {
476  return task{};
477  }
478 
479  static void* operator new(std::size_t) = delete;
480 
481  template<typename... Args>
482  static void* operator new(std::size_t bytes, Args&... args) noexcept
483  {
484  // The task_context parameter is the allocator capability. If an entry
485  // function is called without it, coroutine frame creation is rejected.
487  if (task_ctx == nullptr) {
488  return nullptr;
489  }
490  return task_ctx->allocate_frame(bytes, alignof(std::max_align_t));
491  }
492 
493  static void operator delete(void*) noexcept {}
494  static void operator delete(void*, std::size_t) noexcept {}
495 
496  template<typename... Args>
497  static void operator delete(void*, Args&...) noexcept
498  {
499  }
500 
501  template<typename... Args>
502  static void operator delete(void*, std::size_t, Args&...) noexcept
503  {
504  }
505 
506  [[nodiscard]] task get_return_object() noexcept
507  {
508  return task{ task::handle_type::from_promise(*this) };
509  }
510 
511  [[nodiscard]] std::suspend_always initial_suspend() noexcept
512  {
513  return {};
514  }
515 
517  {
518  [[nodiscard]] bool await_ready() const noexcept
519  {
520  return false;
521  }
523  {
524  if (handle.promise().context_ != nullptr) {
525  handle.promise().context_->mark_completed();
526  }
527  }
528  void await_resume() const noexcept {}
529  };
530 
531  [[nodiscard]] final_awaitable final_suspend() noexcept
532  {
533  return {};
534  }
535  void return_void() noexcept {}
536  void unhandled_exception() noexcept
537  {
538  if (context_ != nullptr) {
540  }
541  }
542 
543  [[nodiscard]] task_context& context() noexcept
544  {
545  return *context_;
546  }
547 
548  private:
549  task_context* context_{};
550 };
551 
552 inline bool
554 {
555  if (handle_ == nullptr || handle_.done()) {
556  return false;
557  }
558  auto& context = handle_.promise().context();
559  if (!context.ready_to_resume()) {
560  return false;
561  }
562  context.mark_running();
563  handle_.resume();
564  return true;
565 }
566 
576 {
577  [[nodiscard]] bool await_ready() const noexcept
578  {
579  return false;
580  }
581  void await_suspend(task::handle_type handle) const noexcept
582  {
583  handle.promise().context().mark_ready();
584  }
585  void await_resume() const noexcept {}
586 };
587 
588 [[nodiscard]] inline yield_awaitable
589 yield() noexcept
590 {
591  return {};
592 }
593 
603 {
605 
606  [[nodiscard]] bool await_ready() const noexcept
607  {
608  return ticks == 0U;
609  }
610  void await_suspend(task::handle_type handle) const noexcept
611  {
612  handle.promise().context().sleep_for(ticks);
613  }
614  void await_cancel(task_context& context) const noexcept
615  {
616  context.cancel_sleep();
617  }
618  void await_resume() const noexcept {}
619 };
620 
621 [[nodiscard]] inline sleep_ticks_awaitable
623 {
624  return sleep_ticks_awaitable{ ticks };
625 }
626 
627 [[nodiscard]] inline sleep_ticks_awaitable
629 {
630  return sleep_ticks_awaitable{ ticks.count() };
631 }
632 
637 [[nodiscard]] inline sleep_ticks_awaitable
639 {
640  return sleep_ticks(ticks);
641 }
642 
643 [[nodiscard]] inline sleep_ticks_awaitable
645 {
646  return sleep_ticks(ticks);
647 }
648 
656 {
657  public:
658  explicit constexpr periodic_ticks(tsm::tick_rep period) noexcept
659  : period_(period)
660  {
661  }
662 
663  explicit constexpr periodic_ticks(tick_count period) noexcept
664  : period_(period.count())
665  {
666  }
667 
668  [[nodiscard]] sleep_ticks_awaitable operator co_await() const noexcept
669  {
670  return sleep_ticks(period_);
671  }
672 
673  [[nodiscard]] tsm::tick_rep period() const noexcept
674  {
675  return period_;
676  }
677 
678  private:
679  tsm::tick_rep period_{};
680 };
681 
683 
691 {
693 
694  [[nodiscard]] bool await_ready() const noexcept
695  {
696  return ticks == 0U;
697  }
698  void await_suspend(task::handle_type handle) const noexcept
699  {
700  handle.promise().context().sleep_for(ticks);
701  }
702  void await_cancel(task_context& context) const noexcept
703  {
704  context.cancel_sleep();
705  }
706  void await_resume() const noexcept {}
707 };
708 
709 [[nodiscard]] inline timeout_ticks_awaitable
711 {
712  return timeout_ticks_awaitable{ ticks };
713 }
714 
715 [[nodiscard]] inline timeout_ticks_awaitable
717 {
718  return timeout_ticks_awaitable{ ticks.count() };
719 }
720 
721 namespace runtime::detail {
722 
723 template<typename Awaitable>
725 {
726  {
727  awaitable.await_ready()
728  } -> std::convertible_to<bool>;
729  awaitable.await_suspend(h);
730  awaitable.await_resume();
731 };
732 
733 template<typename T>
734 struct is_timeout_ticks : std::false_type
735 {
736 };
737 
738 template<>
739 struct is_timeout_ticks<tsm::timeout_ticks_awaitable> : std::true_type
740 {
741 };
742 
743 template<typename Awaitable>
744 void
745 cancel_awaitable(Awaitable& awaitable, task_context& context) noexcept
746 {
747  if constexpr (requires { awaitable.await_cancel(context); }) {
748  awaitable.await_cancel(context);
749  }
750 }
751 
752 } // namespace runtime::detail
753 
756 {
757  std::size_t index{};
758 };
759 
765 template<runtime::detail::basic_awaitable... Awaitables>
767 {
768  public:
769  explicit select_awaitable(Awaitables... awaitables)
770  : awaitables_(std::move(awaitables)...)
771  {
772  }
773 
774  [[nodiscard]] bool await_ready() noexcept
775  {
776  return ready_any(std::make_index_sequence<sizeof...(Awaitables)>{});
777  }
778 
779  void await_suspend(task::handle_type handle) noexcept
780  {
781  context_ = &handle.promise().context();
782  suspend_all(handle, std::make_index_sequence<sizeof...(Awaitables)>{});
783  }
784 
785  [[nodiscard]] select_result await_resume() noexcept
786  {
787  static_cast<void>(
788  ready_any(std::make_index_sequence<sizeof...(Awaitables)>{}));
789  select_timeout_if_due(
790  std::make_index_sequence<sizeof...(Awaitables)>{});
791  cancel_unselected(std::make_index_sequence<sizeof...(Awaitables)>{});
792  return select_result{ selected_ };
793  }
794 
795  private:
796  template<std::size_t... I>
797  [[nodiscard]] bool ready_any(std::index_sequence<I...>) noexcept
798  {
799  bool ready{};
800  ((ready = ready || ready_one<I>()), ...);
801  return ready;
802  }
803 
804  template<std::size_t I>
805  [[nodiscard]] bool ready_one() noexcept
806  {
807  if (selected_ != npos) {
808  return false;
809  }
810  if (std::get<I>(awaitables_).await_ready()) {
811  selected_ = I;
812  return true;
813  }
814  return false;
815  }
816 
817  template<std::size_t... I>
818  void suspend_all(task::handle_type handle,
819  std::index_sequence<I...>) noexcept
820  {
821  (suspend_one<I>(handle), ...);
822  }
823 
824  template<std::size_t I>
825  void suspend_one(task::handle_type handle) noexcept
826  {
827  if (selected_ == I || std::get<I>(awaitables_).await_ready()) {
828  selected_ = I;
829  return;
830  }
831  std::get<I>(awaitables_).await_suspend(handle);
832  }
833 
834  template<std::size_t... I>
835  void select_timeout_if_due(std::index_sequence<I...>) noexcept
836  {
837  if (selected_ != npos) {
838  return;
839  }
840  ((select_timeout_index<I>()), ...);
841  }
842 
843  template<std::size_t I>
844  void select_timeout_index() noexcept
845  {
846  if constexpr (runtime::detail::is_timeout_ticks<
847  std::tuple_element_t<I, std::tuple<Awaitables...>>>::
848  value) {
849  if (selected_ == npos) {
850  selected_ = I;
851  }
852  }
853  }
854 
855  template<std::size_t... I>
856  void cancel_unselected(std::index_sequence<I...>) noexcept
857  {
858  if (context_ == nullptr || selected_ == npos) {
859  return;
860  }
861  (cancel_one<I>(), ...);
862  }
863 
864  template<std::size_t I>
865  void cancel_one() noexcept
866  {
867  if (I != selected_) {
868  runtime::detail::cancel_awaitable(std::get<I>(awaitables_),
869  *context_);
870  }
871  }
872 
873  static constexpr std::size_t npos = static_cast<std::size_t>(-1);
874 
875  std::tuple<Awaitables...> awaitables_;
876  task_context* context_{};
877  std::size_t selected_{ npos };
878 };
879 
880 template<typename... Awaitables>
881 [[nodiscard]] auto
882 select(Awaitables&&... awaitables)
883 {
884  static_assert(sizeof...(Awaitables) > 0U,
885  "tsm: select requires at least one awaitable");
887  std::forward<Awaitables>(awaitables)...
888  };
889 }
890 
891 template<typename Awaitable>
892 [[nodiscard]] auto
894 {
895  return select(std::forward<Awaitable>(awaitable), timeout_ticks(ticks));
896 }
897 
898 template<typename Awaitable>
899 [[nodiscard]] auto
901 {
902  return select(std::forward<Awaitable>(awaitable),
904 }
905 
906 template<typename Runtime, typename Event>
908 {
909  public:
910  send_event_awaitable(Runtime& runtime, Event&& event)
911  : runtime_(&runtime)
912  , event_(std::forward<Event>(event))
913  {
914  }
915 
916  [[nodiscard]] bool await_ready() const noexcept
917  {
918  return false;
919  }
920 
921  [[nodiscard]] bool await_suspend(task::handle_type)
922  {
923  accepted_ = runtime_->send_event(std::move(event_));
924  return false;
925  }
926 
927  bool await_resume() const noexcept
928  {
929  return accepted_;
930  }
931 
932  private:
933  Runtime* runtime_{};
934  std::decay_t<Event> event_;
935  bool accepted_{};
936 };
937 
938 template<typename Runtime, typename Event>
940 {
941  public:
942  checked_send_event_awaitable(Runtime& runtime, Event&& event)
943  : runtime_(&runtime)
944  , event_(std::forward<Event>(event))
945  {
946  }
947 
948  [[nodiscard]] bool await_ready() const noexcept
949  {
950  return false;
951  }
952 
953  [[nodiscard]] bool await_suspend(task::handle_type)
954  {
955  accepted_ = runtime_->send_event(std::move(event_));
956  return false;
957  }
958 
959  [[nodiscard]] bool await_resume() const noexcept
960  {
961  return accepted_;
962  }
963 
964  private:
965  Runtime* runtime_{};
966  std::decay_t<Event> event_;
967  bool accepted_{};
968 };
969 
970 template<typename Runtime, typename Event>
971 [[nodiscard]] auto
972 send_event(Runtime& runtime, Event&& event)
973 {
974  return send_event_awaitable<Runtime, Event>{ runtime,
975  std::forward<Event>(event) };
976 }
977 
978 template<typename Event, typename Runtime, typename... Args>
979 [[nodiscard]] auto
980 send(Runtime& runtime, Args&&... args)
981 {
982  return send_event(runtime, Event{ std::forward<Args>(args)... });
983 }
984 
985 template<typename Runtime, typename Event>
986 [[nodiscard]] auto
987 try_send_event(Runtime& runtime, Event&& event)
988 {
990  runtime,
991  std::forward<Event>(event),
992  };
993 }
994 
995 template<typename Event, typename Runtime, typename... Args>
996 [[nodiscard]] auto
997 try_send(Runtime& runtime, Args&&... args)
998 {
999  return try_send_event(runtime, Event{ std::forward<Args>(args)... });
1000 }
1001 
1002 template<typename State, typename Machine>
1004 {
1005  public:
1006  explicit until_active_awaitable(Machine& machine)
1007  : machine_(&machine)
1008  {
1009  }
1010 
1011  [[nodiscard]] bool await_ready() const noexcept
1012  {
1013  return machine_->template active<State>();
1014  }
1015 
1016  void await_suspend(task::handle_type handle) const noexcept
1017  {
1018  handle.promise().context().wait_until(
1019  *machine_, [](void const* object) noexcept {
1020  return static_cast<Machine const*>(object)
1021  ->template active<State>();
1022  });
1023  }
1024 
1025  void await_resume() const noexcept {}
1026 
1027  private:
1028  Machine* machine_{};
1029 };
1030 
1031 template<typename State, typename Machine>
1032 [[nodiscard]] auto
1033 until_active(Machine& machine) noexcept
1034 {
1035  return until_active_awaitable<State, Machine>{ machine };
1036 }
1037 
1038 template<std::size_t Count>
1040 {
1041  static constexpr std::size_t value = Count;
1042 };
1043 
1044 template<std::size_t Bytes>
1046 {
1047  static constexpr std::size_t value = Bytes;
1048 };
1049 
1050 template<std::uint8_t Priority>
1052 {
1054  static constexpr std::uint8_t value = Priority;
1055 };
1056 
1057 template<std::size_t Count, typename FrameBytes>
1059 {
1062  static constexpr std::size_t count = Count;
1063  static constexpr std::size_t frame_bytes = FrameBytes::value;
1064 
1065  static_assert(count > 0U,
1066  "tsm: dynamic_task_slots requires at least one slot");
1067  static_assert(frame_bytes > 0U,
1068  "tsm: dynamic_task_slots requires non-zero frame storage");
1069 };
1070 
1071 template<auto... Entries>
1073 {
1074  static constexpr std::size_t size = sizeof...(Entries);
1075 };
1076 
1077 namespace runtime::detail {
1078 
1079 consteval std::size_t
1081 {
1082  return 1U;
1083 }
1084 
1085 template<std::size_t Count, typename... Rest>
1086 consteval std::size_t
1088 {
1089  return Count;
1090 }
1091 
1092 template<typename First, typename... Rest>
1093 consteval std::size_t
1094 selected_instances(First, Rest... rest)
1095 {
1096  return selected_instances(rest...);
1097 }
1098 
1099 consteval std::size_t
1101 {
1102  return 0U;
1103 }
1104 
1105 template<std::size_t Bytes, typename... Rest>
1106 consteval std::size_t
1108 {
1109  return Bytes;
1110 }
1111 
1112 template<typename First, typename... Rest>
1113 consteval std::size_t
1114 selected_frame_bytes(First, Rest... rest)
1115 {
1116  return selected_frame_bytes(rest...);
1117 }
1118 
1119 consteval std::uint8_t
1121 {
1122  return 0U;
1123 }
1124 
1125 template<std::uint8_t Priority, typename... Rest>
1126 consteval std::uint8_t
1128 {
1129  return Priority;
1130 }
1131 
1132 template<typename First, typename... Rest>
1133 consteval std::uint8_t
1134 selected_priority(First, Rest... rest)
1135 {
1136  return selected_priority(rest...);
1137 }
1138 
1139 } // namespace runtime::detail
1140 
1141 template<auto Entry, typename... Options>
1142 struct task_def
1143 {
1146  static constexpr auto entry = Entry;
1147  static constexpr std::size_t instances =
1149  static constexpr std::size_t frame_bytes =
1151  static constexpr std::uint8_t priority =
1152  runtime::detail::selected_priority(Options{}...);
1153 
1154  static_assert(instances > 0U,
1155  "tsm: task_def requires at least one instance");
1156  static_assert(frame_bytes > 0U,
1157  "tsm: task_def requires non-zero frame storage");
1158 };
1159 
1160 template<typename... TaskDefinitions>
1161 struct tasks
1162 {
1163  static constexpr std::size_t size = sizeof...(TaskDefinitions);
1164 };
1165 
1166 namespace runtime {
1167 
1168 namespace detail {
1169 
1170 template<typename Definition, typename = void>
1171 struct task_list_of
1172 {
1173  using type = tsm::tasks<>;
1174 };
1175 
1176 template<typename Definition>
1177 struct task_list_of<Definition, std::void_t<typename Definition::tasks>>
1178 {
1179  using type = typename Definition::tasks;
1180 };
1181 
1182 template<typename Runtime, typename = void>
1183 struct runtime_task_list_of
1184 {
1185  using type = tsm::tasks<>;
1186 };
1187 
1188 template<typename Runtime>
1189 struct runtime_task_list_of<Runtime, std::void_t<typename Runtime::definition>>
1190 {
1191  using type = typename task_list_of<typename Runtime::definition>::type;
1192 };
1193 
1194 template<typename TaskList>
1195 struct task_list_traits;
1196 
1197 template<typename Definition>
1199 {
1200  Definition::entry;
1201  Definition::instances;
1202  Definition::frame_bytes;
1203 };
1204 
1205 template<typename Definition>
1207 {
1208  Definition::count;
1209  Definition::frame_bytes;
1210 };
1211 
1212 template<auto Left, auto Right>
1213 concept same_task_entry = std::same_as<std::remove_cvref_t<decltype(Left)>,
1214  std::remove_cvref_t<decltype(Right)>>;
1215 
1216 template<typename T>
1217 struct task_entry_count
1218 {
1219  static constexpr std::size_t value = 0U;
1220 };
1221 
1222 template<auto Entry, typename... Options>
1223 struct task_entry_count<tsm::task_def<Entry, Options...>>
1224 {
1225  static constexpr std::size_t value =
1226  tsm::task_def<Entry, Options...>::instances;
1227 };
1228 
1229 template<typename T>
1230 struct task_dynamic_count
1231 {
1232  static constexpr std::size_t value = 0U;
1233 };
1234 
1235 template<std::size_t Count, typename FrameBytes>
1236 struct task_dynamic_count<tsm::dynamic_task_slots<Count, FrameBytes>>
1237 {
1238  static constexpr std::size_t value = Count;
1239 };
1240 
1241 template<typename T>
1242 struct task_frame_total
1243 {
1244  static constexpr std::size_t value = 0U;
1245 };
1246 
1247 template<auto Entry, typename... Options>
1248 struct task_frame_total<tsm::task_def<Entry, Options...>>
1249 {
1250  static constexpr std::size_t value =
1251  tsm::task_def<Entry, Options...>::instances *
1252  tsm::task_def<Entry, Options...>::frame_bytes;
1253 };
1254 
1255 template<std::size_t Count, typename FrameBytes>
1256 struct task_frame_total<tsm::dynamic_task_slots<Count, FrameBytes>>
1257 {
1258  static constexpr std::size_t value = Count * FrameBytes::value;
1259 };
1260 
1261 template<typename... TaskDefinitions>
1262 struct task_list_traits<tsm::tasks<TaskDefinitions...>>
1263 {
1264  static constexpr std::size_t task_count =
1265  (0U + ... +
1266  (task_entry_count<TaskDefinitions>::value +
1267  task_dynamic_count<TaskDefinitions>::value));
1268  static constexpr std::size_t arena_alignment_padding =
1269  task_count * (alignof(std::max_align_t) - 1U);
1270  static constexpr std::size_t declared_task_count =
1271  (0U + ... + task_entry_count<TaskDefinitions>::value);
1272  static constexpr std::size_t dynamic_task_count =
1273  (0U + ... + task_dynamic_count<TaskDefinitions>::value);
1274  static constexpr std::size_t arena_bytes =
1275  (0U + ... + task_frame_total<TaskDefinitions>::value) +
1276  arena_alignment_padding;
1277 };
1278 
1279 template<typename Runtime>
1280 struct task_resources
1281 {
1282  using task_list = typename runtime_task_list_of<Runtime>::type;
1283  using traits = task_list_traits<task_list>;
1284 
1285  static constexpr std::size_t task_count = traits::task_count;
1286  static constexpr std::size_t declared_task_count =
1287  traits::declared_task_count;
1288  static constexpr std::size_t dynamic_task_count =
1289  traits::dynamic_task_count;
1290  static constexpr std::size_t arena_bytes = traits::arena_bytes;
1291  static constexpr std::size_t timer_slots = traits::task_count;
1292  static constexpr bool uses_heap = false;
1293 };
1294 
1295 template<std::size_t Capacity>
1296 class timer_queue
1297 {
1298  public:
1299  [[nodiscard]] bool schedule(std::size_t task_id,
1300  tsm::tick_rep wake_tick,
1301  std::uint32_t sequence) noexcept
1302  {
1303  if (size_ >= Capacity) {
1304  return false;
1305  }
1306  timers_[size_++] = timer_record{ task_id, wake_tick, sequence };
1307  return true;
1308  }
1309 
1310  template<typename Wake>
1311  std::size_t wake_due(tsm::tick_rep now, Wake wake) noexcept
1312  {
1313  // Capacity is small and static, so selection is intentionally linear.
1314  // Sequence numbers keep equal-deadline wakeups deterministic.
1315  std::size_t woken{};
1316  for (;;) {
1317  std::size_t selected = Capacity;
1318  for (std::size_t i = 0; i < size_; ++i) {
1319  if (timers_[i].wake_tick > now) {
1320  continue;
1321  }
1322  if (selected == Capacity ||
1323  before(timers_[i], timers_[selected])) {
1324  selected = i;
1325  }
1326  }
1327  if (selected == Capacity) {
1328  return woken;
1329  }
1330  const auto record = timers_[selected];
1331  timers_[selected] = timers_[size_ - 1U];
1332  --size_;
1333  wake(record.task_id);
1334  ++woken;
1335  }
1336  }
1337 
1338  [[nodiscard]] std::size_t size() const noexcept
1339  {
1340  return size_;
1341  }
1342 
1343  void cancel(std::size_t task_id) noexcept
1344  {
1345  for (std::size_t i = 0; i < size_; ++i) {
1346  if (timers_[i].task_id != task_id) {
1347  continue;
1348  }
1349  timers_[i] = timers_[size_ - 1U];
1350  --size_;
1351  return;
1352  }
1353  }
1354 
1355  private:
1356  struct timer_record
1357  {
1358  std::size_t task_id{};
1359  tsm::tick_rep wake_tick{};
1360  std::uint32_t sequence{};
1361  };
1362 
1363  static constexpr bool before(timer_record const& lhs,
1364  timer_record const& rhs) noexcept
1365  {
1366  return lhs.wake_tick < rhs.wake_tick ||
1367  (lhs.wake_tick == rhs.wake_tick && lhs.sequence < rhs.sequence);
1368  }
1369 
1370  std::array<timer_record, Capacity> timers_{};
1371  std::size_t size_{};
1372 };
1373 
1374 template<typename Runtime, typename TaskList, bool PriorityAware = false>
1375 class coroutine_scheduler;
1376 
1377 template<typename Runtime, bool PriorityAware>
1378 class coroutine_scheduler<Runtime, tsm::tasks<>, PriorityAware>
1379 {
1380  public:
1381  explicit constexpr coroutine_scheduler(Runtime&) {}
1382 
1383  [[nodiscard]] bool step()
1384  {
1385  return false;
1386  }
1387  std::size_t run_ready()
1388  {
1389  return 0;
1390  }
1391  std::size_t tick(tsm::tick_rep)
1392  {
1393  return 0;
1394  }
1395  std::size_t tick(tsm::tick_count)
1396  {
1397  return 0;
1398  }
1399  std::size_t poll_waiting()
1400  {
1401  return 0;
1402  }
1403 };
1404 
1405 template<typename Runtime, bool PriorityAware, typename... TaskDefinitions>
1406 class coroutine_scheduler<Runtime,
1407  tsm::tasks<TaskDefinitions...>,
1408  PriorityAware>
1409 {
1410  public:
1411  using task_list = tsm::tasks<TaskDefinitions...>;
1412  static constexpr std::size_t task_count =
1413  task_list_traits<task_list>::task_count;
1414  static constexpr std::size_t arena_bytes =
1415  task_list_traits<task_list>::arena_bytes;
1416  static constexpr std::size_t declared_task_count =
1417  task_list_traits<task_list>::declared_task_count;
1418  static constexpr std::size_t dynamic_task_count =
1419  task_list_traits<task_list>::dynamic_task_count;
1420 
1421  explicit coroutine_scheduler(Runtime& runtime)
1422  : runtime_(&runtime)
1423  {
1424  // Construction binds every task slot to the shared arena and starts the
1425  // statically declared task set. Dynamic slots remain idle until
1426  // spawn().
1427  bind_all<TaskDefinitions...>();
1428  start_all();
1429  }
1430 
1431  coroutine_scheduler(coroutine_scheduler const&) = delete;
1432  coroutine_scheduler& operator=(coroutine_scheduler const&) = delete;
1433 
1434  [[nodiscard]] bool step()
1435  {
1436  // The default ready queue is FIFO. Priority-aware schedulers select the
1437  // highest priority from the same bounded queue and keep FIFO order for
1438  // tasks with equal priority.
1439  std::size_t task_id{};
1440  while (pop_ready(task_id)) {
1441  auto& context = contexts_[task_id];
1442  if (context.status() != task_status::ready) {
1443  context.clear_queued();
1444  continue;
1445  }
1446  context.clear_queued();
1447  return tasks_[task_id].resume();
1448  }
1449  return false;
1450  }
1451 
1452  std::size_t run_ready()
1453  {
1454  std::size_t resumed{};
1455  while (step()) {
1456  ++resumed;
1457  }
1458  return resumed;
1459  }
1460 
1461  std::size_t tick(tsm::tick_rep elapsed_ticks = 1U)
1462  {
1463  // Time enters this layer only as integer ticks. Platform clocks convert
1464  // to ticks before calling here, keeping task semantics target-neutral.
1465  now_ += elapsed_ticks;
1466  for (auto& context : contexts_) {
1467  context.set_now(now_);
1468  }
1469  static_cast<void>(timers_.wake_due(now_, [this](std::size_t task_id) {
1470  contexts_[task_id].mark_ready();
1471  }));
1472  return run_ready();
1473  }
1474 
1475  std::size_t tick(tsm::tick_count elapsed_ticks)
1476  {
1477  return tick(elapsed_ticks.count());
1478  }
1479 
1480  std::size_t poll_waiting()
1481  {
1482  // Predicate waits are evaluated cooperatively after HSM/runtime work.
1483  // They do not install callbacks into the observed object.
1484  std::size_t ready{};
1485  for (auto& context : contexts_) {
1486  if (context.predicate_ready()) {
1487  context.mark_ready();
1488  ++ready;
1489  }
1490  }
1491  return ready;
1492  }
1493 
1494  void enqueue(std::size_t task_id) noexcept
1495  {
1496  if (ready_size_ >= ready_queue_.size()) {
1497  contexts_[task_id].mark_failed(
1499  return;
1500  }
1501  ready_queue_[ready_push_] = task_id;
1502  ready_push_ = next_ready(ready_push_);
1503  ++ready_size_;
1504  }
1505 
1506  void start_all()
1507  {
1508  start_all_declared<TaskDefinitions...>();
1509  }
1510 
1511  template<auto Entry, std::size_t Instance = 0U>
1512  [[nodiscard]] spawn_result start()
1513  {
1514  std::size_t base{};
1515  return start_entry<Entry, Instance, TaskDefinitions...>(base);
1516  }
1517 
1518  template<auto Entry, typename... Args>
1519  [[nodiscard]] spawn_result spawn(Args&&... args)
1520  {
1521  // Dynamic tasks occupy the slots after all declared task instances.
1522  // Completed slots can be reused because their coroutine handle has been
1523  // destroyed by assignment before the new entry is installed.
1524  for (std::size_t task_id = declared_task_count; task_id < task_count;
1525  ++task_id) {
1526  if (contexts_[task_id].status() != task_status::not_started &&
1527  contexts_[task_id].status() != task_status::completed &&
1528  contexts_[task_id].status() != task_status::failed) {
1529  continue;
1530  }
1531  static_assert(
1532  std::is_invocable_r_v<tsm::task,
1533  decltype(Entry),
1535  Runtime&,
1536  std::size_t,
1537  Args...>,
1538  "tsm: spawned task must return tsm::task and accept "
1539  "(tsm::task_context&, Runtime&, std::size_t, args...)");
1540  contexts_[task_id].prepare_start();
1541  tasks_[task_id] = Entry(contexts_[task_id],
1542  *runtime_,
1543  task_id - declared_task_count,
1544  std::forward<Args>(args)...);
1545  if (!tasks_[task_id]) {
1546  return spawn_failure(contexts_[task_id]);
1547  }
1548  contexts_[task_id].mark_ready();
1549  return spawn_result::started;
1550  }
1551  return dynamic_task_count == 0U ? spawn_result::invalid_entry
1553  }
1554 
1555  template<auto Entry, std::size_t Instance = 0U>
1556  [[nodiscard]] task_status status() const
1557  {
1558  std::size_t base{};
1559  std::size_t task_id{};
1560  if (!find_entry<Entry, Instance, TaskDefinitions...>(base, task_id)) {
1561  return task_status::failed;
1562  }
1563  return contexts_[task_id].status();
1564  }
1565 
1566  template<auto Entry, std::size_t Instance = 0U>
1567  [[nodiscard]] task_failure_reason failure_reason() const
1568  {
1569  std::size_t base{};
1570  std::size_t task_id{};
1571  if (!find_entry<Entry, Instance, TaskDefinitions...>(base, task_id)) {
1573  }
1574  return contexts_[task_id].failure_reason();
1575  }
1576 
1577  template<auto Entry, std::size_t Instance = 0U>
1578  [[nodiscard]] bool cancel() noexcept
1579  {
1580  std::size_t base{};
1581  std::size_t task_id{};
1582  if (!find_entry<Entry, Instance, TaskDefinitions...>(base, task_id)) {
1583  return false;
1584  }
1585  contexts_[task_id].cancel();
1586  return true;
1587  }
1588 
1589  void cancel_all() noexcept
1590  {
1591  for (auto& context : contexts_) {
1592  context.cancel();
1593  }
1594  }
1595 
1596  private:
1597  static constexpr spawn_result spawn_failure(
1598  task_context const& context) noexcept
1599  {
1600  return context.failure_reason() == task_failure_reason::frame_too_large
1603  }
1604 
1605  static constexpr std::size_t next_ready(std::size_t index) noexcept
1606  {
1607  return (index + 1U) % task_count;
1608  }
1609 
1610  [[nodiscard]] bool pop_ready(std::size_t& task_id) noexcept
1611  {
1612  if (ready_size_ == 0U) {
1613  return false;
1614  }
1615  if constexpr (PriorityAware) {
1616  return pop_highest_priority(task_id);
1617  }
1618  task_id = ready_queue_[ready_pop_];
1619  ready_pop_ = next_ready(ready_pop_);
1620  --ready_size_;
1621  return true;
1622  }
1623 
1624  [[nodiscard]] bool pop_highest_priority(std::size_t& task_id) noexcept
1625  {
1626  std::size_t selected_offset{};
1627  std::size_t selected_task = ready_queue_[ready_pop_];
1628  auto selected_task_priority = priorities_[selected_task];
1629  for (std::size_t offset = 1U; offset < ready_size_; ++offset) {
1630  const auto candidate = ready_queue_[ready_index(offset)];
1631  const auto candidate_priority = priorities_[candidate];
1632  if (candidate_priority > selected_task_priority) {
1633  selected_offset = offset;
1634  selected_task = candidate;
1635  selected_task_priority = candidate_priority;
1636  }
1637  }
1638 
1639  task_id = selected_task;
1640  for (std::size_t offset = selected_offset; offset + 1U < ready_size_;
1641  ++offset) {
1642  ready_queue_[ready_index(offset)] =
1643  ready_queue_[ready_index(offset + 1U)];
1644  }
1645  ready_push_ = ready_index(ready_size_ - 1U);
1646  --ready_size_;
1647  return true;
1648  }
1649 
1650  [[nodiscard]] std::size_t ready_index(std::size_t offset) const noexcept
1651  {
1652  return (ready_pop_ + offset) % task_count;
1653  }
1654 
1655  static void wake_task(void* scheduler, std::size_t task_id) noexcept
1656  {
1657  static_cast<coroutine_scheduler*>(scheduler)->enqueue(task_id);
1658  }
1659 
1660  static bool sleep_task(void* scheduler,
1661  std::size_t task_id,
1662  tsm::tick_rep ticks) noexcept
1663  {
1664  auto* self = static_cast<coroutine_scheduler*>(scheduler);
1665  return self->timers_.schedule(
1666  task_id, self->now_ + ticks, self->timer_sequence_++);
1667  }
1668 
1669  static void cancel_sleep_task(void* scheduler, std::size_t task_id) noexcept
1670  {
1671  static_cast<coroutine_scheduler*>(scheduler)->timers_.cancel(task_id);
1672  }
1673 
1674  template<typename... Definitions>
1675  void bind_all()
1676  {
1677  std::size_t index{};
1678  (bind_definition<Definitions>(index), ...);
1679  }
1680 
1681  template<typename Definition>
1682  void bind_definition(std::size_t& index)
1683  {
1684  // The task list can mix declared entries and dynamic slot blocks. Both
1685  // produce concrete task_context objects with stable numeric IDs.
1686  if constexpr (declared_task_definition<Definition>) {
1687  bind_task_definition<Definition>(index);
1688  } else if constexpr (dynamic_task_definition<Definition>) {
1689  bind_dynamic_slots<Definition>(index);
1690  }
1691  }
1692 
1693  template<typename Definition>
1694  void bind_task_definition(std::size_t& index)
1695  {
1696  for (std::size_t instance = 0; instance < Definition::instances;
1697  ++instance) {
1698  priorities_[index] = Definition::priority;
1699  contexts_[index].bind(arena_,
1700  index,
1701  instance,
1702  Definition::frame_bytes,
1703  this,
1704  wake_task,
1705  sleep_task,
1706  cancel_sleep_task);
1707  ++index;
1708  }
1709  }
1710 
1711  template<typename Definition>
1712  void bind_dynamic_slots(std::size_t& index)
1713  {
1714  for (std::size_t instance = 0; instance < Definition::count;
1715  ++instance) {
1716  contexts_[index].bind(arena_,
1717  index,
1718  instance,
1719  Definition::frame_bytes,
1720  this,
1721  wake_task,
1722  sleep_task,
1723  cancel_sleep_task);
1724  ++index;
1725  }
1726  }
1727 
1728  template<typename Definition>
1729  [[nodiscard]] spawn_result start_definition_instance(std::size_t task_id,
1730  std::size_t instance)
1731  {
1732  if (contexts_[task_id].status() != task_status::not_started &&
1733  contexts_[task_id].status() != task_status::failed) {
1734  return spawn_result::started;
1735  }
1736  static_assert(std::is_invocable_r_v<tsm::task,
1737  decltype(Definition::entry),
1739  Runtime&,
1740  std::size_t>,
1741  "tsm: task entry must return tsm::task and accept "
1742  "(tsm::task_context&, Runtime&, std::size_t)");
1743  contexts_[task_id].prepare_start();
1744  tasks_[task_id] =
1745  Definition::entry(contexts_[task_id], *runtime_, instance);
1746  if (!tasks_[task_id]) {
1747  return spawn_failure(contexts_[task_id]);
1748  }
1749  contexts_[task_id].mark_ready();
1750  return spawn_result::started;
1751  }
1752 
1753  template<typename... Definitions>
1754  void start_all_declared()
1755  {
1756  std::size_t base{};
1757  (start_all_for_definition<Definitions>(base), ...);
1758  }
1759 
1760  template<typename Definition>
1761  void start_all_for_definition(std::size_t& base)
1762  {
1763  if constexpr (declared_task_definition<Definition>) {
1764  for (std::size_t instance = 0; instance < Definition::instances;
1765  ++instance) {
1766  static_cast<void>(start_definition_instance<Definition>(
1767  base + instance, instance));
1768  }
1769  base += Definition::instances;
1770  } else if constexpr (dynamic_task_definition<Definition>) {
1771  base += Definition::count;
1772  }
1773  }
1774 
1775  template<auto Entry,
1776  std::size_t Instance,
1777  typename Definition,
1778  typename... Rest>
1779  [[nodiscard]] spawn_result start_entry(std::size_t& base)
1780  {
1781  if constexpr (declared_task_definition<Definition> &&
1782  same_task_entry<Definition::entry, Entry>) {
1783  if constexpr (Instance < Definition::instances) {
1784  return start_definition_instance<Definition>(base + Instance,
1785  Instance);
1786  } else {
1788  }
1789  } else {
1790  if constexpr (declared_task_definition<Definition>) {
1791  base += Definition::instances;
1792  } else if constexpr (dynamic_task_definition<Definition>) {
1793  base += Definition::count;
1794  }
1795  if constexpr (sizeof...(Rest) > 0U) {
1796  return start_entry<Entry, Instance, Rest...>(base);
1797  } else {
1799  }
1800  }
1801  }
1802 
1803  template<auto Entry,
1804  std::size_t Instance,
1805  typename Definition,
1806  typename... Rest>
1807  [[nodiscard]] bool find_entry(std::size_t& base, std::size_t& task_id) const
1808  {
1809  if constexpr (declared_task_definition<Definition> &&
1810  same_task_entry<Definition::entry, Entry>) {
1811  if constexpr (Instance < Definition::instances) {
1812  task_id = base + Instance;
1813  return true;
1814  } else {
1815  return false;
1816  }
1817  } else {
1818  if constexpr (declared_task_definition<Definition>) {
1819  base += Definition::instances;
1820  } else if constexpr (dynamic_task_definition<Definition>) {
1821  base += Definition::count;
1822  }
1823  if constexpr (sizeof...(Rest) > 0U) {
1824  return find_entry<Entry, Instance, Rest...>(base, task_id);
1825  } else {
1826  return false;
1827  }
1828  }
1829  }
1830 
1831  Runtime* runtime_{};
1833  std::array<tsm::task_context, task_count> contexts_{};
1834  std::array<tsm::task, task_count> tasks_{};
1835  std::array<std::size_t, task_count> ready_queue_{};
1836  std::array<std::uint8_t, task_count> priorities_{};
1837  timer_queue<task_count> timers_{};
1838  std::size_t ready_push_{};
1839  std::size_t ready_pop_{};
1840  std::size_t ready_size_{};
1841  tsm::tick_rep now_{};
1842  std::uint32_t timer_sequence_{};
1843 };
1844 
1845 template<typename Runtime>
1847  coroutine_scheduler<Runtime, typename runtime_task_list_of<Runtime>::type>;
1848 
1849 template<typename Runtime>
1851  coroutine_scheduler<Runtime,
1853  true>;
1854 
1855 template<typename Runtime, template<typename> typename SchedulerFor>
1856 class basic_executor_task_binding
1857 {
1858  public:
1859  explicit basic_executor_task_binding(Runtime& runtime)
1860  : runtime_(&runtime)
1861  , coroutines_(runtime)
1862  {
1863  }
1864 
1865  [[nodiscard]] bool step()
1866  {
1867  // Task code and HSM dispatch share a cooperative turn. A task runs
1868  // first if already ready; otherwise the runtime processes one event and
1869  // predicate waits are checked before one more task opportunity.
1870  if (coroutines_.step()) {
1871  return true;
1872  }
1873  if (runtime_->step()) {
1874  static_cast<void>(coroutines_.poll_waiting());
1875  return true;
1876  }
1877  static_cast<void>(coroutines_.poll_waiting());
1878  return coroutines_.step();
1879  }
1880 
1881  std::size_t run_ready()
1882  {
1883  std::size_t rounds{};
1884  while (step()) {
1885  ++rounds;
1886  }
1887  return rounds;
1888  }
1889 
1890  std::size_t tick(tsm::tick_rep elapsed_ticks = 1U)
1891  {
1892  return coroutines_.tick(elapsed_ticks);
1893  }
1894 
1895  std::size_t tick(tsm::tick_count elapsed_ticks)
1896  {
1897  return tick(elapsed_ticks.count());
1898  }
1899 
1900  void start_all()
1901  {
1902  coroutines_.start_all();
1903  }
1904 
1905  template<auto Entry, std::size_t Instance = 0U>
1906  [[nodiscard]] spawn_result start()
1907  {
1908  return coroutines_.template start<Entry, Instance>();
1909  }
1910 
1911  template<auto Entry, typename... Args>
1912  [[nodiscard]] spawn_result spawn(Args&&... args)
1913  {
1914  return coroutines_.template spawn<Entry>(std::forward<Args>(args)...);
1915  }
1916 
1917  template<auto Entry, std::size_t Instance = 0U>
1918  [[nodiscard]] task_status task_status() const
1919  {
1920  return coroutines_.template status<Entry, Instance>();
1921  }
1922 
1923  template<auto Entry, std::size_t Instance = 0U>
1924  [[nodiscard]] task_failure_reason task_failure_reason() const
1925  {
1926  return coroutines_.template failure_reason<Entry, Instance>();
1927  }
1928 
1929  template<auto Entry, std::size_t Instance = 0U>
1930  [[nodiscard]] bool cancel() noexcept
1931  {
1932  return coroutines_.template cancel<Entry, Instance>();
1933  }
1934 
1935  void cancel_all() noexcept
1936  {
1937  coroutines_.cancel_all();
1938  }
1939 
1940  private:
1941  Runtime* runtime_{};
1942  SchedulerFor<Runtime> coroutines_;
1943 };
1944 
1945 template<typename Runtime>
1947  basic_executor_task_binding<Runtime, coroutine_scheduler_for>;
1948 
1949 template<typename Runtime>
1951  basic_executor_task_binding<Runtime, priority_coroutine_scheduler_for>;
1952 
1953 } // namespace detail
1954 
1962 template<std::size_t Capacity>
1963 using timer_queue = detail::timer_queue<Capacity>;
1964 
1965 template<typename Runtime>
1966 using task_resources = detail::task_resources<Runtime>;
1967 
1968 } // namespace runtime
1969 
1970 } // namespace tsm
Definition: coroutine.h:940
bool await_resume() const noexcept
Definition: coroutine.h:959
checked_send_event_awaitable(Runtime &runtime, Event &&event)
Definition: coroutine.h:942
bool await_suspend(task::handle_type)
Definition: coroutine.h:953
bool await_ready() const noexcept
Definition: coroutine.h:948
Definition: coroutine.h:656
constexpr periodic_ticks(tsm::tick_rep period) noexcept
Definition: coroutine.h:658
tsm::tick_rep period() const noexcept
Definition: coroutine.h:673
constexpr periodic_ticks(tick_count period) noexcept
Definition: coroutine.h:663
Definition: coroutine.h:767
bool await_ready() noexcept
Definition: coroutine.h:774
select_awaitable(Awaitables... awaitables)
Definition: coroutine.h:769
select_result await_resume() noexcept
Definition: coroutine.h:785
void await_suspend(task::handle_type handle) noexcept
Definition: coroutine.h:779
Definition: coroutine.h:908
send_event_awaitable(Runtime &runtime, Event &&event)
Definition: coroutine.h:910
bool await_ready() const noexcept
Definition: coroutine.h:916
bool await_suspend(task::handle_type)
Definition: coroutine.h:921
bool await_resume() const noexcept
Definition: coroutine.h:927
Definition: coroutine.h:113
std::size_t used() const noexcept
Definition: coroutine.h:129
std::size_t capacity() const noexcept
Definition: coroutine.h:133
void * allocate(std::size_t bytes, std::size_t alignment) noexcept
Definition: coroutine.h:118
Definition: coroutine.h:196
task_failure_reason failure_reason() const noexcept
Definition: coroutine.h:279
bool ready_to_resume() const noexcept
Definition: coroutine.h:360
task_context()=default
void prepare_start() noexcept
Definition: coroutine.h:309
void wait_until(Object const &object, Predicate predicate) noexcept
Definition: coroutine.h:352
bool predicate_ready() const noexcept
Definition: coroutine.h:378
void(*)(void *, std::size_t) noexcept cancel_sleep_fn
Definition: coroutine.h:201
void cancel_sleep() noexcept
Definition: coroutine.h:344
void mark_ready() noexcept
Definition: coroutine.h:293
task_wait_kind wait_kind() const noexcept
Definition: coroutine.h:271
void mark_running() noexcept
Definition: coroutine.h:321
void bind(static_coroutine_arena< Bytes > &arena, std::size_t task_id, std::size_t instance_id, std::size_t frame_limit, void *scheduler, wake_fn wake, sleep_fn sleep, cancel_sleep_fn cancel_sleep) noexcept
Definition: coroutine.h:211
bool(*)(void *, std::size_t, tsm::tick_rep) noexcept sleep_fn
Definition: coroutine.h:200
task_context(static_coroutine_arena< 1U > *, std::size_t, std::size_t, std::size_t)=delete
std::uint32_t generation() const noexcept
Definition: coroutine.h:283
void cancel() noexcept
Definition: coroutine.h:401
std::size_t instance_id() const noexcept
Definition: coroutine.h:263
bool(*)(void const *) noexcept predicate_fn
Definition: coroutine.h:198
std::size_t task_id() const noexcept
Definition: coroutine.h:259
void set_now(tsm::tick_rep now) noexcept
Definition: coroutine.h:288
task_status status() const noexcept
Definition: coroutine.h:275
void(*)(void *, std::size_t) noexcept wake_fn
Definition: coroutine.h:199
void * allocate_frame(std::size_t bytes, std::size_t alignment) noexcept
Definition: coroutine.h:239
tsm::tick_rep now() const noexcept
Definition: coroutine.h:267
void request_wake() noexcept
Definition: coroutine.h:407
void mark_failed(task_failure_reason reason=task_failure_reason::unhandled_exception) noexcept
Definition: coroutine.h:390
void sleep_for(tsm::tick_rep ticks) noexcept
Definition: coroutine.h:331
void mark_completed() noexcept
Definition: coroutine.h:384
void clear_queued() noexcept
Definition: coroutine.h:416
Definition: coroutine.h:43
std::coroutine_handle< promise_type > handle_type
Definition: coroutine.h:46
handle_type handle() const noexcept
Definition: coroutine.h:88
task & operator=(task &&other) noexcept
Definition: coroutine.h:63
task(handle_type handle) noexcept
Definition: coroutine.h:49
~task()
Definition: coroutine.h:73
task(task &&other) noexcept
Definition: coroutine.h:57
bool resume()
Definition: coroutine.h:553
task & operator=(task const &)=delete
task(task const &)=delete
task()=default
bool done() const noexcept
Definition: coroutine.h:83
Definition: coroutine.h:1004
until_active_awaitable(Machine &machine)
Definition: coroutine.h:1006
bool await_ready() const noexcept
Definition: coroutine.h:1011
void await_suspend(task::handle_type handle) const noexcept
Definition: coroutine.h:1016
void await_resume() const noexcept
Definition: coroutine.h:1025
constexpr std::uint16_t npos
Definition: core_algorithms.h:31
requires(!has_transition_type_c< T > &&has_transition_member_c< T >) struct transitions_of< T >
Definition: transition.h:479
concept awaitable
Definition: io.h:67
task_context * find_task_context() noexcept
Definition: coroutine.h:446
concept dynamic_task_definition
Definition: coroutine.h:1206
consteval std::size_t selected_frame_bytes()
Definition: coroutine.h:1100
concept declared_task_definition
Definition: coroutine.h:1198
consteval std::size_t selected_instances()
Definition: coroutine.h:1080
coroutine_scheduler< Runtime, typename runtime_task_list_of< Runtime >::type > coroutine_scheduler_for
Definition: coroutine.h:1847
consteval std::uint8_t selected_priority()
Definition: coroutine.h:1120
basic_executor_task_binding< Runtime, priority_coroutine_scheduler_for > priority_executor_task_binding
Definition: coroutine.h:1951
basic_executor_task_binding< Runtime, coroutine_scheduler_for > executor_task_binding
Definition: coroutine.h:1947
concept basic_awaitable
Definition: coroutine.h:724
void cancel_awaitable(Awaitable &awaitable, task_context &context) noexcept
Definition: coroutine.h:745
concept same_task_entry
Definition: coroutine.h:1213
coroutine_scheduler< Runtime, typename runtime_task_list_of< Runtime >::type, true > priority_coroutine_scheduler_for
Definition: coroutine.h:1853
detail::runtime_impl< Definition, Policy, MachinePolicy > Runtime
Definition: runtime.h:531
detail::timer_queue< Capacity > timer_queue
Definition: coroutine.h:1963
detail::task_resources< Runtime > task_resources
Definition: coroutine.h:1966
status
Definition: transport.h:47
Definition: bare_metal.h:20
auto send_event(Runtime &runtime, Event &&event)
Definition: coroutine.h:972
task_failure_reason
Definition: coroutine.h:170
runtime::Runtime< Definition, Policy, MachinePolicy > Runtime
Definition: runtime.h:35
constexpr tick_count ticks(tick_rep value) noexcept
Definition: ticks.h:85
sleep_ticks_awaitable after_ticks(tsm::tick_rep ticks) noexcept
Definition: coroutine.h:638
task_wait_kind
Definition: coroutine.h:150
auto until_active(Machine &machine) noexcept
Definition: coroutine.h:1033
sleep_ticks_awaitable sleep_ticks(tsm::tick_rep ticks) noexcept
Definition: coroutine.h:622
auto select(Awaitables &&... awaitables)
Definition: coroutine.h:882
task_status
Definition: coroutine.h:160
auto with_timeout(Awaitable &&awaitable, tsm::tick_rep ticks)
Definition: coroutine.h:893
timeout_ticks_awaitable timeout_ticks(tsm::tick_rep ticks) noexcept
Definition: coroutine.h:710
spawn_result
Definition: coroutine.h:181
yield_awaitable yield() noexcept
Definition: coroutine.h:589
auto try_send_event(Runtime &runtime, Event &&event)
Definition: coroutine.h:987
auto try_send(Runtime &runtime, Args &&... args)
Definition: coroutine.h:997
auto send(Runtime &runtime, Args &&... args)
Definition: coroutine.h:980
TSM_TICK_REP tick_rep
Definition: ticks.h:35
Definition: coroutine.h:1059
static constexpr std::size_t count
Definition: coroutine.h:1062
Definition: coroutine.h:1046
static constexpr std::size_t value
Definition: coroutine.h:1047
Definition: coroutine.h:1040
static constexpr std::size_t value
Definition: coroutine.h:1041
Result of a select wait.
Definition: coroutine.h:756
std::size_t index
Definition: coroutine.h:757
Definition: coroutine.h:603
void await_cancel(task_context &context) const noexcept
Definition: coroutine.h:614
void await_resume() const noexcept
Definition: coroutine.h:618
void await_suspend(task::handle_type handle) const noexcept
Definition: coroutine.h:610
bool await_ready() const noexcept
Definition: coroutine.h:606
tsm::tick_rep ticks
Definition: coroutine.h:604
Definition: coroutine.h:517
void await_resume() const noexcept
Definition: coroutine.h:528
void await_suspend(task::handle_type handle) const noexcept
Definition: coroutine.h:522
bool await_ready() const noexcept
Definition: coroutine.h:518
Definition: coroutine.h:465
final_awaitable final_suspend() noexcept
Definition: coroutine.h:531
void return_void() noexcept
Definition: coroutine.h:535
std::suspend_always initial_suspend() noexcept
Definition: coroutine.h:511
task get_return_object() noexcept
Definition: coroutine.h:506
static task get_return_object_on_allocation_failure() noexcept
Definition: coroutine.h:474
promise_type(First &first, Args &... args) noexcept
Definition: coroutine.h:469
task_context & context() noexcept
Definition: coroutine.h:543
void unhandled_exception() noexcept
Definition: coroutine.h:536
Definition: coroutine.h:1143
static constexpr std::uint8_t priority
Definition: coroutine.h:1151
static constexpr auto entry
Definition: coroutine.h:1146
Definition: coroutine.h:1073
static constexpr std::size_t size
Definition: coroutine.h:1074
Definition: coroutine.h:1052
static constexpr std::uint8_t value
Larger numeric values run first in priority-aware cooperative executors.
Definition: coroutine.h:1054
Definition: coroutine.h:1162
static constexpr std::size_t size
Definition: coroutine.h:1163
Strong value type for semantic scheduler ticks.
Definition: ticks.h:54
constexpr tick_rep count() const noexcept
Definition: ticks.h:73
Definition: coroutine.h:691
void await_cancel(task_context &context) const noexcept
Definition: coroutine.h:702
bool await_ready() const noexcept
Definition: coroutine.h:694
void await_resume() const noexcept
Definition: coroutine.h:706
void await_suspend(task::handle_type handle) const noexcept
Definition: coroutine.h:698
tsm::tick_rep ticks
Definition: coroutine.h:692
Definition: coroutine.h:576
void await_suspend(task::handle_type handle) const noexcept
Definition: coroutine.h:581
void await_resume() const noexcept
Definition: coroutine.h:585
bool await_ready() const noexcept
Definition: coroutine.h:577
Target-neutral tick value type.