netgen/libsrc/core/archive.hpp
2024-01-13 21:15:55 +01:00

1213 lines
40 KiB
C++

#ifndef NETGEN_CORE_ARCHIVE_HPP
#define NETGEN_CORE_ARCHIVE_HPP
#include <any>
#include <array> // for array
#include <complex> // for complex
#include <cstring> // for size_t, strlen
#include <filesystem> // for path
#include <fstream> // for ifstream, ofstream
#include <functional> // for function
#include <map> // for map
#include <memory> // for shared_ptr
#include <optional> // for optional
#include <string> // for string
#include <type_traits> // for declval, enable_if_t, false_type, is_co...
#include <cstddef> // for std::byte
#include <set> // for set
#include <typeinfo> // for type_info
#include <utility> // for move, swap, pair
#include <vector> // 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 <typename T>
struct Shallow {
T val;
Shallow() = default;
Shallow(T aval) : val(aval) { ; }
operator T&() { return val; }
};
// Helper to detect shared_from_this
template <typename T>
class has_shared_from_this2
{
private:
// typedef T* T_ptr;
template <typename C> static std::true_type test(decltype(((C*)nullptr)->shared_from_this()));
template <typename C> static std::false_type test(...);
public:
// If the test returns true_type, then T has shared_from_this
static constexpr bool value = decltype(test<T>(0))::value;
};
#ifdef NETGEN_PYTHON
pybind11::object CastAnyToPy(const std::any& a);
#endif // NETGEN_PYTHON
class NGCORE_API Archive;
namespace detail
{
template <class T, class Tuple, size_t... Is>
T* construct_from_tuple(Tuple&& tuple, std::index_sequence<Is...> ) {
return new T{std::get<Is>(std::forward<Tuple>(tuple))...};
}
template <class T, class Tuple>
T* construct_from_tuple(Tuple&& tuple) {
return construct_from_tuple<T>(std::forward<Tuple>(tuple),
std::make_index_sequence<std::tuple_size<std::decay_t<Tuple>>::value>{}
);
}
// create new pointer of type T if it is default constructible, else throw
template<typename T, typename... TArgs>
T* constructIfPossible(std::tuple<TArgs...> args)
{
if constexpr(std::is_constructible_v<T, TArgs...>)
return construct_from_tuple<T>(args);
throw Exception(std::string(Demangle(typeid(T).name())) +
" is not constructible!");
}
template <typename T> T *constructIfPossible()
{
if constexpr(std::is_constructible_v<T>)
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<typename T>
struct has_DoArchive
{
private:
template<typename T2>
static constexpr auto check(T2*) ->
typename std::is_same<decltype(std::declval<T2>().DoArchive(std::declval<Archive&>())),void>::type;
template<typename>
static constexpr std::false_type check(...);
using type = decltype(check<T>(nullptr)); // NOLINT
public:
NGCORE_API static constexpr bool value = type::value;
};
// Check if class is archivable
template<typename T>
struct is_Archivable_struct
{
private:
template<typename T2>
static constexpr auto check(T2*) ->
typename std::is_same<decltype(std::declval<Archive>() & std::declval<T2&>()),Archive&>::type;
template<typename>
static constexpr std::false_type check(...);
using type = decltype(check<T>(nullptr)); // NOLINT
public:
NGCORE_API static constexpr bool value = type::value;
};
template <typename T>
struct has_GetCArgs
{
template <typename C> static std::true_type check( decltype( sizeof(&C::GetCArgs )) ) { return std::true_type(); }
template <typename> static std::false_type check(...) { return std::false_type(); }
typedef decltype( check<T>(sizeof(char)) ) type;
static constexpr type value = type();
};
template<typename T>
constexpr bool has_GetCArgs_v = has_GetCArgs<T>::value;
template<typename T,
typename std::enable_if<!has_GetCArgs_v<T>>::type* = nullptr>
std::tuple<> GetCArgs(T&val) { return {}; }
template<typename T,
typename std::enable_if<has_GetCArgs_v<T>>::type* = nullptr>
auto GetCArgs(T&val) {
return val.GetCArgs();
}
template<typename T>
using TCargs = decltype(GetCArgs<T>(*static_cast<T*>(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<void*(const std::type_info&)> 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<void*(const std::type_info&, void*)> 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<void*(const std::type_info&, void*)> downcaster;
void* (*downcaster)(const std::type_info&, void*);
// Archive constructor arguments
// std::function<void(Archive&, void*)> cargs_archiver;
void (*cargs_archiver)(Archive&, void*);
#ifdef NETGEN_PYTHON
// std::function<pybind11::object(const std::any&)> anyToPyCaster;
pybind11::object (*anyToPyCaster)(const std::any&);
#endif // NETGEN_PYTHON
};
} // namespace detail
template<typename T>
constexpr bool is_archivable = detail::is_Archivable_struct<T>::value;
template <typename T, typename ... Trest>
constexpr size_t TotSize ()
{
if constexpr (sizeof...(Trest) == 0)
return sizeof(T);
else
return sizeof(T) + TotSize<Trest...> ();
}
// 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<void*, int> shared_ptr2nr{}, ptr2nr{};
// vectors for storing the unarchived (shared) pointers
std::vector<std::shared_ptr<void>> nr2shared_ptr{};
std::vector<void*> nr2ptr{};
protected:
bool shallow_to_python = false;
std::map<std::string, VersionInfo> version_map = GetLibraryVersions();
public:
template<typename T>
static constexpr bool is_archivable = detail::is_Archivable_struct<T>::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<typename T>
Archive& Shallow(T& val); // implemented in python_ngcore.hpp
#else // NETGEN_PYTHON
template<typename T>
Archive& Shallow(T& val)
{
static_assert(detail::is_any_pointer<T>, "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<typename T>
Archive& operator & (std::complex<T>& c)
{
if(Output())
(*this) << c.real() << c.imag();
else
{
T tmp;
(*this) & tmp;
c.real(tmp);
(*this) & tmp;
c.imag(tmp);
}
return (*this);
}
template<typename T>
Archive& operator & (std::vector<T>& 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<typename T>
auto operator & (T& val) -> std::enable_if_t<std::is_enum<T>::value, Archive&>
{
int enumval;
if(Output())
enumval = int(val);
*this & enumval;
if(Input())
val = T(enumval);
return *this;
}
// vector<bool> 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<bool>& v)
{
size_t size;
if(Output())
size = v.size();
(*this) & size;
if(Input())
{
v.resize(size);
bool b;
for(size_t i=0; i<size; i++)
{
(*this) & b;
v[i] = b;
}
}
else
{
for(bool b : v)
(*this) & b;
}
return *this;
}
template<typename T1, typename T2>
Archive& operator& (std::map<T1, T2>& 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<typename T>
Archive& operator& (std::optional<T>& 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 <typename T>
Archive& operator&(std::set<T> &s)
{
auto size = s.size();
(*this) & size;
if(Output())
for(const auto & val : s)
(*this) << val;
else
{
for(size_t i=0; i<size; i++)
{
T val;
(*this) & val;
s.insert(val);
}
}
return *this;
}
// Archive arrays =====================================================
// this functions can be overloaded in Archive implementations for more efficiency
template <typename T, typename = std::enable_if_t<is_archivable<T>>>
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<typename T, typename=std::enable_if_t<detail::has_DoArchive<T>::value>>
Archive& operator & (T& val)
{
val.DoArchive(*this); return *this;
}
// pack elements to binary
template <typename ... Types>
Archive & DoPacked (Types & ... args)
{
if (true) // (isbinary)
{
constexpr size_t totsize = TotSize<Types...>(); // (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 <typename T, typename ... Trest>
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 <typename T, typename ... Trest>
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 <typename T>
Archive& operator & (ngcore::Shallow<T>& shallow)
{
this->Shallow(shallow.val);
return *this;
}
// Archive shared_ptrs =================================================
template <typename T>
Archive& operator & (std::shared_ptr<T>& ptr)
{
if (has_shared_from_this2<T>::value && 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<void*>(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<T>(p);
// if we did downcast we need to store a shared_ptr<void> 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<void>(std::static_pointer_cast<void>(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<void> in the register, but pointing to our object
ptr = std::static_pointer_cast<T>(std::shared_ptr<void>(other,
info.upcaster(typeid(T),
other.get())));
}
else
{
ptr = std::static_pointer_cast<T>(other);
}
}
}
return *this;
}
// Archive pointers =======================================================
template <typename T>
Archive & operator& (T *& p)
{
if (Output())
{
// if the pointer is null store -2
if (!p)
return (*this) << -2;
auto reg_ptr = static_cast<void*>(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<void*>(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<T>::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<void*>(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<T>();
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<T*>(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<T*>(info.upcaster(typeid(T), nr2ptr[nr]));
}
else
p = static_cast<T*>(nr2ptr[nr]);
}
}
return *this;
}
Archive& operator&(std::tuple<>&) { return *this; }
template <typename... T>
Archive& operator&(std::tuple<T...> &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<typename T>
Archive& operator &(const T*& t)
{
return (*this) & const_cast<T*&>(t); // NOLINT
}
// Write a read only variable
template <typename T>
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<typename T, typename Bases>
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<type, bases...>
static void SetArchiveRegister(const std::string& classname, const detail::ClassArchiveInfo& info);
static bool IsRegistered(const std::string& classname);
// Helper class for up-/downcasting
template<typename T, typename ... Bases>
struct Caster{};
template<typename T>
struct Caster<T, std::tuple<>>
{
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<typename T, typename B1>
struct Caster<T,B1>
{
static void* tryUpcast(const std::type_info& ti, T* p)
{
try {
return GetArchiveRegister(Demangle(typeid(B1).name()))
.upcaster(ti, static_cast<void *>(dynamic_cast<B1 *>(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<T*>(static_cast<B1*>(p));
try
{
return dynamic_cast<T*>(static_cast<B1*>(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<typename T, typename B1, typename ... Brest>
struct Caster<T,std::tuple<B1, Brest...>>
{
static void* tryUpcast(const std::type_info& ti, T* p)
{
try
{ return GetArchiveRegister(Demangle(typeid(B1).name())).
upcaster(ti, static_cast<void*>(dynamic_cast<B1*>(p))); }
catch(const Exception&)
{ return Caster<T, std::tuple<Brest...>>::tryUpcast(ti, p); }
}
static void* tryDowncast(const std::type_info& ti, void* p)
{
if(typeid(B1) == ti)
return dynamic_cast<T*>(static_cast<B1*>(p));
try
{
return dynamic_cast<T*>(static_cast<B1*>(GetArchiveRegister(Demangle(typeid(B1).name())).
downcaster(ti, p)));
}
catch(const Exception&)
{
return Caster<T, std::tuple<Brest...>>::tryDowncast(ti, p);
}
}
};
};
// BinaryOutArchive ======================================================================
class NGCORE_API BinaryOutArchive : public Archive
{
static constexpr size_t BUFFERSIZE = 1024;
std::array<char,BUFFERSIZE> buffer{};
size_t ptr = 0;
protected:
std::shared_ptr<std::ostream> stream;
public:
BinaryOutArchive() = delete;
BinaryOutArchive(const BinaryOutArchive&) = delete;
BinaryOutArchive(BinaryOutArchive&&) = delete;
BinaryOutArchive(std::shared_ptr<std::ostream>&& astream)
: Archive(true), stream(std::move(astream))
{ }
BinaryOutArchive(const std::filesystem::path& filename)
: BinaryOutArchive(std::make_shared<std::ofstream>(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<int64_t>(i));
}
Archive & operator & (size_t & i) override
{
// for platform independence
if constexpr (sizeof(size_t) == 8)
return Write(i);
else
return Write(static_cast<uint64_t>(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<long>(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<char*>(d), n*sizeof(std::byte)); return *this;
}
private:
template <typename T>
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<std::istream> stream;
public:
BinaryInArchive (std::shared_ptr<std::istream>&& astream)
: Archive(false), stream(std::move(astream))
{ }
BinaryInArchive (const std::filesystem::path& filename)
: BinaryInArchive(std::make_shared<std::ifstream>(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<char*>(d), n*sizeof(std::byte)); return *this; } // NOLINT
Archive & Do (double * d, size_t n) override
{ stream->read(reinterpret_cast<char*>(d), n*sizeof(double)); return *this; } // NOLINT
Archive & Do (int * i, size_t n) override
{ stream->read(reinterpret_cast<char*>(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<char*>(i), n*sizeof(size_t)); // NOLINT
else
for(size_t j = 0; j < n; j++)
(*this) & i[j];
return *this;
}
private:
template<typename T>
inline void Read(T& val)
{ stream->read(reinterpret_cast<char*>(&val), sizeof(T)); } // NOLINT
};
// TextOutArchive ======================================================================
class NGCORE_API TextOutArchive : public Archive
{
protected:
std::shared_ptr<std::ostream> stream;
public:
TextOutArchive (std::shared_ptr<std::ostream>&& astream)
: Archive(true), stream(std::move(astream))
{ }
TextOutArchive (const std::filesystem::path& filename) :
TextOutArchive(std::make_shared<std::ofstream>(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<long>(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<std::istream> stream;
public:
TextInArchive (std::shared_ptr<std::istream>&& astream) :
Archive(false), stream(std::move(astream))
{ }
TextInArchive (const std::filesystem::path& filename)
: TextInArchive(std::make_shared<std::ifstream>(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
{
int len;
*stream >> len;
char ch;
stream->get(ch); // '\n'
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');
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<typename T>
Archive & operator& (const T& val) const
{ return (*this) & const_cast<T&>(val); }
size_t GetHash() const { return hash_value; }
private:
template<typename T>
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