OXYGENHUB
◆ main openmsx / src / DynamicClock.hh
6.5 KiB · C++ · last changed chore(): initial commit 1 month ago source blame history raw
1
#ifndef DYNAMICCLOCK_HH
2
#define DYNAMICCLOCK_HH
3
4
#include "EmuTime.hh"
5
6
#include "DivModBySame.hh"
7
#include "narrow.hh"
8
9
#include <cassert>
10
11
namespace openmsx {
12
13
/** Represents a clock with a variable frequency.
14
  * The frequency is in Hertz, so every tick is 1/frequency second.
15
  * A clock has a current time, which can be increased by
16
  * an integer number of ticks.
17
  */
18
class DynamicClock
19
{
20
public:
21
	// Note: default copy constructor and assignment operator are ok.
22
23
	/** Create a new clock, which starts ticking at given time.
24
	  * The initial frequency is infinite;
25
	  * in other words, the clock stands still.
26
	  */
27
	explicit DynamicClock(EmuTime time) : lastTick(time) {}
28
29
	/** Create a new clock, which starts ticking at given time with
30
	  * given frequency.
31
	  */
32
	DynamicClock(EmuTime time, unsigned freq)
33
		: lastTick(time)
34
	{
35
		setFreq(freq);
36
	}
37
38
	/** Gets the time at which the last clock tick occurred.
39
	  */
40
	[[nodiscard]] EmuTime getTime() const {
41
		return lastTick;
42
	}
43
44
	/** Checks whether this clock's last tick is or is not before the
45
	  * given time stamp.
46
	  */
47
	[[nodiscard]] bool before(EmuTime e) const {
48
		return lastTick.time < e.time;
49
	}
50
51
	/** Calculate the number of ticks for this clock until the given time.
52
	  * It is not allowed to call this method for a time in the past.
53
	  */
54
	[[nodiscard]] unsigned getTicksTill(EmuTime e) const {
55
		assert(e.time >= lastTick.time);
56
		return divMod.div(e.time - lastTick.time);
57
	}
58
	/** Like getTicksTill(), but also returns the fractional part (in range [0, 1)).
59
	  * This is equivalent to, but numerically more stable than:
60
	  *   EmuDuration dur = e - this->getTime();
61
	  *   double d = dur / this->getPeriod();
62
	  *   int i = int(d);
63
	  *   double frac = d - i;
64
	  *   return {i, frac};
65
	  */
66
	struct IntegralFractional {
67
		unsigned integral;
68
		float fractional;
69
	};
70
	[[nodiscard]] IntegralFractional getTicksTillAsIntFloat(EmuTime e) const {
71
		assert(e.time >= lastTick.time);
72
		auto dur = e.time - lastTick.time;
73
		auto [q, r] = divMod.divMod(dur);
74
		auto f = float(r) / float(getStep());
75
		assert(0.0f <= f); assert(f < 1.0f);
76
		return {.integral = q, .fractional = f};
77
	}
78
79
	template<typename FIXED>
80
	void getTicksTill(EmuTime e, FIXED& result) const {
81
		assert(e.time >= lastTick.time);
82
		uint64_t tmp = (e.time - lastTick.time) << FIXED::FRACTION_BITS;
83
		result = FIXED::create(divMod.div(tmp + (getStep() / 2)));
84
	}
85
86
	/** Calculate the number of ticks this clock has to tick to reach
87
	  * or go past the given time.
88
	  * It is not allowed to call this method for a time in the past.
89
	  */
90
	[[nodiscard]] unsigned getTicksTillUp(EmuTime e) const {
91
		assert(e.time >= lastTick.time);
92
		return divMod.div(e.time - lastTick.time + (getStep() - 1));
93
	}
94
95
	[[nodiscard]] double getTicksTillDouble(EmuTime e) const {
96
		assert(e.time >= lastTick.time);
97
		return double(e.time - lastTick.time) / getStep();
98
	}
99
100
	[[nodiscard]] uint64_t getTotalTicks() const {
101
		// note: don't use divMod.div() because that one only returns a
102
		//       32 bit result. Maybe improve in the future.
103
		return lastTick.time / getStep();
104
	}
105
106
	/** Change the frequency at which this clock ticks.
107
	  * When possible prefer setPeriod() over this method because it may
108
	  * introduce less rounding errors.
109
	  * @param freq New frequency in Hertz.
110
	  */
111
	void setFreq(unsigned freq) {
112
		unsigned newStep = (MAIN_FREQ32 + (freq / 2)) / freq;
113
		setPeriod(EmuDuration(uint64_t(newStep)));
114
	}
115
	/** Equivalent to setFreq(freq_num / freq_denom), but possibly with
116
	  * less rounding errors.
117
	  * When possible prefer setPeriod() over this method because it may
118
	  * introduce less rounding errors.
119
	  */
120
	void setFreq(unsigned freq_num, unsigned freq_denom) {
121
		static_assert(MAIN_FREQ < (1ULL << 32), "must fit in 32 bit");
122
		uint64_t p = MAIN_FREQ * freq_denom + (freq_num / 2);
123
		uint64_t newStep = p / freq_num;
124
		setPeriod(EmuDuration(newStep));
125
	}
126
127
	/** Returns the frequency (in Hz) at which this clock ticks.
128
	  * @see setFreq()
129
	  */
130
	[[nodiscard]] unsigned getFreq() const {
131
		auto step = getStep();
132
		return narrow<unsigned>((MAIN_FREQ + (step / 2)) / step);
133
	}
134
135
	/** Returns the length of one clock-cycle.
136
	  */
137
	[[nodiscard]] EmuDuration getPeriod() const {
138
		return EmuDuration(uint64_t(getStep()));
139
	}
140
141
	/** Set the duration of a clock tick. See also setFreq(). */
142
	void setPeriod(EmuDuration period) {
143
		assert(period.toUint64() < (1ULL << 32));
144
		divMod.setDivisor(uint32_t(period.toUint64()));
145
	}
146
147
	/** Reset the clock to start ticking at the given time.
148
	  */
149
	void reset(EmuTime e) {
150
		lastTick.time = e.time;
151
	}
152
153
	/** Advance this clock in time until the last tick which is not past
154
	  * the given time.
155
	  * It is not allowed to advance a clock to a time in the past.
156
	  */
157
	void advance(EmuTime e) {
158
		assert(lastTick.time <= e.time);
159
		lastTick.time = e.time - divMod.mod(e.time - lastTick.time);
160
	}
161
162
	/** Advance this clock by the given number of ticks.
163
	  */
164
	void operator+=(uint64_t n) {
165
		lastTick.time += n * getStep();
166
	}
167
168
	/** Calculate the time at which this clock will have ticked the given
169
	  * number of times (counted from its last tick).
170
	  */
171
	[[nodiscard]] friend EmuTime operator+(const DynamicClock& c, uint64_t n) {
172
		return EmuTime::fromUint64(c.lastTick.toUint64() + n * c.getStep());
173
	}
174
175
	/** Advance this clock by the given number of ticks.
176
	  * This method is similar to operator+=, but it's optimized for
177
	  * speed. OTOH the amount of ticks should not be too large,
178
	  * otherwise an overflow occurs. Use operator+() when the duration
179
	  * of the ticks approaches 1 second.
180
	  */
181
	void fastAdd(unsigned n) {
182
		#ifdef DEBUG
183
		// we don't even want this overhead in development versions
184
		assert((uint64_t(n) * getStep()) < (1ULL << 32));
185
		#endif
186
		lastTick.time += n * getStep();
187
	}
188
	[[nodiscard]] EmuTime getFastAdd(unsigned n) const {
189
		return add(lastTick, n);
190
	}
191
	[[nodiscard]] EmuTime add(EmuTime time, unsigned n) const {
192
		#ifdef DEBUG
193
		assert((uint64_t(n) * getStep()) < (1ULL << 32));
194
		#endif
195
		return EmuTime(time.time + n * getStep());
196
	}
197
198
	template<typename Archive>
199
	void serialize(Archive& ar, unsigned version);
200
201
private:
202
	/** Length of a this clock's ticks, expressed in master clock ticks.
203
	  *   changed uint64_t -> unsigned for performance reasons
204
	  *   this is _heavily_ used in the CPU code
205
	  * We used to store this as a member, but DivModBySame also stores
206
	  * it, so we can as well get it from there (getter is inlined).
207
	  */
208
	[[nodiscard]] unsigned getStep() const { return divMod.getDivisor(); }
209
210
private:
211
	/** Time of this clock's last tick.
212
	  */
213
	EmuTime lastTick;
214
215
	DivModBySame divMod;
216
};
217
SERIALIZE_CLASS_VERSION(DynamicClock, 2);
218
219
} // namespace openmsx
220
221
#endif