OXYGENHUB
◆ main openmsx / src / input / StateChange.hh
12.3 KiB · C++ · last changed chore(): initial commit 1 month ago source blame history raw
1
#ifndef STATECHANGE_HH
2
#define STATECHANGE_HH
3
4
#include "EmuTime.hh"
5
#include "TclObject.hh"
6
7
#include "dynarray.hh"
8
#include "serialize_meta.hh"
9
10
#include <variant>
11
#include <vector>
12
13
namespace openmsx {
14
15
/** Base class for all external MSX state changing events.
16
 * These are typically triggered by user input, like keyboard presses. The main
17
 * reason why these events exist is to be able to record and replay them.
18
 */
19
class StateChangeBase
20
{
21
public:
22
	[[nodiscard]] EmuTime getTime() const { return time; }
23
24
	template<typename Archive>
25
	void serialize(Archive& ar, unsigned /*version*/)
26
	{
27
		ar.serialize("time", time);
28
	}
29
30
protected:
31
	StateChangeBase() = default; // for serialize
32
	explicit StateChangeBase(EmuTime time_)
33
		: time(time_) {}
34
35
private:
36
	EmuTime time = EmuTime::zero();
37
};
38
REGISTER_BASE_NAME_HELPER(StateChangeBase, "StateChange");
39
40
41
class EndLogEvent final : public StateChangeBase
42
{
43
public:
44
	EndLogEvent() = default; // for serialize
45
	explicit EndLogEvent(EmuTime time_)
46
		: StateChangeBase(time_) {}
47
48
	template<typename Archive> void serialize(Archive& ar, unsigned /*version*/)
49
	{
50
		ar.template serializeBase<StateChangeBase>(*this);
51
	}
52
};
53
54
55
class KeyMatrixState final : public StateChangeBase
56
{
57
public:
58
	KeyMatrixState() = default; // for serialize
59
	KeyMatrixState(EmuTime time_, uint8_t row_, uint8_t press_, uint8_t release_)
60
		: StateChangeBase(time_)
61
		, row(row_), press(press_), release(release_)
62
	{
63
		// disallow useless events
64
		assert((press != 0) || (release != 0));
65
		// avoid confusion about what happens when some bits are both
66
		// set and reset (in other words: don't rely on order of and-
67
		// and or-operations)
68
		assert((press & release) == 0);
69
	}
70
71
	[[nodiscard]] auto getRow()     const { return row; }
72
	[[nodiscard]] auto getPress()   const { return press; }
73
	[[nodiscard]] auto getRelease() const { return release; }
74
75
	template<typename Archive> void serialize(Archive& ar, unsigned /*version*/)
76
	{
77
		ar.template serializeBase<StateChangeBase>(*this);
78
		ar.serialize("row",     row,
79
		             "press",   press,
80
		             "release", release);
81
	}
82
private:
83
	uint8_t row, press, release;
84
};
85
86
87
class ArkanoidState final : public StateChangeBase
88
{
89
public:
90
	ArkanoidState() = default; // for serialize
91
	ArkanoidState(EmuTime time_, int delta_, bool press_, bool release_)
92
		: StateChangeBase(time_)
93
		, delta(delta_), press(press_), release(release_) {}
94
95
	[[nodiscard]] auto getDelta()   const { return delta; }
96
	[[nodiscard]] auto getPress()   const { return press; }
97
	[[nodiscard]] auto getRelease() const { return release; }
98
99
	template<typename Archive> void serialize(Archive& ar, unsigned /*version*/)
100
	{
101
		ar.template serializeBase<StateChangeBase>(*this);
102
		ar.serialize("delta",   delta,
103
		             "press",   press,
104
		             "release", release);
105
	}
106
private:
107
	int delta;
108
	bool press, release;
109
};
110
111
112
/** This class is used to for Tcl commands that directly influence the MSX
113
  * state (e.g. plug, disk<x>, cassetteplayer, reset). It's passed via an
114
  * event because the recording needs to see these.
115
  */
116
class MSXCommandEvent final : public StateChangeBase
117
{
118
public:
119
	MSXCommandEvent() = default; // for serialize
120
121
	MSXCommandEvent(EmuTime time_, std::span<const TclObject> tokens_)
122
		: StateChangeBase(time_)
123
		, tokens(dynarray<TclObject>::construct_from_range_tag{}, tokens_)
124
	{
125
	}
126
127
	[[nodiscard]] const auto& getTokens() const { return tokens; }
128
129
	template<typename Archive>
130
	void serialize(Archive& ar, unsigned /*version*/)
131
	{
132
		ar.template serializeBase<StateChangeBase>(*this);
133
134
		// serialize vector<TclObject> as vector<string>
135
		std::vector<std::string> str;
136
		if constexpr (!Archive::IS_LOADER) {
137
			str = to_vector(std::views::transform(
138
				tokens, [](auto& t) { return std::string(t.getString()); }));
139
		}
140
		ar.serialize("tokens", str);
141
		if constexpr (Archive::IS_LOADER) {
142
			assert(tokens.empty());
143
			tokens = dynarray<TclObject>(dynarray<TclObject>::construct_from_range_tag{},
144
				std::views::transform(str, [](const auto& s) { return TclObject(s); }));
145
		}
146
	}
147
148
private:
149
	dynarray<TclObject> tokens;
150
};
151
152
153
class TrackballState final : public StateChangeBase
154
{
155
public:
156
	TrackballState() = default; // for serialize
157
	TrackballState(EmuTime time_, int deltaX_, int deltaY_,
158
	               uint8_t press_, uint8_t release_)
159
		: StateChangeBase(time_)
160
		, deltaX(deltaX_), deltaY(deltaY_)
161
		, press(press_), release(release_) {}
162
163
	[[nodiscard]] auto getDeltaX()  const { return deltaX; }
164
	[[nodiscard]] auto getDeltaY()  const { return deltaY; }
165
	[[nodiscard]] auto getPress()   const { return press; }
166
	[[nodiscard]] auto getRelease() const { return release; }
167
168
	template<typename Archive> void serialize(Archive& ar, unsigned /*version*/)
169
	{
170
		ar.template serializeBase<StateChangeBase>(*this);
171
		ar.serialize("deltaX",  deltaX,
172
		             "deltaY",  deltaY,
173
		             "press",   press,
174
		             "release", release);
175
	}
176
private:
177
	int deltaX, deltaY;
178
	uint8_t press, release;
179
};
180
181
182
enum class AutofireID : uint8_t { RENSHATURBO, UNKNOWN };
183
inline std::string_view nameForId(AutofireID id)
184
{
185
	switch (id) {
186
		case AutofireID::RENSHATURBO: return "renshaturbo";
187
		default: return "unknown-autofire";
188
	}
189
}
190
class AutofireStateChange final : public StateChangeBase
191
{
192
public:
193
	AutofireStateChange() = default; // for serialize
194
	AutofireStateChange(EmuTime time_, AutofireID id_, int value_)
195
		: StateChangeBase(time_)
196
		, id(id_), value(value_) {}
197
198
	[[nodiscard]] auto getId()    const { return id; }
199
	[[nodiscard]] auto getValue() const { return value; }
200
201
	template<typename Archive> void serialize(Archive& ar, unsigned /*version*/)
202
	{
203
		ar.template serializeBase<StateChangeBase>(*this);
204
		// for backwards compatibility serialize 'id' as 'name'
205
		std::string name = Archive::IS_LOADER ? "" : std::string(nameForId(id));
206
		ar.serialize("name",  name,
207
		             "value", value);
208
		if constexpr (Archive::IS_LOADER) {
209
			using enum AutofireID;
210
			id = (name == nameForId(RENSHATURBO)) ? RENSHATURBO : UNKNOWN;
211
		}
212
	}
213
private:
214
	AutofireID id;
215
	int value;
216
};
217
218
219
class MSXJoyState final : public StateChangeBase
220
{
221
public:
222
	MSXJoyState() = default; // for serialize
223
	MSXJoyState(EmuTime time_, uint8_t id_,
224
	            uint8_t press_, uint8_t release_)
225
		: StateChangeBase(time_), id(id_)
226
		, press(press_), release(release_) {}
227
228
	[[nodiscard]] auto getId()      const { return id; }
229
	[[nodiscard]] auto getPress()   const { return press; }
230
	[[nodiscard]] auto getRelease() const { return release; }
231
232
	template<typename Archive> void serialize(Archive& ar, unsigned /*version*/) {
233
		ar.template serializeBase<StateChangeBase>(*this);
234
		ar.serialize("id",      id,
235
		             "press",   press,
236
		             "release", release);
237
	}
238
private:
239
	uint8_t id, press, release;
240
};
241
242
243
class JoyMegaState final : public StateChangeBase
244
{
245
public:
246
	JoyMegaState() = default; // for serialize
247
	JoyMegaState(EmuTime time_, uint8_t id_,
248
	             unsigned press_, unsigned release_)
249
		: StateChangeBase(time_)
250
		, press(press_), release(release_), id(id_) {}
251
252
	[[nodiscard]] auto getId()       const { return id; }
253
	[[nodiscard]] auto getPress()    const { return press; }
254
	[[nodiscard]] auto getRelease()  const { return release; }
255
256
	template<typename Archive> void serialize(Archive& ar, unsigned /*version*/)
257
	{
258
		ar.template serializeBase<StateChangeBase>(*this);
259
		ar.serialize("id",      id,
260
		             "press",   press,
261
		             "release", release);
262
	}
263
private:
264
	unsigned press, release;
265
	uint8_t id;
266
};
267
268
269
class JoyHandleState final : public StateChangeBase
270
{
271
public:
272
	JoyHandleState() = default; // for serialize
273
	JoyHandleState(EmuTime time_, uint8_t id_,
274
	               uint8_t press_, uint8_t release_, float analog_)
275
		: StateChangeBase(time_), id(id_)
276
		, press(press_), release(release_), analog(analog_) {}
277
278
	[[nodiscard]] auto getId()      const { return id; }
279
	[[nodiscard]] auto getPress()   const { return press; }
280
	[[nodiscard]] auto getRelease() const { return release; }
281
	[[nodiscard]] auto getAnalog()  const { return analog; }
282
283
	template<typename Archive> void serialize(Archive& ar, unsigned /*version*/) {
284
		ar.template serializeBase<StateChangeBase>(*this);
285
		ar.serialize("id",      id,
286
		             "press",   press,
287
		             "release", release,
288
		             "analog",  analog);
289
	}
290
private:
291
	uint8_t id, press, release;
292
	float analog;
293
};
294
295
296
class PaddleState final : public StateChangeBase
297
{
298
public:
299
	PaddleState() = default; // for serialize
300
	PaddleState(EmuTime time_, int delta_)
301
		: StateChangeBase(time_), delta(delta_) {}
302
303
	[[nodiscard]] auto getDelta() const { return delta; }
304
305
	template<typename Archive> void serialize(Archive& ar, unsigned /*version*/)
306
	{
307
		ar.template serializeBase<StateChangeBase>(*this);
308
		ar.serialize("delta", delta);
309
	}
310
private:
311
	int delta;
312
};
313
314
315
class TouchpadState final : public StateChangeBase
316
{
317
public:
318
	TouchpadState() = default; // for serialize
319
	TouchpadState(EmuTime time_,
320
	              uint8_t x_, uint8_t y_, bool touch_, bool button_)
321
		: StateChangeBase(time_)
322
		, x(x_), y(y_), touch(touch_), button(button_) {}
323
324
	[[nodiscard]] auto getX()      const { return x; }
325
	[[nodiscard]] auto getY()      const { return y; }
326
	[[nodiscard]] auto getTouch()  const { return touch; }
327
	[[nodiscard]] auto getButton() const { return button; }
328
329
	template<typename Archive> void serialize(Archive& ar, unsigned /*version*/)
330
	{
331
		ar.template serializeBase<StateChangeBase>(*this);
332
		ar.serialize("x",      x,
333
		             "y",      y,
334
		             "touch",  touch,
335
		             "button", button);
336
	}
337
private:
338
	uint8_t x, y;
339
	bool touch, button;
340
};
341
342
343
class MouseState final : public StateChangeBase
344
{
345
public:
346
	MouseState() = default; // for serialize
347
	MouseState(EmuTime time_, int deltaX_, int deltaY_,
348
	           uint8_t press_, uint8_t release_)
349
		: StateChangeBase(time_)
350
		, deltaX(deltaX_), deltaY(deltaY_)
351
		, press(press_), release(release_) {}
352
353
	[[nodiscard]] auto getDeltaX()  const { return deltaX; }
354
	[[nodiscard]] auto getDeltaY()  const { return deltaY; }
355
	[[nodiscard]] auto getPress()   const { return press; }
356
	[[nodiscard]] auto getRelease() const { return release; }
357
358
	template<typename Archive> void serialize(Archive& ar, unsigned version)
359
	{
360
		ar.template serializeBase<StateChangeBase>(*this);
361
		ar.serialize("deltaX",  deltaX,
362
		             "deltaY",  deltaY,
363
		             "press",   press,
364
		             "release", release);
365
		if (ar.versionBelow(version, 2)) {
366
			assert(Archive::IS_LOADER);
367
			// Old versions stored host (=unscaled) mouse movement
368
			// in the replay-event-log. Apply the (old) algorithm
369
			// to scale host to msx mouse movement.
370
			// In principle the code snippet below does:
371
			//    delta{X,Y} /= SCALE
372
			// except that it doesn't accumulate rounding errors
373
			static constexpr int SCALE = 2;
374
			int oldMsxX = absHostX / SCALE;
375
			int oldMsxY = absHostY / SCALE;
376
			absHostX += deltaX;
377
			absHostY += deltaY;
378
			int newMsxX = absHostX / SCALE;
379
			int newMsxY = absHostY / SCALE;
380
			deltaX = newMsxX - oldMsxX;
381
			deltaY = newMsxY - oldMsxY;
382
		}
383
	}
384
private:
385
	int deltaX, deltaY; // msx mouse movement
386
	uint8_t press, release;
387
public:
388
	inline static int absHostX = 0, absHostY = 0; // (only) for old savestates
389
};
390
SERIALIZE_CLASS_VERSION(MouseState, 2);
391
392
393
using StateChange = std::variant<
394
	EndLogEvent,
395
	KeyMatrixState,
396
	ArkanoidState,
397
	MSXCommandEvent,
398
	TrackballState,
399
	AutofireStateChange,
400
	MSXJoyState,
401
	PaddleState,
402
	TouchpadState,
403
	MouseState,
404
	JoyMegaState,
405
	JoyHandleState
406
>;
407
408
inline auto getTime(const StateChange& event)
409
{
410
	return std::visit(
411
		[](const StateChangeBase& e) { return e.getTime(); },
412
		event);
413
}
414
415
416
template<> struct Serializer<StateChange> : VariantSerializer<StateChange> {
417
	static constexpr auto stateChangeInfo = std::to_array<enum_string<size_t>>({
418
		{"EndLog",              index<EndLogEvent>        },
419
		{"KeyMatrixState",      index<KeyMatrixState>     },
420
		{"ArkanoidState",       index<ArkanoidState>      },
421
		{"MSXCommandEvent",     index<MSXCommandEvent>    },
422
		{"TrackballState",      index<TrackballState>     },
423
		{"AutofireStateChange", index<AutofireStateChange>},
424
		{"MSXJoyState",         index<MSXJoyState>        },
425
		{"PaddleState",         index<PaddleState>        },
426
		{"TouchpadState",       index<TouchpadState>      },
427
		{"MouseState",          index<MouseState>         },
428
		{"JoyMegaState",        index<JoyMegaState>       },
429
		{"JoyHandleState",      index<JoyHandleState>     }
430
	});
431
	static constexpr std::span<const enum_string<size_t>> info() {
432
		return stateChangeInfo;
433
	}
434
};
435
436
} // namespace openmsx
437
438
#endif