tin  1.5.9
executor.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 
13 
14 #pragma once
15 
16 #include <algorithm>
17 #include <array>
18 #include <cstddef>
19 #include <cstdint>
20 #include <tuple>
21 #include <type_traits>
22 #include <utility>
23 
24 #ifndef TSM_SUPPRESS_AUTO_PLATFORM_ADAPTERS
25 #define TSM_SUPPRESS_AUTO_PLATFORM_ADAPTERS
26 #define TSM_RESTORE_AUTO_PLATFORM_ADAPTERS
27 #endif
28 #include "tsm.h"
29 #ifdef TSM_RESTORE_AUTO_PLATFORM_ADAPTERS
30 #undef TSM_SUPPRESS_AUTO_PLATFORM_ADAPTERS
31 #undef TSM_RESTORE_AUTO_PLATFORM_ADAPTERS
32 #endif
33 #include "tsm/runtime/concepts.h"
34 #include "tsm/runtime/coroutine.h"
35 
36 namespace tsm::runtime {
37 
44 {
45  template<typename Work>
46  decltype(auto) post(Work&& work)
47  {
48  return std::forward<Work>(work)();
49  }
50 
51  [[nodiscard]] bool step()
52  {
53  return false;
54  }
55  std::size_t run_ready()
56  {
57  return 0;
58  }
59 };
60 
67 template<typename... Runtimes>
69 {
70  public:
71  explicit constexpr runtime_group(Runtimes&... runtimes)
72  : runtimes_(&runtimes...)
73  {
74  }
75 
76  [[nodiscard]] bool step()
77  {
78  bool progressed{};
79  std::apply(
80  [&progressed](auto*... runtime) {
81  ((progressed = runtime->step() || progressed), ...);
82  },
83  runtimes_);
84  return progressed;
85  }
86 
87  std::size_t run_ready()
88  {
89  std::size_t rounds{};
90  while (step()) {
91  ++rounds;
92  }
93  return rounds;
94  }
95 
96  [[nodiscard]] bool empty() const
97  {
98  bool all_empty{ true };
99  std::apply(
100  [&all_empty](auto const*... runtime) {
101  ((all_empty = all_empty && runtime->empty()), ...);
102  },
103  runtimes_);
104  return all_empty;
105  }
106 
107  [[nodiscard]] std::size_t pending_events() const
108  {
109  std::size_t pending{};
110  std::apply(
111  [&pending](auto const*... runtime) {
112  ((pending += runtime->pending_events()), ...);
113  },
114  runtimes_);
115  return pending;
116  }
117 
118  private:
119  std::tuple<Runtimes*...> runtimes_;
120 };
121 
122 template<typename... Runtimes>
123 runtime_group(Runtimes&...) -> runtime_group<Runtimes...>;
124 
130 template<typename Executor>
132 {
133  public:
134  explicit constexpr task_spawner(Executor& executor)
135  : executor_(&executor)
136  {
137  }
138 
139  template<auto Entry, typename... Args>
140  [[nodiscard]] spawn_result spawn(Args&&... args)
141  {
142  return executor_->template spawn<Entry>(std::forward<Args>(args)...);
143  }
144 
145  private:
146  Executor* executor_{};
147 };
148 
149 template<typename Executor>
151 
152 template<template<typename> typename Binding, typename... Tasks>
154 {
155  public:
156  explicit constexpr basic_cooperative_executor(Tasks&... tasks)
157  : tasks_(tasks...)
158  {
159  }
160 
161  [[nodiscard]] bool step()
162  {
163  bool progressed{};
164  std::apply(
165  [&progressed](auto&... task) {
166  ((progressed = task.step() || progressed), ...);
167  },
168  tasks_);
169  return progressed;
170  }
171 
172  std::size_t run_ready()
173  {
174  std::size_t rounds{};
175  while (step()) {
176  ++rounds;
177  }
178  return rounds;
179  }
180 
181  std::size_t tick(tsm::tick_rep elapsed_ticks = 1U)
182  {
183  std::size_t resumed{};
184  std::apply(
185  [&resumed, elapsed_ticks](auto&... task) {
186  ((resumed += task.tick(elapsed_ticks)), ...);
187  },
188  tasks_);
189  return resumed;
190  }
191 
192  std::size_t tick(tsm::tick_count elapsed_ticks)
193  {
194  return tick(elapsed_ticks.count());
195  }
196 
197  void start_all()
198  {
199  // Forward to runtime task bindings. Runtime definitions decide which
200  // declared coroutine tasks exist; the executor only starts them.
201  std::apply([](auto&... task) { (task.start_all(), ...); }, tasks_);
202  }
203 
204  template<auto Entry, std::size_t Instance = 0U>
205  [[nodiscard]] spawn_result start()
206  {
207  static_assert(sizeof...(Tasks) == 1U,
208  "tsm: start<Entry, Instance>() is available on a "
209  "single-runtime executor");
210  return std::get<0>(tasks_).template start<Entry, Instance>();
211  }
212 
213  template<auto... Entries>
215  {
216  static_assert(sizeof...(Tasks) == 1U,
217  "tsm: start_group(...) is available on a "
218  "single-runtime executor");
219  (static_cast<void>(start<Entries>()), ...);
220  }
221 
222  template<auto Entry, typename... Args>
223  [[nodiscard]] spawn_result spawn(Args&&... args)
224  {
225  static_assert(sizeof...(Tasks) == 1U,
226  "tsm: spawn<Entry>() is available on a "
227  "single-runtime executor");
228  // Dynamic task spawning is intentionally scoped to one runtime so the
229  // entry point cannot be ambiguous across a runtime group.
230  return std::get<0>(tasks_).template spawn<Entry>(
231  std::forward<Args>(args)...);
232  }
233 
235  {
237  }
238 
239  template<auto Entry, std::size_t Instance = 0U>
240  [[nodiscard]] task_status task_status() const
241  {
242  static_assert(sizeof...(Tasks) == 1U,
243  "tsm: task_status<Entry, Instance>() is available on a "
244  "single-runtime executor");
245  return std::get<0>(tasks_).template task_status<Entry, Instance>();
246  }
247 
248  template<auto Entry, std::size_t Instance = 0U>
250  {
251  static_assert(sizeof...(Tasks) == 1U,
252  "tsm: task_failure_reason<Entry, Instance>() is "
253  "available on a single-runtime executor");
254  return std::get<0>(tasks_)
256  }
257 
258  template<auto Entry, std::size_t Instance = 0U>
259  [[nodiscard]] bool cancel() noexcept
260  {
261  static_assert(sizeof...(Tasks) == 1U,
262  "tsm: cancel<Entry, Instance>() is available on a "
263  "single-runtime executor");
264  return std::get<0>(tasks_).template cancel<Entry, Instance>();
265  }
266 
267  template<auto... Entries>
269  {
270  static_assert(sizeof...(Tasks) == 1U,
271  "tsm: cancel_group(...) is available on a "
272  "single-runtime executor");
273  (static_cast<void>(cancel<Entries>()), ...);
274  }
275 
276  void cancel_all() noexcept
277  {
278  std::apply([](auto&... task) { (task.cancel_all(), ...); }, tasks_);
279  }
280 
281  private:
282  std::tuple<Binding<Tasks>...> tasks_;
283 };
284 
291 template<typename... Tasks>
293  : public basic_cooperative_executor<detail::executor_task_binding, Tasks...>
294 {
295  using base =
297 
298  public:
299  using base::base;
300 };
301 
302 template<typename... Tasks>
304 
311 template<typename... Tasks>
313  : public basic_cooperative_executor<detail::priority_executor_task_binding,
314  Tasks...>
315 {
316  using base =
318  Tasks...>;
319 
320  public:
321  using base::base;
322 };
323 
324 template<typename... Tasks>
326  -> priority_cooperative_executor<Tasks...>;
327 
336 template<typename TickSource, typename Executor, typename DelayedEvents>
337 [[nodiscard]] tsm::tick_rep
339  Executor& executor,
340  DelayedEvents& delayed_events)
341 {
342  const auto elapsed_ticks = tsm::ticks(tick_source.poll());
343  if (elapsed_ticks.count() == 0U) {
344  return tsm::tick_rep{};
345  }
346 
347  static_cast<void>(executor.tick(elapsed_ticks));
348  static_cast<void>(delayed_events.tick(elapsed_ticks));
349  static_cast<void>(executor.run_ready());
350  return elapsed_ticks.count();
351 }
352 
359 template<typename Runtime, std::size_t Capacity = 16U>
361 {
362  public:
364  using events = typename Runtime::events;
366 
367  static_assert(Capacity > 0U,
368  "tsm: tick_executor requires at least one timer slot");
369 
370  explicit constexpr tick_executor(Runtime& runtime)
371  : runtime_(&runtime)
372  {
373  }
374 
375  template<typename Event>
376  requires(tsm::detail::contains_type<std::decay_t<Event>>(
378  [[nodiscard]] bool after_ticks(Event&& event, tsm::tick_rep ticks)
379  {
380  return schedule(std::forward<Event>(event), ticks, 0U, false);
381  }
382 
383  template<typename Event>
384  requires(tsm::detail::contains_type<std::decay_t<Event>>(
386  [[nodiscard]] bool after_ticks(Event&& event, tsm::tick_count ticks)
387  {
388  return after_ticks(std::forward<Event>(event), ticks.count());
389  }
390 
391  template<typename Event>
392  requires(tsm::detail::contains_type<std::decay_t<Event>>(
394  [[nodiscard]] bool every_ticks(Event&& event, tsm::tick_rep period)
395  {
396  return period != 0U &&
397  schedule(std::forward<Event>(event), period, period, true);
398  }
399 
400  template<typename Event>
401  requires(tsm::detail::contains_type<std::decay_t<Event>>(
403  [[nodiscard]] bool every_ticks(Event&& event, tsm::tick_count period)
404  {
405  return every_ticks(std::forward<Event>(event), period.count());
406  }
407 
408  std::size_t tick(tsm::tick_rep elapsed_ticks = 1U)
409  {
410  std::size_t sent{};
411  tsm::tick_rep remaining_elapsed = elapsed_ticks;
412  while (remaining_elapsed > 0U) {
413  const tsm::tick_rep tick_step = next_step(remaining_elapsed);
414  advance(tick_step);
415  remaining_elapsed -= tick_step;
416  sent += send_due();
417  }
418  return sent;
419  }
420 
421  std::size_t tick(tsm::tick_count elapsed_ticks)
422  {
423  return tick(elapsed_ticks.count());
424  }
425 
426  [[nodiscard]] bool step()
427  {
428  return false;
429  }
430  std::size_t run_ready()
431  {
432  return 0;
433  }
434 
435  [[nodiscard]] std::size_t pending() const
436  {
437  return static_cast<std::size_t>(
438  std::count_if(timers_.begin(), timers_.end(), [](auto const& timer) {
439  return timer.active;
440  }));
441  }
442 
443  [[nodiscard]] bool empty() const
444  {
445  return pending() == 0U;
446  }
447 
448  void clear()
449  {
450  std::fill(timers_.begin(), timers_.end(), timer_slot{});
451  }
452 
453  private:
454  class scheduled_event
455  {
456  public:
457  static constexpr std::size_t storage_size =
459  static constexpr std::size_t storage_alignment =
461 
462  scheduled_event() = default;
463  scheduled_event(scheduled_event const& other)
464  {
465  copy_from(other);
466  }
467  scheduled_event(scheduled_event&& other) noexcept
468  {
469  move_from(other);
470  }
471 
472  scheduled_event& operator=(scheduled_event const& other)
473  {
474  if (this != &other) {
475  reset();
476  storage_ = {};
477  copy_from(other);
478  }
479  return *this;
480  }
481 
482  scheduled_event& operator=(scheduled_event&& other) noexcept
483  {
484  if (this != &other) {
485  reset();
486  storage_ = {};
487  move_from(other);
488  }
489  return *this;
490  }
491 
492  ~scheduled_event()
493  {
494  reset();
495  }
496 
497  template<typename Event>
498  requires(!std::same_as<
499  std::decay_t<Event>,
500  scheduled_event>) explicit scheduled_event(Event&& event)
501  {
502  emplace<std::decay_t<Event>>(std::forward<Event>(event));
503  }
504 
505  [[nodiscard]] bool empty() const
506  {
507  return send_ == nullptr;
508  }
509 
510  bool send(Runtime& runtime)
511  {
512  if (send_ == nullptr) {
513  return false;
514  }
515  return send_(runtime, data());
516  }
517 
518  private:
519  using send_fn = bool (*)(Runtime&, void*);
520  using copy_fn = void (*)(void*, void const*);
521  using move_fn = void (*)(void*, void*);
522  using destroy_fn = void (*)(void*);
523 
524  template<typename Event, typename... Args>
525  void emplace(Args&&... args)
526  {
527  static_assert(sizeof(Event) <= storage_size);
528  static_assert(alignof(Event) <= storage_alignment);
529  new (data()) Event(std::forward<Args>(args)...);
530  send_ = [](Runtime& runtime, void* storage) -> bool {
531  return runtime.send_event(*static_cast<Event*>(storage));
532  };
533  copy_ = [](void* destination, void const* source) {
534  new (destination) Event(*static_cast<Event const*>(source));
535  };
536  move_ = [](void* destination, void* source) {
537  new (destination)
538  Event(std::move(*static_cast<Event*>(source)));
539  };
540  destroy_ = [](void* storage) {
541  static_cast<Event*>(storage)->~Event();
542  };
543  }
544 
545  void copy_from(scheduled_event const& other)
546  {
547  if (other.copy_ == nullptr) {
548  return;
549  }
550  other.copy_(data(), other.data());
551  send_ = other.send_;
552  copy_ = other.copy_;
553  move_ = other.move_;
554  destroy_ = other.destroy_;
555  }
556 
557  void move_from(scheduled_event& other)
558  {
559  if (other.move_ == nullptr) {
560  return;
561  }
562  other.move_(data(), other.data());
563  send_ = other.send_;
564  copy_ = other.copy_;
565  move_ = other.move_;
566  destroy_ = other.destroy_;
567  other.reset();
568  }
569 
570  void reset()
571  {
572  if (destroy_ != nullptr) {
573  destroy_(data());
574  }
575  send_ = nullptr;
576  copy_ = nullptr;
577  move_ = nullptr;
578  destroy_ = nullptr;
579  }
580 
581  void* data()
582  {
583  return storage_.data();
584  }
585  void const* data() const
586  {
587  return storage_.data();
588  }
589 
590  alignas(
591  storage_alignment) std::array<unsigned char, storage_size> storage_{};
592  send_fn send_{};
593  copy_fn copy_{};
594  move_fn move_{};
595  destroy_fn destroy_{};
596  };
597 
598  struct timer_slot
599  {
600  scheduled_event event{};
601  tsm::tick_rep remaining{};
603  bool periodic{};
604  bool active{};
605  };
606 
607  template<typename Event>
608  [[nodiscard]] bool schedule(Event&& event,
611  bool periodic)
612  {
613  auto slot =
614  std::find_if(timers_.begin(), timers_.end(), [](auto const& timer) {
615  return !timer.active;
616  });
617  if (slot != timers_.end()) {
618  slot->event = scheduled_event(std::forward<Event>(event));
619  slot->remaining = ticks;
620  slot->period = period;
621  slot->periodic = periodic;
622  slot->active = true;
623  return true;
624  }
625  return false;
626  }
627 
628  [[nodiscard]] tsm::tick_rep next_step(tsm::tick_rep elapsed_limit) const
629  {
630  tsm::tick_rep tick_step = elapsed_limit;
631  for (auto const& timer : timers_) {
632  if (!timer.active) {
633  continue;
634  }
635  if (timer.remaining == 0U) {
636  return 1U;
637  }
638  if (timer.remaining < tick_step) {
639  tick_step = timer.remaining;
640  }
641  }
642  return tick_step;
643  }
644 
645  void advance(tsm::tick_rep ticks)
646  {
647  for (auto& timer : timers_) {
648  if (!timer.active) {
649  continue;
650  }
651  if (timer.remaining > ticks) {
652  timer.remaining -= ticks;
653  } else {
654  timer.remaining = 0U;
655  }
656  }
657  }
658 
659  std::size_t send_due()
660  {
661  std::size_t sent{};
662  for (auto& timer : timers_) {
663  if (!timer.active || timer.remaining != 0U) {
664  continue;
665  }
666  ++sent;
667  if (timer.periodic) {
668  auto event = timer.event;
669  (void)event.send(*runtime_);
670  timer.remaining = timer.period;
671  } else {
672  (void)timer.event.send(*runtime_);
673  timer = timer_slot{};
674  }
675  }
676  return sent;
677  }
678 
679  Runtime* runtime_{};
680  std::array<timer_slot, Capacity> timers_{};
681 };
682 
683 template<typename Runtime>
685 
686 template<typename Runtime, std::size_t Capacity = 16U>
688 
689 } // namespace tsm::runtime
task_spawner< basic_cooperative_executor > spawner()
Definition: executor.h:234
spawn_result start()
Definition: executor.h:205
void start_group(tsm::task_group< Entries... >)
Definition: executor.h:214
spawn_result spawn(Args &&... args)
Definition: executor.h:223
task_status task_status() const
Definition: executor.h:240
constexpr basic_cooperative_executor(Tasks &... tasks)
Definition: executor.h:156
void start_all()
Definition: executor.h:197
bool step()
Definition: executor.h:161
void cancel_group(tsm::task_group< Entries... >) noexcept
Definition: executor.h:268
bool cancel() noexcept
Definition: executor.h:259
std::size_t tick(tsm::tick_rep elapsed_ticks=1U)
Definition: executor.h:181
void cancel_all() noexcept
Definition: executor.h:276
task_failure_reason task_failure_reason() const
Definition: executor.h:249
std::size_t run_ready()
Definition: executor.h:172
std::size_t tick(tsm::tick_count elapsed_ticks)
Definition: executor.h:192
Definition: executor.h:294
Definition: executor.h:69
bool empty() const
Definition: executor.h:96
std::size_t run_ready()
Definition: executor.h:87
std::size_t pending_events() const
Definition: executor.h:107
constexpr runtime_group(Runtimes &... runtimes)
Definition: executor.h:71
bool step()
Definition: executor.h:76
Definition: executor.h:132
spawn_result spawn(Args &&... args)
Definition: executor.h:140
constexpr task_spawner(Executor &executor)
Definition: executor.h:134
Definition: executor.h:361
std::size_t pending() const
Definition: executor.h:435
std::size_t run_ready()
Definition: executor.h:430
tsm::detail::as_type_list_t< events > event_list
Definition: executor.h:365
std::size_t tick(tsm::tick_rep elapsed_ticks=1U)
Definition: executor.h:408
Runtime runtime_type
Definition: executor.h:363
requires(tsm::detail::contains_type< std::decay_t< Event >>(tsm::detail::as_type_list_t< events >{})) bool every_ticks(Event &&event
typename Runtime::events events
Definition: executor.h:364
std::size_t tick(tsm::tick_count elapsed_ticks)
Definition: executor.h:421
bool step()
Definition: executor.h:426
tsm::tick_rep ticks
Definition: executor.h:379
tsm::tick_rep period
Definition: executor.h:395
void clear()
Definition: executor.h:448
requires(tsm::detail::contains_type< std::decay_t< Event >>(tsm::detail::as_type_list_t< events >{})) bool after_ticks(Event &&event
constexpr tick_executor(Runtime &runtime)
Definition: executor.h:370
bool empty() const
Definition: executor.h:443
Definition: coroutine.h:43
Concepts for runtime queues and dispatch policies.
Static coroutine tasks for tsm runtime executors.
consteval std::size_t max_alignof(type_list< T, Ts... >)
Definition: type_list.h:146
typename as_type_list< T >::type as_type_list_t
Definition: type_list.h:175
consteval std::size_t max_sizeof(type_list< T, Ts... >)
Definition: type_list.h:134
consteval bool contains_type(type_list< Ts... >)
Definition: type_list.h:124
concept tick_source
Definition: io.h:243
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
Definition: freertos.h:26
detail::runtime_impl< Definition, Policy, MachinePolicy > Runtime
Definition: runtime.h:531
tick_executor(Runtime &) -> tick_executor< Runtime >
priority_cooperative_executor(Tasks &...) -> priority_cooperative_executor< Tasks... >
tsm::tick_rep drive_elapsed_ticks(TickSource &tick_source, Executor &executor, DelayedEvents &delayed_events)
Definition: executor.h:338
cooperative_executor(Tasks &...) -> cooperative_executor< Tasks... >
runtime_group(Runtimes &...) -> runtime_group< Runtimes... >
task_failure_reason
Definition: coroutine.h:170
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
periodic_ticks every_ticks
Definition: coroutine.h:682
task_status
Definition: coroutine.h:160
spawn_result
Definition: coroutine.h:181
auto send(Runtime &runtime, Args &&... args)
Definition: coroutine.h:980
TSM_TICK_REP tick_rep
Definition: ticks.h:35
Definition: executor.h:44
bool step()
Definition: executor.h:51
decltype(auto) post(Work &&work)
Definition: executor.h:46
std::size_t run_ready()
Definition: executor.h:55
Definition: coroutine.h:1073
Definition: coroutine.h:1162
Strong value type for semantic scheduler ticks.
Definition: ticks.h:54
constexpr tick_rep count() const noexcept
Definition: ticks.h:73