55 CadenceKind kind = CadenceKind::EveryTick;
56 std::uint32_t every_n = 1;
57 std::uint32_t dirty_mask = 0;
60 [[nodiscard]]
static constexpr auto every_tick()
noexcept ->
Cadence {
64 [[nodiscard]]
static constexpr auto every_ticks(std::uint32_t n)
noexcept
66 return Cadence{CadenceKind::EveryN, n == 0 ? 1u : n, 0, {}};
69 [[nodiscard]]
static constexpr auto on_dirty(std::uint32_t mask)
noexcept
71 return Cadence{CadenceKind::OnDirty, 1, mask, {}};
74 [[nodiscard]]
static constexpr auto background(
77 CadenceKind::Background, 1, 0,
81 [[nodiscard]]
static constexpr auto manual()
noexcept ->
Cadence {
82 return Cadence{CadenceKind::Manual, 1, 0, {}};
168 using TaskId = std::uint32_t;
172 void reserve_tasks(std::size_t count) {
173 tasks_.reserve(count);
174 phase_order_.reserve(count);
175 dirty_task_ids_.reserve(count);
180 TESS_ASSERT(!sealed_);
181 TESS_ASSERT(fn !=
nullptr);
182 TESS_ASSERT(desc.phase != SimPhase::Count);
187 TESS_ASSERT(desc.cadence.kind != CadenceKind::EveryN ||
188 desc.cadence.every_n != 0);
189 TESS_ASSERT(desc.cadence.kind != CadenceKind::Background ||
190 desc.cadence.budget.max_items != 0);
191 auto record = TaskRecord{};
195 if (record.desc.cadence.every_n == 0) {
196 record.desc.cadence.every_n = 1;
198 if (record.desc.cadence.budget.max_items == 0) {
199 record.desc.cadence.budget.max_items = 1;
201 if (desc.cadence.kind == CadenceKind::EveryN) {
202 record.ticks_until_due = record.desc.cadence.every_n;
204 tasks_.push_back(record);
205 return static_cast<TaskId
>(tasks_.size() - 1);
210 template <
typename T>
213 desc,
static_cast<void*
>(&task),
235 phase_order_.clear();
236 dirty_task_ids_.clear();
237 for (std::uint8_t phase = 0;
238 phase < static_cast<std::uint8_t>(SimPhase::Count); ++phase) {
239 for (std::size_t i = 0; i < tasks_.size(); ++i) {
240 if (
static_cast<std::uint8_t
>(tasks_[i].desc.phase) == phase) {
241 phase_order_.push_back(
static_cast<TaskId
>(i));
245 for (std::size_t i = 0; i < tasks_.size(); ++i) {
246 if (tasks_[i].desc.cadence.kind == CadenceKind::OnDirty) {
247 dirty_task_ids_.push_back(
static_cast<TaskId
>(i));
253 [[nodiscard]]
auto sealed()
const noexcept ->
bool {
return sealed_; }
255 void set_enabled(TaskId
id,
bool enabled)
noexcept {
256 TESS_ASSERT(
id < tasks_.size());
257 if (
id < tasks_.size()) {
258 tasks_[id].enabled = enabled;
266 void request_run(TaskId
id)
noexcept {
267 TESS_ASSERT(
id < tasks_.size());
268 if (
id < tasks_.size()) {
269 tasks_[id].run_requested =
true;
277 void notify_dirty(std::uint32_t mask)
noexcept {
279 for (
const auto id : dirty_task_ids_) {
280 tasks_[id].pending_mask |= mask;
284 for (
auto& task : tasks_) {
285 task.pending_mask |= mask;
290 TESS_ASSERT(sealed_);
291 TESS_ASSERT(!in_run_);
297 ~InRunGuard() { flag =
false; }
300 const InRunGuard guard{in_run_};
302 stats.tick = advance_sim_tick(clock);
304 for (
const auto id : phase_order_) {
305 run_task_if_due(tasks_[
id], clock, stats);
311 TESS_ASSERT(
id < tasks_.size());
312 if (
id >= tasks_.size()) {
315 return tasks_[id].stats;
318 [[nodiscard]]
auto task_count()
const noexcept -> std::size_t {
319 return tasks_.size();
326 ScheduleTaskFn fn =
nullptr;
327 std::uint32_t pending_mask = 0;
328 std::uint32_t ticks_until_due = 0;
329 bool run_requested =
false;
330 bool in_progress =
false;
335 void run_task_if_due(TaskRecord& task,
SimClock clock,
342 auto fired_dirty = std::uint32_t{0};
343 auto budget = std::uint32_t{0};
344 switch (task.desc.cadence.kind) {
345 case CadenceKind::EveryTick:
348 case CadenceKind::EveryN: {
352 const auto counted = --task.ticks_until_due == 0;
354 task.ticks_until_due = task.desc.cadence.every_n;
356 due = counted || task.run_requested;
359 case CadenceKind::OnDirty:
360 fired_dirty = task.pending_mask & task.desc.cadence.dirty_mask;
361 due = fired_dirty != 0 || task.run_requested;
363 case CadenceKind::Background:
364 due = task.in_progress || task.run_requested;
365 budget = task.desc.cadence.budget.max_items;
367 case CadenceKind::Manual:
368 due = task.run_requested;
378 ++stats.tasks_skipped;
379 ++task.stats.skipped;
385 task.pending_mask &= ~fired_dirty;
386 task.run_requested =
false;
389 context.clock = clock;
390 context.pending_dirty = fired_dirty;
391 context.budget_items = budget;
392 const auto result = task.fn(task.ctx, context);
393 TESS_ASSERT(task.desc.cadence.kind != CadenceKind::Background ||
394 result.items_done <= budget);
397 task.desc.cadence.kind == CadenceKind::Background && result.more_work;
398 if (result.dirty_mask != 0) {
402 for (
const auto id : dirty_task_ids_) {
403 tasks_[id].pending_mask |= result.dirty_mask;
405 stats.dirty_mask_produced |= result.dirty_mask;
409 stats.background_items += result.items_done;
411 task.stats.background_items += result.items_done;
412 task.stats.last_run_tick = clock.tick;
415 std::vector<TaskRecord> tasks_;
418 std::vector<TaskId> phase_order_;
419 std::vector<TaskId> dirty_task_ids_;
420 bool sealed_ =
false;
421 bool in_run_ =
false;
Supplies a task with the current tick, trigger bits, and work allowance.
Definition schedule.h:107