tess
0.4.0
Performance-first tile and path simulation substrate
Toggle main menu visibility
Loading...
Searching...
No Matches
time.h
1
#pragma once
2
3
#include <algorithm>
4
#include <cstddef>
5
#include <cstdint>
6
#include <limits>
7
8
namespace
tess {
9
11
enum class
SimSpeed : std::uint8_t {
12
Paused,
13
Speed1x,
14
Speed2x,
15
Speed4x,
16
};
17
static_assert
(
sizeof
(SimSpeed) ==
sizeof
(std::uint8_t));
18
20
struct
SimTimeControl
{
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).
28
struct
SimClock
{
29
std::uint64_t tick = 0;
30
};
31
33
inline
auto
advance_sim_tick(
SimClock
& clock)
noexcept
-> std::uint64_t {
34
++clock.tick;
35
return
clock.tick;
36
}
37
39
struct
FixedStepFrame
{
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
51
class
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
tess::FixedStepFrame
Reports the fixed-tick grant and interpolation state for one frame.
Definition
time.h:39
tess::SimClock
Stores the authoritative monotonically increasing fixed-tick count.
Definition
time.h:28
tess::SimTimeControl
Supplies the time-control state consumed for one rendered frame.
Definition
time.h:20
include
tess
sim
time.h
Generated by
1.17.0