OXYGENHUB
◆ main openmsx / src / serialize.hh
33.1 KiB · C++ · last changed chore(): initial commit 1 month ago source blame history raw
1
#ifndef SERIALIZE_HH
2
#define SERIALIZE_HH
3
4
#include "SerializeBuffer.hh"
5
#include "XMLElement.hh"
6
#include "XMLOutputStream.hh"
7
#include "serialize_core.hh"
8
9
#include "MemBuffer.hh"
10
#include "StringOp.hh"
11
#include "hash_map.hh"
12
#include "inline.hh"
13
#include "strCat.hh"
14
#include "unreachable.hh"
15
#include "zstring_view.hh"
16
17
#include <zlib.h>
18
19
#include <array>
20
#include <cassert>
21
#include <memory>
22
#include <optional>
23
#include <span>
24
#include <sstream>
25
#include <string>
26
#include <type_traits>
27
#include <typeindex>
28
#include <vector>
29
30
namespace openmsx {
31
32
class LastDeltaBlocks;
33
class DeltaBlock;
34
35
// TODO move somewhere in utils once we use this more often
36
struct HashPair {
37
	template<typename T1, typename T2>
38
	size_t operator()(const std::pair<T1, T2>& p) const {
39
		return 31 * std::hash<T1>()(p.first) +
40
			    std::hash<T2>()(p.second);
41
	}
42
};
43
44
45
template<typename T> struct SerializeClassVersion;
46
47
// In this section, the archive classes are defined.
48
//
49
// Archives can be categorized in two ways:
50
//   - backing stream they use
51
//   - input or output (each backing stream has exactly one input and one
52
//     output variant)
53
//
54
// ATM these backing streams implemented:
55
//   - Mem
56
//      Stores stream in memory. Is meant to be very compact and very fast.
57
//      It does not support versioning (it's not possible to load this stream
58
//      in a newer version of openMSX). It is also not platform independent
59
//      (e.g. integers are stored using native platform endianess).
60
//      The main use case for this archive format is regular in memory
61
//      snapshots, for example to support replay/rewind.
62
//   - XML
63
//      Stores the stream in a XML file. These files are meant to be portable
64
//      to different architectures (e.g. little/big endian, 32/64 bit system).
65
//      There is version information in the stream, so it should be possible
66
//      to load streams created with older openMSX versions a newer openMSX.
67
//      The XML files are meant to be human readable. Having editable XML files
68
//      is not a design goal (e.g. simply changing a value will probably work,
69
//      but swapping the position of two tag or adding or removing tags can
70
//      easily break the stream).
71
//   - Text
72
//      This stores to stream in a flat ascii file (one item per line). This
73
//      format is only written as a proof-of-concept to test the design. It's
74
//      not meant to be used in practice.
75
//
76
// Archive code is heavily templatized. It uses the CRTP (curiously recurring
77
// template pattern ; a base class templatized on it's derived class). To
78
// implement static polymorphism. This means there is practically no run-time
79
// overhead of using this mechanism compared to 6 separately hand coded functions
80
// (Mem/XML/Text x input/output).
81
// TODO At least in theory, still need to verify this in practice.
82
//      Though my experience is that gcc is generally very good in this.
83
84
template<typename Derived> class ArchiveBase
85
{
86
public:
87
	/** Is this archive a loader or a saver.
88
	static constexpr bool IS_LOADER = ...;*/
89
90
	/** Serialize the base class of this classtype.
91
	 * Should preferably be called as the first statement in the
92
	 * implementation of a serialize() method of a class type.
93
	 * See also serializeInlinedBase() below.
94
	 */
95
	template<typename Base, typename T>
96
	void serializeBase(T& t)
97
	{
98
		const char* tag = BaseClassName<Base>::getName();
99
		self().serialize(tag, static_cast<Base&>(t));
100
	}
101
102
	/** Serialize the base class of this classtype.
103
	 * Should preferably be called as the first statement in the
104
	 * implementation of a serialize() method of a class type.
105
	 * See also serializeBase() above.
106
	 *
107
	 * The difference between serializeBase() and serializeInlinedBase()
108
	 * is only relevant for versioned archives (see NEED_VERSION, e.g.
109
	 * XML archives). In XML archives serializeBase() will put the base
110
	 * class in a new subtag, serializeInlinedBase() puts the members
111
	 * of the base class (inline) in the current tag. The advantage
112
	 * of serializeBase() is that the base class can be versioned
113
	 * separately from the subclass. The disadvantage is that it exposes
114
	 * an internal implementation detail in the XML file, and thus makes
115
	 * it harder to for example change the class hierarchy or move
116
	 * members from base to subclass or vice-versa.
117
	 */
118
	template<typename Base, typename T>
119
	void serializeInlinedBase(T& t, unsigned version)
120
	{
121
		::openmsx::serialize(self(), static_cast<Base&>(t), version);
122
	}
123
124
	// Each concrete archive class also has the following methods:
125
	// Because of the implementation with static polymorphism, this
126
	// interface is not explicitly visible in the base class.
127
	//
128
	//
129
	// template<typename T> void serializeWithID(const char* tag, const T& t, ...)
130
	//
131
	//   This is _the_most_important_ method of the serialization
132
	//   framework. Depending on the concrete archive type (loader/saver)
133
	//   this method will load or save the given type 't'. In case of an XML
134
	//   archive the 'tag' parameter will be used as tagname.
135
	//
136
	//   At the end there are still a number of optional parameters (in the
137
	//   current implementation there can be between 0 and 3, but can be
138
	//   extened when needed). These are 'global' constructor parameters,
139
	//   constructor parameters that are not stored in the stream, but that
140
	//   are needed to reconstruct the object (for example can be references
141
	//   to structures that were already stored in the stream). So these
142
	//   parameters are only actually used while loading.
143
	//   TODO document this in more detail in some section where the
144
	//        (polymorphic) constructors are also described.
145
	//
146
	//
147
	// void serialize_blob(const char* tag, std::span<uint8_t> data, bool diff = true)
148
	//
149
	//   Serialize the given data as a binary blob.
150
	//   This cannot be part of the serialize() method above because we
151
	//   cannot know whether a byte-array should be serialized as a blob
152
	//   or as a collection of bytes (IOW we cannot decide it based on the
153
	//   type).
154
155
	template<typename T>
156
	void serialize_blob(const char* tag, std::span<T> data, bool diff = true)
157
	{
158
		self().serialize_blob(tag, std::span<uint8_t>{static_cast<uint8_t*>(data.data()), data.size_bytes()}, diff);
159
	}
160
	template<typename T>
161
	void serialize_blob(const char* tag, std::span<const T> data, bool diff = true)
162
	{
163
		self().serialize_blob(tag, std::span<uint8_t>{static_cast<const uint8_t*>(data.data()), data.size_bytes()}, diff);
164
	}
165
166
	//
167
	//
168
	// template<typename T> void serialize(const char* tag, const T& t)
169
	//
170
	//   This is much like the serializeWithID() method above, but it doesn't
171
	//   store an ID with this element. This means that it's not possible,
172
	//   later on in the stream, to refer to this element. For many elements
173
	//   you know this will not happen. This method results in a slightly
174
	//   more compact stream.
175
	//
176
	//   Note that for primitive types we already don't store an ID, because
177
	//   pointers to primitive types are not supported (at least not ATM).
178
	//
179
	//
180
	// template<typename T, typename ...Args>
181
	// void serialize(const char* tag, const T& t, Args&& ...args)
182
	//
183
	//   Optionally serialize() accepts more than one tag-variable pair.
184
	//   This does conceptually the same as repeated calls to serialize()
185
	//   with each time a single pair, but it might be more efficient. E.g.
186
	//   the MemOutputArchive implementation is more efficient when called
187
	//   with multiple simple types.
188
	//
189
	// template<typename T> void serializePointerID(const char* tag, const T& t)
190
	//
191
	//   Serialize a pointer by storing the ID of the object it points to.
192
	//   This only works if the object was already serialized. The only
193
	//   reason to use this method instead of the more general serialize()
194
	//   method is that this one does not instantiate the object
195
	//   construction code. (So in some cases you can avoid having to
196
	//   provide specializations of SerializeConstructorArgs.)
197
	//
198
	//
199
	// template<typename T> void serializePolymorphic(const char* tag, const T& t)
200
	//
201
	//   Serialize a value-type whose concrete type is not yet known at
202
	//   compile-time (polymorphic pointers are already handled by the
203
	//   generic serialize() method).
204
	//
205
	//   The difference between pointer and value-types is that for
206
	//   pointers, the de-serialize code also needs to construct the
207
	//   object, while for value-types, the object (with the correct
208
	//   concrete type) is already constructed, it only needs to be
209
	//   initialized.
210
	//
211
	//
212
	// bool versionAtLeast(unsigned actual, unsigned required) const
213
	// bool versionBelow  (unsigned actual, unsigned required) const
214
	//
215
	//   Checks whether the actual version is respective 'bigger or equal'
216
	//   or 'strictly lower' than the required version. So in fact these are
217
	//   equivalent to respectively:
218
	//       return actual >= required;
219
	//       return actual <  required;
220
	//   Note that these two methods are the exact opposite of each other.
221
	//   Though for memory-archives and output-archives we know that the
222
	//   actual version is always equal to the latest class version and the
223
	//   required version can never be bigger than this latest version, so
224
	//   in these cases the methods can be optimized to respectively:
225
	//       return true;
226
	//       return false;
227
	//   By using these methods instead of direct comparisons, the compiler
228
	//   is able to perform more dead-code-elimination.
229
230
/*internal*/
231
	// These must be public for technical reasons, but they should only
232
	// be used by the serialization framework.
233
234
	/** Does this archive store version information. */
235
	static constexpr bool NEED_VERSION = true;
236
237
	/** Is this a reverse-snapshot? */
238
	[[nodiscard]] bool isReverseSnapshot() const { return false; }
239
240
	/** Does this archive store enums as strings.
241
	 * See also struct serialize_as_enum.
242
	 */
243
	static constexpr bool TRANSLATE_ENUM_TO_STRING = false;
244
245
	/** Load/store an attribute from/in the archive.
246
	 * Depending on the underlying concrete stream, attributes are either
247
	 * stored like XML attributes or as regular values. Because of this
248
	 * (and thus unlike XML attributes) the order of attributes matter. It
249
	 * also matters whether an attribute is present or not.
250
	 */
251
	template<typename T> void attribute(const char* name, T& t)
252
	{
253
		self().serialize(name, t);
254
	}
255
	void attribute(const char* name, const char* value);
256
257
	/** Some archives (like XML archives) can store optional attributes.
258
	 * This method indicates whether that's the case or not.
259
	 * This can be used to for example in XML files don't store attributes
260
	 * with default values (thus to make the XML look prettier).
261
	 */
262
	static constexpr bool CAN_HAVE_OPTIONAL_ATTRIBUTES = false;
263
264
	/** Check the presence of a (optional) attribute.
265
	 * It's only allowed to call this method on archives that can have
266
	 * optional attributes.
267
	 */
268
	[[nodiscard]] bool hasAttribute(const char* /*name*/) const
269
	{
270
		UNREACHABLE;
271
	}
272
273
	/** Optimization: combination of hasAttribute() and getAttribute().
274
	 * Returns true if hasAttribute() and (if so) also fills in the value.
275
	 */
276
	template<typename T>
277
	[[nodiscard]] std::optional<T> findAttributeAs(const char* /*name*/)
278
	{
279
		UNREACHABLE;
280
	}
281
282
	/** Some archives (like XML archives) can count the number of subtags
283
	 * that belong to the current tag. This method indicates whether that's
284
	 * the case for this archive or not.
285
	 * This can for example be used to make the XML files look prettier in
286
	 * case of serialization of collections: in that case we don't need to
287
	 * explicitly store the size of the collection, it can be derived from
288
	 * the number of subtags.
289
	 */
290
	static constexpr bool CAN_COUNT_CHILDREN = false;
291
292
	/** Count the number of child tags.
293
	 * It's only allowed to call this method on archives that have support
294
	 * for this operation.
295
	 */
296
	[[nodiscard]] int countChildren() const
297
	{
298
		UNREACHABLE;
299
	}
300
301
	/** Indicate begin of a tag.
302
	 * Only XML archives use this, other archives ignore it.
303
	 * XML saver uses it as a name for the current tag, it doesn't
304
	 * interpret the name in any way.
305
	 * XML loader uses it only as a check: it checks whether the current
306
	 * tag name matches the given name. So we will NOT search the tag
307
	 * with the given name, the tags have to be in the correct order.
308
	 */
309
	void beginTag(const char* /*tag*/) const
310
	{
311
		// nothing
312
	}
313
	/** Indicate begin of a tag.
314
	 * Only XML archives use this, other archives ignore it.
315
	 * Both XML loader and saver only use the given tag name to do some
316
	 * internal checks (with checks disabled, the tag parameter has no
317
	 * influence at all on loading or saving of the stream).
318
	 */
319
	void endTag(const char* /*tag*/) const
320
	{
321
		// nothing
322
	}
323
324
	// These (internal) methods should be implemented in the concrete
325
	// archive classes.
326
	//
327
	// template<typename T> void save(const T& t)
328
	//
329
	//   Should only be implemented for OuputArchives. Is called to
330
	//   store primitive types in the stream. In the end all structures
331
	//   are broken down to primitive types, so all data that ends up
332
	//   in the stream passes via this method (ok, depending on how
333
	//   attribute() and serialize_blob() is implemented, that data may
334
	//   not pass via save()).
335
	//
336
	//   Often this method will be overloaded to handle certain types in a
337
	//   specific way.
338
	//
339
	//
340
	// template<typename T> void load(T& t)
341
	//
342
	//   Should only be implemented for InputArchives. This is similar (but
343
	//   opposite) to the save() method above. Loading of primitive types
344
	//   is done via this method.
345
346
	// void beginSection()
347
	// void endSection()
348
	// void skipSection(bool skip)
349
	//   The methods beginSection() and endSection() can only be used in
350
	//   output archives. These mark the location of a section that can
351
	//   later be skipped during loading.
352
	//   The method skipSection() can only be used in input archives. It
353
	//   optionally skips a section that was marked during saving.
354
	//   For every beginSection() call in the output, there must be a
355
	//   corresponding skipSection() call in the input (even if you don't
356
	//   actually want to skip the section).
357
358
protected:
359
	/** Returns a reference to the most derived class.
360
	 * Helper function to implement static polymorphism.
361
	 */
362
	Derived& self()
363
	{
364
		return static_cast<Derived&>(*this);
365
	}
366
};
367
368
// The part of OutputArchiveBase that doesn't depend on the template parameter
369
class OutputArchiveBase2
370
{
371
public:
372
	static constexpr bool IS_LOADER = false;
373
	[[nodiscard]] bool versionAtLeast(unsigned /*actual*/, unsigned /*required*/) const
374
	{
375
		return true;
376
	}
377
	[[nodiscard]] bool versionBelow(unsigned /*actual*/, unsigned /*required*/) const
378
	{
379
		return false;
380
	}
381
382
	void skipSection(bool /*skip*/) const
383
	{
384
		UNREACHABLE;
385
	}
386
387
/*internal*/
388
	#ifdef linux
389
	// This routine is not portable, for example it breaks in
390
	// windows (mingw) because there the location of the stack
391
	// is _below_ the heap.
392
	// But this is anyway only used to check assertions. So for now
393
	// only do that in linux.
394
	[[nodiscard]] static NEVER_INLINE bool addressOnStack(const void* p)
395
	{
396
		// This is not portable, it assumes:
397
		//  - stack grows downwards
398
		//  - heap is at lower address than stack
399
		// Also in c++ comparison between pointers is only defined when
400
		// the two pointers point to objects in the same array.
401
		int dummy;
402
		return &dummy < p;
403
	}
404
	#endif
405
406
	// Generate a new ID for the given pointer and store this association
407
	// for later (see getId()).
408
	template<typename T> [[nodiscard]] unsigned generateId(const T* p)
409
	{
410
		// For composed structures, for example
411
		//   struct A { ... };
412
		//   struct B { A a; ... };
413
		// The pointer to the outer and inner structure can be the
414
		// same while we still want a different ID to refer to these
415
		// two. That's why we use a std::pair<void*, type_index> as key
416
		// in the map.
417
		// For polymorphic types you do sometimes use a base pointer
418
		// to refer to a subtype. So there we only use the pointer
419
		// value as key in the map.
420
		if constexpr (std::is_polymorphic_v<T>) {
421
			return generateID1(p);
422
		} else {
423
			return generateID2(p, typeid(T));
424
		}
425
	}
426
427
	template<typename T> [[nodiscard]] unsigned getId(const T* p)
428
	{
429
		if constexpr (std::is_polymorphic_v<T>) {
430
			return getID1(p);
431
		} else {
432
			return getID2(p, typeid(T));
433
		}
434
	}
435
436
protected:
437
	OutputArchiveBase2() = default;
438
439
private:
440
	[[nodiscard]] unsigned generateID1(const void* p);
441
	[[nodiscard]] unsigned generateID2(const void* p, const std::type_info& typeInfo);
442
	[[nodiscard]] unsigned getID1(const void* p);
443
	[[nodiscard]] unsigned getID2(const void* p, const std::type_info& typeInfo);
444
445
private:
446
	hash_map<std::pair<const void*, std::type_index>, unsigned, HashPair> idMap;
447
	hash_map<const void*, unsigned> polyIdMap;
448
	unsigned lastId = 0;
449
};
450
451
template<typename Derived>
452
class OutputArchiveBase : public ArchiveBase<Derived>, public OutputArchiveBase2
453
{
454
public:
455
	template<typename Base, typename T>
456
	void serializeInlinedBase(T& t, unsigned version)
457
	{
458
		// same implementation as base class, but with extra check
459
		static_assert(SerializeClassVersion<Base>::value ==
460
		              SerializeClassVersion<T   >::value,
461
		              "base and derived must have same version when "
462
		              "using serializeInlinedBase()");
463
		ArchiveBase<Derived>::template serializeInlinedBase<Base>(t, version);
464
	}
465
	// Main saver method. Heavy lifting is done in the Saver class.
466
	// The 'global constructor arguments' parameters are ignored because
467
	// the saver archives also completely ignore those extra parameters.
468
	// But we need to provide them because the same (templatized) code path
469
	// is used both for saving and loading.
470
	template<typename T, typename... Args>
471
	void serializeWithID(const char* tag, const T& t, Args... /*globalConstrArgs*/)
472
	{
473
		this->self().beginTag(tag);
474
		Saver<T> saver;
475
		saver(this->self(), t, true);
476
		this->self().endTag(tag);
477
	}
478
479
	template<typename T> void serialize(const char* tag, const T& t)
480
	{
481
		this->self().beginTag(tag);
482
		Saver<T> saver;
483
		saver(this->self(), t, false);
484
		this->self().endTag(tag);
485
	}
486
	template<typename T> void serializePointerID(const char* tag, const T& t)
487
	{
488
		this->self().beginTag(tag);
489
		IDSaver<T> saver;
490
		saver(this->self(), t);
491
		this->self().endTag(tag);
492
	}
493
	template<typename T> void serializePolymorphic(const char* tag, const T& t)
494
	{
495
		static_assert(std::is_polymorphic_v<T>,
496
		              "must be a polymorphic type");
497
		PolymorphicSaverRegistry<Derived>::save(tag, this->self(), t);
498
	}
499
	template<typename T> void serializeOnlyOnce(const char* tag, const T& t)
500
	{
501
		if (!getId(&t)) {
502
			serializeWithID(tag, t);
503
		}
504
	}
505
506
	// You shouldn't use this, it only exists for backwards compatibility
507
	void serializeChar(const char* tag, char c)
508
	{
509
		this->self().beginTag(tag);
510
		this->self().saveChar(c);
511
		this->self().endTag(tag);
512
	}
513
514
protected:
515
	OutputArchiveBase() = default;
516
};
517
518
519
// Part of InputArchiveBase that doesn't depend on the template parameter
520
class InputArchiveBase2
521
{
522
public:
523
	static constexpr bool IS_LOADER = true;
524
525
	void beginSection() const
526
	{
527
		UNREACHABLE;
528
	}
529
	void endSection() const
530
	{
531
		UNREACHABLE;
532
	}
533
534
/*internal*/
535
	[[nodiscard]] void* getPointer(unsigned id);
536
	void addPointer(unsigned id, const void* p);
537
	[[nodiscard]] unsigned getId(const void* p) const;
538
539
	template<typename T> void resetSharedPtr(std::shared_ptr<T>& s, T* r)
540
	{
541
		if (!r) {
542
			s.reset();
543
			return;
544
		}
545
		auto it = sharedPtrMap.find(r);
546
		if (it == end(sharedPtrMap)) {
547
			s.reset(r);
548
			sharedPtrMap.emplace_noDuplicateCheck(r, s);
549
		} else {
550
			s = std::static_pointer_cast<T>(it->second);
551
		}
552
	}
553
554
protected:
555
	InputArchiveBase2() = default;
556
557
private:
558
	hash_map<unsigned, void*> idMap;
559
	hash_map<void*, std::shared_ptr<void>> sharedPtrMap;
560
};
561
562
template<typename Derived>
563
class InputArchiveBase : public ArchiveBase<Derived>, public InputArchiveBase2
564
{
565
public:
566
	template<typename T, typename... Args>
567
	void serializeWithID(const char* tag, T& t, Args... args)
568
	{
569
		doSerialize(tag, t, std::tuple<Args...>(args...));
570
	}
571
572
	template<typename T>
573
	void serialize(const char* tag, T& t)
574
	{
575
		this->self().beginTag(tag);
576
		using TNC = std::remove_const_t<T>;
577
		auto& tnc = const_cast<TNC&>(t);
578
		Loader<TNC> loader;
579
		loader(this->self(), tnc, std::tuple<>(), -1); // don't load id
580
		this->self().endTag(tag);
581
	}
582
	template<typename T> void serializePointerID(const char* tag, const T& t)
583
	{
584
		this->self().beginTag(tag);
585
		using TNC = std::remove_const_t<T>;
586
		auto& tnc = const_cast<TNC&>(t);
587
		IDLoader<TNC> loader;
588
		loader(this->self(), tnc);
589
		this->self().endTag(tag);
590
	}
591
	template<typename T> void serializePolymorphic(const char* tag, T& t)
592
	{
593
		static_assert(std::is_polymorphic_v<T>,
594
		              "must be a polymorphic type");
595
		PolymorphicInitializerRegistry<Derived>::init(tag, this->self(), &t);
596
	}
597
	template<typename T> void serializeOnlyOnce(const char* tag, const T& t)
598
	{
599
		if (!getId(&t)) {
600
			serializeWithID(tag, t);
601
		}
602
	}
603
604
	// You shouldn't use this, it only exists for backwards compatibility
605
	void serializeChar(const char* tag, char& c)
606
	{
607
		this->self().beginTag(tag);
608
		this->self().loadChar(c);
609
		this->self().endTag(tag);
610
	}
611
612
/*internal*/
613
	// Actual loader method. Heavy lifting is done in the Loader class.
614
	template<typename T, typename TUPLE>
615
	void doSerialize(const char* tag, T& t, TUPLE args, int id = 0)
616
	{
617
		this->self().beginTag(tag);
618
		using TNC = std::remove_const_t<T>;
619
		auto& tnc = const_cast<TNC&>(t);
620
		Loader<TNC> loader;
621
		loader(this->self(), tnc, args, id);
622
		this->self().endTag(tag);
623
	}
624
625
protected:
626
	InputArchiveBase() = default;
627
};
628
629
630
// Enumerate all types which can be serialized using a simple memcpy. This
631
// trait can be used by MemOutputArchive to apply certain optimizations.
632
template<typename T> struct SerializeAsMemcpy : std::false_type {};
633
template<> struct SerializeAsMemcpy<         bool     > : std::true_type {};
634
template<> struct SerializeAsMemcpy<         char     > : std::true_type {};
635
template<> struct SerializeAsMemcpy<  signed char     > : std::true_type {};
636
template<> struct SerializeAsMemcpy<unsigned char     > : std::true_type {};
637
template<> struct SerializeAsMemcpy<         short    > : std::true_type {};
638
template<> struct SerializeAsMemcpy<unsigned short    > : std::true_type {};
639
template<> struct SerializeAsMemcpy<         int      > : std::true_type {};
640
template<> struct SerializeAsMemcpy<unsigned int      > : std::true_type {};
641
template<> struct SerializeAsMemcpy<         long     > : std::true_type {};
642
template<> struct SerializeAsMemcpy<unsigned long     > : std::true_type {};
643
template<> struct SerializeAsMemcpy<         long long> : std::true_type {};
644
template<> struct SerializeAsMemcpy<unsigned long long> : std::true_type {};
645
template<> struct SerializeAsMemcpy<         float    > : std::true_type {};
646
template<> struct SerializeAsMemcpy<         double   > : std::true_type {};
647
template<> struct SerializeAsMemcpy<    long double   > : std::true_type {};
648
template<typename T, size_t N> struct SerializeAsMemcpy<std::array<T, N>> : SerializeAsMemcpy<T> {};
649
650
class MemOutputArchive final : public OutputArchiveBase<MemOutputArchive>
651
{
652
public:
653
	MemOutputArchive(LastDeltaBlocks& lastDeltaBlocks_,
654
	                 std::vector<std::shared_ptr<DeltaBlock>>& deltaBlocks_,
655
			 bool reverseSnapshot_)
656
		: lastDeltaBlocks(lastDeltaBlocks_)
657
		, deltaBlocks(deltaBlocks_)
658
		, reverseSnapshot(reverseSnapshot_)
659
	{
660
	}
661
662
	~MemOutputArchive()
663
	{
664
		assert(openSections.empty());
665
	}
666
667
	static constexpr bool NEED_VERSION = false;
668
	[[nodiscard]] bool isReverseSnapshot() const { return reverseSnapshot; }
669
670
	template<typename T> void save(const T& t)
671
	{
672
		buffer.insert(&t, sizeof(t));
673
	}
674
	void saveChar(char c)
675
	{
676
		save(c);
677
	}
678
	void save(const std::string& s) { save(std::string_view(s)); }
679
	void save(zstring_view s) { save(std::string_view(s)); }
680
	void save(std::string_view s);
681
	void serialize_blob(const char* tag, std::span<const uint8_t> data,
682
	                    bool diff = true);
683
684
	using OutputArchiveBase<MemOutputArchive>::serialize;
685
	template<typename T, typename ...Args>
686
	ALWAYS_INLINE void serialize(const char* tag, const T& t, Args&& ...args)
687
	{
688
		// - Walk over all elements. Process non-memcpy-able elements at
689
		//   once. Collect memcpy-able elements in a tuple. At the end
690
		//   process the collected tuple with a single call.
691
		// - Only do this when there are at least two pairs (it is
692
		//   correct for a single pair, but it's less tuned for that
693
		//   case).
694
		serialize_group(std::tuple<>(), tag, t, std::forward<Args>(args)...);
695
	}
696
	template<typename T, size_t N>
697
	ALWAYS_INLINE void serialize(const char* /*tag*/, const std::array<T, N>& t)
698
		requires(SerializeAsMemcpy<T>::value)
699
	{
700
		buffer.insert(t.data(), N * sizeof(T));
701
	}
702
703
	void beginSection()
704
	{
705
		size_t skip = 0; // filled in later
706
		save(skip);
707
		size_t beginPos = buffer.getPosition();
708
		openSections.push_back(beginPos);
709
	}
710
	void endSection()
711
	{
712
		assert(!openSections.empty());
713
		size_t endPos   = buffer.getPosition();
714
		size_t beginPos = openSections.back();
715
		openSections.pop_back();
716
		size_t skip = endPos - beginPos;
717
		buffer.insertAt(beginPos - sizeof(skip),
718
		                &skip, sizeof(skip));
719
	}
720
721
	[[nodiscard]] MemBuffer<uint8_t> releaseBuffer() &&
722
	{
723
		return std::move(buffer).release();
724
	}
725
726
private:
727
	ALWAYS_INLINE void serialize_group(const std::tuple<>& /*tuple*/) const
728
	{
729
		// done categorizing, there were no memcpy-able elements
730
	}
731
	template<typename ...Args>
732
	ALWAYS_INLINE void serialize_group(const std::tuple<Args...>& tuple)
733
	{
734
		// done categorizing, process all memcpy-able elements
735
		buffer.insert_tuple_ptr(tuple);
736
	}
737
	template<typename TUPLE, typename T, typename ...Args>
738
	ALWAYS_INLINE void serialize_group(const TUPLE& tuple, const char* tag, const T& t, Args&& ...args)
739
	{
740
		// categorize one element
741
		if constexpr (SerializeAsMemcpy<T>::value) {
742
			// add to the group and continue categorizing
743
			(void)tag;
744
			serialize_group(std::tuple_cat(tuple, std::tuple(&t)), std::forward<Args>(args)...);
745
		} else {
746
			serialize(tag, t);      // process single (ungroupable) element
747
			serialize_group(tuple, std::forward<Args>(args)...); // continue categorizing
748
		}
749
	}
750
751
private:
752
	OutputBuffer buffer;
753
	std::vector<size_t> openSections;
754
	LastDeltaBlocks& lastDeltaBlocks;
755
	std::vector<std::shared_ptr<DeltaBlock>>& deltaBlocks;
756
	const bool reverseSnapshot;
757
};
758
759
class MemInputArchive final : public InputArchiveBase<MemInputArchive>
760
{
761
public:
762
	MemInputArchive(std::span<const uint8_t> buf_,
763
	                std::span<const std::shared_ptr<DeltaBlock>> deltaBlocks_)
764
		: buffer(buf_)
765
		, deltaBlocks(deltaBlocks_)
766
	{
767
	}
768
769
	static constexpr bool NEED_VERSION = false;
770
	[[nodiscard]] bool versionAtLeast(unsigned /*actual*/, unsigned /*required*/) const
771
	{
772
		return true;
773
	}
774
	[[nodiscard]] bool versionBelow(unsigned /*actual*/, unsigned /*required*/) const
775
	{
776
		return false;
777
	}
778
779
	template<typename T> void load(T& t)
780
	{
781
		buffer.read(&t, sizeof(t));
782
	}
783
	void loadChar(char& c)
784
	{
785
		load(c);
786
	}
787
	void load(std::string& s);
788
	[[nodiscard]] std::string_view loadStr();
789
	void serialize_blob(const char* tag, std::span<uint8_t> data,
790
	                    bool diff = true);
791
792
	using InputArchiveBase<MemInputArchive>::serialize;
793
	template<typename T, typename ...Args>
794
	ALWAYS_INLINE void serialize(const char* tag, T& t, Args&& ...args)
795
	{
796
		// see comments in MemOutputArchive
797
		serialize_group(std::tuple<>(), tag, t, std::forward<Args>(args)...);
798
	}
799
800
	template<typename T, size_t N>
801
	ALWAYS_INLINE void serialize(const char* /*tag*/, std::array<T, N>& t)
802
		requires(SerializeAsMemcpy<T>::value)
803
	{
804
		buffer.read(t.data(), N * sizeof(T));
805
	}
806
807
	void skipSection(bool skip)
808
	{
809
		size_t num;
810
		load(num);
811
		if (skip) {
812
			buffer.skip(num);
813
		}
814
	}
815
816
private:
817
	// See comments in MemOutputArchive
818
	template<typename TUPLE>
819
	ALWAYS_INLINE void serialize_group(const TUPLE& tuple)
820
	{
821
		auto read = [&](auto* p) { buffer.read(p, sizeof(*p)); };
822
		std::apply([&](auto&&... args) { (read(args), ...); }, tuple);
823
	}
824
	template<typename TUPLE, typename T, typename ...Args>
825
	ALWAYS_INLINE void serialize_group(const TUPLE& tuple, const char* tag, T& t, Args&& ...args)
826
	{
827
		if constexpr (SerializeAsMemcpy<T>::value) {
828
			(void)tag;
829
			serialize_group(std::tuple_cat(tuple, std::tuple(&t)), std::forward<Args>(args)...);
830
		} else {
831
			serialize(tag, t);
832
			serialize_group(tuple, std::forward<Args>(args)...);
833
		}
834
	}
835
836
private:
837
	InputBuffer buffer;
838
	std::span<const std::shared_ptr<DeltaBlock>> deltaBlocks;
839
};
840
841
////
842
843
class XmlOutputArchive final : public OutputArchiveBase<XmlOutputArchive>
844
{
845
public:
846
	explicit XmlOutputArchive(zstring_view filename);
847
	void close();
848
	~XmlOutputArchive();
849
850
	template<typename T> void saveImpl(const T& t)
851
	{
852
		// TODO make sure floating point is printed with enough digits
853
		//      maybe print as hex?
854
		save(std::string_view(tmpStrCat(t)));
855
	}
856
	template<typename T> void save(const T& t)
857
	{
858
		saveImpl(t);
859
	}
860
	void saveChar(char c);
861
	void save(std::string_view str);
862
	void save(bool b);
863
	void save(unsigned char b);
864
	void save(signed char c);
865
	void save(char c);
866
	void save(int i);                  // these 3 are not strictly needed
867
	void save(unsigned u);             // but having them non-inline
868
	void save(unsigned long long ull); // saves quite a bit of code
869
870
	void beginSection() const { /*nothing*/ }
871
	void endSection()   const { /*nothing*/ }
872
873
	// workaround(?) for visual studio 2015:
874
	//   put the default here instead of in the base class
875
	using OutputArchiveBase<XmlOutputArchive>::serialize;
876
	template<typename T, typename ...Args>
877
	ALWAYS_INLINE void serialize(const char* tag, const T& t, Args&& ...args)
878
	{
879
		// by default just repeatedly call the single-pair serialize() variant
880
		this->self().serialize(tag, t);
881
		this->self().serialize(std::forward<Args>(args)...);
882
	}
883
884
	void serialize_blob(const char* tag, std::span<const uint8_t> data,
885
	                    bool diff = true);
886
887
	auto& getXMLOutputStream() { return writer; }
888
889
//internal:
890
	static constexpr bool TRANSLATE_ENUM_TO_STRING = true;
891
	static constexpr bool CAN_HAVE_OPTIONAL_ATTRIBUTES = true;
892
	static constexpr bool CAN_COUNT_CHILDREN = true;
893
894
	void beginTag(const char* tag);
895
	void endTag(const char* tag);
896
897
	template<typename T> void attributeImpl(const char* name, const T& t)
898
	{
899
		attribute(name, std::string_view(tmpStrCat(t)));
900
	}
901
	template<typename T> void attribute(const char* name, const T& t)
902
	{
903
		attributeImpl(name, t);
904
	}
905
	void attribute(const char* name, std::string_view str);
906
	void attribute(const char* name, int i);
907
	void attribute(const char* name, unsigned u);
908
909
//internal: // called from XMLOutputStream
910
	void write(std::span<const char> buf);
911
	void write1(char c);
912
	void check(bool condition) const;
913
	[[noreturn]] void error();
914
915
private:
916
	zstring_view filename;
917
	gzFile file = nullptr;
918
	XMLOutputStream<XmlOutputArchive> writer;
919
};
920
921
class XmlInputArchive final : public InputArchiveBase<XmlInputArchive>
922
{
923
public:
924
	explicit XmlInputArchive(zstring_view filename);
925
926
	[[nodiscard]] bool versionAtLeast(unsigned actual, unsigned required) const
927
	{
928
		return actual >= required;
929
	}
930
	[[nodiscard]] bool versionBelow(unsigned actual, unsigned required) const
931
	{
932
		return actual < required;
933
	}
934
935
	void loadChar(char& c) const;
936
	void load(bool& b) const;
937
	void load(unsigned char& b) const;
938
	void load(signed char& c) const;
939
	void load(char& c) const;
940
	void load(int& i) const;                  // these 3 are not strictly needed
941
	void load(unsigned& u) const;             // but having them non-inline
942
	void load(unsigned long long& ull) const; // saves quite a bit of code
943
	void load(std::string& t) const;
944
	template<typename T> void load(T& t) const
945
	{
946
		std::string str;
947
		load(str);
948
		std::istringstream is(str);
949
		is >> t;
950
	}
951
	[[nodiscard]] std::string_view loadStr() const;
952
953
	void serialize_blob(const char* tag, std::span<uint8_t> data,
954
	                    bool diff = true);
955
956
	void skipSection(bool /*skip*/) const { /*nothing*/ }
957
958
	// workaround(?) for visual studio 2015:
959
	//   put the default here instead of in the base class
960
	using InputArchiveBase<XmlInputArchive>::serialize;
961
	template<typename T, typename ...Args>
962
	ALWAYS_INLINE void serialize(const char* tag, T& t, Args&& ...args)
963
	{
964
		// by default just repeatedly call the single-pair serialize() variant
965
		this->self().serialize(tag, t);
966
		this->self().serialize(std::forward<Args>(args)...);
967
	}
968
969
	[[nodiscard]] XMLElement* currentElement() const {
970
		return elems.back().first;
971
	}
972
973
//internal:
974
	static constexpr bool TRANSLATE_ENUM_TO_STRING = true;
975
	static constexpr bool CAN_HAVE_OPTIONAL_ATTRIBUTES = true;
976
	static constexpr bool CAN_COUNT_CHILDREN = true;
977
978
	void beginTag(const char* tag);
979
	void endTag(const char* tag);
980
981
	template<typename T> void attributeImpl(const char* name, T& t) const
982
	{
983
		std::string str;
984
		attribute(name, str);
985
		std::istringstream is(str);
986
		is >> t;
987
	}
988
	template<typename T> void attribute(const char* name, T& t) const
989
	{
990
		attributeImpl(name, t);
991
	}
992
	void attribute(const char* name, std::string& t) const;
993
	void attribute(const char* name, int& i) const;
994
	void attribute(const char* name, unsigned& u) const;
995
996
	[[nodiscard]] bool hasAttribute(const char* name) const;
997
	[[nodiscard]] int countChildren() const;
998
999
	template<typename T>
1000
	std::optional<T> findAttributeAs(const char* name)
1001
	{
1002
		if (const auto* attr = currentElement()->findAttribute(name)) {
1003
			return StringOp::stringTo<T>(attr->getValue());
1004
		}
1005
		return {};
1006
	}
1007
1008
private:
1009
	XMLDocument xmlDoc{16384}; // tweak: initial allocator buffer size
1010
	std::vector<std::pair<XMLElement*, XMLElement*>> elems;
1011
};
1012
1013
#define INSTANTIATE_SERIALIZE_METHODS(CLASS) \
1014
template void CLASS::serialize(MemInputArchive&,   unsigned); \
1015
template void CLASS::serialize(MemOutputArchive&,  unsigned); \
1016
template void CLASS::serialize(XmlInputArchive&,   unsigned); \
1017
template void CLASS::serialize(XmlOutputArchive&,  unsigned);
1018
1019
} // namespace openmsx
1020
1021
#endif