/** * The purpose of this file is to provide the definition of LoadWithReadCallback. * This function is heavy on template metaprogramming, so we don't want to * recompile it in every translation unit. Instead, each serialized structure * should have exactly one file associated to it that includes this header and * then explicitly instantiates LoadWithReadCallback. */ #pragma once #ifndef FORMATS_IMPL_EXPLICIT_FORMAT_INSTANTIATIONS_INTENDED #error This file should only be included if you want to explicitly instantiate LoadWithReadCallback for user-defined structures #endif #include #include #include #include #include #include namespace FileFormat { /// Type-erasing helper class to allow compiling the deserialization code only once template class TypeMapper> struct StreamReader { T operator()(std::function& reader) const { typename TypeMapper::type val; reader(reinterpret_cast(&val), sizeof(val)); return T { val }; } }; template class TypeMapper> struct StreamReader, TypeMapper> { auto operator()(std::function& reader) const { std::array ret; for (auto& val : ret) val = StreamReader(reader); return ret; } }; template class TypeMapper> struct StreamWriter { void operator()(const T& val, std::function& writer) const { // TODO: Check if Data is brace-intializable from T... typename TypeMapper::type mapped_val{val}; writer(reinterpret_cast(&mapped_val), sizeof(mapped_val)); } }; template class TypeMapper> struct StreamWriter, TypeMapper> { void operator()(const std::array& val, std::function& writer) const { for (auto& elem : val) StreamWriter{}(elem, writer); } }; template using void_t = void; namespace detail { struct DefaultTags : endianness_tag, expected_size_tag<0> { }; template struct SelectEndiannessTagT { using type = endianness_tag; }; template struct SelectEndiannessTagT> { using type = endianness_tag; }; template using SelectEndiannessTag = typename SelectEndiannessTagT::type; template struct SelectExpectedSizeTagT { // No size expectation for the new data unless explicitly provided using type = expected_size_tag<0>; }; template struct SelectExpectedSizeTagT> { using type = expected_size_tag; }; template using SelectExpectedSizeTag = typename SelectExpectedSizeTagT::type; } // namespace detail template using MergeTags = Meta::inherited, detail::SelectExpectedSizeTag>; template using TagsOf = MergeTags; template static constexpr auto HasTag = std::is_base_of::value; template struct HasBigEndianTag : std::integral_constant> { }; template class OnTrue, template class OnFalse, typename T> using MetaConditional = typename std::conditional, OnFalse>::type; template struct Mapper { template using mapper_template = MetaConditional::value, TypeMapperBigEndian, TypeMapper3DS, T>; }; namespace hana = boost::hana; template struct is_std_array : std::false_type {}; template struct is_std_array> : std::true_type {}; template struct DataToTupleT; template struct DataToTupleT::value && !is_std_array::value>::type> { auto operator()(const T& data) const { return hana::to(hana::transform(hana::to_tuple(data), hana::second)); } }; template struct DataToTupleT::value || is_std_array::value>::type> { auto operator()(const T& data) const { return std::tuple(data); } }; template struct MembersAsTuple; template struct MembersAsTuple::value && !is_std_array::value>::type> { using type = decltype(DataToTupleT{}(std::declval())); }; template struct MembersAsTuple::value || is_std_array::value>::type> { using type = std::tuple; }; template using MembersAsTuple_t = typename MembersAsTuple::type; template> struct SerializedSize : std::integral_constant { }; template struct MakeSum; template<> struct MakeSum<> : std::integral_constant {}; // TODO: Static assert against overflows! template struct MakeSum : std::integral_constant::value> {}; template struct MakeProduct; template<> struct MakeProduct<> : std::integral_constant {}; // TODO: Static assert against overflows! template struct MakeProduct : std::integral_constant::value> {}; // Non-array structure types only template struct SerializedSize && !Meta::is_std_array_v && !Meta::is_std_tuple_v>::type> : SerializedSize> { }; // Arrays only template struct SerializedSize, void> : MakeProduct::value> { }; // Non-structure and non-tuple types template struct SerializedSize && !Meta::is_std_tuple_v>::type> : std::integral_constant { }; // Collections of types template struct SerializedSize, void> : MakeSum::value...> {}; template struct CheckSizeExpectations { // No size expectations specified, so just return a match }; template struct CheckSizeExpectations::type> { static_assert(Meta::CHECK_EQUALITY_VERBOSELY::value, ""); }; template struct ConstructFromReadCallback; // Non-array structure types only template struct ConstructFromReadCallback, typename std::enable_if::value && !is_std_array::value>::type> { // TODO: Check if Data is brace-intializable from T... Data operator()(std::function& reader) const { (void)CheckSizeExpectations::value>{}; namespace hana = boost::hana; return Data { ConstructFromReadCallback, MembersAsTuple_t>{}(reader)... }; } }; // Arrays only template struct ConstructFromReadCallback, typename std::enable_if::value>::type> { // TODO: Check if Data is brace-intializable from T... Data operator()(std::function& reader) const { using Value = typename Data::value_type; Data ret; for (auto& val : ret) val = ConstructFromReadCallback, std::tuple>{}(reader); return ret; } }; // Non-array plain elements only template struct ConstructFromReadCallback, typename std::enable_if::value && !is_std_array::value>::type> { // TODO: Check if Data is brace-intializable from T... Data operator()(std::function& reader) const { // TODO: Why do we bother using T here at all? Should be sufficient to just use Data directly... return Data { StreamReader::template mapper_template>{}(reader)... }; } }; template Data SerializationInterface::Load(std::function reader) { // For small data, read to a stack buffer first std::array raw; if (sizeof(Data) <= 0x200) { size_t read_size = sizeof(Data); if constexpr (TagsOf::expected_serialized_size != 0) { read_size = Data::Tags::expected_serialized_size; } reader(raw.data(), read_size); reader = [&raw, offset = size_t { 0 }](char* dest, size_t size) mutable { memcpy(dest, raw.data() + offset, size); offset += size; }; } namespace hana = boost::hana; using hana_tuple = decltype(hana::to_tuple(std::declval())); using hana_types_tuple = decltype(hana::transform(hana_tuple{}, hana::second)); using TupleType = decltype(hana::to(hana_types_tuple{})); return ConstructFromReadCallback, TupleType>{}(reader); } template<> uint32_t SerializationInterface::Load(std::function reader); template<> boost::endian::little_uint32_t SerializationInterface::Load(std::function reader); template<> boost::endian::big_uint32_t SerializationInterface::Load(std::function reader); template struct SaveWithWriteCallback; template struct SaveWithWriteCallbackHelper { void operator()(const MembersTuple& data, std::function& writer) { // Do nothing if index is larger than the tuple size } }; template //struct SaveWithWriteCallbackHelper(std::declval()))>> { struct SaveWithWriteCallbackHelper)>> { void operator()(const MembersTuple& data, std::function& writer) { // using SubType = std::remove_cv_t(data))>>; using SubType = std::remove_cv_t>>; SaveWithWriteCallback, MembersAsTuple_t>{}(std::get(data), writer); SaveWithWriteCallbackHelper{}(data, writer); } }; // Non-array structure types only template struct SaveWithWriteCallback, typename std::enable_if::value && !is_std_array::value>::type> { void operator()(const Data& data, std::function& writer) const { (void)CheckSizeExpectations::value>{}; namespace hana = boost::hana; SaveWithWriteCallbackHelper<0, DataTags, MembersAsTuple_t>{}(DataToTupleT{}(data), writer); } }; // Arrays only template struct SaveWithWriteCallback, typename std::enable_if::value>::type> { void operator()(const Data& data, std::function& writer) const { using Value = typename Data::value_type; for (auto& val : data) SaveWithWriteCallback, std::tuple>{}(val, writer); } }; // Non-array plain elements only template struct SaveWithWriteCallback, typename std::enable_if::value && !is_std_array::value>::type> { void operator()(const Data& data, std::function& writer) const { StreamWriter::template mapper_template>{}(data, writer); } }; template void SerializationInterface::Save(const Data& data, std::function writer) { namespace hana = boost::hana; using hana_tuple = decltype(hana::to_tuple(std::declval())); using hana_types_tuple = decltype(hana::transform(hana_tuple{}, hana::second)); using TupleType = decltype(hana::to(hana_types_tuple{})); SaveWithWriteCallback, TupleType>{}(data, writer); } template<> inline void SerializationInterface::Save(const uint32_t& data, std::function writer) { SaveWithWriteCallback>{}(data, writer); } template<> void SerializationInterface::Save(const boost::endian::big_uint32_t& data, std::function writer); } // namespace FileFormat