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