tess 0.4.0
Performance-first tile and path simulation substrate
Loading...
Searching...
No Matches
time.h
1#pragma once
2
3#include <algorithm>
4#include <cstddef>
5#include <cstdint>
6#include <limits>
7
8namespace tess {
9
11enum class SimSpeed : std::uint8_t {
12 Paused,
13 Speed1x,
14 Speed2x,
15 Speed4x,
16};
17static_assert(sizeof(SimSpeed) == sizeof(std::uint8_t));
18
21 SimSpeed speed = SimSpeed::Speed1x;
22};
23
24// The authoritative fixed-tick counter every cadence derives from: the
25// schedule and the path-agent tick share this one type (hoisted here so
26// neither layer redefines it).
28struct SimClock {
29 std::uint64_t tick = 0;
30};
31
33inline auto advance_sim_tick(SimClock& clock) noexcept -> std::uint64_t {
34 ++clock.tick;
35 return clock.tick;
36}
37
40 std::size_t ticks = 0;
41 double alpha = 0.0;
42 // Sim-time seconds discarded because the frame hit max_ticks_per_frame
43 // with more than one step of backlog remaining. Nonzero means the
44 // simulation is running behind real time.
45 double dropped_seconds = 0.0;
46};
47
51class FixedStepAccumulator {
52 public:
53 constexpr FixedStepAccumulator(std::uint32_t base_tps,
54 std::size_t max_ticks_per_frame) noexcept
55 : base_tps_(base_tps), max_ticks_per_frame_(max_ticks_per_frame) {}
56
57 [[nodiscard]] constexpr auto base_tps() const noexcept -> std::uint32_t {
58 return base_tps_;
59 }
60
61 [[nodiscard]] constexpr auto max_ticks_per_frame() const noexcept
62 -> std::size_t {
63 return max_ticks_per_frame_;
64 }
65
66 [[nodiscard]] constexpr auto accumulated_seconds() const noexcept -> double {
67 return accumulated_seconds_;
68 }
69
70 constexpr void reset() noexcept { accumulated_seconds_ = 0.0; }
71
72 constexpr auto consume(double real_delta_seconds,
73 SimTimeControl control) noexcept -> FixedStepFrame {
74 if (control.speed == SimSpeed::Paused || base_tps_ == 0 ||
75 max_ticks_per_frame_ == 0) {
76 return FixedStepFrame{0, alpha(), 0.0};
77 }
78
79 // NaN and negative deltas contribute nothing (NaN fails the comparison).
80 if (real_delta_seconds > 0.0) {
81 accumulated_seconds_ +=
82 real_delta_seconds * speed_multiplier(control.speed);
83 }
84
85 const auto step_seconds = 1.0 / static_cast<double>(base_tps_);
86 // Compare availability in the double domain so the size_t cast below is
87 // always in range, even for absurd frame deltas.
88 const auto available = accumulated_seconds_ / step_seconds;
89 std::size_t ticks = 0;
90 if (available >= static_cast<double>(max_ticks_per_frame_)) {
91 ticks = max_ticks_per_frame_;
92 } else if (available >= 1.0) {
93 ticks = static_cast<std::size_t>(available);
94 }
95 accumulated_seconds_ -= static_cast<double>(ticks) * step_seconds;
96 // The rounded division above can round `available` up across an
97 // integer boundary, granting one tick that is not quite fully banked
98 // (a bounded one-tick borrow); the subtraction then leaves the bank
99 // ~1 ulp negative. Clamp so consumers never observe a negative bank.
100 accumulated_seconds_ = std::max(accumulated_seconds_, 0.0);
101
102 // When the tick cap was hit, drop backlog beyond one step instead of
103 // banking it: retained debt would force max-tick catch-up frames (or an
104 // unrecoverable spiral), while one step of carry preserves alpha
105 // continuity. Sim time slows instead; the drop is reported.
106 double dropped_seconds = 0.0;
107 if (ticks == max_ticks_per_frame_ && accumulated_seconds_ > step_seconds) {
108 dropped_seconds = accumulated_seconds_ - step_seconds;
109 accumulated_seconds_ = step_seconds;
110 }
111
112 return FixedStepFrame{ticks, alpha(), dropped_seconds};
113 }
114
115 private:
116 [[nodiscard]] static constexpr auto speed_multiplier(SimSpeed speed) noexcept
117 -> double {
118 switch (speed) {
119 case SimSpeed::Paused:
120 return 0.0;
121 case SimSpeed::Speed1x:
122 return 1.0;
123 case SimSpeed::Speed2x:
124 return 2.0;
125 case SimSpeed::Speed4x:
126 return 4.0;
127 }
128 return 1.0;
129 }
130
131 [[nodiscard]] constexpr auto alpha() const noexcept -> double {
132 if (base_tps_ == 0) {
133 return 0.0;
134 }
135 return std::clamp(accumulated_seconds_ * static_cast<double>(base_tps_),
136 0.0, 1.0);
137 }
138
139 std::uint32_t base_tps_ = 0;
140 std::size_t max_ticks_per_frame_ = 0;
141 double accumulated_seconds_ = 0.0;
142};
143
145[[nodiscard]] constexpr auto sim_speed_multiplier(SimSpeed speed) noexcept
146 -> std::uint32_t {
147 switch (speed) {
148 case SimSpeed::Paused:
149 return 0;
150 case SimSpeed::Speed1x:
151 return 1;
152 case SimSpeed::Speed2x:
153 return 2;
154 case SimSpeed::Speed4x:
155 return 4;
156 }
157 return 1;
158}
159
161[[nodiscard]] constexpr auto effective_tps(std::uint32_t base_tps,
162 SimSpeed speed) noexcept
163 -> std::uint32_t {
164 const auto product = static_cast<std::uint64_t>(base_tps) *
165 static_cast<std::uint64_t>(sim_speed_multiplier(speed));
166 constexpr auto max_tps = std::numeric_limits<std::uint32_t>::max();
167 return product > max_tps ? max_tps : static_cast<std::uint32_t>(product);
168}
169
170} // namespace tess
Reports the fixed-tick grant and interpolation state for one frame.
Definition time.h:39
Stores the authoritative monotonically increasing fixed-tick count.
Definition time.h:28
Supplies the time-control state consumed for one rendered frame.
Definition time.h:20