195 using TaskId = std::uint32_t;
199 void reserve_tasks(std::size_t count) {
201 detail::fail_fast(
"Schedule::reserve_tasks called after seal()");
203 tasks_.reserve(count);
204 phase_order_.reserve(count);
205 dirty_task_ids_.reserve(count);
206 event_task_ids_.reserve(count);
211 return add_task_record(desc, ctx, fn,
nullptr);
215 ScheduleNoThrowTaskFn fn) -> TaskId {
216 return add_task_record(desc, ctx,
nullptr, fn);
223 return add_task_record(desc, ctx,
nullptr,
nullptr);
228 template <
typename T>
232 return add_task(desc,
static_cast<void*
>(&task),
235 return (*
static_cast<T*
>(ctx))(context);
239 desc,
static_cast<void*
>(&task),
242 return (*
static_cast<T*
>(ctx))(context);
264 phase_order_.clear();
265 dirty_task_ids_.clear();
266 event_task_ids_.clear();
267 for (std::uint8_t phase = 0;
268 phase < static_cast<std::uint8_t>(SimPhase::Count); ++phase) {
269 for (std::size_t i = 0; i < tasks_.size(); ++i) {
270 if (
static_cast<std::uint8_t
>(tasks_[i].desc.phase) == phase) {
271 phase_order_.push_back(
static_cast<TaskId
>(i));
275 for (std::size_t i = 0; i < tasks_.size(); ++i) {
276 if (tasks_[i].desc.cadence.kind == CadenceKind::OnDirty) {
277 dirty_task_ids_.push_back(
static_cast<TaskId
>(i));
279 if (tasks_[i].desc.cadence.kind == CadenceKind::OnEvent) {
280 event_task_ids_.push_back(
static_cast<TaskId
>(i));
286 [[nodiscard]]
auto sealed()
const noexcept ->
bool {
return sealed_; }
293 void set_enabled(TaskId
id,
bool enabled)
noexcept {
294 if (
id >= tasks_.size()) {
295 detail::fail_fast(
"Schedule::set_enabled called with an unknown TaskId");
297 tasks_[id].enabled = enabled;
304 void request_run(TaskId
id)
noexcept {
305 if (
id >= tasks_.size()) {
306 detail::fail_fast(
"Schedule::request_run called with an unknown TaskId");
308 tasks_[id].run_requested =
true;
315 void notify_dirty(
DirtyMask mask)
noexcept {
317 for (
const auto id : dirty_task_ids_) {
318 tasks_[id].pending_mask |= mask;
322 for (
auto& task : tasks_) {
323 task.pending_mask |= mask;
330 void notify_events(std::uint32_t mask)
noexcept {
332 for (
const auto id : event_task_ids_) {
333 tasks_[id].pending_events |= mask;
337 for (
auto& task : tasks_) {
338 task.pending_events |= mask;
344 template <
typename T>
345 [[nodiscard]]
bool publish_event(std::uint32_t mask,
EventStream<T>& stream,
346 std::uint64_t tick,
const T& value) {
347 if (!stream.publish(tick, value)) {
355#if TESS_DIAGNOSTICS_ENABLED
360 detail::fail_fast(
"Schedule::run_tick called before seal()");
363 detail::fail_fast(
"Schedule::run_tick rejected a reentrant run_tick");
370 ~InRunGuard() { flag =
false; }
373 const InRunGuard guard{in_run_};
375 stats.tick = advance_sim_tick(clock);
377 for (std::size_t position = 0; position < phase_order_.size(); ++position) {
378#if TESS_HAS_EXCEPTIONS
380 run_task_if_due(tasks_[phase_order_[position]], clock, stats);
386 for (++position; position < phase_order_.size(); ++position) {
387 advance_aborted_tick_cadence(tasks_[phase_order_[position]]);
392 run_task_if_due(tasks_[phase_order_[position]], clock, stats);
403 if (
id >= tasks_.size()) {
404 detail::fail_fast(
"Schedule::task_stats called with an unknown TaskId");
406 return tasks_[id].stats;
409 [[nodiscard]]
auto task_count()
const noexcept -> std::size_t {
410 return tasks_.size();
417 ScheduleTaskFn fn =
nullptr;
418 ScheduleNoThrowTaskFn no_throw_fn =
nullptr;
420 std::uint32_t pending_events = 0;
421 std::uint32_t ticks_until_due = 0;
422 bool run_requested =
false;
423 bool in_progress =
false;
429 ScheduleTaskFn fn, ScheduleNoThrowTaskFn no_throw_fn)
432 detail::fail_fast(
"Schedule::add_task called after seal()");
434 if (fn ==
nullptr && no_throw_fn ==
nullptr) {
435 detail::fail_fast(
"Schedule::add_task received a null callback");
437 if (
static_cast<std::uint8_t
>(desc.phase) >=
438 static_cast<std::uint8_t
>(SimPhase::Count)) {
439 detail::fail_fast(
"Schedule::add_task received an invalid SimPhase");
441 if (
static_cast<std::uint8_t
>(desc.cadence.kind) >
442 static_cast<std::uint8_t
>(CadenceKind::Manual)) {
443 detail::fail_fast(
"Schedule::add_task received an invalid CadenceKind");
445 if (desc.cadence.kind == CadenceKind::EveryN && desc.cadence.every_n == 0) {
447 "Schedule::add_task EveryN cadence requires every_n > 0; use "
448 "Cadence::every_ticks() to normalize input");
450 if (desc.cadence.kind == CadenceKind::Background &&
451 desc.cadence.budget.max_items == 0) {
453 "Schedule::add_task Background cadence requires a nonzero budget; "
454 "use Cadence::background() to normalize input");
456 auto record = TaskRecord{};
460 record.no_throw_fn = no_throw_fn;
461 if (record.desc.cadence.every_n == 0) {
462 record.desc.cadence.every_n = 1;
464 if (record.desc.cadence.budget.max_items == 0) {
465 record.desc.cadence.budget.max_items = 1;
467 if (desc.cadence.kind == CadenceKind::EveryN) {
468 record.ticks_until_due = record.desc.cadence.every_n;
470 tasks_.push_back(record);
471 return static_cast<TaskId
>(tasks_.size() - 1);
474 static void advance_aborted_tick_cadence(TaskRecord& task)
noexcept {
475 if (task.desc.cadence.kind != CadenceKind::EveryN) {
478 if (--task.ticks_until_due == 0) {
479 task.ticks_until_due = task.desc.cadence.every_n;
483 void run_task_if_due(TaskRecord& task,
SimClock clock,
491 auto fired_events = std::uint32_t{0};
492 auto budget = std::uint32_t{0};
493 switch (task.desc.cadence.kind) {
494 case CadenceKind::EveryTick:
497 case CadenceKind::EveryN: {
501 const auto counted = --task.ticks_until_due == 0;
503 task.ticks_until_due = task.desc.cadence.every_n;
505 due = counted || task.run_requested;
508 case CadenceKind::OnDirty:
509 fired_dirty = task.pending_mask & task.desc.cadence.dirty_mask;
510 due =
static_cast<bool>(fired_dirty) || task.run_requested;
512 case CadenceKind::OnEvent:
513 fired_events = task.pending_events & task.desc.cadence.event_mask;
514 due = fired_events != 0 || task.run_requested;
516 case CadenceKind::Background:
517 due = task.in_progress || task.run_requested;
518 budget = task.desc.cadence.budget.max_items;
520 case CadenceKind::Manual:
521 due = task.run_requested;
531 ++stats.tasks_skipped;
532 ++task.stats.skipped;
538 task.pending_mask &= ~fired_dirty;
539 task.pending_events &= ~fired_events;
540#if TESS_HAS_EXCEPTIONS
541 const auto consumed_request = task.run_requested;
543 task.run_requested =
false;
546 context.clock = clock;
547 context.pending_dirty = fired_dirty;
548 context.budget_items = budget;
549 context.pending_events = fired_events;
551#if TESS_DIAGNOSTICS_ENABLED
555#if TESS_HAS_EXCEPTIONS
556 if (task.no_throw_fn !=
nullptr) {
557 result = task.no_throw_fn(task.ctx, context);
560 result = task.fn(task.ctx, context);
565 task.pending_mask |= fired_dirty;
566 task.pending_events |= fired_events;
567 task.run_requested = task.run_requested || consumed_request;
572 if (task.no_throw_fn !=
nullptr) {
573 result = task.no_throw_fn(task.ctx, context);
575 result = task.fn(task.ctx, context);
578 if (task.desc.cadence.kind == CadenceKind::Background &&
579 result.items_done > budget) {
581 "Schedule task reported more background items than offered");
583 if (task.desc.cadence.kind != CadenceKind::Background &&
584 result.items_done != 0) {
586 "Schedule non-background task reported background items");
590 task.desc.cadence.kind == CadenceKind::Background && result.more_work;
591 if (result.dirty_mask) {
595 for (
const auto id : dirty_task_ids_) {
596 tasks_[id].pending_mask |= result.dirty_mask;
598 stats.dirty_mask_produced |= result.dirty_mask;
600 if (result.event_mask != 0) {
601 for (
const auto id : event_task_ids_) {
602 tasks_[id].pending_events |= result.event_mask;
604 stats.event_mask_produced |= result.event_mask;
608 stats.background_items += result.items_done;
610 task.stats.background_items += result.items_done;
611 task.stats.last_run_tick = clock.tick;
614 std::vector<TaskRecord> tasks_;
616 std::vector<TaskId> phase_order_;
617 std::vector<TaskId> dirty_task_ids_;
618 std::vector<TaskId> event_task_ids_;
619 bool sealed_ =
false;
620 bool in_run_ =
false;
Supplies a task with the current tick, trigger bits, and work allowance.
Definition schedule.h:122