#pragma once #include #include #include #include #include namespace FileFormat { enum class Endianness { Big, Little, Undefined }; template struct endianness_tag { static constexpr auto endianness = E; }; using big_endian_tag = endianness_tag; using little_endian_tag = endianness_tag; struct expected_size_tag_base { }; /// Used to annotate serializeable structures with the expected total size of the serialized data (used as a static assertion) template struct expected_size_tag : expected_size_tag_base { static constexpr auto expected_serialized_size = Size; }; template struct StreamIn; template struct StreamOut; /*template<> struct StreamIn { static constexpr bool IsStreamInInstance = true; // TODO: Consider changing this to a template for size? Or better yet, the storage type? void Read(char* dest, size_t size); private: std::istream stream; };*/ template struct StreamInFromContainer { StreamInFromContainer(ForwardIt begin, EndIt end) : cursor(begin), end(end) { } void Read(char* dest, size_t size) { // static_assert(sizeof(typename std::iterator_traits::value_type) == sizeof(char), ""); // TODO: Assert we don't copy past the end! for (auto remaining = size; remaining != 0; --remaining) { assert(cursor != end); *dest++ = *cursor++; } } operator std::function() { return [this](char* dest, size_t size) { Read(dest, size); }; } static constexpr bool IsStreamInInstance = true; private: ForwardIt cursor; EndIt end; }; template StreamInFromContainer MakeStreamInFromContainer(ForwardIt begin, EndIt end) { return StreamInFromContainer{begin, end}; } template auto MakeStreamInFromContainer(Rng&& rng) -> StreamInFromContainer { return StreamInFromContainer { std::begin(rng), std::end(rng) }; } template struct StreamOutFromContainer { StreamOutFromContainer(ForwardIt begin) : cursor(begin) { } void Write(char* data, size_t size) { for (auto remaining = size; remaining != 0; --remaining) *cursor++ = *data++; } static constexpr bool IsStreamOutInstance = true; private: ForwardIt cursor; }; template StreamOutFromContainer MakeStreamOutFromContainer(ForwardIt begin) { return StreamOutFromContainer{begin}; } /** * Maps given type to a buffer type. The buffer type must be convertible to the type. * For instance, the type uint32_t would map to little_uint32_t. */ template struct TypeMapper3DS; template<> struct TypeMapper3DS { using type = boost::endian::little_uint64_at; }; template<> struct TypeMapper3DS { using type = boost::endian::little_uint32_at; }; template<> struct TypeMapper3DS { using type = boost::endian::little_uint16_at; }; template<> struct TypeMapper3DS { using type = boost::endian::little_uint8_at; }; template struct TypeMapper3DS> { using type = std::array::type, size>; }; /** * Maps given type to a buffer type. The buffer type must be convertible to the type. * For instance, the type uint32_t would map to little_uint32_t. */ template struct TypeMapperBigEndian; template<> struct TypeMapperBigEndian { using type = boost::endian::big_uint64_at; }; template<> struct TypeMapperBigEndian { using type = boost::endian::big_uint32_at; }; template<> struct TypeMapperBigEndian { using type = boost::endian::big_uint16_at; }; template<> struct TypeMapperBigEndian { using type = boost::endian::big_uint8_at; }; template struct TypeMapperBigEndian> { using type = std::array::type, size>; }; // TODO: TypeMapper for enum -> TypeMapper // TODO: TypeMapper for union -> TypeMapper>, iff the union has a tag "TypeMap_to_storage" /** * Helper class to offload compilation-time intensive part of the implementation in a standalone * translation unit. The implementation is quite heavy on metaprogramming and hence would make * compile time explode if we were to recompile it in each unit. Unfortunately, this means we * cannot have a proper generic implementation in terms of the Stream parameter, but the overhead * of std::function is probably small compared to the actual file reading operation anyway. * NOTE: This is a template struct rather than a template function because that eases explicit instantiation in the main translation unit. */ template struct SerializationInterface { static Data Load(std::function reader); static void Save(const Data& data, std::function writer); }; template Data Load(Stream& stream_in) { static_assert(Stream::IsStreamInInstance, "Given stream must have an IsStreamInInstance member"); return SerializationInterface::Load([&stream_in](char* dest, size_t size) {stream_in.Read(dest, size); }); } template Data Load(std::istream& istream) { // Wrap in a dummy enable_if_t so that this header doesn't need to pull in (only users of this function do) auto& istream2 = static_cast 0), std::istream&>>(istream); return SerializationInterface::Load([&istream2](char* dest, size_t size) {istream2.read(dest, size); }); } template Data LoadValue(std::function reader) { boost::endian::endian_buffer buffer; reader(reinterpret_cast(buffer.data()), sizeof(Data)); return buffer.value(); } template auto Save(const Data& data, Stream& stream_out) -> std::enable_if_t { static_assert(Stream::IsStreamOutInstance, "Given stream must have an IsStreamOutInstance member"); SerializationInterface::Save(data, [&stream_out](char* data, size_t size) {stream_out.Write(data, size); }); } // Wrap in a dummy enable_if_t so that this header doesn't need to pull in (only users of this function do) template void Save(const Data& data, std::ostream& ostream) { // Wrap in a dummy enable_if_t so that this header doesn't need to pull in (only users of this function do) auto& ostream2 = static_cast 0), std::ostream&>>(ostream); SerializationInterface::Save(data, [&ostream2](char* data, size_t size) {ostream2.write(data, size); }); } } // namespace FileFormat