#ifndef NETGEN_CORE_ARCHIVE_HPP #define NETGEN_CORE_ARCHIVE_HPP #include #include #include // for array #include // for complex #include // for size_t, strlen #include // for path #include // for ifstream, ofstream #include // for function #include // for map #include // for shared_ptr #include // for optional #include // for string #include // for declval, enable_if_t, false_type, is_co... #include // for std::byte #include // for set #include // for type_info #include // for move, swap, pair #include // for vector #include "exception.hpp" // for UnreachableCodeException, Exception #include "ngcore_api.hpp" // for NGCORE_API #include "type_traits.hpp" // for all_of_tmpl #include "utils.hpp" // for Demangle, unlikely #include "version.hpp" // for VersionInfo #ifdef NETGEN_PYTHON namespace pybind11 { class object; } #endif // NETGEN_PYTHON namespace ngcore { template struct Shallow { T val; Shallow() = default; Shallow(T aval) : val(aval) { ; } operator T&() { return val; } }; // Helper to detect shared_from_this template class has_shared_from_this2 { private: // typedef T* T_ptr; template static std::true_type test(decltype(((C*)nullptr)->shared_from_this())); template static std::false_type test(...); public: // If the test returns true_type, then T has shared_from_this static constexpr bool value = decltype(test(0))::value; }; template class has_shallow_archive : public std::false_type {}; template class has_shallow_archive> : public std::is_same {}; #ifdef NETGEN_PYTHON pybind11::object CastAnyToPy(const std::any& a); #endif // NETGEN_PYTHON class NGCORE_API Archive; namespace detail { template T* construct_from_tuple(Tuple&& tuple, std::index_sequence ) { // return new T{std::get(std::forward(tuple))...}; return new T{std::get(std::move(tuple))...}; } template T* construct_from_tuple(Tuple&& tuple) { return construct_from_tuple(std::forward(tuple), std::make_index_sequence>::value>{} ); } // create new pointer of type T if it is default constructible, else throw template T* constructIfPossible(std::tuple args) { if constexpr(std::is_constructible_v) return construct_from_tuple(args); throw Exception(std::string(Demangle(typeid(T).name())) + " is not constructible!"); } template T *constructIfPossible() { if constexpr(std::is_constructible_v) return new T(); throw Exception(std::string(Demangle(typeid(T).name())) + " is not default constructible!"); } //Type trait to check if a class implements a 'void DoArchive(Archive&)' function template struct has_DoArchive { private: template static constexpr auto check(T2*) -> typename std::is_same().DoArchive(std::declval())),void>::type; template static constexpr std::false_type check(...); using type = decltype(check(nullptr)); // NOLINT public: NGCORE_API static constexpr bool value = type::value; }; // Check if class is archivable template struct is_Archivable_struct { private: template static constexpr auto check(T2*) -> typename std::is_same() & std::declval()),Archive&>::type; template static constexpr std::false_type check(...); using type = decltype(check(nullptr)); // NOLINT public: NGCORE_API static constexpr bool value = type::value; }; template struct has_GetCArgs { template static std::true_type check( decltype( sizeof(&C::GetCArgs )) ) { return std::true_type(); } template static std::false_type check(...) { return std::false_type(); } typedef decltype( check(sizeof(char)) ) type; static constexpr type value = type(); }; template constexpr bool has_GetCArgs_v = has_GetCArgs::value; template>::type* = nullptr> std::tuple<> GetCArgs(T&val) { return {}; } template>::type* = nullptr> auto GetCArgs(T&val) { return val.GetCArgs(); } template using TCargs = decltype(GetCArgs(*static_cast(nullptr))); struct ClassArchiveInfo { // create new object of this type and return a void* pointer that is points to the location // of the (base)class given by type_info // std::function creator; void* (*creator)(const std::type_info&, Archive&); // This caster takes a void* pointer to the type stored in this info and casts it to a // void* pointer pointing to the (base)class type_info // std::function upcaster; void* (*upcaster) (const std::type_info&, void*); // This caster takes a void* pointer to the (base)class type_info and returns void* pointing // to the type stored in this info // std::function downcaster; void* (*downcaster)(const std::type_info&, void*); // Archive constructor arguments // std::function cargs_archiver; void (*cargs_archiver)(Archive&, void*); #ifdef NETGEN_PYTHON // std::function anyToPyCaster; pybind11::object (*anyToPyCaster)(const std::any&); #endif // NETGEN_PYTHON }; } // namespace detail template constexpr bool is_archivable = detail::is_Archivable_struct::value; template constexpr size_t TotSize () { if constexpr (sizeof...(Trest) == 0) return sizeof(T); else return sizeof(T) + TotSize (); } // Base Archive class class NGCORE_API Archive { const bool is_output; // how many different shared_ptr/pointer have been (un)archived int shared_ptr_count{0}, ptr_count{0}; // maps for archived shared pointers and pointers std::map shared_ptr2nr{}, ptr2nr{}; // vectors for storing the unarchived (shared) pointers std::vector> nr2shared_ptr{}; std::vector nr2ptr{}; protected: bool shallow_to_python = false; std::map version_map = GetLibraryVersions(); public: template static constexpr bool is_archivable = detail::is_Archivable_struct::value; Archive() = delete; Archive(const Archive&) = delete; Archive(Archive&&) = delete; Archive (bool ais_output) : is_output(ais_output) { ; } virtual ~Archive() { ; } // If the object is pickled, all shallow archived objects will be pickled as a list, // instead of written as a binary archive. This allows pickle to serialize every object only // once and put them together correctly afterwards. Therefore all objects that may live in // Python should be archived using this Shallow function. If Shallow is called from C++ code // it archives the object normally. #ifdef NETGEN_PYTHON template Archive& Shallow(T& val); // implemented in python_ngcore.hpp #else // NETGEN_PYTHON template Archive& Shallow(T& val) { static_assert(detail::is_any_pointer, "ShallowArchive must be given pointer type!"); *this & val; return *this; } #endif // NETGEN_PYTHON #ifdef NETGEN_PYTHON virtual void ShallowOutPython(const pybind11::object& /*unused*/) { throw UnreachableCodeException{}; } virtual void ShallowInPython(pybind11::object &) { throw UnreachableCodeException{}; } #endif // NETGEN_PYTHON Archive& operator=(const Archive&) = delete; Archive& operator=(Archive&&) = delete; bool Output () const { return is_output; } bool Input () const { return !is_output; } const VersionInfo& GetVersion(const std::string& library) { return version_map[library]; } // only used for PyArchive virtual void NeedsVersion(const std::string& /*unused*/, const std::string& /*unused*/) {} // Pure virtual functions that have to be implemented by In-/OutArchive virtual Archive & operator & (std::byte & d) = 0; virtual Archive & operator & (float & d) = 0; virtual Archive & operator & (double & d) = 0; virtual Archive & operator & (int & i) = 0; virtual Archive & operator & (long & i) = 0; virtual Archive & operator & (size_t & i) = 0; virtual Archive & operator & (short & i) = 0; virtual Archive & operator & (unsigned char & i) = 0; virtual Archive & operator & (bool & b) = 0; virtual Archive & operator & (std::string & str) = 0; virtual Archive & operator & (char *& str) = 0; Archive & operator & (VersionInfo & version) { if(Output()) (*this) << version.to_string(); else { std::string s; (*this) & s; version = VersionInfo(s); } return *this; } // Archive std classes ================================================ template Archive& operator & (std::complex& c) { if(Output()) (*this) << c.real() << c.imag(); else { T tmp; (*this) & tmp; c.real(tmp); (*this) & tmp; c.imag(tmp); } return (*this); } template Archive& operator & (std::vector& v) { size_t size; if(Output()) size = v.size(); (*this) & size; if(Input()) v.resize(size); Do(&v[0], size); return (*this); } // archive implementation for enums template auto operator & (T& val) -> std::enable_if_t::value, Archive&> { int enumval; if(Output()) enumval = int(val); *this & enumval; if(Input()) val = T(enumval); return *this; } // vector has special implementation (like a bitarray) therefore // it needs a special overload (this could probably be more efficient, but we // don't use it that often anyway) Archive& operator& (std::vector& v) { size_t size; if(Output()) size = v.size(); (*this) & size; if(Input()) { v.resize(size); bool b; for(size_t i=0; i Archive& operator& (std::map& map) { if(Output()) { (*this) << size_t(map.size()); for(auto& pair : map) (*this) << pair.first << pair.second; } else { size_t size = 0; (*this) & size; T1 key; T2 val; for(size_t i = 0; i < size; i++) { T1 key; T2 val; (*this) & key & val; map[key] = val; } } return (*this); } template Archive& operator& (std::optional& opt) { bool has_value = opt.has_value(); (*this) & has_value; if(has_value) { if(Output()) (*this) << *opt; else { T value; (*this) & value; opt = value; } } return (*this); } template Archive& operator&(std::set &s) { auto size = s.size(); (*this) & size; if(Output()) for(const auto & val : s) (*this) << val; else { for(size_t i=0; i>> Archive & Do (T * data, size_t n) { for (size_t j = 0; j < n; j++) { (*this) & data[j]; }; return *this; }; // NOLINT virtual Archive & Do (std::byte * d, size_t n) { for (size_t j = 0; j < n; j++) { (*this) & d[j]; }; return *this; }; // NOLINT virtual Archive & Do (double * d, size_t n) { for (size_t j = 0; j < n; j++) { (*this) & d[j]; }; return *this; }; // NOLINT virtual Archive & Do (int * i, size_t n) { for (size_t j = 0; j < n; j++) { (*this) & i[j]; }; return *this; }; // NOLINT virtual Archive & Do (long * i, size_t n) { for (size_t j = 0; j < n; j++) { (*this) & i[j]; }; return *this; }; // NOLINT virtual Archive & Do (size_t * i, size_t n) { for (size_t j = 0; j < n; j++) { (*this) & i[j]; }; return *this; }; // NOLINT virtual Archive & Do (short * i, size_t n) { for (size_t j = 0; j < n; j++) { (*this) & i[j]; }; return *this; }; // NOLINT virtual Archive & Do (unsigned char * i, size_t n) { for (size_t j = 0; j < n; j++) { (*this) & i[j]; }; return *this; }; // NOLINT virtual Archive & Do (bool * b, size_t n) { for (size_t j = 0; j < n; j++) { (*this) & b[j]; }; return *this; }; // NOLINT // Archive a class implementing a (void DoArchive(Archive&)) method ======= template::value>> Archive& operator & (T& val) { val.DoArchive(*this); return *this; } // pack elements to binary template Archive & DoPacked (Types & ... args) { if (true) // (isbinary) { constexpr size_t totsize = TotSize(); // (args...); std::byte mem[totsize]; if (is_output) { CopyToBin (&mem[0], args...); Do(&mem[0], totsize); } else { Do(&mem[0], totsize); CopyFromBin (&mem[0], args...); } } // else // cout << "DoPacked of non-binary called --> individual pickling" << endl; return *this; } template constexpr void CopyToBin (std::byte * ptr, T & first, Trest & ...rest) const { memcpy (ptr, &first, sizeof(first)); CopyToBin(ptr+sizeof(first), rest...); } constexpr void CopyToBin (std::byte * ptr) const { } template constexpr void CopyFromBin (std::byte * ptr, T & first, Trest & ...rest) const { memcpy (&first, ptr, sizeof(first)); CopyFromBin(ptr+sizeof(first), rest...); } constexpr void CopyFromBin (std::byte * ptr) const { } template Archive& operator & (ngcore::Shallow& shallow) { this->Shallow(shallow.val); return *this; } // Archive shared_ptrs ================================================= template Archive& operator & (std::shared_ptr& ptr) { if constexpr(has_shallow_archive::value) if (shallow_to_python) { Shallow (ptr); return *this; } if(Output()) { // save -2 for nullptr if(!ptr) return (*this) << -2; void* reg_ptr = ptr.get(); bool neededDowncast = false; // Downcasting is only possible for our registered classes if(typeid(T) != typeid(*ptr)) { if(!IsRegistered(Demangle(typeid(*ptr).name()))) throw Exception(std::string("Archive error: Polymorphic type ") + Demangle(typeid(*ptr).name()) + " not registered for archive"); reg_ptr = GetArchiveRegister(Demangle(typeid(*ptr).name())).downcaster(typeid(T), ptr.get()); // if there was a true downcast we have to store more information if(reg_ptr != static_cast(ptr.get())) neededDowncast = true; } auto pos = shared_ptr2nr.find(reg_ptr); // if not found store -1 and the pointer if(pos == shared_ptr2nr.end()) { auto p = ptr.get(); (*this) << -1; (*this) & neededDowncast & p; // if we did downcast we store the true type as well if(neededDowncast) (*this) << Demangle(typeid(*ptr).name()); shared_ptr2nr[reg_ptr] = shared_ptr_count++; return *this; } // if found store the position and if it has to be downcasted and how (*this) << pos->second << neededDowncast; if(neededDowncast) (*this) << Demangle(typeid(*ptr).name()); } else // Input { int nr; (*this) & nr; // -2 restores a nullptr if(nr == -2) { ptr = nullptr; return *this; } // -1 restores a new shared ptr by restoring the inner pointer and creating a shared_ptr to it if (nr == -1) { T* p = nullptr; bool neededDowncast; (*this) & neededDowncast & p; ptr = std::shared_ptr(p); // if we did downcast we need to store a shared_ptr to the true object if(neededDowncast) { std::string name; (*this) & name; auto info = GetArchiveRegister(name); // for this we use an aliasing constructor to create a shared pointer sharing lifetime // with our shared ptr, but pointing to the true object nr2shared_ptr.push_back(std::shared_ptr(std::static_pointer_cast(ptr), info.downcaster(typeid(T), ptr.get()))); } else nr2shared_ptr.push_back(ptr); } else { auto other = nr2shared_ptr[nr]; bool neededDowncast; (*this) & neededDowncast; if(neededDowncast) { // if there was a downcast we can expect the class to be registered (since archiving // wouldn't have worked else) std::string name; (*this) & name; auto info = GetArchiveRegister(name); // same trick as above, create a shared ptr sharing lifetime with // the shared_ptr in the register, but pointing to our object ptr = std::static_pointer_cast(std::shared_ptr(other, info.upcaster(typeid(T), other.get()))); } else { ptr = std::static_pointer_cast(other); } } } return *this; } // Archive pointers ======================================================= template Archive & operator& (T *& p) { if (Output()) { // if the pointer is null store -2 if (!p) return (*this) << -2; auto reg_ptr = static_cast(p); if(typeid(T) != typeid(*p)) { if(!IsRegistered(Demangle(typeid(*p).name()))) throw Exception(std::string("Archive error: Polymorphic type ") + Demangle(typeid(*p).name()) + " not registered for archive"); reg_ptr = GetArchiveRegister(Demangle(typeid(*p).name())).downcaster(typeid(T), static_cast(p)); } auto pos = ptr2nr.find(reg_ptr); // if the pointer is not found in the map create a new entry if (pos == ptr2nr.end()) { ptr2nr[reg_ptr] = ptr_count++; if(typeid(*p) == typeid(T)) if (std::is_constructible::value) return (*this) << -1 & (*p); else { if (IsRegistered(Demangle(typeid(*p).name()))) { (*this) << -3 << Demangle(typeid(*p).name()); GetArchiveRegister(Demangle(typeid(*p).name())). cargs_archiver(*this, p); return (*this) & (*p); } else throw Exception(std::string("Archive error: Class ") + Demangle(typeid(*p).name()) + " does not provide a default constructor!"); } else { // if a pointer to a base class is archived, the class hierarchy must be registered // to avoid compile time issues we allow this behaviour only for "our" classes that // implement a void DoArchive(Archive&) member function // To recreate the object we need to store the true type of it if(!IsRegistered(Demangle(typeid(*p).name()))) throw Exception(std::string("Archive error: Polymorphic type ") + Demangle(typeid(*p).name()) + " not registered for archive"); (*this) << -3 << Demangle(typeid(*p).name()); GetArchiveRegister(Demangle(typeid(*p).name())). cargs_archiver(*this, p); return (*this) & (*p); } } else { (*this) & pos->second; bool downcasted = !(reg_ptr == static_cast(p) ); // store if the class has been downcasted and the name (*this) << downcasted << Demangle(typeid(*p).name()); } } else { int nr; (*this) & nr; if (nr == -2) // restore a nullptr p = nullptr; else if (nr == -1) // create a new pointer of standard type (no virtual or multiple inheritance,...) { p = detail::constructIfPossible(); nr2ptr.push_back(p); (*this) & *p; } else if(nr == -3) // restore one of our registered classes that can have multiple inheritance,... { // As stated above, we want this special behaviour only for our classes that implement DoArchive std::string name; (*this) & name; auto info = GetArchiveRegister(name); // the creator creates a new object of type name, and returns a void* pointing // to T (which may have an offset) p = static_cast(info.creator(typeid(T), *this)); // we store the downcasted pointer (to be able to find it again from // another class in a multiple inheritance tree) nr2ptr.push_back(info.downcaster(typeid(T),p)); (*this) & *p; } else { bool downcasted; std::string name; (*this) & downcasted & name; if(downcasted) { // if the class has been downcasted we can assume it is in the register auto info = GetArchiveRegister(name); p = static_cast(info.upcaster(typeid(T), nr2ptr[nr])); } else p = static_cast(nr2ptr[nr]); } } return *this; } Archive& operator&(std::tuple<>&) { return *this; } template Archive& operator&(std::tuple &t) { // call operator& for each element of the tuple std::apply([this](auto&... arg) { std::make_tuple(((*this) & arg).IsParallel()...);}, t); return *this; } // const ptr template Archive& operator &(const T*& t) { return (*this) & const_cast(t); // NOLINT } // Write a read only variable template Archive & operator << (const T & t) { T ht(t); (*this) & ht; return *this; } virtual void FlushBuffer() {} bool parallel = false; bool IsParallel() const { return parallel; } void SetParallel (bool _parallel) { parallel = _parallel; } private: template friend class RegisterClassForArchive; #ifdef NETGEN_PYTHON friend pybind11::object CastAnyToPy(const std::any&); #endif // NETGEN_PYTHON // Returns ClassArchiveInfo of Demangled typeid static const detail::ClassArchiveInfo& GetArchiveRegister(const std::string& classname); // Set ClassArchiveInfo for Demangled typeid, this is done by creating an instance of // RegisterClassForArchive static void SetArchiveRegister(const std::string& classname, const detail::ClassArchiveInfo& info); static bool IsRegistered(const std::string& classname); // Helper class for up-/downcasting template struct Caster{}; template struct Caster> { static void* tryUpcast (const std::type_info& /*unused*/, T* /*unused*/) { throw Exception("Upcast not successful, some classes are not registered properly for archiving!"); } static void* tryDowncast (const std::type_info& /*unused*/, void* /*unused*/) { throw Exception("Downcast not successful, some classes are not registered properly for archiving!"); } }; template struct Caster { static void* tryUpcast(const std::type_info& ti, T* p) { try { return GetArchiveRegister(Demangle(typeid(B1).name())) .upcaster(ti, static_cast(dynamic_cast(p))); } catch (const Exception &) { throw Exception("Upcast not successful, some classes are not " "registered properly for archiving!"); } } static void* tryDowncast(const std::type_info& ti, void* p) { if(typeid(B1) == ti) return dynamic_cast(static_cast(p)); try { return dynamic_cast(static_cast(GetArchiveRegister(Demangle(typeid(B1).name())). downcaster(ti, p))); } catch (const Exception &) { throw Exception("Downcast not successful, some classes are not " "registered properly for archiving!"); } } }; template struct Caster> { static void* tryUpcast(const std::type_info& ti, T* p) { try { return GetArchiveRegister(Demangle(typeid(B1).name())). upcaster(ti, static_cast(dynamic_cast(p))); } catch(const Exception&) { return Caster>::tryUpcast(ti, p); } } static void* tryDowncast(const std::type_info& ti, void* p) { if(typeid(B1) == ti) return dynamic_cast(static_cast(p)); try { return dynamic_cast(static_cast(GetArchiveRegister(Demangle(typeid(B1).name())). downcaster(ti, p))); } catch(const Exception&) { return Caster>::tryDowncast(ti, p); } } }; }; // BinaryOutArchive ====================================================================== class NGCORE_API BinaryOutArchive : public Archive { static constexpr size_t BUFFERSIZE = 1024; std::array buffer{}; size_t ptr = 0; protected: std::shared_ptr stream; public: BinaryOutArchive() = delete; BinaryOutArchive(const BinaryOutArchive&) = delete; BinaryOutArchive(BinaryOutArchive&&) = delete; BinaryOutArchive(std::shared_ptr&& astream) : Archive(true), stream(std::move(astream)) { } BinaryOutArchive(const std::filesystem::path& filename) : BinaryOutArchive(std::make_shared(filename)) {} ~BinaryOutArchive () override { FlushBuffer(); } BinaryOutArchive& operator=(const BinaryOutArchive&) = delete; BinaryOutArchive& operator=(BinaryOutArchive&&) = delete; using Archive::operator&; Archive & operator & (std::byte & d) override { return Write(d); } Archive & operator & (float & f) override { return Write(f); } Archive & operator & (double & d) override { return Write(d); } Archive & operator & (int & i) override { return Write(i); } Archive & operator & (short & i) override { return Write(i); } Archive & operator & (long & i) override { // for platform independence if constexpr (sizeof(long) == 8) return Write(i); else return Write(static_cast(i)); } Archive & operator & (size_t & i) override { // for platform independence if constexpr (sizeof(size_t) == 8) return Write(i); else return Write(static_cast(i)); } Archive & operator & (unsigned char & i) override { return Write(i); } Archive & operator & (bool & b) override { return Write(b); } Archive & operator & (std::string & str) override { int len = str.length(); (*this) & len; FlushBuffer(); if(len) stream->write (&str[0], len); return *this; } Archive & operator & (char *& str) override { long len = str ? static_cast(strlen (str)) : -1; (*this) & len; FlushBuffer(); if(len > 0) stream->write (&str[0], len); // NOLINT return *this; } void FlushBuffer() override { if (ptr > 0) { stream->write(&buffer[0], ptr); ptr = 0; } } Archive & Do (std::byte * d, size_t n) override { FlushBuffer(); stream->write(reinterpret_cast(d), n*sizeof(std::byte)); return *this; } private: template Archive & Write (T x) { static_assert(sizeof(T) < BUFFERSIZE, "Cannot write large types with this function!"); if (unlikely(ptr > BUFFERSIZE-sizeof(T))) { stream->write(&buffer[0], ptr); ptr = 0; } memcpy(&buffer[ptr], &x, sizeof(T)); ptr += sizeof(T); return *this; } }; // BinaryInArchive ====================================================================== class NGCORE_API BinaryInArchive : public Archive { protected: std::shared_ptr stream; public: BinaryInArchive (std::shared_ptr&& astream) : Archive(false), stream(std::move(astream)) { } BinaryInArchive (const std::filesystem::path& filename) : BinaryInArchive(std::make_shared(filename)) { ; } using Archive::operator&; Archive & operator & (std::byte & d) override { Read(d); return *this; } Archive & operator & (float & f) override { Read(f); return *this; } Archive & operator & (double & d) override { Read(d); return *this; } Archive & operator & (int & i) override { Read(i); return *this; } Archive & operator & (short & i) override { Read(i); return *this; } Archive & operator & (long & i) override { // for platform independence if constexpr (sizeof(long) == 8) Read(i); else { int64_t tmp = 0; Read(tmp); i = tmp; } return *this; } Archive & operator & (size_t & i) override { // for platform independence if constexpr (sizeof(long) == 8) Read(i); else { uint64_t tmp = 0; Read(tmp); i = tmp; } return *this; } Archive & operator & (unsigned char & i) override { Read(i); return *this; } Archive & operator & (bool & b) override { Read(b); return *this; } Archive & operator & (std::string & str) override { int len; (*this) & len; str.resize(len); if(len) stream->read(&str[0], len); // NOLINT return *this; } Archive & operator & (char *& str) override { long len; (*this) & len; if(len == -1) str = nullptr; else { str = new char[len+1]; // NOLINT stream->read(&str[0], len); // NOLINT str[len] = '\0'; // NOLINT } return *this; } Archive & Do (std::byte * d, size_t n) override { stream->read(reinterpret_cast(d), n*sizeof(std::byte)); return *this; } // NOLINT Archive & Do (double * d, size_t n) override { stream->read(reinterpret_cast(d), n*sizeof(double)); return *this; } // NOLINT Archive & Do (int * i, size_t n) override { stream->read(reinterpret_cast(i), n*sizeof(int)); return *this; } // NOLINT Archive & Do (size_t * i, size_t n) override { // for platform independence if constexpr (sizeof(long) == 8) stream->read(reinterpret_cast(i), n*sizeof(size_t)); // NOLINT else for(size_t j = 0; j < n; j++) (*this) & i[j]; return *this; } private: template inline void Read(T& val) { stream->read(reinterpret_cast(&val), sizeof(T)); } // NOLINT }; // TextOutArchive ====================================================================== class NGCORE_API TextOutArchive : public Archive { protected: std::shared_ptr stream; public: TextOutArchive (std::shared_ptr&& astream) : Archive(true), stream(std::move(astream)) { } TextOutArchive (const std::filesystem::path& filename) : TextOutArchive(std::make_shared(filename)) { } using Archive::operator&; Archive & operator & (std::byte & d) override { *stream << int(d) << ' '; return *this; } Archive & operator & (float & f) override { *stream << f << '\n'; return *this; } Archive & operator & (double & d) override { *stream << d << '\n'; return *this; } Archive & operator & (int & i) override { *stream << i << '\n'; return *this; } Archive & operator & (short & i) override { *stream << i << '\n'; return *this; } Archive & operator & (long & i) override { *stream << i << '\n'; return *this; } Archive & operator & (size_t & i) override { *stream << i << '\n'; return *this; } Archive & operator & (unsigned char & i) override { *stream << int(i) << '\n'; return *this; } Archive & operator & (bool & b) override { *stream << (b ? 't' : 'f') << '\n'; return *this; } Archive & operator & (std::string & str) override { int len = str.length(); *stream << len << '\n'; if(len) { stream->write(&str[0], len); // NOLINT *stream << '\n'; } return *this; } Archive & operator & (char *& str) override { long len = str ? static_cast(strlen (str)) : -1; *this & len; if(len > 0) { stream->write (&str[0], len); // NOLINT *stream << '\n'; } return *this; } }; // TextInArchive ====================================================================== class NGCORE_API TextInArchive : public Archive { protected: std::shared_ptr stream; public: TextInArchive (std::shared_ptr&& astream) : Archive(false), stream(std::move(astream)) { } TextInArchive (const std::filesystem::path& filename) : TextInArchive(std::make_shared(filename)) {} using Archive::operator&; Archive & operator & (std::byte & d) override { int tmp; *stream >> tmp; d = std::byte(tmp); return *this; } Archive & operator & (float & f) override { *stream >> f; return *this; } Archive & operator & (double & d) override { *stream >> d; return *this; } Archive & operator & (int & i) override { *stream >> i; return *this; } Archive & operator & (short & i) override { *stream >> i; return *this; } Archive & operator & (long & i) override { *stream >> i; return *this; } Archive & operator & (size_t & i) override { *stream >> i; return *this; } Archive & operator & (unsigned char & i) override { int _i; *stream >> _i; i = _i; return *this; } Archive & operator & (bool & b) override { char c; *stream >> c; b = (c=='t'); return *this; } Archive & operator & (std::string & str) override { // Ignore \r (carriage return) characters when reading strings // this is necessary for instance when a file was written on Windows and is read on Unix int len; *stream >> len; char ch; stream->get(ch); // read newline character if(ch == '\r') // windows line endings -> read \n as well stream->get(ch); str.resize(len); if(len) stream->get(&str[0], len+1, '\0'); // remove all \r characters from the string, check if size changed // if so, read the remaining characters str.erase(std::remove(str.begin(), str.end(), '\r'), str.cend()); size_t chars_to_read = len-str.size(); while (chars_to_read>0) { auto old_size = str.size(); str.resize(len); stream->get(&str[old_size], chars_to_read+1, '\0'); str.erase(std::remove(str.begin()+old_size, str.end(), '\r'), str.cend()); chars_to_read = len - str.size(); } return *this; } Archive & operator & (char *& str) override { long len; (*this) & len; char ch; if(len == -1) { str = nullptr; return (*this); } str = new char[len+1]; // NOLINT if(len) { stream->get(ch); // \n if(ch == '\r') // windows line endings, read \n as well stream->get(ch); stream->get(&str[0], len+1, '\0'); // NOLINT } str[len] = '\0'; // NOLINT return *this; } }; // HashArchive ================================================================= // This class enables to easily create hashes for archivable objects by xoring // threw its data class NGCORE_API HashArchive : public Archive { size_t hash_value = 0; char* h; int offset = 0; public: HashArchive() : Archive(true) { h = (char*)&hash_value; } using Archive::operator&; Archive & operator & (std::byte & d) override { return ApplyHash(d); } Archive & operator & (float & f) override { return ApplyHash(f); } Archive & operator & (double & d) override { return ApplyHash(d); } Archive & operator & (int & i) override { return ApplyHash(i); } Archive & operator & (short & i) override { return ApplyHash(i); } Archive & operator & (long & i) override { return ApplyHash(i); } Archive & operator & (size_t & i) override { return ApplyHash(i); } Archive & operator & (unsigned char & i) override { return ApplyHash(i); } Archive & operator & (bool & b) override { return ApplyHash(b); } Archive & operator & (std::string & str) override { for(auto c : str) ApplyHash(c); return *this; } Archive & operator & (char *& str) override { char* s = str; while(*s != '\0') ApplyHash(*(s++)); return *this; } // HashArchive can be used in const context template Archive & operator& (const T& val) const { return (*this) & const_cast(val); } size_t GetHash() const { return hash_value; } private: template Archive& ApplyHash(T val) { size_t n = sizeof(T); char* pval = (char*)&val; for(size_t i = 0; i < n; i++) { h[offset++] ^= pval[i]; offset %= 8; } return *this; } }; } // namespace ngcore #endif // NETGEN_CORE_ARCHIVE_HPP