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