/* * flat_buffers.h * * This source file is part of the FoundationDB open source project * * Copyright 2013-2018 Apple Inc. and the FoundationDB project authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #pragma once #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "flow/FileIdentifier.h" #include "flow/ObjectSerializerTraits.h" template , class...> struct concat { using type = T; }; template struct concat, pack, Ts...> : concat, Ts...> {}; template using concat_t = typename concat::type; template constexpr auto pack_size(pack) { return sizeof...(Ts); } inline constexpr int RightAlign(int offset, int alignment) { return offset % alignment == 0 ? offset : ((offset / alignment) + 1) * alignment; } inline int RightAlign(int offset, int alignment, int* padding) { auto aligned = RightAlign(offset, alignment); *padding = aligned - offset; return aligned; } template struct struct_like_traits> : std::true_type { using Member = std::tuple; using types = pack; template static const index_t& get(const Member& m, Context&) { return std::get(m); } template static void assign(Member& m, const Type& t, Context&) { std::get(m) = t; } }; template struct scalar_traits< T, std::enable_if_t::value || std::is_floating_point::value || std::is_enum::value>> : std::true_type { constexpr static size_t size = sizeof(T); template static void save(uint8_t* out, const T& t, Context&) { memcpy(out, &t, size); } template static void load(const uint8_t* in, T& t, Context&) { memcpy(&t, in, size); } }; template struct serializable_traits> : std::true_type { template static void serialize(Archiver& ar, std::pair& p) { serializer(ar, p.first, p.second); } }; template struct vector_like_traits> : std::true_type { using Vec = std::vector; using value_type = typename Vec::value_type; using iterator = typename Vec::const_iterator; using insert_iterator = std::back_insert_iterator; template static size_t num_entries(const Vec& v, Context&) { return v.size(); } template static void reserve(Vec& v, size_t size, Context&) { v.clear(); v.reserve(size); } template static insert_iterator insert(Vec& v, Context&) { return std::back_inserter(v); } template static iterator begin(const Vec& v, Context&) { return v.begin(); } }; template struct vector_like_traits> : std::true_type { using Deq = std::deque; using value_type = typename Deq::value_type; using iterator = typename Deq::const_iterator; using insert_iterator = std::back_insert_iterator; template static size_t num_entries(const Deq& v, Context&) { return v.size(); } template static void reserve(Deq& v, size_t size, Context&) { v.resize(size); v.clear(); } template static insert_iterator insert(Deq& v, Context&) { return std::back_inserter(v); } template static iterator begin(const Deq& v, Context&) { return v.begin(); } }; template struct vector_like_traits> : std::true_type { using Vec = std::array; using value_type = typename Vec::value_type; using iterator = typename Vec::const_iterator; using insert_iterator = typename Vec::iterator; template static size_t num_entries(const Vec& v, Context&) { return N; } template static void reserve(Vec& v, size_t size, Context&) {} template static insert_iterator insert(Vec& v, Context&) { return v.begin(); } template static iterator begin(const Vec& v, Context&) { return v.begin(); } }; template struct vector_like_traits> : std::true_type { using Vec = std::map; using value_type = std::pair; using iterator = typename Vec::const_iterator; using insert_iterator = std::insert_iterator; template static size_t num_entries(const Vec& v, Context&) { return v.size(); } template static void reserve(Vec& v, size_t size, Context&) {} template static insert_iterator insert(Vec& v, Context&) { return std::inserter(v, v.end()); } template static iterator begin(const Vec& v, Context&) { return v.begin(); } }; template struct vector_like_traits> : std::true_type { using Vec = std::unordered_map; using value_type = std::pair; using iterator = typename Vec::const_iterator; using insert_iterator = std::insert_iterator; template static size_t num_entries(const Vec& v, Context&) { return v.size(); } template static void reserve(Vec& v, size_t size, Context&) {} template static insert_iterator insert(Vec& v, Context&) { return std::inserter(v, v.end()); } template static iterator begin(const Vec& v, Context&) { return v.begin(); } }; template struct vector_like_traits> : std::true_type { using Vec = std::set; using value_type = Key; using iterator = typename Vec::const_iterator; using insert_iterator = std::insert_iterator; template static size_t num_entries(const Vec& v, Context&) { return v.size(); } template static void reserve(Vec&, size_t, Context&) {} template static insert_iterator insert(Vec& v, Context&) { return std::inserter(v, v.end()); } template static iterator begin(const Vec& v, Context&) { return v.begin(); } }; template <> struct dynamic_size_traits : std::true_type { private: using T = std::string; public: template static size_t size(const T& t, Context&) { return t.size(); } template static void save(uint8_t* out, const T& t, Context&) { std::copy(t.begin(), t.end(), out); } // Context is an arbitrary type that is plumbed by reference throughout the // load call tree. template static void load(const uint8_t* p, size_t n, T& t, Context&) { t.assign(reinterpret_cast(p), n); } }; namespace detail { template T interpret_as(const uint8_t* current) { T t; memcpy(&t, current, sizeof(t)); return t; } // Used to select an overload for |MessageWriter::write| that fixes relative // offsets. struct RelativeOffset { int value; }; static_assert(sizeof(RelativeOffset) == 4, ""); template constexpr bool is_scalar = scalar_traits::value; template constexpr bool is_dynamic_size = dynamic_size_traits::value; template constexpr bool is_union_like = union_like_traits::value; template constexpr bool is_vector_like = vector_like_traits::value; template constexpr bool is_vector_of_union_like = is_vector_like&& is_union_like::value_type>; template constexpr bool is_struct_like = struct_like_traits::value; template constexpr bool expect_serialize_member = !is_scalar && !is_vector_like && !is_union_like && !is_dynamic_size && !is_struct_like; template constexpr bool use_indirection = !(is_scalar || is_struct_like); using VTable = std::vector; template constexpr int fb_scalar_size = is_scalar ? scalar_traits::size : sizeof(RelativeOffset); template struct struct_offset_impl; template struct struct_offset_impl { static_assert(index == 0); static constexpr auto offset = o; }; template struct struct_offset_impl { private: static constexpr size_t offset_() { if constexpr (index == 0) { return RightAlign(o, fb_scalar_size); } else { return struct_offset_impl) + fb_scalar_size, index - 1, Ts...>::offset; } #ifdef __INTEL_COMPILER // ICC somehow thinks that this method does not return // see: https://software.intel.com/en-us/forums/intel-c-compiler/topic/799473 return 1; #endif } public: static_assert(!is_struct_like, "Nested structs not supported yet"); static constexpr auto offset = offset_(); }; constexpr size_t AlignToPowerOfTwo(size_t s) { if (s > 4) { return 8; } else if (s > 2) { return 4; } else if (s > 1) { return 2; } else { return 1; } } template constexpr auto align_helper(pack) { return std::max({ size_t{ 1 }, AlignToPowerOfTwo(fb_scalar_size)... }); } template constexpr auto struct_size(pack) { return std::max(1, RightAlign(struct_offset_impl<0, sizeof...(T), T...>::offset, align_helper(pack{}))); } template constexpr auto struct_offset(pack) { static_assert(i < sizeof...(T)); return struct_offset_impl<0, i, T...>::offset; } static_assert(struct_offset<0>(pack{}) == 0); static_assert(struct_offset<1>(pack{}) == 4); static_assert(struct_offset<2>(pack{}) == 8); static_assert(struct_size(pack<>{}) == 1); static_assert(struct_size(pack{}) == 4); static_assert(struct_size(pack{}) == 8); static_assert(struct_size(pack{}) == 16); template constexpr int fb_size = is_struct_like ? struct_size(typename struct_like_traits::types{}) : fb_scalar_size; template constexpr int fb_align = is_struct_like ? align_helper(typename struct_like_traits::types{}) : AlignToPowerOfTwo(fb_scalar_size); template struct _SizeOf { static constexpr unsigned int size = fb_size; static constexpr unsigned int align = fb_align; }; // Re-use this intermediate memory to avoid frequent new/delete void swapWithThreadLocalGlobal(std::vector& writeToOffsets); template struct PrecomputeSize : Context { PrecomputeSize(const Context& context) : Context(context) { swapWithThreadLocalGlobal(writeToOffsets); writeToOffsets.clear(); } ~PrecomputeSize() { swapWithThreadLocalGlobal(writeToOffsets); } // |offset| is measured from the end of the buffer. Precondition: len <= // offset. void write(const void*, int offset, int /*len*/) { current_buffer_size = std::max(current_buffer_size, offset); } template std::enable_if_t, bool> visitDynamicSize(const T& t) { uint32_t size = dynamic_size_traits::size(t, this->context()); if (size == 0 && emptyVector.value != -1) { return true; } int start = RightAlign(current_buffer_size + size + 4, 4); current_buffer_size = std::max(current_buffer_size, start); if (size == 0) { emptyVector = RelativeOffset{ current_buffer_size }; } return false; } struct Noop { void write(const void* src, int offset, int /*len*/) {} void writeTo(PrecomputeSize& writer, int offset) { writer.write(nullptr, offset, size); writer.writeToOffsets[writeToIndex] = offset; } void writeTo(PrecomputeSize& writer) { writeTo(writer, writer.current_buffer_size + size); } int size; int writeToIndex; }; Noop getMessageWriter(int size, bool /*zeroed*/ = false) { int writeToIndex = writeToOffsets.size(); writeToOffsets.push_back({}); return Noop{ size, writeToIndex }; } int current_buffer_size = 0; const int buffer_length = -1; // Dummy, the value of this should not affect anything. const int vtable_start = -1; // Dummy, the value of this should not affect anything. std::vector writeToOffsets; // We only need to write an empty vector once, then we can re-use the relative offset. RelativeOffset emptyVector{ -1 }; }; template void load_helper(Member&, const uint8_t*, const Context&); struct VTableSet; template struct is_array : std::false_type {}; template struct is_array> : std::true_type {}; template struct WriteToBuffer : Context { // |offset| is measured from the end of the buffer. Precondition: len <= // offset. void write(const void* src, int offset, int len) { copy_memory(src, offset, len); current_buffer_size = std::max(current_buffer_size, offset); } WriteToBuffer(Context& context, int buffer_length, int vtable_start, uint8_t* buffer, std::vector::iterator writeToOffsetsIter) : Context(context), buffer_length(buffer_length), vtable_start(vtable_start), writeToOffsetsIter(writeToOffsetsIter), buffer(buffer) {} struct MessageWriter { template void write(const T* src, int offset, size_t len) { if constexpr (std::is_same_v) { uint32_t fixed_offset = finalLocation - offset - src->value; writer.copy_memory(&fixed_offset, finalLocation - offset, len); } else if constexpr (is_array::value) { writer.copy_memory(src, finalLocation - offset, std::min(src->size(), len)); } else { writer.copy_memory(src, finalLocation - offset, len); } } void writeTo(WriteToBuffer&) { writer.current_buffer_size += size; } void writeTo(WriteToBuffer&, int offset) { writer.current_buffer_size = std::max(writer.current_buffer_size, offset); } WriteToBuffer& writer; int finalLocation; int size; }; MessageWriter getMessageWriter(int size, bool zeroed = false) { MessageWriter m{ *this, *writeToOffsetsIter++, size }; if (zeroed) { memset(&buffer[buffer_length - m.finalLocation], 0, size); } return m; } template std::enable_if_t, bool> visitDynamicSize(const T& t) { uint32_t size = dynamic_size_traits::size(t, this->context()); if (size == 0 && emptyVector.value != -1) { return true; } int padding = 0; int start = RightAlign(current_buffer_size + size + 4, 4, &padding); write(&size, start, 4); start -= 4; dynamic_size_traits::save(&buffer[buffer_length - start], t, this->context()); start -= size; memset(&buffer[buffer_length - start], 0, padding); if (size == 0) { emptyVector = RelativeOffset{ current_buffer_size }; } return false; } const int buffer_length; const int vtable_start; int current_buffer_size = 0; RelativeOffset emptyVector{ -1 }; private: void copy_memory(const void* src, int offset, int len) { memcpy(static_cast(&buffer[buffer_length - offset]), src, len); } std::vector::iterator writeToOffsetsIter; uint8_t* buffer; }; template constexpr auto fields_helper() { if constexpr (_SizeOf::size == 0) { return pack<>{}; } else if constexpr (is_union_like) { return pack{}; } else if constexpr (is_vector_of_union_like) { return pack{}; } else { return pack{}; } } template using Fields = decltype(fields_helper()); // It's important that get_vtable always returns the same VTable pointer // so that we can decide equality by comparing the pointers. // First |numMembers| elements of sizesAndAlignments are sizes, the second // |numMembers| elements are alignments. extern VTable generate_vtable(size_t numMembers, const std::vector& sizesAndAlignments); template const VTable* gen_vtable3() { static thread_local VTable table = generate_vtable(sizeof...(MembersAndAlignments) / 2, std::vector{ MembersAndAlignments... }); return &table; } template const VTable* gen_vtable2(pack p) { return gen_vtable3<_SizeOf::size..., _SizeOf::align...>(); } template const VTable* get_vtable() { return gen_vtable2(concat_t...>{}); } template void for_each(F&& f, Members&&... members) { (std::forward(f)(std::forward(members)), ...); } struct VTableSet { // Precondition: vtable is in offsets int getOffset(const VTable* vtable) const { return std::lower_bound(offsets.begin(), offsets.end(), std::make_pair(vtable, -1))->second; } // Sorted map std::vector> offsets; std::vector packed_tables; }; template struct InsertVTableLambda; template struct TraverseMessageTypes : Context { TraverseMessageTypes(InsertVTableLambda& context) : Context(context), f(context) {} InsertVTableLambda& f; template std::enable_if_t> operator()(const Member& member) { if constexpr (serializable_traits::value) { serializable_traits::serialize(f, const_cast(member)); } else { const_cast(member).serialize(f); } }; template std::enable_if_t && !is_vector_like && !is_union_like> operator()(const T&) {} template std::enable_if_t> operator()(const VectorLike& members) { using VectorTraits = vector_like_traits; using T = typename VectorTraits::value_type; // we don't need to check for recursion here because the next call // to operator() will do that and we don't generate a vtable for the // vector-like type itself T t{}; (*this)(t); } template std::enable_if_t> operator()(const UnionLike& members) { using UnionTraits = union_like_traits; static_assert(pack_size(typename UnionTraits::alternatives{}) <= 254, "Up to 254 alternatives are supported for unions"); union_helper(typename UnionTraits::alternatives{}); } private: template void union_helper(pack) { T t{}; (*this)(t); union_helper(pack{}); } void union_helper(pack<>) {} }; template struct InsertVTableLambda : Context { InsertVTableLambda(const Context& context, std::set& vtables) : Context(context), vtables(vtables) {} static constexpr bool isDeserializing = false; static constexpr bool isSerializing = false; static constexpr bool is_fb_visitor = true; std::set& vtables; template void operator()(const Members&... members) { vtables.insert(get_vtable()); for_each(TraverseMessageTypes{ *this }, members...); } }; template int vec_bytes(const T& begin, const T& end) { return sizeof(typename T::value_type) * (end - begin); } template VTableSet get_vtableset_impl(const Root& root, const Context& context) { std::set vtables; InsertVTableLambda vlambda{ context, vtables }; if constexpr (serializable_traits::value) { serializable_traits::serialize(vlambda, const_cast(root)); } else { const_cast(root).serialize(vlambda); } size_t size = 0; for (const auto* vtable : vtables) { size += vec_bytes(vtable->begin(), vtable->end()); } std::vector packed_tables(size); int i = 0; std::vector> offsets; offsets.reserve(vtables.size()); for (const auto* vtable : vtables) { memcpy(&packed_tables[i], reinterpret_cast(&(*vtable)[0]), vec_bytes(vtable->begin(), vtable->end())); offsets.push_back({ vtable, i }); i += vec_bytes(vtable->begin(), vtable->end()); } return VTableSet{ offsets, packed_tables }; } template const VTableSet* get_vtableset(const Root& root, const Context& context) { static thread_local VTableSet result = get_vtableset_impl(root, context); return &result; } constexpr static std::array zeros{}; template void save_with_vtables(const Root& root, const VTableSet* vtableset, Writer& writer, int* vtable_start, FileIdentifier file_identifier, const Context& context) { auto vtable_writer = writer.getMessageWriter(vtableset->packed_tables.size()); vtable_writer.write(&vtableset->packed_tables[0], 0, vtableset->packed_tables.size()); RelativeOffset offset = save_helper(const_cast(root), writer, vtableset, context); vtable_writer.writeTo(writer); *vtable_start = writer.current_buffer_size; int root_writer_size = sizeof(uint32_t) + sizeof(file_identifier); auto root_writer = writer.getMessageWriter(root_writer_size); root_writer.write(&offset, 0, sizeof(offset)); root_writer.write(&file_identifier, sizeof(offset), sizeof(file_identifier)); int padding = 0; root_writer.writeTo(writer, RightAlign(writer.current_buffer_size + root_writer_size, 8, &padding)); writer.write(&zeros, writer.current_buffer_size - root_writer_size, padding); } template struct SaveAlternative : Context { Writer& writer; const VTableSet* vtables; SaveAlternative(Writer& writer, const VTableSet* vtables, const Context& context) : Context(context), writer(writer), vtables(vtables) {} RelativeOffset save(uint8_t type_tag, const typename UnionTraits::Member& member) { return save_<0>(type_tag, member); } private: template RelativeOffset save_(uint8_t type_tag, const typename UnionTraits::Member& member) { if constexpr (Alternative < pack_size(typename UnionTraits::alternatives{})) { if (type_tag == Alternative) { auto result = save_helper(UnionTraits::template get(member, this->context()), writer, vtables, this->context()); if constexpr (use_indirection>) { return result; } writer.write(&result, writer.current_buffer_size + sizeof(result), sizeof(result)); return RelativeOffset{ writer.current_buffer_size }; } else { return save_(type_tag, member); } } throw std::runtime_error("type_tag out of range. This should never happen."); } }; template struct LoadAlternative { Context& context; const uint8_t* current; void load(uint8_t type_tag, typename UnionTraits::Member& member) { return load_<0>(type_tag, member); } private: template void load_(uint8_t type_tag, typename UnionTraits::Member& member) { if constexpr (Alternative < pack_size(typename UnionTraits::alternatives{})) { if (type_tag == Alternative) { using AlternativeT = index_t; AlternativeT alternative; if constexpr (use_indirection) { load_helper(alternative, current, context); } else { uint32_t current_offset = interpret_as(current); current += current_offset; load_helper(alternative, current, context); } UnionTraits::template assign(member, alternative, context); } else { load_(type_tag, member); } } else { member = std::decay_t{}; } } }; template struct SaveVisitorLambda : Context { static constexpr bool isDeserializing = false; static constexpr bool isSerializing = true; static constexpr bool is_fb_visitor = true; const VTableSet* vtableset; Writer& writer; SaveVisitorLambda(const Context& context, const VTableSet* vtableset, Writer& writer) : Context(context), vtableset(vtableset), writer(writer) {} template void operator()(const Members&... members) { const auto& vtable = *get_vtable(); auto self = writer.getMessageWriter(/*length*/ vtable[1], /*zeroed*/ true); int i = 2; for_each( [&](const auto& member) { using Member = std::decay_t; if constexpr (is_vector_of_union_like) { using VectorTraits = vector_like_traits; using T = typename VectorTraits::value_type; using UnionTraits = union_like_traits; uint32_t num_entries = VectorTraits::num_entries(member, this->context()); auto typeVectorWriter = writer.getMessageWriter(num_entries); // type tags are one byte auto offsetVectorWriter = writer.getMessageWriter(num_entries * sizeof(RelativeOffset)); auto iter = VectorTraits::begin(member, this->context()); for (int i = 0; i < num_entries; ++i) { uint8_t type_tag = UnionTraits::index(*iter, this->context()); uint8_t fb_type_tag = UnionTraits::empty(*iter, this->context()) ? 0 : type_tag + 1; // Flatbuffers indexes from 1. typeVectorWriter.write(&fb_type_tag, i, sizeof(fb_type_tag)); if (!UnionTraits::empty(*iter, this->context())) { RelativeOffset offset = (SaveAlternative{ writer, vtableset, *this }) .save(type_tag, *iter); offsetVectorWriter.write(&offset, i * sizeof(offset), sizeof(offset)); } else { offsetVectorWriter.write(&zeros, i * sizeof(RelativeOffset), sizeof(RelativeOffset)); } ++iter; } int padding = 0; int start = RightAlign(writer.current_buffer_size + num_entries, 4, &padding) + 4; writer.write(&num_entries, start, sizeof(uint32_t)); typeVectorWriter.writeTo(writer, start - sizeof(uint32_t)); writer.write(&zeros, start - 4 - num_entries, padding); auto typeVectorOffset = RelativeOffset{ writer.current_buffer_size }; start = RightAlign(writer.current_buffer_size + num_entries * sizeof(RelativeOffset), 4, &padding) + 4; writer.write(&num_entries, start, sizeof(uint32_t)); offsetVectorWriter.writeTo(writer, start - sizeof(uint32_t)); writer.write(&zeros, start - 4 - num_entries * sizeof(RelativeOffset), padding); auto offsetVectorOffset = RelativeOffset{ writer.current_buffer_size }; self.write(&typeVectorOffset, vtable[i++], sizeof(typeVectorOffset)); self.write(&offsetVectorOffset, vtable[i++], sizeof(offsetVectorOffset)); } else if constexpr (is_union_like) { using UnionTraits = union_like_traits; uint8_t type_tag = UnionTraits::index(member, this->context()); uint8_t fb_type_tag = UnionTraits::empty(member, this->context()) ? 0 : type_tag + 1; // Flatbuffers indexes from 1. self.write(&fb_type_tag, vtable[i++], sizeof(fb_type_tag)); if (!UnionTraits::empty(member, this->context())) { RelativeOffset offset = (SaveAlternative{ writer, vtableset, *this }) .save(type_tag, member); self.write(&offset, vtable[i++], sizeof(offset)); } else { // Don't need to zero memory here since we already zeroed all of |self| ++i; } } else if constexpr (_SizeOf::size == 0) { save_helper(member, writer, vtableset, this->context()); } else { auto result = save_helper(member, writer, vtableset, this->context()); self.write(&result, vtable[i++], sizeof(result)); } }, members...); int vtable_offset = writer.vtable_start - vtableset->getOffset(&vtable); int padding = 0; int start = RightAlign(writer.current_buffer_size + vtable[1] - 4, std::max({ 4, fb_align... }), &padding) + 4; int32_t relative = vtable_offset - start; self.write(&relative, 0, sizeof(relative)); self.writeTo(writer, start); writer.write(&zeros, start - vtable[1], padding); } }; template struct LoadMember { static constexpr bool isDeserializing = true; static constexpr bool isSerializing = false; const uint16_t* const vtable; const uint8_t* const message; const uint16_t vtable_length; const uint16_t table_length; int& i; Context& context; template void operator()(Member& member) { if constexpr (is_vector_of_union_like) { if (!field_present()) { i += 2; member = std::decay_t{}; return; } const uint8_t* types_current = &message[vtable[i++]]; uint32_t types_current_offset = interpret_as(types_current); types_current += types_current_offset; types_current += sizeof(uint32_t); // num entries in types vector using VectorTraits = vector_like_traits; using T = typename Member::value_type; const uint8_t* current = &message[vtable[i++]]; uint32_t current_offset = interpret_as(current); current += current_offset; uint32_t numEntries = interpret_as(current); current += sizeof(uint32_t); VectorTraits::reserve(member, numEntries, context); auto inserter = VectorTraits::insert(member, context); for (int i = 0; i < numEntries; ++i) { T value; if (types_current[i] > 0) { uint8_t type_tag = types_current[i] - 1; // Flatbuffers indexes from 1. (LoadAlternative>{ context, current }).load(type_tag, value); } else { value = std::decay_t{}; } *inserter = std::move(value); ++inserter; current += sizeof(RelativeOffset); } } else if constexpr (is_union_like) { if (!field_present()) { i += 2; member = std::decay_t{}; return; } uint8_t fb_type_tag; load_helper(fb_type_tag, &message[vtable[i]], context); uint8_t type_tag = fb_type_tag - 1; // Flatbuffers indexes from 1. ++i; if (field_present() && fb_type_tag > 0) { (LoadAlternative>{ context, &message[vtable[i]] }) .load(type_tag, member); } else { member = std::decay_t{}; } ++i; } else if constexpr (_SizeOf::size == 0) { load_helper(member, nullptr, context); } else { if (field_present()) { load_helper(member, &message[vtable[i]], context); } else { member = std::decay_t{}; } ++i; } } private: bool field_present() { return i < vtable_length && vtable[i] >= 4; } }; template struct int_type { static constexpr int value = i; }; template void for_each_i_impl(F&& f, std::index_sequence) { for_each(std::forward(f), int_type{}...); } template void for_each_i(F&& f) { for_each_i_impl(std::forward(f), std::make_index_sequence{}); } template struct LoadSaveHelper : Context { LoadSaveHelper(const Context& context) : Context(context) {} template std::enable_if_t> load(U& member, const uint8_t* current) { scalar_traits::load(current, member, this->context()); } template std::enable_if_t> load(U& member, const uint8_t* current) { using StructTraits = struct_like_traits; using types = typename StructTraits::types; for_each_i([&](auto i_type) { constexpr int i = decltype(i_type)::value; using type = index_t; type t; load_helper(t, current + struct_offset(types{}), *this); StructTraits::template assign(member, t, this->context()); }); } template std::enable_if_t> load(U& member, const uint8_t* current) { uint32_t current_offset = interpret_as(current); current += current_offset; uint32_t size = interpret_as(current); current += sizeof(size); dynamic_size_traits::load(current, size, member, this->context()); } struct SerializeFun : Context { static constexpr bool isDeserializing = true; static constexpr bool isSerializing = false; static constexpr bool is_fb_visitor = true; const uint8_t* current; SerializeFun(const uint8_t* current, Context& context) : Context(context), current(current) {} template void operator()(Args&... members) { if (sizeof...(Args) == 0) { return; } uint32_t current_offset = interpret_as(current); current += current_offset; int32_t vtable_offset = interpret_as(current); const uint16_t* vtable = reinterpret_cast(current - vtable_offset); int i = 0; uint16_t vtable_length = vtable[i++] / sizeof(uint16_t); uint16_t table_length = vtable[i++]; for_each(LoadMember{ vtable, current, vtable_length, table_length, i, this->context() }, members...); } }; template std::enable_if_t> load(Member& member, const uint8_t* current) { SerializeFun fun(current, this->context()); if constexpr (serializable_traits::value) { serializable_traits::serialize(fun, member); } else { member.serialize(fun); } } template std::enable_if_t> load(VectorLike& member, const uint8_t* current) { using VectorTraits = vector_like_traits; using T = typename VectorTraits::value_type; uint32_t current_offset = interpret_as(current); current += current_offset; uint32_t numEntries = interpret_as(current); current += sizeof(uint32_t); VectorTraits::reserve(member, numEntries, this->context()); auto inserter = VectorTraits::insert(member, this->context()); for (uint32_t i = 0; i < numEntries; ++i) { T value; load_helper(value, current, this->context()); *inserter = std::move(value); ++inserter; current += fb_size; } } template >> auto save(const U& message, Writer& writer, const VTableSet*) { constexpr auto size = scalar_traits::size; std::array result = {}; if constexpr (size > 0) { scalar_traits::save(&result[0], message, this->context()); } return result; } template auto save(const U& message, Writer& writer, const VTableSet* vtables, std::enable_if_t, int> _ = 0) { using StructTraits = struct_like_traits; using types = typename StructTraits::types; constexpr auto size = struct_size(types{}); std::array struct_bytes = {}; for_each_i([&](auto i_type) { constexpr int i = decltype(i_type)::value; auto result = save_helper( StructTraits::template get(message, this->context()), writer, vtables, this->context()); memcpy(&struct_bytes[struct_offset(types{})], &result, sizeof(result)); }); return struct_bytes; } template >> RelativeOffset save(const U& message, Writer& writer, const VTableSet*, std::enable_if_t, int> _ = 0) { if (writer.visitDynamicSize(message)) return writer.emptyVector; return RelativeOffset{ writer.current_buffer_size }; } template RelativeOffset save(const Member& member, Writer& writer, const VTableSet* vtables, std::enable_if_t, int> _ = 0) { SaveVisitorLambda l{ *this, vtables, writer }; if constexpr (serializable_traits::value) { serializable_traits::serialize(l, const_cast(member)); } else { const_cast(member).serialize(l); } return RelativeOffset{ writer.current_buffer_size }; } template >> RelativeOffset save(const VectorLike& members, Writer& writer, const VTableSet* vtables) { using VectorTraits = vector_like_traits; using T = typename VectorTraits::value_type; constexpr auto size = fb_size; uint32_t num_entries = VectorTraits::num_entries(members, this->context()); if (num_entries == 0 && writer.emptyVector.value != -1) { return writer.emptyVector; } uint32_t len = num_entries * size; auto self = writer.getMessageWriter(len); auto iter = VectorTraits::begin(members, this->context()); for (uint32_t i = 0; i < num_entries; ++i) { auto result = save_helper(*iter, writer, vtables, this->context()); self.write(&result, i * size, size); ++iter; } int padding = 0; int start = RightAlign(writer.current_buffer_size + len, std::max(4, num_entries == 0 ? 0 : fb_align), &padding) + 4; writer.write(&num_entries, start, sizeof(uint32_t)); self.writeTo(writer, start - sizeof(uint32_t)); writer.write(&zeros, start - len - 4, padding); if (num_entries == 0) { writer.emptyVector = RelativeOffset{ writer.current_buffer_size }; } return RelativeOffset{ writer.current_buffer_size }; } }; template struct LoadSaveHelper, Context> : Context { LoadSaveHelper(const Context& context) : Context(context) {} void load(std::vector& member, const uint8_t* current) { uint32_t current_offset = interpret_as(current); current += current_offset; uint32_t length = interpret_as(current); current += sizeof(uint32_t); member.clear(); member.resize(length); uint8_t m; for (uint32_t i = 0; i < length; ++i) { load_helper(m, current, *this); member[i] = m != 0; current += fb_size; } } template RelativeOffset save(const std::vector& members, Writer& writer, const VTableSet* vtables) { uint32_t len = members.size(); int padding = 0; int start = RightAlign(writer.current_buffer_size + sizeof(uint32_t) + len, sizeof(uint32_t), &padding); writer.write(&len, start, sizeof(uint32_t)); int i = 0; for (bool b : members) { writer.write(&b, start - sizeof(uint32_t) - i++, 1); } writer.write(&zeros, start - sizeof(uint32_t) - i, padding); return RelativeOffset{ writer.current_buffer_size }; } }; template void load_helper(Member& member, const uint8_t* current, const Context& context) { LoadSaveHelper helper(context); helper.load(member, current); } template auto save_helper(const Member& member, Writer& writer, const VTableSet* vtables, const Context& context) { LoadSaveHelper helper(context); return helper.save(member, writer, vtables); } } // namespace detail namespace detail { template struct FakeRoot { std::tuple members; FakeRoot(Members&... members) : members(members...) {} template void serialize(Archive& archive) { serialize_impl(archive, std::index_sequence_for{}); } private: template void serialize_impl(Archive& archive, std::index_sequence) { serializer(archive, std::get(members)...); } }; template auto fake_root(Members&... members) { return FakeRoot(members...); } template uint8_t* save(Context& context, const Root& root, FileIdentifier file_identifier) { const auto* vtableset = get_vtableset(root, context); PrecomputeSize precompute_size(context); int vtable_start; save_with_vtables(root, vtableset, precompute_size, &vtable_start, file_identifier, context); uint8_t* out = context.allocate(precompute_size.current_buffer_size); WriteToBuffer writeToBuffer{ context, precompute_size.current_buffer_size, vtable_start, out, precompute_size.writeToOffsets.begin() }; save_with_vtables(root, vtableset, writeToBuffer, &vtable_start, file_identifier, context); return out; } template void load(Root& root, const uint8_t* in, Context& context) { detail::load_helper(root, in, context); } } // namespace detail template uint8_t* save_members(Context& context, FileIdentifier file_identifier, const FirstMember& first, const Members&... members) { if constexpr (serialize_raw::value) { return serialize_raw::save_raw(context, first); } else { const auto& root = detail::fake_root(const_cast(first), const_cast(members)...); return detail::save(context, root, file_identifier); } } template void load_members(const uint8_t* in, Context& context, Members&... members) { auto root = detail::fake_root(members...); detail::load(root, in, context); } inline FileIdentifier read_file_identifier(const uint8_t* in) { FileIdentifier result; memcpy(&result, in + sizeof(result), sizeof(result)); return result; } namespace detail { template struct YesFileIdentifier { constexpr static FileIdentifier file_identifier = FileIdentifierFor::value; constexpr static bool composition_depth = CompositionDepthFor::value; }; struct NoFileIdentifier {}; }; // namespace detail // members of unions must be tables in flatbuffers, so you can use this to // introduce the indirection only when necessary. template struct EnsureTable : std::conditional_t::value, detail::YesFileIdentifier, detail::NoFileIdentifier> { EnsureTable() : t() {} EnsureTable(const T& t) : t(t) {} template void serialize(Archive& ar) { if constexpr (is_fb_function) { // This is only for vtable collection. Load and save use the LoadSaveHelper specialization below if constexpr (detail::expect_serialize_member) { if constexpr (serializable_traits::value) { serializable_traits::serialize(ar, t); } else { t.serialize(ar); } } else { serializer(ar, t); } } else { serializer(ar, t); } } T& asUnderlyingType() { return t; } const T& asUnderlyingType() const { return t; } private: T t; }; namespace detail { // Ensure if there's a LoadSaveHelper specialization available for T it gets used. template struct LoadSaveHelper, Context> : Context { LoadSaveHelper(const Context& context) : Context(context), alreadyATable(context), wrapInTable(context) {} void load(EnsureTable& member, const uint8_t* current) { if constexpr (expect_serialize_member) { alreadyATable.load(member.asUnderlyingType(), current); } else { FakeRoot t{ member.asUnderlyingType() }; wrapInTable.load(t, current); } } template RelativeOffset save(const EnsureTable& member, Writer& writer, const VTableSet* vtables) { if constexpr (expect_serialize_member) { return alreadyATable.save(member.asUnderlyingType(), writer, vtables); } else { FakeRoot t{ const_cast(member.asUnderlyingType()) }; return wrapInTable.save(t, writer, vtables); } } private: LoadSaveHelper alreadyATable; LoadSaveHelper, Context> wrapInTable; }; } // namespace detail