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*/)
266 {
267 UNREACHABLE; return false;
268 }
269
273 template<typename T>
274 [[nodiscard]] std::optional<T> findAttributeAs(const char* /*name*/)
275 {
276 UNREACHABLE; return false;
277 }
278
287 static constexpr bool CAN_COUNT_CHILDREN = false;
288
293 [[nodiscard]] int countChildren() const
294 {
295 UNREACHABLE; return 0;
296 }
297
306 void beginTag(const char* /*tag*/)
307 {
308 // nothing
309 }
316 void endTag(const char* /*tag*/)
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*/)
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
528 {
530 }
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 put(&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
724 [[nodiscard]] MemBuffer<uint8_t> releaseBuffer(size_t& size);
725
726private:
727 void put(const void* data, size_t len)
728 {
729 if (len) {
730 buffer.insert(data, len);
731 }
732 }
733
734 ALWAYS_INLINE void serialize_group(const std::tuple<>& /*tuple*/)
735 {
736 // done categorizing, there were no memcpy-able elements
737 }
738 template<typename ...Args>
739 ALWAYS_INLINE void serialize_group(const std::tuple<Args...>& tuple)
740 {
741 // done categorizing, process all memcpy-able elements
742 buffer.insert_tuple_ptr(tuple);
743 }
744 template<typename TUPLE, typename T, typename ...Args>
745 ALWAYS_INLINE void serialize_group(const TUPLE& tuple, const char* tag, const T& t, Args&& ...args)
746 {
747 // categorize one element
748 if constexpr (SerializeAsMemcpy<T>::value) {
749 // add to the group and continue categorizing
750 (void)tag;
751 serialize_group(std::tuple_cat(tuple, std::tuple(&t)), std::forward<Args>(args)...);
752 } else {
753 serialize(tag, t); // process single (ungroupable) element
754 serialize_group(tuple, std::forward<Args>(args)...); // continue categorizing
755 }
756 }
757
758private:
759 OutputBuffer buffer;
760 std::vector<size_t> openSections;
761 LastDeltaBlocks& lastDeltaBlocks;
762 std::vector<std::shared_ptr<DeltaBlock>>& deltaBlocks;
763 const bool reverseSnapshot;
764};
765
766class MemInputArchive final : public InputArchiveBase<MemInputArchive>
767{
768public:
769 MemInputArchive(const uint8_t* data, size_t size,
770 std::span<const std::shared_ptr<DeltaBlock>> deltaBlocks_)
771 : buffer(data, size)
772 , deltaBlocks(deltaBlocks_)
773 {
774 }
775
776 static constexpr bool NEED_VERSION = false;
777 [[nodiscard]] inline bool versionAtLeast(unsigned /*actual*/, unsigned /*required*/) const
778 {
779 return true;
780 }
781 [[nodiscard]] inline bool versionBelow(unsigned /*actual*/, unsigned /*required*/) const
782 {
783 return false;
784 }
785
786 template<typename T> void load(T& t)
787 {
788 get(&t, sizeof(t));
789 }
790 inline void loadChar(char& c)
791 {
792 load(c);
793 }
794 void load(std::string& s);
795 [[nodiscard]] std::string_view loadStr();
796 void serialize_blob(const char* tag, std::span<uint8_t> data,
797 bool diff = true);
798
800 template<typename T, typename ...Args>
801 ALWAYS_INLINE void serialize(const char* tag, T& t, Args&& ...args)
802 {
803 // see comments in MemOutputArchive
804 serialize_group(std::tuple<>(), tag, t, std::forward<Args>(args)...);
805 }
806
807 template<typename T, size_t N>
808 ALWAYS_INLINE void serialize(const char* /*tag*/, std::array<T, N>& t)
810 {
811 buffer.read(t.data(), N * sizeof(T));
812 }
813
814 void skipSection(bool skip)
815 {
816 size_t num;
817 load(num);
818 if (skip) {
819 buffer.skip(num);
820 }
821 }
822
823private:
824 void get(void* data, size_t len)
825 {
826 if (len) {
827 buffer.read(data, len);
828 }
829 }
830
831 // See comments in MemOutputArchive
832 template<typename TUPLE>
833 ALWAYS_INLINE void serialize_group(const TUPLE& tuple)
834 {
835 auto read = [&](auto* p) { buffer.read(p, sizeof(*p)); };
836 std::apply([&](auto&&... args) { (read(args), ...); }, tuple);
837 }
838 template<typename TUPLE, typename T, typename ...Args>
839 ALWAYS_INLINE void serialize_group(const TUPLE& tuple, const char* tag, T& t, Args&& ...args)
840 {
841 if constexpr (SerializeAsMemcpy<T>::value) {
842 (void)tag;
843 serialize_group(std::tuple_cat(tuple, std::tuple(&t)), std::forward<Args>(args)...);
844 } else {
845 serialize(tag, t);
846 serialize_group(tuple, std::forward<Args>(args)...);
847 }
848 }
849
850private:
851 InputBuffer buffer;
852 std::span<const std::shared_ptr<DeltaBlock>> deltaBlocks;
853};
854
856
857class XmlOutputArchive final : public OutputArchiveBase<XmlOutputArchive>
858{
859public:
860 explicit XmlOutputArchive(zstring_view filename);
861 void close();
863
864 template<typename T> void saveImpl(const T& t)
865 {
866 // TODO make sure floating point is printed with enough digits
867 // maybe print as hex?
868 save(std::string_view(tmpStrCat(t)));
869 }
870 template<typename T> void save(const T& t)
871 {
872 saveImpl(t);
873 }
874 void saveChar(char c);
875 void save(std::string_view str);
876 void save(bool b);
877 void save(unsigned char b);
878 void save(signed char c);
879 void save(char c);
880 void save(int i); // these 3 are not strictly needed
881 void save(unsigned u); // but having them non-inline
882 void save(unsigned long long ull); // saves quite a bit of code
883
884 void beginSection() { /*nothing*/ }
885 void endSection() { /*nothing*/ }
886
887 // workaround(?) for visual studio 2015:
888 // put the default here instead of in the base class
890 template<typename T, typename ...Args>
891 ALWAYS_INLINE void serialize(const char* tag, const T& t, Args&& ...args)
892 {
893 // by default just repeatedly call the single-pair serialize() variant
894 this->self().serialize(tag, t);
895 this->self().serialize(std::forward<Args>(args)...);
896 }
897
898 auto& getXMLOutputStream() { return writer; }
899
900//internal:
901 static constexpr bool TRANSLATE_ENUM_TO_STRING = true;
902 static constexpr bool CAN_HAVE_OPTIONAL_ATTRIBUTES = true;
903 static constexpr bool CAN_COUNT_CHILDREN = true;
904
905 void beginTag(const char* tag);
906 void endTag(const char* tag);
907
908 template<typename T> void attributeImpl(const char* name, const T& t)
909 {
910 attribute(name, std::string_view(tmpStrCat(t)));
911 }
912 template<typename T> void attribute(const char* name, const T& t)
913 {
914 attributeImpl(name, t);
915 }
916 void attribute(const char* name, std::string_view str);
917 void attribute(const char* name, int i);
918 void attribute(const char* name, unsigned u);
919
920//internal: // called from XMLOutputStream
921 void write(std::span<const char> buf);
922 void write1(char c);
923 void check(bool condition) const;
924 void error();
925
926private:
927 zstring_view filename;
928 gzFile file = nullptr;
930};
931
932class XmlInputArchive final : public InputArchiveBase<XmlInputArchive>
933{
934public:
935 explicit XmlInputArchive(const std::string& filename);
936
937 [[nodiscard]] inline bool versionAtLeast(unsigned actual, unsigned required) const
938 {
939 return actual >= required;
940 }
941 [[nodiscard]] inline bool versionBelow(unsigned actual, unsigned required) const
942 {
943 return actual < required;
944 }
945
946 template<typename T> void load(T& t)
947 {
948 std::string str;
949 load(str);
950 std::istringstream is(str);
951 is >> t;
952 }
953 void loadChar(char& c);
954 void load(bool& b);
955 void load(unsigned char& b);
956 void load(signed char& c);
957 void load(char& c);
958 void load(int& i); // these 3 are not strictly needed
959 void load(unsigned& u); // but having them non-inline
960 void load(unsigned long long& ull); // saves quite a bit of code
961 void load(std::string& t);
962 [[nodiscard]] std::string_view loadStr();
963
964 void skipSection(bool /*skip*/) { /*nothing*/ }
965
966 // workaround(?) for visual studio 2015:
967 // put the default here instead of in the base class
969 template<typename T, typename ...Args>
970 ALWAYS_INLINE void serialize(const char* tag, T& t, Args&& ...args)
971 {
972 // by default just repeatedly call the single-pair serialize() variant
973 this->self().serialize(tag, t);
974 this->self().serialize(std::forward<Args>(args)...);
975 }
976
977 [[nodiscard]] const XMLElement* currentElement() const {
978 return elems.back().first;
979 }
980
981//internal:
982 static constexpr bool TRANSLATE_ENUM_TO_STRING = true;
983 static constexpr bool CAN_HAVE_OPTIONAL_ATTRIBUTES = true;
984 static constexpr bool CAN_COUNT_CHILDREN = true;
985
986 void beginTag(const char* tag);
987 void endTag(const char* tag);
988
989 template<typename T> void attributeImpl(const char* name, T& t)
990 {
991 std::string str;
992 attribute(name, str);
993 std::istringstream is(str);
994 is >> t;
995 }
996 template<typename T> void attribute(const char* name, T& t)
997 {
998 attributeImpl(name, t);
999 }
1000 void attribute(const char* name, std::string& t);
1001 void attribute(const char* name, int& i);
1002 void attribute(const char* name, unsigned& u);
1003
1004 [[nodiscard]] bool hasAttribute(const char* name) const;
1005 [[nodiscard]] int countChildren() const;
1006
1007 template<typename T>
1008 std::optional<T> findAttributeAs(const char* name)
1009 {
1010 if (const auto* attr = currentElement()->findAttribute(name)) {
1011 return StringOp::stringTo<T>(attr->getValue());
1012 }
1013 return {};
1014 }
1015
1016private:
1017 XMLDocument xmlDoc{16384}; // tweak: initial allocator buffer size
1018 std::vector<std::pair<const XMLElement*, const XMLElement*>> elems;
1019};
1020
1021#define INSTANTIATE_SERIALIZE_METHODS(CLASS) \
1022template void CLASS::serialize(MemInputArchive&, unsigned); \
1023template void CLASS::serialize(MemOutputArchive&, unsigned); \
1024template void CLASS::serialize(XmlInputArchive&, unsigned); \
1025template void CLASS::serialize(XmlOutputArchive&, unsigned);
1026
1027} // namespace openmsx
1028
1029#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:467
iterator find(const K &key)
Definition hash_set.hh:545
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 beginTag(const char *)
Indicate begin of a tag.
Definition serialize.hh:306
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 endTag(const char *)
Indicate begin of a tag.
Definition serialize.hh:316
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
bool hasAttribute(const char *)
Check the presence of a (optional) attribute.
Definition serialize.hh:265
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
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:116
unsigned getId(const void *p) const
Definition serialize.cc:128
void addPointer(unsigned id, const void *p)
Definition serialize.cc:122
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:137
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:29
bool versionBelow(unsigned, unsigned) const
Definition serialize.hh:781
void serialize_blob(const char *tag, std::span< uint8_t > data, bool diff=true)
Definition serialize.cc:233
ALWAYS_INLINE void serialize(const char *tag, T &t, Args &&...args)
Definition serialize.hh:801
std::string_view loadStr()
Definition serialize.cc:200
ALWAYS_INLINE void serialize(const char *, std::array< T, N > &t)
Definition serialize.hh:808
MemInputArchive(const uint8_t *data, size_t size, std::span< const std::shared_ptr< DeltaBlock > > deltaBlocks_)
Definition serialize.hh:769
static constexpr bool NEED_VERSION
Definition serialize.hh:776
void skipSection(bool skip)
Definition serialize.hh:814
bool versionAtLeast(unsigned, unsigned) const
Definition serialize.hh:777
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:217
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(size_t &size)
Definition serialize.cc:183
bool isReverseSnapshot() const
Definition serialize.hh:672
unsigned generateId(const T *p)
Definition serialize.hh:405
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:78
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:996
bool versionBelow(unsigned actual, unsigned required) const
Definition serialize.hh:941
static constexpr bool CAN_HAVE_OPTIONAL_ATTRIBUTES
Definition serialize.hh:983
bool hasAttribute(const char *name) const
Definition serialize.cc:556
std::string_view loadStr()
Definition serialize.cc:401
static constexpr bool CAN_COUNT_CHILDREN
Definition serialize.hh:984
void endTag(const char *tag)
Definition serialize.cc:528
const XMLElement * currentElement() const
Definition serialize.hh:977
void attributeImpl(const char *name, T &t)
Definition serialize.hh:989
bool versionAtLeast(unsigned actual, unsigned required) const
Definition serialize.hh:937
std::optional< T > findAttributeAs(const char *name)
ALWAYS_INLINE void serialize(const char *tag, T &t, Args &&...args)
Definition serialize.hh:970
static constexpr bool TRANSLATE_ENUM_TO_STRING
Definition serialize.hh:982
void beginTag(const char *tag)
Definition serialize.cc:515
void saveImpl(const T &t)
Definition serialize.hh:864
void check(bool condition) const
Definition serialize.cc:319
void write(std::span< const char > buf)
Definition serialize.cc:305
void save(const T &t)
Definition serialize.hh:870
static constexpr bool CAN_HAVE_OPTIONAL_ATTRIBUTES
Definition serialize.hh:902
ALWAYS_INLINE void serialize(const char *tag, const T &t, Args &&...args)
Definition serialize.hh:891
void attributeImpl(const char *name, const T &t)
Definition serialize.hh:908
static constexpr bool TRANSLATE_ENUM_TO_STRING
Definition serialize.hh:901
void endTag(const char *tag)
Definition serialize.cc:387
void beginTag(const char *tag)
Definition serialize.cc:383
static constexpr bool CAN_COUNT_CHILDREN
Definition serialize.hh:903
void attribute(const char *name, const T &t)
Definition serialize.hh:912
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:9
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)