OXYGENHUB
◆ main openmsx / src / serialize_core.hh
25.3 KiB · C++ · last changed chore(): initial commit 1 month ago source blame history raw
1
#ifndef SERIALIZE_CORE_HH
2
#define SERIALIZE_CORE_HH
3
4
#include "serialize_constr.hh"
5
#include "serialize_meta.hh"
6
7
#include "one_of.hh"
8
#include "unreachable.hh"
9
#include "xrange.hh"
10
11
#include <array>
12
#include <cassert>
13
#include <memory>
14
#include <span>
15
#include <string>
16
#include <type_traits>
17
#include <variant>
18
19
namespace openmsx {
20
21
// Is there a specialized way to serialize 'T'.
22
template<typename T> struct Serializer : std::false_type {
23
	struct Saver {};
24
	struct Loader {};
25
};
26
27
// Type-queries for serialization framework
28
// is_primitive<T>
29
template<typename T> struct is_primitive           : std::false_type {};
30
template<> struct is_primitive<bool>               : std::true_type {};
31
template<> struct is_primitive<char>               : std::true_type {};
32
template<> struct is_primitive<signed char>        : std::true_type {};
33
template<> struct is_primitive<signed short>       : std::true_type {};
34
template<> struct is_primitive<signed int>         : std::true_type {};
35
template<> struct is_primitive<signed long>        : std::true_type {};
36
template<> struct is_primitive<unsigned char>      : std::true_type {};
37
template<> struct is_primitive<unsigned short>     : std::true_type {};
38
template<> struct is_primitive<unsigned int>       : std::true_type {};
39
template<> struct is_primitive<unsigned long>      : std::true_type {};
40
template<> struct is_primitive<float>              : std::true_type {};
41
template<> struct is_primitive<double>             : std::true_type {};
42
template<> struct is_primitive<long double>        : std::true_type {};
43
template<> struct is_primitive<long long>          : std::true_type {};
44
template<> struct is_primitive<unsigned long long> : std::true_type {};
45
template<> struct is_primitive<std::string>        : std::true_type {};
46
47
48
// Normally to make a class serializable, you have to implement a serialize()
49
// method on the class. For some classes we cannot extend the source code. So
50
// we need an alternative, non-intrusive, way to make those classes
51
// serializable.
52
template<typename Archive, typename T>
53
void serialize(Archive& ar, T& t, unsigned version)
54
{
55
	// By default use the serialize() member. But this function can
56
	// be overloaded to serialize classes in a non-intrusive way.
57
	t.serialize(ar, version);
58
}
59
60
template<typename Archive, typename T1, typename T2>
61
void serialize(Archive& ar, std::pair<T1, T2>& p, unsigned /*version*/)
62
{
63
	ar.serialize("first",  p.first,
64
	             "second", p.second);
65
}
66
template<typename T1, typename T2> struct SerializeClassVersion<std::pair<T1, T2>>
67
{
68
	static constexpr unsigned value = 0;
69
};
70
71
///////////
72
73
/** serialize_as_enum<T>
74
 *
75
 * For serialization of enums to work you have to specialize the
76
 * serialize_as_enum struct for that specific enum. This has a double purpose:
77
 *  - let the framework know this type should be treated as an enum
78
 *  - define a mapping between enum values and string representations
79
 *
80
 * The serialize_as_enum class has the following members:
81
 *  - static bool value
82
 *      True iff this type must be serialized as an enum.
83
 *      The fields below are only valid (even only present) if this variable
84
 *      is true.
85
 *  - static constexpr std::span<const enum_string<T>> info()
86
 *      Returns a view over the enum-to-string mapping table.
87
 *
88
 * If the enum has all consecutive values, starting from zero (as is the case
89
 * if you don't explicitly mention the numeric values in the enum definition),
90
 * you can use the SERIALIZE_ENUM macro as a convenient way to define a
91
 * specialization of serialize_as_enum:
92
 *
93
 * example:
94
 *    enum MyEnum { FOO, BAR };
95
 *    static constexpr auto myEnumInfo = std::to_array<enum_string<MyEnum>>({
96
 *          { "FOO", FOO },
97
 *          { "BAR", BAR },
98
 *    });
99
 *    SERIALIZE_ENUM(MyEnum, myEnumInfo);
100
 *
101
 * Note: when an enum definition evolves it has impact on the serialization,
102
 *   more specific on de-serialization of older version of the enum: adding
103
 *   values or changing the numerical value is no problem. But be careful with
104
 *   removing a value or changing the string representation.
105
 */
106
107
template<typename T> struct serialize_as_enum : std::false_type {};
108
109
template<typename T> struct enum_string {
110
	std::string_view str;
111
	T e;
112
};
113
[[noreturn]] void enumError(std::string_view str);
114
115
template<typename T>
116
inline std::string_view toString(std::span<const enum_string<T>> list, T t_)
117
{
118
	for (const auto& [str, t] : list) {
119
		if (t == t_) return str;
120
	}
121
	assert(false);
122
	return "internal-error-unknown-enum-value";
123
}
124
125
template<typename T>
126
T fromString(std::span<const enum_string<T>> list, std::string_view str_)
127
{
128
	for (const auto& [str, t] : list) {
129
		if (str == str_) return t;
130
	}
131
	enumError(str_); // does not return (throws)
132
	return T(); // avoid warning
133
}
134
135
#define SERIALIZE_ENUM(TYPE, INFO) \
136
template<> struct serialize_as_enum<TYPE> : std::true_type { \
137
	static constexpr std::span<const enum_string<TYPE>> info() { \
138
		return INFO; \
139
	} \
140
};
141
142
template<typename Archive, typename T, typename SaveAction>
143
void saveEnum(std::span<const enum_string<T>> list, T t, SaveAction save)
144
{
145
	if constexpr (Archive::TRANSLATE_ENUM_TO_STRING) {
146
		save(toString(list, t));
147
	} else {
148
		save(int(t));
149
	}
150
}
151
152
template<typename Archive, typename T, typename LoadAction>
153
void loadEnum(std::span<const enum_string<T>> list, T& t, LoadAction load)
154
{
155
	if constexpr (Archive::TRANSLATE_ENUM_TO_STRING) {
156
		std::string str;
157
		load(str);
158
		t = fromString(list, str);
159
	} else {
160
		int i;
161
		load(i);
162
		t = T(i);
163
	}
164
}
165
166
///////////
167
168
template<size_t I, typename Variant>
169
struct DefaultConstructVariant {
170
	Variant operator()(size_t index) const {
171
		if constexpr (I == std::variant_size_v<Variant>) {
172
			UNREACHABLE;
173
		} else if (index == I) {
174
			return std::variant_alternative_t<I, Variant>{};
175
		} else {
176
			return DefaultConstructVariant<I + 1, Variant>{}(index);
177
		}
178
	}
179
};
180
template<typename Variant>
181
Variant defaultConstructVariant(size_t index)
182
{
183
	return DefaultConstructVariant<0, Variant>{}(index);
184
}
185
186
template<typename V> struct VariantSerializer : std::true_type
187
{
188
	template<typename A>
189
	static constexpr size_t index = get_index<A, V>::value;
190
191
	struct Saver {
192
		template<typename Archive>
193
		void operator()(Archive& ar, const V& v, bool saveId) const {
194
			saveEnum<Archive>(Serializer<V>::info(), v.index(),
195
				[&](const auto& t) { ar.attribute("type", t); });
196
			std::visit([&]<typename T>(T& e) {
197
				using TNC = std::remove_cvref_t<T>;
198
				auto& e2 = const_cast<TNC&>(e);
199
				ClassSaver<TNC> saver;
200
				saver(ar, e2, saveId);
201
			}, v);
202
		}
203
	};
204
	struct Loader {
205
		template<typename Archive, typename TUPLE>
206
		void operator()(Archive& ar, V& v, TUPLE args, int id) const {
207
			size_t idx;
208
			loadEnum<Archive>(Serializer<V>::info(), idx,
209
				[&](auto& l) { ar.attribute("type", l); });
210
			v = defaultConstructVariant<V>(idx);
211
			std::visit([&]<typename T>(T& e) {
212
				ClassLoader<T> loader;
213
				loader(ar, e, args, id);
214
			}, v);
215
		}
216
	};
217
};
218
219
220
221
/////////////
222
223
// serialize_as_pointer<T>
224
//
225
// Type-trait class that indicates whether a certain type should be serialized
226
// as a pointer or not. There can be multiple pointers to the same object,
227
// only the first such pointer is actually stored, the others are stored as
228
// a reference to this first object.
229
//
230
// By default all pointer types are treated as pointer, but also smart pointer
231
// can be treated as such. Though only unique_ptr<T> is implemented ATM.
232
//
233
// The serialize_as_pointer class has the following members:
234
//  - static bool value
235
//      True iff this type must be serialized as a pointer.
236
//      The fields below are only valid (even only present) if this variable
237
//      is true.
238
//  - using type = T
239
//      The pointed-to type
240
//  - T* getPointer(X x)
241
//      Get an actual pointer from the abstract pointer x
242
//      (X can be a smart pointer type)
243
//  - void setPointer(X x, T* p, Archive& ar)
244
//      Copy the raw-pointer p to the abstract pointer x
245
//      The archive can be used to store per-archive data, this is for example
246
//      needed for shared_ptr.
247
248
template<typename T> struct serialize_as_pointer : std::false_type {};
249
template<typename T> struct serialize_as_pointer_impl : std::true_type
250
{
251
	// pointer to primitive types not supported
252
	static_assert(!is_primitive<T>::value,
253
	              "can't serialize ptr to primitive type");
254
	using type = T;
255
};
256
template<typename T> struct serialize_as_pointer<T*>
257
	: serialize_as_pointer_impl<T>
258
{
259
	static T* getPointer(T* t) { return t; }
260
	template<typename Archive>
261
	static void setPointer(T*& t, T* p, Archive& /*ar*/) {
262
		t = p;
263
	}
264
};
265
template<typename T> struct serialize_as_pointer<std::unique_ptr<T>>
266
	: serialize_as_pointer_impl<T>
267
{
268
	static T* getPointer(const std::unique_ptr<T>& t) { return t.get(); }
269
	template<typename Archive>
270
	static void setPointer(std::unique_ptr<T>& t, T* p, Archive& /*ar*/) {
271
		t.reset(p);
272
	}
273
};
274
template<typename T> struct serialize_as_pointer<std::shared_ptr<T>>
275
	: serialize_as_pointer_impl<T>
276
{
277
	static T* getPointer(const std::shared_ptr<T>& t) { return t.get(); }
278
	template<typename Archive>
279
	static void setPointer(std::shared_ptr<T>& t, T* p, Archive& ar) {
280
		ar.resetSharedPtr(t, p);
281
	}
282
};
283
284
///////////
285
286
// serialize_as_collection<T>
287
//
288
// Type-trait class that indicates whether a certain type should be serialized
289
// as a collection or not. The serialization code 'knows' how to serialize
290
// collections, so as a user of the serializer you only need to list the
291
// collection to have it serialized (you don't have to iterate over it
292
// manually).
293
//
294
// By default arrays, std::vector, std::list, std::deque and std::map are
295
// recognized as collections. Though for STL collections you need to add
296
//    #include "serialize_stl.hh"
297
//
298
// The serialize_as_collection class has the following members:
299
//
300
//  - static bool value
301
//      True iff this type must be serialized as a collection.
302
//      The fields below are only valid (even only present) if this variable
303
//      is true.
304
//  - int size
305
//      The size of the collection, -1 for variable sized collections.
306
//      Fixed sized collections can be serialized slightly more efficient
307
//      because we don't need to explicitly store the size.
308
//  - using value_type = ...
309
//      The type stored in the collection (only homogeneous collections are
310
//      supported).
311
//  - bool loadInPlace
312
//      Indicates whether we can directly load the elements in the correct
313
//      position in the container, otherwise there will be an extra assignment.
314
//      For this to be possible, the output iterator must support a dereference
315
//      operator that returns a 'regular' value_type.
316
//  - using const_iterator = ...
317
//  - const_iterator begin(...)
318
//  - const_iterator end(...)
319
//      Returns begin/end iterator for the given collection. Used for saving.
320
//  - void prepare(..., int n)
321
//  - output_iterator output(...)
322
//      These are used for loading. The prepare() method should prepare the
323
//      collection to receive 'n' elements. The output() method returns an
324
//      output_iterator to the beginning of the collection.
325
326
template<typename T> struct serialize_as_collection : std::false_type {};
327
328
template<typename T, size_t N> struct serialize_as_collection<std::array<T, N>> : std::true_type
329
{
330
	static constexpr int size = N; // fixed size
331
	using value_type = T;
332
	// save
333
	using const_iterator = typename std::array<T, N>::const_iterator;
334
	static auto begin(const std::array<T, N>& a) { return a.begin(); }
335
	static auto end  (const std::array<T, N>& a) { return a.end(); }
336
	// load
337
	static constexpr bool loadInPlace = true;
338
	static void prepare(std::array<T, N>& /*a*/, int /*n*/) { }
339
	static T* output(std::array<T, N>& a) { return a.data(); }
340
};
341
342
///////////
343
344
// Implementation of the different save-strategies.
345
//
346
// ATM we have
347
//  - PrimitiveSaver
348
//      Saves primitive values: int, bool, string, ...
349
//      Primitive values cannot be versioned.
350
//  - EnumSaver
351
//      Depending on the archive type, enums are either saved as strings (XML
352
//      archive) or as integers (memory archive).
353
//      This does not work automatically: it needs a specialization of
354
//      serialize_as_enum, see above.
355
//  - ClassSaver
356
//      From a serialization POV classes are a (heterogeneous) collection of
357
//      other to-be-serialized items.
358
//      Classes can have a version number, this allows to evolve the class
359
//      structure while still being able to load older versions (the load
360
//      method receive the version number as parameter, the user still needs
361
//      to keep the old loading code in place).
362
//      Optionally the name of the (concrete) class is saved in the stream.
363
//      This is used to support loading of polymorphic classes.
364
//      There is also an (optional) id saved in the stream. This used to
365
//      resolve possible multiple pointers to the same class.
366
//  - PointerSaver
367
//      Saves a pointer to a class (pointers to primitive types are not
368
//      supported). See also serialize_as_pointer
369
//  - IDSaver
370
//      Weaker version of PointerSaver: it can only save pointers to objects
371
//      that are already saved before (so it's will be saved by storing a
372
//      reference). To only reason to use IDSaver (iso PointerSaver) is that
373
//      it will not instantiate the object construction code.
374
//  - CollectionSaver
375
//      Saves a whole collection. See also serialize_as_collection
376
//
377
// All these strategies have a method:
378
//   template<typename Archive> void operator()(Archive& ar, const T& t)
379
//     'ar' is archive where the serialized stream will go
380
//     't'  is the to-be-saved object
381
//     'saveId' Should ID be saved
382
383
template<typename T> struct PrimitiveSaver
384
{
385
	template<typename Archive> void operator()(Archive& ar, const T& t,
386
	                                           bool /*saveId*/) const
387
	{
388
		static_assert(is_primitive<T>::value, "must be primitive type");
389
		ar.save(t);
390
	}
391
};
392
template<typename T> struct EnumSaver
393
{
394
	template<typename Archive> void operator()(Archive& ar, const T& t,
395
	                                           bool /*saveId*/) const
396
	{
397
		saveEnum<Archive>(serialize_as_enum<T>::info(), t,
398
			[&](const auto& s) { ar.save(s); });
399
	}
400
};
401
template<typename T> struct ClassSaver
402
{
403
	template<typename Archive> void operator()(
404
		Archive& ar, const T& t, bool saveId,
405
		const char* type = nullptr, bool saveConstrArgs = false) const
406
	{
407
		// Order is important (for non-xml archives). We use this order:
408
		//    - id
409
		//    - type
410
		//    - version
411
		//    - constructor args
412
		// Rational:
413
		//  - 'id' must be first: it could be nullptr, in that
414
		//    case the other fields are not even present.
415
		//  - 'type' must be before version, because for some types we
416
		//    might not need to store version (though it's unlikely)
417
		//  - version must be before constructor args because the
418
		//    constr args depend on the version
419
		if (saveId) {
420
			unsigned id = ar.generateId(&t);
421
			ar.attribute("id", id);
422
		}
423
424
		if (type) {
425
			ar.attribute("type", type);
426
		}
427
428
		unsigned version = SerializeClassVersion<T>::value;
429
		if ((version != 0) && ar.NEED_VERSION) {
430
			if (!ar.CAN_HAVE_OPTIONAL_ATTRIBUTES ||
431
			    (version != 1)) {
432
				ar.attribute("version", version);
433
			}
434
		}
435
436
		if (saveConstrArgs) {
437
			// save local constructor args (if any)
438
			SerializeConstructorArgs<T> constrArgs;
439
			constrArgs.save(ar, t);
440
		}
441
442
		using TNC = std::remove_const_t<T>;
443
		auto& t2 = const_cast<TNC&>(t);
444
		serialize(ar, t2, version);
445
	}
446
};
447
template<typename TP> struct PointerSaver
448
{
449
	// note: we only support pointer to class
450
	template<typename Archive> void operator()(Archive& ar, const TP& tp2,
451
	                                           bool /*saveId*/) const
452
	{
453
		static_assert(serialize_as_pointer<TP>::value,
454
		              "must be serialized as pointer");
455
		using T = typename serialize_as_pointer<TP>::type;
456
		const T* tp = serialize_as_pointer<TP>::getPointer(tp2);
457
		if (!tp) {
458
			unsigned id = 0;
459
			ar.attribute("id_ref", id);
460
			return;
461
		}
462
		if (unsigned id = ar.getId(tp)) {
463
			ar.attribute("id_ref", id);
464
		} else {
465
			if constexpr (std::is_polymorphic_v<T>) {
466
				PolymorphicSaverRegistry<Archive>::save(ar, tp);
467
			} else {
468
				ClassSaver<T> saver;
469
				// don't store type
470
				// store id, constr-args
471
				saver(ar, *tp, true, nullptr, true);
472
			}
473
		}
474
	}
475
};
476
template<typename TP> struct IDSaver
477
{
478
	template<typename Archive> void operator()(Archive& ar, const TP& tp2) const
479
	{
480
		static_assert(serialize_as_pointer<TP>::value,
481
		              "must be serialized as pointer");
482
		auto tp = serialize_as_pointer<TP>::getPointer(tp2);
483
		unsigned id;
484
		if (!tp) {
485
			id = 0;
486
		} else {
487
			id = ar.getId(tp);
488
			assert(id);
489
		}
490
		ar.attribute("id_ref", id);
491
	}
492
};
493
template<typename TC> struct CollectionSaver
494
{
495
	template<typename Archive> void operator()(Archive& ar, const TC& tc,
496
	                                           bool saveId) const
497
	{
498
		using sac = serialize_as_collection<TC>;
499
		static_assert(sac::value, "must be serialized as collection");
500
		auto begin = sac::begin(tc);
501
		auto end   = sac::end  (tc);
502
		if ((sac::size < 0) && (!ar.CAN_COUNT_CHILDREN)) {
503
			// variable size
504
			// e.g. in an XML archive the loader can look-ahead and
505
			// count the number of sub-tags, so no need to
506
			// explicitly store the size for such archives.
507
			int n = int(std::distance(begin, end));
508
			ar.serialize("size", n);
509
		}
510
		for (/**/; begin != end; ++begin) {
511
			if (saveId) {
512
				ar.serializeWithID("item", *begin);
513
			} else {
514
				ar.serialize("item", *begin);
515
			}
516
		}
517
	}
518
};
519
520
// Delegate to a specific Saver class
521
// (implemented as inheriting from a specific base class).
522
template<typename T> struct Saver
523
	: std::conditional_t<is_primitive<T>::value,            PrimitiveSaver<T>,
524
	  std::conditional_t<Serializer<T>::value,              typename Serializer<T>::Saver,
525
	  std::conditional_t<serialize_as_enum<T>::value,       EnumSaver<T>,
526
	  std::conditional_t<serialize_as_pointer<T>::value,    PointerSaver<T>,
527
	  std::conditional_t<serialize_as_collection<T>::value, CollectionSaver<T>,
528
	                                                        ClassSaver<T>>>>>> {};
529
530
////
531
532
// Implementation of the different load-strategies.
533
//
534
// This matches very closely with the save-strategies above.
535
//
536
// All these strategies have a method:
537
//   template<typename Archive, typename TUPLE>
538
//   void operator()(Archive& ar, const T& t, TUPLE args)
539
//     'ar' Is archive where the serialized stream will go
540
//     't'  Is the object that has to be restored.
541
//          In case of a class (not a pointer to a class) the actual object
542
//          is already constructed, but it still needs to be filled in with
543
//          the correct data.
544
//     'args' (Only used by PointerLoader) holds extra parameters used
545
//            to construct objects.
546
//     'id' Used to skip loading an ID, see comment in ClassLoader
547
548
template<typename T> struct PrimitiveLoader
549
{
550
	template<typename Archive, typename TUPLE>
551
	void operator()(Archive& ar, T& t, TUPLE /*args*/, int /*id*/) const
552
	{
553
		static_assert(std::tuple_size_v<TUPLE> == 0,
554
		              "can't have constructor arguments");
555
		ar.load(t);
556
	}
557
};
558
template<typename T> struct EnumLoader
559
{
560
	template<typename Archive, typename TUPLE>
561
	void operator()(Archive& ar, T& t, TUPLE /*args*/, int /*id*/) const
562
	{
563
		static_assert(std::tuple_size_v<TUPLE> == 0,
564
		              "can't have constructor arguments");
565
		loadEnum<Archive>(serialize_as_enum<T>::info(), t,
566
			[&](auto& l) { ar.load(l); });
567
	}
568
};
569
570
unsigned loadVersionHelper(MemInputArchive& ar, const char* className,
571
                           unsigned latestVersion);
572
unsigned loadVersionHelper(XmlInputArchive& ar, const char* className,
573
                           unsigned latestVersion);
574
template<typename T, typename Archive> unsigned loadVersion(Archive& ar)
575
{
576
	unsigned latestVersion = SerializeClassVersion<T>::value;
577
	if ((latestVersion != 0) && ar.NEED_VERSION) {
578
		return loadVersionHelper(ar, typeid(T).name(), latestVersion);
579
	} else {
580
		return latestVersion;
581
	}
582
}
583
template<typename T> struct ClassLoader
584
{
585
	template<typename Archive, typename TUPLE>
586
	void operator()(Archive& ar, T& t, TUPLE /*args*/, int id = 0,
587
	                int version = -1) const
588
	{
589
		static_assert(std::tuple_size_v<TUPLE> == 0,
590
		              "can't have constructor arguments");
591
592
		// id == -1: don't load id, don't addPointer
593
		// id ==  0: load id from archive, addPointer
594
		// id ==  N: id already loaded, still addPointer
595
		if (id != -1) {
596
			if (id == 0) {
597
				ar.attribute("id", id);
598
			}
599
			ar.addPointer(id, &t);
600
		}
601
602
		// version == -1: load version
603
		// version ==  N: version already loaded
604
		if (version == -1) {
605
			version = loadVersion<T>(ar);
606
		}
607
608
		using TNC = std::remove_const_t<T>;
609
		auto& t2 = const_cast<TNC&>(t);
610
		serialize(ar, t2, version);
611
	}
612
};
613
template<typename T> struct NonPolymorphicPointerLoader
614
{
615
	template<typename Archive, typename GlobalTuple>
616
	T* operator()(Archive& ar, unsigned id, GlobalTuple globalArgs)
617
	{
618
		int version = loadVersion<T>(ar);
619
620
		// load (local) constructor args (if any)
621
		using TNC = std::remove_const_t<T>;
622
		using ConstrArgs = SerializeConstructorArgs<TNC>;
623
		ConstrArgs constrArgs;
624
		auto localArgs = constrArgs.load(ar, version);
625
626
		// combine global and local constr args
627
		auto args = std::tuple_cat(globalArgs, localArgs);
628
		// TODO make combining global/local constr args configurable
629
630
		Creator<T> creator;
631
		auto tp = creator(args);
632
		ClassLoader<T> loader;
633
		loader(ar, *tp, std::tuple<>(), id, version);
634
		return tp.release();
635
	}
636
};
637
template<typename T> struct PolymorphicPointerLoader
638
{
639
	template<typename Archive, typename TUPLE>
640
	T* operator()(Archive& ar, unsigned id, TUPLE args)
641
	{
642
		using ArgsType = typename PolymorphicConstructorArgs<T>::type;
643
		static_assert(std::is_same_v<TUPLE, ArgsType>,
644
		              "constructor arguments types must match");
645
		return static_cast<T*>(
646
			PolymorphicLoaderRegistry<Archive>::load(ar, id, &args));
647
	}
648
};
649
template<typename T> struct PointerLoader2
650
	// extra indirection needed because inlining the body of
651
	// NonPolymorphicPointerLoader in PointerLoader does not compile
652
	// for abstract types
653
	: std::conditional_t<std::is_polymorphic_v<T>,
654
	                     PolymorphicPointerLoader<T>,
655
	                     NonPolymorphicPointerLoader<T>> {};
656
657
template<typename TP> struct PointerLoader
658
{
659
	template<typename Archive, typename GlobalTuple>
660
	void operator()(Archive& ar, TP& tp2, GlobalTuple globalArgs, int /*id*/) const
661
	{
662
		static_assert(serialize_as_pointer<TP>::value,
663
		              "must be serialized as a pointer");
664
		// in XML archives we use 'id_ref' or 'id', in other archives
665
		// we don't care about the name
666
		unsigned id = [&] {
667
			if constexpr (Archive::CAN_HAVE_OPTIONAL_ATTRIBUTES) {
668
				if (auto i = ar.template findAttributeAs<unsigned>("id_ref")) {
669
					return *i;
670
				}
671
			}
672
			unsigned i;
673
			ar.attribute("id", i);
674
			return i;
675
		}();
676
677
		using T = typename serialize_as_pointer<TP>::type;
678
		T* tp = [&] -> T* {
679
			if (id == 0) {
680
				return nullptr;
681
			} else {
682
				if (void* p = ar.getPointer(id)) {
683
					return static_cast<T*>(p);
684
				} else {
685
					PointerLoader2<T> loader;
686
					return loader(ar, id, globalArgs);
687
				}
688
			}
689
		}();
690
		serialize_as_pointer<TP>::setPointer(tp2, tp, ar);
691
	}
692
};
693
[[noreturn]] void pointerError(unsigned id);
694
template<typename TP> struct IDLoader
695
{
696
	template<typename Archive>
697
	void operator()(Archive& ar, TP& tp2) const
698
	{
699
		static_assert(serialize_as_pointer<TP>::value,
700
		              "must be serialized as a pointer");
701
		unsigned id;
702
		ar.attribute("id_ref", id);
703
704
		using T = typename serialize_as_pointer<TP>::type;
705
		T* tp;
706
		if (id == 0) {
707
			tp = nullptr;
708
		} else {
709
			void* p = ar.getPointer(id);
710
			if (!p) {
711
				pointerError(id);
712
			}
713
			tp = static_cast<T*>(p);
714
		}
715
		serialize_as_pointer<TP>::setPointer(tp2, tp, ar);
716
	}
717
};
718
719
template<typename sac, bool IN_PLACE = sac::loadInPlace> struct CollectionLoaderHelper;
720
template<typename sac> struct CollectionLoaderHelper<sac, true>
721
{
722
	// used for array and vector
723
	template<typename Archive, typename TUPLE, typename OUT_ITER>
724
	void operator()(Archive& ar, TUPLE args, OUT_ITER it, int id) const
725
	{
726
		ar.doSerialize("item", *it, args, id);
727
	}
728
};
729
template<typename sac> struct CollectionLoaderHelper<sac, false>
730
{
731
	// We can't directly load the element in the correct position:
732
	// This screws-up id/pointer management because the element is still
733
	// copied after construction (and pointer value of initial object is
734
	// stored).
735
	template<typename Archive, typename TUPLE, typename OUT_ITER>
736
	void operator()(Archive& ar, TUPLE args, OUT_ITER it, int id) const
737
	{
738
		typename sac::value_type elem;
739
		ar.doSerialize("item", elem, args, id);
740
		*it = std::move(elem);
741
	}
742
};
743
template<typename TC> struct CollectionLoader
744
{
745
	template<typename Archive, typename TUPLE>
746
	void operator()(Archive& ar, TC& tc, TUPLE args, int id = 0) const
747
	{
748
		assert(id == one_of(0, -1));
749
		using sac = serialize_as_collection<TC>;
750
		static_assert(sac::value, "must be serialized as a collection");
751
		int n = sac::size;
752
		if (n < 0) {
753
			// variable size
754
			if constexpr (Archive::CAN_COUNT_CHILDREN) {
755
				n = ar.countChildren();
756
			} else {
757
				ar.serialize("size", n);
758
			}
759
		}
760
		sac::prepare(tc, n);
761
		auto it = sac::output(tc);
762
		CollectionLoaderHelper<sac> loadOneElement;
763
		repeat(n, [&] {
764
			loadOneElement(ar, args, it, id);
765
			++it;
766
		});
767
	}
768
};
769
template<typename T> struct Loader
770
	: std::conditional_t<is_primitive<T>::value,            PrimitiveLoader<T>,
771
	  std::conditional_t<Serializer<T>::value,              typename Serializer<T>::Loader,
772
	  std::conditional_t<serialize_as_enum<T>::value,       EnumLoader<T>,
773
	  std::conditional_t<serialize_as_pointer<T>::value,    PointerLoader<T>,
774
	  std::conditional_t<serialize_as_collection<T>::value, CollectionLoader<T>,
775
	                                                        ClassLoader<T>>>>>> {};
776
777
} // namespace openmsx
778
779
#endif