| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you 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 "fory/serialization/array_serializer.h" |
| #include "fory/serialization/serializer.h" |
| #include <array> |
| #include <cstdint> |
| #include <deque> |
| #include <forward_list> |
| #include <limits> |
| #include <list> |
| #include <set> |
| #include <typeindex> |
| #include <unordered_set> |
| #include <vector> |
| |
| namespace fory { |
| namespace serialization { |
| |
| // ============================================================================ |
| // Collection Header Constants |
| // ============================================================================ |
| |
| /// Collection header bit flags (per xlang spec section 5.4.4) |
| constexpr uint8_t COLL_TRACKING_REF = 0b0001; |
| constexpr uint8_t COLL_HAS_NULL = 0b0010; |
| constexpr uint8_t COLL_DECL_ELEMENT_TYPE = 0b0100; |
| constexpr uint8_t COLL_IS_SAME_TYPE = 0b1000; |
| |
| // ============================================================================ |
| // Collection Header |
| // ============================================================================ |
| |
| /// Collection elements header encoding per xlang spec section 5.4.4. |
| struct CollectionHeader { |
| bool track_ref; |
| bool has_null; |
| bool is_declared_type; |
| bool is_same_type; |
| |
| /// Encode header to single byte |
| inline uint8_t encode() const { |
| uint8_t header = 0; |
| if (track_ref) |
| header |= 0b0001; |
| if (has_null) |
| header |= 0b0010; |
| if (is_declared_type) |
| header |= 0b0100; |
| if (is_same_type) |
| header |= 0b1000; |
| return header; |
| } |
| |
| /// Decode header from single byte |
| static inline CollectionHeader decode(uint8_t byte) { |
| return { |
| (byte & 0b0001) != 0, // track_ref |
| (byte & 0b0010) != 0, // has_null |
| (byte & 0b0100) != 0, // is_declared_type |
| (byte & 0b1000) != 0, // is_same_type |
| }; |
| } |
| |
| /// Create default header for non-polymorphic, non-null collections |
| static inline CollectionHeader default_header(bool track_ref) { |
| return { |
| track_ref, // track_ref |
| false, // has_null |
| true, // is_declared_type |
| true, // is_same_type |
| }; |
| } |
| |
| /// Create header for potentially polymorphic collections |
| static inline CollectionHeader polymorphic_header(bool track_ref) { |
| return { |
| track_ref, // track_ref |
| true, // has_null |
| false, // is_declared_type |
| false, // is_same_type |
| }; |
| } |
| }; |
| |
| // ============================================================================ |
| // Collection Serialization Helpers |
| // ============================================================================ |
| |
| /// Check if we need to write type info for a collection element type. |
| template <typename T> inline constexpr bool need_type_for_collection_elem() { |
| constexpr TypeId tid = Serializer<T>::type_id; |
| return tid == TypeId::STRUCT || tid == TypeId::COMPATIBLE_STRUCT || |
| tid == TypeId::NAMED_STRUCT || |
| tid == TypeId::NAMED_COMPATIBLE_STRUCT || tid == TypeId::EXT || |
| tid == TypeId::NAMED_EXT; |
| } |
| |
| /// Write collection data for non-polymorphic, non-shared-ref elements. |
| template <typename T, typename Container> |
| inline void write_collection_data_fast(const Container &coll, WriteContext &ctx, |
| bool has_generics) { |
| static_assert(!is_polymorphic_v<T>, |
| "Fast path is for non-polymorphic types only"); |
| static_assert(!is_shared_ref_v<T>, |
| "Fast path is for non-shared-ref types only"); |
| |
| // Write length |
| ctx.write_varuint32(static_cast<uint32_t>(coll.size())); |
| |
| if (coll.empty()) { |
| return; |
| } |
| |
| // Check for null elements |
| bool has_null = false; |
| if constexpr (is_nullable_v<T>) { |
| for (const auto &elem : coll) { |
| if (is_null_value(elem)) { |
| has_null = true; |
| break; |
| } |
| } |
| } |
| |
| // Build header bitmap |
| uint8_t bitmap = COLL_IS_SAME_TYPE; |
| if (has_null) { |
| bitmap |= COLL_HAS_NULL; |
| } |
| |
| // Determine if element type is declared |
| using ElemType = nullable_element_t<T>; |
| bool is_elem_declared = |
| has_generics && !need_type_for_collection_elem<ElemType>(); |
| if (is_elem_declared) { |
| bitmap |= COLL_DECL_ELEMENT_TYPE; |
| } |
| |
| // Write header |
| ctx.write_uint8(bitmap); |
| |
| // Write element type info if not declared |
| if (!is_elem_declared) { |
| Serializer<ElemType>::write_type_info(ctx); |
| } |
| |
| // Write elements |
| if constexpr (is_nullable_v<T>) { |
| using Inner = nullable_element_t<T>; |
| if (has_null) { |
| for (const auto &elem : coll) { |
| if (is_null_value(elem)) { |
| ctx.write_int8(NULL_FLAG); |
| } else { |
| ctx.write_int8(NOT_NULL_VALUE_FLAG); |
| if (is_elem_declared) { |
| Serializer<Inner>::write_data(deref_nullable(elem), ctx); |
| } else { |
| Serializer<Inner>::write(deref_nullable(elem), ctx, RefMode::None, |
| false); |
| } |
| } |
| } |
| } else { |
| for (const auto &elem : coll) { |
| if (is_elem_declared) { |
| Serializer<Inner>::write_data(deref_nullable(elem), ctx); |
| } else { |
| Serializer<Inner>::write(deref_nullable(elem), ctx, RefMode::None, |
| false); |
| } |
| } |
| } |
| } else { |
| for (const auto &elem : coll) { |
| if (is_elem_declared) { |
| if constexpr (is_generic_type_v<T>) { |
| Serializer<T>::write_data_generic(elem, ctx, true); |
| } else { |
| Serializer<T>::write_data(elem, ctx); |
| } |
| } else { |
| Serializer<T>::write(elem, ctx, RefMode::None, false); |
| } |
| } |
| } |
| } |
| |
| /// Write collection data for polymorphic or shared-ref elements. |
| template <typename T, typename Container> |
| inline void write_collection_data_slow(const Container &coll, WriteContext &ctx, |
| bool has_generics) { |
| // Write length |
| ctx.write_varuint32(static_cast<uint32_t>(coll.size())); |
| |
| if (coll.empty()) { |
| return; |
| } |
| |
| constexpr bool elem_is_polymorphic = is_polymorphic_v<T>; |
| constexpr bool elem_is_shared_ref = is_shared_ref_v<T>; |
| |
| using ElemType = nullable_element_t<T>; |
| bool is_elem_declared = |
| has_generics && !need_type_for_collection_elem<ElemType>(); |
| |
| // Scan collection to determine header flags |
| bool has_null = false; |
| bool is_same_type = true; |
| std::type_index first_type{typeid(void)}; |
| bool first_type_set = false; |
| |
| for (const auto &elem : coll) { |
| // Check for nulls |
| if constexpr (is_nullable_v<T>) { |
| if (is_null_value(elem)) { |
| has_null = true; |
| continue; |
| } |
| } |
| // Check runtime types for polymorphic elements |
| if constexpr (elem_is_polymorphic) { |
| if (is_same_type) { |
| auto concrete_id = get_concrete_type_id(elem); |
| if (!first_type_set) { |
| first_type = concrete_id; |
| first_type_set = true; |
| } else if (concrete_id != first_type) { |
| is_same_type = false; |
| } |
| } |
| } |
| } |
| |
| // If all polymorphic elements are null, treat as heterogeneous |
| if constexpr (elem_is_polymorphic) { |
| if (is_same_type && !first_type_set) { |
| is_same_type = false; |
| } |
| } |
| |
| // Build header bitmap |
| uint8_t bitmap = 0; |
| if (has_null) { |
| bitmap |= COLL_HAS_NULL; |
| } |
| if (is_elem_declared && !elem_is_polymorphic) { |
| bitmap |= COLL_DECL_ELEMENT_TYPE; |
| } |
| if (is_same_type) { |
| bitmap |= COLL_IS_SAME_TYPE; |
| } |
| // Only set TRACKING_REF if element is shared ref AND global ref tracking is |
| // enabled |
| if constexpr (elem_is_shared_ref) { |
| if (ctx.track_ref()) { |
| bitmap |= COLL_TRACKING_REF; |
| } |
| } |
| |
| // Write header |
| ctx.write_uint8(bitmap); |
| |
| // Write element type info if IS_SAME_TYPE && !IS_DECL_ELEMENT_TYPE |
| if (is_same_type && !(bitmap & COLL_DECL_ELEMENT_TYPE)) { |
| if constexpr (elem_is_polymorphic) { |
| // Write concrete type info for polymorphic elements |
| ctx.write_any_typeinfo(static_cast<uint32_t>(TypeId::UNKNOWN), |
| first_type); |
| } else { |
| Serializer<ElemType>::write_type_info(ctx); |
| } |
| } |
| |
| // Determine if we're actually tracking refs for this collection |
| const bool tracking_refs = (bitmap & COLL_TRACKING_REF) != 0; |
| |
| // Write elements |
| if (is_same_type) { |
| // All elements have same type - type info written once above |
| if (tracking_refs) { |
| // Track refs - write ref flag per element per xlang spec |
| for (const auto &elem : coll) { |
| Serializer<T>::write(elem, ctx, RefMode::Tracking, false, has_generics); |
| } |
| } else if (!has_null) { |
| // No nulls, no ref tracking - write data directly without null flag |
| for (const auto &elem : coll) { |
| if constexpr (is_nullable_v<T>) { |
| using Inner = nullable_element_t<T>; |
| Serializer<Inner>::write_data(deref_nullable(elem), ctx); |
| } else if constexpr (elem_is_shared_ref) { |
| // For shared_ptr, use write_data which handles polymorphic types |
| Serializer<T>::write_data(elem, ctx); |
| } else { |
| if constexpr (is_generic_type_v<T>) { |
| Serializer<T>::write_data_generic(elem, ctx, has_generics); |
| } else { |
| Serializer<T>::write_data(elem, ctx); |
| } |
| } |
| } |
| } else { |
| // Has null elements - write with null flag |
| for (const auto &elem : coll) { |
| Serializer<T>::write(elem, ctx, RefMode::NullOnly, false, has_generics); |
| } |
| } |
| } else { |
| // Heterogeneous types - write type info per element |
| if (tracking_refs) { |
| // Track refs - write ref flag + type info per element |
| for (const auto &elem : coll) { |
| Serializer<T>::write(elem, ctx, RefMode::Tracking, true, has_generics); |
| } |
| } else if (!has_null) { |
| // No nulls - write without null flag (RefMode::None) |
| for (const auto &elem : coll) { |
| Serializer<T>::write(elem, ctx, RefMode::None, true, has_generics); |
| } |
| } else { |
| // Has null elements - write with null flag (RefMode::NullOnly) |
| for (const auto &elem : coll) { |
| Serializer<T>::write(elem, ctx, RefMode::NullOnly, true, has_generics); |
| } |
| } |
| } |
| } |
| |
| // Helper trait to detect if container has push_back |
| template <typename Container, typename T, typename = void> |
| struct has_push_back : std::false_type {}; |
| |
| template <typename Container, typename T> |
| struct has_push_back<Container, T, |
| std::void_t<decltype(std::declval<Container>().push_back( |
| std::declval<T>()))>> : std::true_type {}; |
| |
| template <typename Container, typename T> |
| inline constexpr bool has_push_back_v = has_push_back<Container, T>::value; |
| |
| // Helper trait to detect if container has reserve |
| template <typename Container, typename = void> |
| struct has_reserve : std::false_type {}; |
| |
| template <typename Container> |
| struct has_reserve<Container, |
| std::void_t<decltype(std::declval<Container>().reserve(0))>> |
| : std::true_type {}; |
| |
| template <typename Container> |
| inline constexpr bool has_reserve_v = has_reserve<Container>::value; |
| |
| // Helper to insert element into container (vector or set) |
| template <typename Container, typename T> |
| inline void collection_insert(Container &result, T &&elem) { |
| if constexpr (has_push_back_v<Container, T>) { |
| result.push_back(std::forward<T>(elem)); |
| } else { |
| result.insert(std::forward<T>(elem)); |
| } |
| } |
| |
| /// Read collection data for polymorphic or shared-ref elements. |
| template <typename T, typename Container> |
| inline Container read_collection_data_slow(ReadContext &ctx, uint32_t length) { |
| Container result; |
| if constexpr (has_reserve_v<Container>) { |
| result.reserve(length); |
| } |
| |
| if (length == 0) { |
| return result; |
| } |
| |
| constexpr bool elem_is_polymorphic = is_polymorphic_v<T>; |
| |
| uint8_t bitmap = ctx.read_uint8(ctx.error()); |
| if (FORY_PREDICT_FALSE(ctx.has_error())) { |
| return result; |
| } |
| |
| bool track_ref = (bitmap & COLL_TRACKING_REF) != 0; |
| bool has_null = (bitmap & COLL_HAS_NULL) != 0; |
| bool is_decl_type = (bitmap & COLL_DECL_ELEMENT_TYPE) != 0; |
| bool is_same_type = (bitmap & COLL_IS_SAME_TYPE) != 0; |
| |
| // Read element type info if IS_SAME_TYPE && !IS_DECL_ELEMENT_TYPE |
| const TypeInfo *elem_type_info = nullptr; |
| if (is_same_type && !is_decl_type) { |
| elem_type_info = ctx.read_any_typeinfo(ctx.error()); |
| if (FORY_PREDICT_FALSE(ctx.has_error())) { |
| return result; |
| } |
| } |
| |
| // Read elements |
| if (is_same_type) { |
| if (track_ref) { |
| for (uint32_t i = 0; i < length; ++i) { |
| if (FORY_PREDICT_FALSE(ctx.has_error())) { |
| return result; |
| } |
| if constexpr (elem_is_polymorphic) { |
| // Use RefMode::Tracking to read ref flag per element |
| auto elem = Serializer<T>::read_with_type_info(ctx, RefMode::Tracking, |
| *elem_type_info); |
| collection_insert(result, std::move(elem)); |
| } else { |
| auto elem = Serializer<T>::read(ctx, RefMode::Tracking, false); |
| collection_insert(result, std::move(elem)); |
| } |
| } |
| } else if (!has_null) { |
| for (uint32_t i = 0; i < length; ++i) { |
| if (FORY_PREDICT_FALSE(ctx.has_error())) { |
| return result; |
| } |
| if constexpr (elem_is_polymorphic) { |
| auto elem = Serializer<T>::read_with_type_info(ctx, RefMode::None, |
| *elem_type_info); |
| collection_insert(result, std::move(elem)); |
| } else { |
| auto elem = Serializer<T>::read(ctx, RefMode::None, false); |
| collection_insert(result, std::move(elem)); |
| } |
| } |
| } else { |
| // Has null elements |
| for (uint32_t i = 0; i < length; ++i) { |
| if (FORY_PREDICT_FALSE(ctx.has_error())) { |
| return result; |
| } |
| bool has_value = read_null_only_flag(ctx, RefMode::NullOnly); |
| if (!has_value) { |
| if constexpr (has_push_back_v<Container, T>) { |
| result.push_back(T{}); |
| } |
| // For sets, skip null elements |
| } else { |
| if constexpr (elem_is_polymorphic) { |
| auto elem = Serializer<T>::read_with_type_info(ctx, RefMode::None, |
| *elem_type_info); |
| collection_insert(result, std::move(elem)); |
| } else { |
| auto elem = Serializer<T>::read(ctx, RefMode::None, false); |
| collection_insert(result, std::move(elem)); |
| } |
| } |
| } |
| } |
| } else { |
| // Heterogeneous types - read type info per element |
| if (has_null && !track_ref) { |
| // has_null but no tracking ref - read nullability flag per element |
| for (uint32_t i = 0; i < length; ++i) { |
| if (FORY_PREDICT_FALSE(ctx.has_error())) { |
| return result; |
| } |
| bool has_value = read_null_only_flag(ctx, RefMode::NullOnly); |
| if (!has_value) { |
| if constexpr (has_push_back_v<Container, T>) { |
| result.push_back(T{}); |
| } |
| } else { |
| // Read type info + data without ref flag |
| auto elem = Serializer<T>::read(ctx, RefMode::None, true); |
| collection_insert(result, std::move(elem)); |
| } |
| } |
| } else { |
| // Read ref flags based on Fory config |
| for (uint32_t i = 0; i < length; ++i) { |
| if (FORY_PREDICT_FALSE(ctx.has_error())) { |
| return result; |
| } |
| auto elem = Serializer<T>::read( |
| ctx, track_ref ? RefMode::Tracking : RefMode::None, true); |
| collection_insert(result, std::move(elem)); |
| } |
| } |
| } |
| |
| return result; |
| } |
| |
| // ============================================================================ |
| // std::vector serializer |
| // ============================================================================ |
| |
| /// Vector serializer for arithmetic (non-bool) types encoded as typed arrays |
| template <typename T, typename Alloc> |
| struct Serializer< |
| std::vector<T, Alloc>, |
| std::enable_if_t<std::is_arithmetic_v<T> && !std::is_same_v<T, bool>>> { |
| static constexpr TypeId type_id = []() { |
| if constexpr (std::is_same_v<T, int8_t> || std::is_same_v<T, uint8_t>) { |
| return TypeId::BINARY; |
| } |
| return Serializer<std::array<T, 1>>::type_id; |
| }(); |
| |
| static inline void write_type_info(WriteContext &ctx) { |
| ctx.write_varuint32(static_cast<uint32_t>(type_id)); |
| } |
| |
| static inline void read_type_info(ReadContext &ctx) { |
| uint32_t actual = ctx.read_varuint32(ctx.error()); |
| if (FORY_PREDICT_FALSE(ctx.has_error())) { |
| return; |
| } |
| if (!type_id_matches(actual, static_cast<uint32_t>(type_id))) { |
| ctx.set_error( |
| Error::type_mismatch(actual, static_cast<uint32_t>(type_id))); |
| } |
| } |
| |
| static inline void write(const std::vector<T, Alloc> &vec, WriteContext &ctx, |
| RefMode ref_mode, bool write_type, |
| bool has_generics = false) { |
| write_not_null_ref_flag(ctx, ref_mode); |
| if (write_type) { |
| ctx.write_varuint32(static_cast<uint32_t>(type_id)); |
| } |
| write_data(vec, ctx); |
| } |
| |
| static inline void write_data(const std::vector<T, Alloc> &vec, |
| WriteContext &ctx) { |
| uint64_t total_bytes = static_cast<uint64_t>(vec.size()) * sizeof(T); |
| if (total_bytes > std::numeric_limits<uint32_t>::max()) { |
| ctx.set_error(Error::invalid("Vector byte size exceeds uint32_t range")); |
| return; |
| } |
| Buffer &buffer = ctx.buffer(); |
| // bulk write may write 8 bytes for varint32 |
| size_t max_size = 8 + total_bytes; |
| buffer.Grow(static_cast<uint32_t>(max_size)); |
| uint32_t writer_index = buffer.writer_index(); |
| writer_index += |
| buffer.PutVarUint32(writer_index, static_cast<uint32_t>(total_bytes)); |
| if (total_bytes > 0) { |
| buffer.UnsafePut(writer_index, vec.data(), |
| static_cast<uint32_t>(total_bytes)); |
| } |
| buffer.WriterIndex(writer_index + static_cast<uint32_t>(total_bytes)); |
| } |
| |
| static inline void write_data_generic(const std::vector<T, Alloc> &vec, |
| WriteContext &ctx, bool has_generics) { |
| write_data(vec, ctx); |
| } |
| |
| static inline std::vector<T, Alloc> read(ReadContext &ctx, RefMode ref_mode, |
| bool read_type) { |
| bool has_value = read_null_only_flag(ctx, ref_mode); |
| if (ctx.has_error() || !has_value) { |
| return std::vector<T, Alloc>(); |
| } |
| |
| if (read_type) { |
| uint32_t type_id_read = ctx.read_varuint32(ctx.error()); |
| if (FORY_PREDICT_FALSE(ctx.has_error())) { |
| return std::vector<T, Alloc>(); |
| } |
| if (type_id_read != static_cast<uint32_t>(type_id)) { |
| ctx.set_error( |
| Error::type_mismatch(type_id_read, static_cast<uint32_t>(type_id))); |
| return std::vector<T, Alloc>(); |
| } |
| } |
| return read_data(ctx); |
| } |
| |
| static inline std::vector<T, Alloc> |
| read_with_type_info(ReadContext &ctx, RefMode ref_mode, |
| const TypeInfo &type_info) { |
| return read(ctx, ref_mode, false); |
| } |
| |
| static inline std::vector<T, Alloc> read_data(ReadContext &ctx) { |
| uint32_t total_bytes_u32 = ctx.read_varuint32(ctx.error()); |
| if (FORY_PREDICT_FALSE(ctx.has_error())) { |
| return std::vector<T, Alloc>(); |
| } |
| if (sizeof(T) == 0) { |
| return std::vector<T, Alloc>(); |
| } |
| if (total_bytes_u32 % sizeof(T) != 0) { |
| ctx.set_error(Error::invalid_data( |
| "Vector byte size not aligned with element size")); |
| return std::vector<T, Alloc>(); |
| } |
| size_t elem_count = total_bytes_u32 / sizeof(T); |
| std::vector<T, Alloc> result(elem_count); |
| if (total_bytes_u32 > 0) { |
| ctx.read_bytes(result.data(), static_cast<uint32_t>(total_bytes_u32), |
| ctx.error()); |
| } |
| return result; |
| } |
| }; |
| |
| /// Vector serializer for non-bool, non-arithmetic types |
| template <typename T, typename Alloc> |
| struct Serializer< |
| std::vector<T, Alloc>, |
| std::enable_if_t<!std::is_same_v<T, bool> && !std::is_arithmetic_v<T>>> { |
| static constexpr TypeId type_id = TypeId::LIST; |
| |
| static inline void write_type_info(WriteContext &ctx) { |
| ctx.write_varuint32(static_cast<uint32_t>(type_id)); |
| } |
| |
| static inline void read_type_info(ReadContext &ctx) { |
| uint32_t actual = ctx.read_varuint32(ctx.error()); |
| if (FORY_PREDICT_FALSE(ctx.has_error())) { |
| return; |
| } |
| if (!type_id_matches(actual, static_cast<uint32_t>(type_id))) { |
| ctx.set_error( |
| Error::type_mismatch(actual, static_cast<uint32_t>(type_id))); |
| } |
| } |
| |
| static inline std::vector<T, Alloc> read(ReadContext &ctx, RefMode ref_mode, |
| bool read_type) { |
| // List-level reference flag |
| bool has_value = read_null_only_flag(ctx, ref_mode); |
| if (ctx.has_error() || !has_value) { |
| return std::vector<T, Alloc>(); |
| } |
| |
| // Optional type info for polymorphic containers |
| if (read_type) { |
| uint32_t type_id_read = ctx.read_varuint32(ctx.error()); |
| if (FORY_PREDICT_FALSE(ctx.has_error())) { |
| return std::vector<T, Alloc>(); |
| } |
| uint32_t low = type_id_read & 0xffu; |
| if (low != static_cast<uint32_t>(type_id)) { |
| ctx.set_error( |
| Error::type_mismatch(type_id_read, static_cast<uint32_t>(type_id))); |
| return std::vector<T, Alloc>(); |
| } |
| } |
| |
| // Length written via writeVarUint32Small7 |
| uint32_t length = ctx.read_varuint32(ctx.error()); |
| if (FORY_PREDICT_FALSE(ctx.has_error())) { |
| return std::vector<T, Alloc>(); |
| } |
| // Per xlang spec: header and type_info are omitted when length is 0 |
| if (length == 0) { |
| return std::vector<T, Alloc>(); |
| } |
| |
| // Dispatch to slow path for polymorphic/shared-ref elements |
| constexpr bool is_slow_path = is_polymorphic_v<T> || is_shared_ref_v<T>; |
| if constexpr (is_slow_path) { |
| return read_collection_data_slow<T, std::vector<T, Alloc>>(ctx, length); |
| } else { |
| // Fast path for non-polymorphic, non-shared-ref elements |
| |
| // Elements header bitmap (CollectionFlags) |
| uint8_t bitmap = ctx.read_uint8(ctx.error()); |
| if (FORY_PREDICT_FALSE(ctx.has_error())) { |
| return std::vector<T, Alloc>(); |
| } |
| bool track_ref = (bitmap & COLL_TRACKING_REF) != 0; |
| bool has_null = (bitmap & COLL_HAS_NULL) != 0; |
| bool is_decl_type = (bitmap & COLL_DECL_ELEMENT_TYPE) != 0; |
| bool is_same_type = (bitmap & COLL_IS_SAME_TYPE) != 0; |
| |
| // Read element type info if IS_SAME_TYPE is set but IS_DECL_ELEMENT_TYPE |
| // is not. |
| if (is_same_type && !is_decl_type) { |
| const TypeInfo *elem_type_info = ctx.read_any_typeinfo(ctx.error()); |
| if (FORY_PREDICT_FALSE(ctx.has_error())) { |
| return std::vector<T, Alloc>(); |
| } |
| using ElemType = nullable_element_t<T>; |
| uint32_t expected = |
| static_cast<uint32_t>(Serializer<ElemType>::type_id); |
| if (!type_id_matches(elem_type_info->type_id, expected)) { |
| ctx.set_error( |
| Error::type_mismatch(elem_type_info->type_id, expected)); |
| return std::vector<T, Alloc>(); |
| } |
| } |
| |
| std::vector<T, Alloc> result; |
| result.reserve(length); |
| |
| // Fast path: no tracking, no nulls, elements have declared type |
| if (!track_ref && !has_null && is_same_type) { |
| for (uint32_t i = 0; i < length; ++i) { |
| if (FORY_PREDICT_FALSE(ctx.has_error())) { |
| return result; |
| } |
| auto elem = Serializer<T>::read(ctx, RefMode::None, false); |
| result.push_back(std::move(elem)); |
| } |
| return result; |
| } |
| |
| // General path: handle HAS_NULL and/or TRACKING_REF |
| for (uint32_t i = 0; i < length; ++i) { |
| if (FORY_PREDICT_FALSE(ctx.has_error())) { |
| return result; |
| } |
| if (track_ref) { |
| auto elem = Serializer<T>::read(ctx, RefMode::Tracking, false); |
| result.push_back(std::move(elem)); |
| } else if (has_null) { |
| bool has_value_elem = read_null_only_flag(ctx, RefMode::NullOnly); |
| if (!has_value_elem) { |
| result.emplace_back(); |
| } else { |
| if constexpr (is_nullable_v<T>) { |
| using Inner = nullable_element_t<T>; |
| auto inner = Serializer<Inner>::read(ctx, RefMode::None, false); |
| result.emplace_back(std::move(inner)); |
| } else { |
| auto elem = Serializer<T>::read(ctx, RefMode::None, false); |
| result.push_back(std::move(elem)); |
| } |
| } |
| } else { |
| auto elem = Serializer<T>::read(ctx, RefMode::None, false); |
| result.push_back(std::move(elem)); |
| } |
| } |
| |
| return result; |
| } |
| } |
| |
| static inline void write(const std::vector<T, Alloc> &vec, WriteContext &ctx, |
| RefMode ref_mode, bool write_type, |
| bool has_generics = false) { |
| // Write ref flag if requested |
| write_not_null_ref_flag(ctx, ref_mode); |
| |
| // Write type info if requested |
| if (write_type) { |
| ctx.write_varuint32(static_cast<uint32_t>(type_id)); |
| } |
| |
| write_data_generic(vec, ctx, has_generics); |
| } |
| |
| static inline void write_data(const std::vector<T, Alloc> &vec, |
| WriteContext &ctx) { |
| ctx.write_varuint32(static_cast<uint32_t>(vec.size())); |
| for (const auto &elem : vec) { |
| Serializer<T>::write_data(elem, ctx); |
| } |
| } |
| |
| static inline void write_data_generic(const std::vector<T, Alloc> &vec, |
| WriteContext &ctx, bool has_generics) { |
| // Dispatch to fast or slow path based on element type characteristics |
| constexpr bool is_fast_path = !is_polymorphic_v<T> && !is_shared_ref_v<T>; |
| |
| if constexpr (is_fast_path) { |
| write_collection_data_fast<T>(vec, ctx, has_generics); |
| } else { |
| write_collection_data_slow<T>(vec, ctx, has_generics); |
| } |
| } |
| |
| static inline std::vector<T, Alloc> |
| read_with_type_info(ReadContext &ctx, RefMode ref_mode, |
| const TypeInfo &type_info) { |
| return read(ctx, ref_mode, false); |
| } |
| |
| static inline std::vector<T, Alloc> read_data(ReadContext &ctx) { |
| uint32_t size = ctx.read_varuint32(ctx.error()); |
| if (FORY_PREDICT_FALSE(ctx.has_error())) { |
| return std::vector<T, Alloc>(); |
| } |
| std::vector<T, Alloc> result; |
| result.reserve(size); |
| for (uint32_t i = 0; i < size; ++i) { |
| if (FORY_PREDICT_FALSE(ctx.has_error())) { |
| return result; |
| } |
| auto elem = Serializer<T>::read_data(ctx); |
| result.push_back(std::move(elem)); |
| } |
| return result; |
| } |
| }; |
| |
| /// Specialized serializer for std::vector<bool> |
| template <typename Alloc> struct Serializer<std::vector<bool, Alloc>> { |
| static constexpr TypeId type_id = TypeId::BOOL_ARRAY; |
| |
| static inline void write_type_info(WriteContext &ctx) { |
| ctx.write_varuint32(static_cast<uint32_t>(type_id)); |
| } |
| |
| static inline void read_type_info(ReadContext &ctx) { |
| uint32_t actual = ctx.read_varuint32(ctx.error()); |
| if (FORY_PREDICT_FALSE(ctx.has_error())) { |
| return; |
| } |
| if (!type_id_matches(actual, static_cast<uint32_t>(type_id))) { |
| ctx.set_error( |
| Error::type_mismatch(actual, static_cast<uint32_t>(type_id))); |
| } |
| } |
| |
| static inline void write(const std::vector<bool, Alloc> &vec, |
| WriteContext &ctx, RefMode ref_mode, bool write_type, |
| bool has_generics = false) { |
| write_not_null_ref_flag(ctx, ref_mode); |
| if (write_type) { |
| ctx.write_varuint32(static_cast<uint32_t>(type_id)); |
| } |
| write_data(vec, ctx); |
| } |
| |
| static inline void write_data(const std::vector<bool, Alloc> &vec, |
| WriteContext &ctx) { |
| Buffer &buffer = ctx.buffer(); |
| // bulk write may write 8 bytes for varint32 |
| size_t max_size = 8 + vec.size(); |
| buffer.Grow(static_cast<uint32_t>(max_size)); |
| uint32_t writer_index = buffer.writer_index(); |
| writer_index += |
| buffer.PutVarUint32(writer_index, static_cast<uint32_t>(vec.size())); |
| for (size_t i = 0; i < vec.size(); ++i) { |
| buffer.UnsafePutByte(writer_index + i, |
| static_cast<uint8_t>(vec[i] ? 1 : 0)); |
| } |
| buffer.WriterIndex(writer_index + vec.size()); |
| } |
| |
| static inline void write_data_generic(const std::vector<bool, Alloc> &vec, |
| WriteContext &ctx, bool has_generics) { |
| write_data(vec, ctx); |
| } |
| |
| static inline std::vector<bool, Alloc> |
| read(ReadContext &ctx, RefMode ref_mode, bool read_type) { |
| bool has_value = read_null_only_flag(ctx, ref_mode); |
| if (ctx.has_error() || !has_value) { |
| return std::vector<bool, Alloc>(); |
| } |
| |
| if (read_type) { |
| uint32_t type_id_read = ctx.read_varuint32(ctx.error()); |
| if (FORY_PREDICT_FALSE(ctx.has_error())) { |
| return std::vector<bool, Alloc>(); |
| } |
| if (type_id_read != static_cast<uint32_t>(type_id)) { |
| ctx.set_error( |
| Error::type_mismatch(type_id_read, static_cast<uint32_t>(type_id))); |
| return std::vector<bool, Alloc>(); |
| } |
| } |
| return read_data(ctx); |
| } |
| |
| static inline std::vector<bool, Alloc> read_data(ReadContext &ctx) { |
| uint32_t size = ctx.read_varuint32(ctx.error()); |
| if (FORY_PREDICT_FALSE(ctx.has_error())) { |
| return std::vector<bool, Alloc>(); |
| } |
| std::vector<bool, Alloc> result(size); |
| // Fast path: bulk read all bytes at once if we have enough buffer |
| Buffer &buffer = ctx.buffer(); |
| if (size > 0 && buffer.reader_index() + size <= buffer.size()) { |
| const uint8_t *src = buffer.data() + buffer.reader_index(); |
| for (uint32_t i = 0; i < size; ++i) { |
| result[i] = (src[i] != 0); |
| } |
| buffer.IncreaseReaderIndex(size); |
| } else { |
| // Fallback: read byte-by-byte with bounds checking |
| for (uint32_t i = 0; i < size; ++i) { |
| if (FORY_PREDICT_FALSE(ctx.has_error())) { |
| return result; |
| } |
| uint8_t byte = ctx.read_uint8(ctx.error()); |
| result[i] = (byte != 0); |
| } |
| } |
| return result; |
| } |
| }; |
| |
| // ============================================================================ |
| // std::list serializer |
| // ============================================================================ |
| |
| template <typename T, typename Alloc> struct Serializer<std::list<T, Alloc>> { |
| static constexpr TypeId type_id = TypeId::LIST; |
| |
| static inline void write_type_info(WriteContext &ctx) { |
| ctx.write_varuint32(static_cast<uint32_t>(type_id)); |
| } |
| |
| static inline void read_type_info(ReadContext &ctx) { |
| uint32_t actual = ctx.read_varuint32(ctx.error()); |
| if (FORY_PREDICT_FALSE(ctx.has_error())) { |
| return; |
| } |
| if (!type_id_matches(actual, static_cast<uint32_t>(type_id))) { |
| ctx.set_error( |
| Error::type_mismatch(actual, static_cast<uint32_t>(type_id))); |
| } |
| } |
| |
| static inline std::list<T, Alloc> read(ReadContext &ctx, RefMode ref_mode, |
| bool read_type) { |
| // List-level reference flag |
| bool has_value = read_null_only_flag(ctx, ref_mode); |
| if (ctx.has_error() || !has_value) { |
| return std::list<T, Alloc>(); |
| } |
| |
| // Optional type info for polymorphic containers |
| if (read_type) { |
| uint32_t type_id_read = ctx.read_varuint32(ctx.error()); |
| if (FORY_PREDICT_FALSE(ctx.has_error())) { |
| return std::list<T, Alloc>(); |
| } |
| uint32_t low = type_id_read & 0xffu; |
| if (low != static_cast<uint32_t>(type_id)) { |
| ctx.set_error( |
| Error::type_mismatch(type_id_read, static_cast<uint32_t>(type_id))); |
| return std::list<T, Alloc>(); |
| } |
| } |
| |
| // Length written via writeVarUint32Small7 |
| uint32_t length = ctx.read_varuint32(ctx.error()); |
| if (FORY_PREDICT_FALSE(ctx.has_error())) { |
| return std::list<T, Alloc>(); |
| } |
| // Per xlang spec: header and type_info are omitted when length is 0 |
| if (length == 0) { |
| return std::list<T, Alloc>(); |
| } |
| |
| // Dispatch to slow path for polymorphic/shared-ref elements |
| constexpr bool is_slow_path = is_polymorphic_v<T> || is_shared_ref_v<T>; |
| if constexpr (is_slow_path) { |
| return read_collection_data_slow<T, std::list<T, Alloc>>(ctx, length); |
| } else { |
| // Fast path for non-polymorphic, non-shared-ref elements |
| |
| // Elements header bitmap (CollectionFlags) |
| uint8_t bitmap = ctx.read_uint8(ctx.error()); |
| if (FORY_PREDICT_FALSE(ctx.has_error())) { |
| return std::list<T, Alloc>(); |
| } |
| bool track_ref = (bitmap & COLL_TRACKING_REF) != 0; |
| bool has_null = (bitmap & COLL_HAS_NULL) != 0; |
| bool is_decl_type = (bitmap & COLL_DECL_ELEMENT_TYPE) != 0; |
| bool is_same_type = (bitmap & COLL_IS_SAME_TYPE) != 0; |
| |
| // Read element type info if IS_SAME_TYPE is set but IS_DECL_ELEMENT_TYPE |
| // is not. |
| if (is_same_type && !is_decl_type) { |
| const TypeInfo *elem_type_info = ctx.read_any_typeinfo(ctx.error()); |
| if (FORY_PREDICT_FALSE(ctx.has_error())) { |
| return std::list<T, Alloc>(); |
| } |
| using ElemType = nullable_element_t<T>; |
| uint32_t expected = |
| static_cast<uint32_t>(Serializer<ElemType>::type_id); |
| if (!type_id_matches(elem_type_info->type_id, expected)) { |
| ctx.set_error( |
| Error::type_mismatch(elem_type_info->type_id, expected)); |
| return std::list<T, Alloc>(); |
| } |
| } |
| |
| std::list<T, Alloc> result; |
| |
| // Fast path: no tracking, no nulls, elements have declared type |
| if (!track_ref && !has_null && is_same_type) { |
| for (uint32_t i = 0; i < length; ++i) { |
| if (FORY_PREDICT_FALSE(ctx.has_error())) { |
| return result; |
| } |
| auto elem = Serializer<T>::read(ctx, RefMode::None, false); |
| result.push_back(std::move(elem)); |
| } |
| return result; |
| } |
| |
| // General path: handle HAS_NULL and/or TRACKING_REF |
| for (uint32_t i = 0; i < length; ++i) { |
| if (FORY_PREDICT_FALSE(ctx.has_error())) { |
| return result; |
| } |
| if (track_ref) { |
| auto elem = Serializer<T>::read(ctx, RefMode::Tracking, false); |
| result.push_back(std::move(elem)); |
| } else if (has_null) { |
| bool has_value_elem = read_null_only_flag(ctx, RefMode::NullOnly); |
| if (!has_value_elem) { |
| result.emplace_back(); |
| } else { |
| if constexpr (is_nullable_v<T>) { |
| using Inner = nullable_element_t<T>; |
| auto inner = Serializer<Inner>::read(ctx, RefMode::None, false); |
| result.emplace_back(std::move(inner)); |
| } else { |
| auto elem = Serializer<T>::read(ctx, RefMode::None, false); |
| result.push_back(std::move(elem)); |
| } |
| } |
| } else { |
| auto elem = Serializer<T>::read(ctx, RefMode::None, false); |
| result.push_back(std::move(elem)); |
| } |
| } |
| |
| return result; |
| } |
| } |
| |
| static inline void write(const std::list<T, Alloc> &lst, WriteContext &ctx, |
| RefMode ref_mode, bool write_type, |
| bool has_generics = false) { |
| // Write ref flag if requested |
| write_not_null_ref_flag(ctx, ref_mode); |
| |
| // Write type info if requested |
| if (write_type) { |
| ctx.write_varuint32(static_cast<uint32_t>(type_id)); |
| } |
| |
| write_data_generic(lst, ctx, has_generics); |
| } |
| |
| static inline void write_data(const std::list<T, Alloc> &lst, |
| WriteContext &ctx) { |
| ctx.write_varuint32(static_cast<uint32_t>(lst.size())); |
| for (const auto &elem : lst) { |
| Serializer<T>::write_data(elem, ctx); |
| } |
| } |
| |
| static inline void write_data_generic(const std::list<T, Alloc> &lst, |
| WriteContext &ctx, bool has_generics) { |
| // Dispatch to fast or slow path based on element type characteristics |
| constexpr bool is_fast_path = !is_polymorphic_v<T> && !is_shared_ref_v<T>; |
| |
| if constexpr (is_fast_path) { |
| write_collection_data_fast<T>(lst, ctx, has_generics); |
| } else { |
| write_collection_data_slow<T>(lst, ctx, has_generics); |
| } |
| } |
| |
| static inline std::list<T, Alloc> |
| read_with_type_info(ReadContext &ctx, RefMode ref_mode, |
| const TypeInfo &type_info) { |
| return read(ctx, ref_mode, false); |
| } |
| |
| static inline std::list<T, Alloc> read_data(ReadContext &ctx) { |
| uint32_t size = ctx.read_varuint32(ctx.error()); |
| if (FORY_PREDICT_FALSE(ctx.has_error())) { |
| return std::list<T, Alloc>(); |
| } |
| std::list<T, Alloc> result; |
| for (uint32_t i = 0; i < size; ++i) { |
| if (FORY_PREDICT_FALSE(ctx.has_error())) { |
| return result; |
| } |
| auto elem = Serializer<T>::read_data(ctx); |
| result.push_back(std::move(elem)); |
| } |
| return result; |
| } |
| }; |
| |
| // ============================================================================ |
| // std::deque serializer |
| // ============================================================================ |
| |
| template <typename T, typename Alloc> struct Serializer<std::deque<T, Alloc>> { |
| static constexpr TypeId type_id = TypeId::LIST; |
| |
| static inline void write_type_info(WriteContext &ctx) { |
| ctx.write_varuint32(static_cast<uint32_t>(type_id)); |
| } |
| |
| static inline void read_type_info(ReadContext &ctx) { |
| uint32_t actual = ctx.read_varuint32(ctx.error()); |
| if (FORY_PREDICT_FALSE(ctx.has_error())) { |
| return; |
| } |
| if (!type_id_matches(actual, static_cast<uint32_t>(type_id))) { |
| ctx.set_error( |
| Error::type_mismatch(actual, static_cast<uint32_t>(type_id))); |
| } |
| } |
| |
| static inline std::deque<T, Alloc> read(ReadContext &ctx, RefMode ref_mode, |
| bool read_type) { |
| // Deque-level reference flag |
| bool has_value = read_null_only_flag(ctx, ref_mode); |
| if (ctx.has_error() || !has_value) { |
| return std::deque<T, Alloc>(); |
| } |
| |
| // Optional type info for polymorphic containers |
| if (read_type) { |
| uint32_t type_id_read = ctx.read_varuint32(ctx.error()); |
| if (FORY_PREDICT_FALSE(ctx.has_error())) { |
| return std::deque<T, Alloc>(); |
| } |
| uint32_t low = type_id_read & 0xffu; |
| if (low != static_cast<uint32_t>(type_id)) { |
| ctx.set_error( |
| Error::type_mismatch(type_id_read, static_cast<uint32_t>(type_id))); |
| return std::deque<T, Alloc>(); |
| } |
| } |
| |
| // Length written via writeVarUint32Small7 |
| uint32_t length = ctx.read_varuint32(ctx.error()); |
| if (FORY_PREDICT_FALSE(ctx.has_error())) { |
| return std::deque<T, Alloc>(); |
| } |
| // Per xlang spec: header and type_info are omitted when length is 0 |
| if (length == 0) { |
| return std::deque<T, Alloc>(); |
| } |
| |
| // Dispatch to slow path for polymorphic/shared-ref elements |
| constexpr bool is_slow_path = is_polymorphic_v<T> || is_shared_ref_v<T>; |
| if constexpr (is_slow_path) { |
| return read_collection_data_slow<T, std::deque<T, Alloc>>(ctx, length); |
| } else { |
| // Fast path for non-polymorphic, non-shared-ref elements |
| |
| // Elements header bitmap (CollectionFlags) |
| uint8_t bitmap = ctx.read_uint8(ctx.error()); |
| if (FORY_PREDICT_FALSE(ctx.has_error())) { |
| return std::deque<T, Alloc>(); |
| } |
| bool track_ref = (bitmap & COLL_TRACKING_REF) != 0; |
| bool has_null = (bitmap & COLL_HAS_NULL) != 0; |
| bool is_decl_type = (bitmap & COLL_DECL_ELEMENT_TYPE) != 0; |
| bool is_same_type = (bitmap & COLL_IS_SAME_TYPE) != 0; |
| |
| // Read element type info if IS_SAME_TYPE is set but IS_DECL_ELEMENT_TYPE |
| // is not. |
| if (is_same_type && !is_decl_type) { |
| const TypeInfo *elem_type_info = ctx.read_any_typeinfo(ctx.error()); |
| if (FORY_PREDICT_FALSE(ctx.has_error())) { |
| return std::deque<T, Alloc>(); |
| } |
| using ElemType = nullable_element_t<T>; |
| uint32_t expected = |
| static_cast<uint32_t>(Serializer<ElemType>::type_id); |
| if (!type_id_matches(elem_type_info->type_id, expected)) { |
| ctx.set_error( |
| Error::type_mismatch(elem_type_info->type_id, expected)); |
| return std::deque<T, Alloc>(); |
| } |
| } |
| |
| std::deque<T, Alloc> result; |
| |
| // Fast path: no tracking, no nulls, elements have declared type |
| if (!track_ref && !has_null && is_same_type) { |
| for (uint32_t i = 0; i < length; ++i) { |
| if (FORY_PREDICT_FALSE(ctx.has_error())) { |
| return result; |
| } |
| auto elem = Serializer<T>::read(ctx, RefMode::None, false); |
| result.push_back(std::move(elem)); |
| } |
| return result; |
| } |
| |
| // General path: handle HAS_NULL and/or TRACKING_REF |
| for (uint32_t i = 0; i < length; ++i) { |
| if (FORY_PREDICT_FALSE(ctx.has_error())) { |
| return result; |
| } |
| if (track_ref) { |
| auto elem = Serializer<T>::read(ctx, RefMode::Tracking, false); |
| result.push_back(std::move(elem)); |
| } else if (has_null) { |
| bool has_value_elem = read_null_only_flag(ctx, RefMode::NullOnly); |
| if (!has_value_elem) { |
| result.emplace_back(); |
| } else { |
| if constexpr (is_nullable_v<T>) { |
| using Inner = nullable_element_t<T>; |
| auto inner = Serializer<Inner>::read(ctx, RefMode::None, false); |
| result.emplace_back(std::move(inner)); |
| } else { |
| auto elem = Serializer<T>::read(ctx, RefMode::None, false); |
| result.push_back(std::move(elem)); |
| } |
| } |
| } else { |
| auto elem = Serializer<T>::read(ctx, RefMode::None, false); |
| result.push_back(std::move(elem)); |
| } |
| } |
| |
| return result; |
| } |
| } |
| |
| static inline void write(const std::deque<T, Alloc> &deq, WriteContext &ctx, |
| RefMode ref_mode, bool write_type, |
| bool has_generics = false) { |
| // Write ref flag if requested |
| write_not_null_ref_flag(ctx, ref_mode); |
| |
| // Write type info if requested |
| if (write_type) { |
| ctx.write_varuint32(static_cast<uint32_t>(type_id)); |
| } |
| |
| write_data_generic(deq, ctx, has_generics); |
| } |
| |
| static inline void write_data(const std::deque<T, Alloc> &deq, |
| WriteContext &ctx) { |
| ctx.write_varuint32(static_cast<uint32_t>(deq.size())); |
| for (const auto &elem : deq) { |
| Serializer<T>::write_data(elem, ctx); |
| } |
| } |
| |
| static inline void write_data_generic(const std::deque<T, Alloc> &deq, |
| WriteContext &ctx, bool has_generics) { |
| // Dispatch to fast or slow path based on element type characteristics |
| constexpr bool is_fast_path = !is_polymorphic_v<T> && !is_shared_ref_v<T>; |
| |
| if constexpr (is_fast_path) { |
| write_collection_data_fast<T>(deq, ctx, has_generics); |
| } else { |
| write_collection_data_slow<T>(deq, ctx, has_generics); |
| } |
| } |
| |
| static inline std::deque<T, Alloc> |
| read_with_type_info(ReadContext &ctx, RefMode ref_mode, |
| const TypeInfo &type_info) { |
| return read(ctx, ref_mode, false); |
| } |
| |
| static inline std::deque<T, Alloc> read_data(ReadContext &ctx) { |
| uint32_t size = ctx.read_varuint32(ctx.error()); |
| if (FORY_PREDICT_FALSE(ctx.has_error())) { |
| return std::deque<T, Alloc>(); |
| } |
| std::deque<T, Alloc> result; |
| for (uint32_t i = 0; i < size; ++i) { |
| if (FORY_PREDICT_FALSE(ctx.has_error())) { |
| return result; |
| } |
| auto elem = Serializer<T>::read_data(ctx); |
| result.push_back(std::move(elem)); |
| } |
| return result; |
| } |
| }; |
| |
| // ============================================================================ |
| // std::forward_list serializer |
| // ============================================================================ |
| |
| template <typename T, typename Alloc> |
| struct Serializer<std::forward_list<T, Alloc>> { |
| static constexpr TypeId type_id = TypeId::LIST; |
| |
| static inline void write_type_info(WriteContext &ctx) { |
| ctx.write_varuint32(static_cast<uint32_t>(type_id)); |
| } |
| |
| static inline void read_type_info(ReadContext &ctx) { |
| uint32_t actual = ctx.read_varuint32(ctx.error()); |
| if (FORY_PREDICT_FALSE(ctx.has_error())) { |
| return; |
| } |
| if (!type_id_matches(actual, static_cast<uint32_t>(type_id))) { |
| ctx.set_error( |
| Error::type_mismatch(actual, static_cast<uint32_t>(type_id))); |
| } |
| } |
| |
| static inline std::forward_list<T, Alloc> |
| read(ReadContext &ctx, RefMode ref_mode, bool read_type) { |
| // List-level reference flag |
| bool has_value = read_null_only_flag(ctx, ref_mode); |
| if (ctx.has_error() || !has_value) { |
| return std::forward_list<T, Alloc>(); |
| } |
| |
| // Optional type info for polymorphic containers |
| if (read_type) { |
| uint32_t type_id_read = ctx.read_varuint32(ctx.error()); |
| if (FORY_PREDICT_FALSE(ctx.has_error())) { |
| return std::forward_list<T, Alloc>(); |
| } |
| uint32_t low = type_id_read & 0xffu; |
| if (low != static_cast<uint32_t>(type_id)) { |
| ctx.set_error( |
| Error::type_mismatch(type_id_read, static_cast<uint32_t>(type_id))); |
| return std::forward_list<T, Alloc>(); |
| } |
| } |
| |
| // Length written via writeVarUint32Small7 |
| uint32_t length = ctx.read_varuint32(ctx.error()); |
| if (FORY_PREDICT_FALSE(ctx.has_error())) { |
| return std::forward_list<T, Alloc>(); |
| } |
| // Per xlang spec: header and type_info are omitted when length is 0 |
| if (length == 0) { |
| return std::forward_list<T, Alloc>(); |
| } |
| |
| // Read elements into a temporary vector then build forward_list |
| // (forward_list doesn't have push_back, only push_front) |
| std::vector<T> temp; |
| temp.reserve(length); |
| |
| // Dispatch to slow path for polymorphic/shared-ref elements |
| constexpr bool is_slow_path = is_polymorphic_v<T> || is_shared_ref_v<T>; |
| if constexpr (is_slow_path) { |
| temp = read_collection_data_slow<T, std::vector<T>>(ctx, length); |
| } else { |
| // Fast path for non-polymorphic, non-shared-ref elements |
| |
| // Elements header bitmap (CollectionFlags) |
| uint8_t bitmap = ctx.read_uint8(ctx.error()); |
| if (FORY_PREDICT_FALSE(ctx.has_error())) { |
| return std::forward_list<T, Alloc>(); |
| } |
| bool track_ref = (bitmap & COLL_TRACKING_REF) != 0; |
| bool has_null = (bitmap & COLL_HAS_NULL) != 0; |
| bool is_decl_type = (bitmap & COLL_DECL_ELEMENT_TYPE) != 0; |
| bool is_same_type = (bitmap & COLL_IS_SAME_TYPE) != 0; |
| |
| // Read element type info if IS_SAME_TYPE is set but IS_DECL_ELEMENT_TYPE |
| // is not. |
| if (is_same_type && !is_decl_type) { |
| const TypeInfo *elem_type_info = ctx.read_any_typeinfo(ctx.error()); |
| if (FORY_PREDICT_FALSE(ctx.has_error())) { |
| return std::forward_list<T, Alloc>(); |
| } |
| using ElemType = nullable_element_t<T>; |
| uint32_t expected = |
| static_cast<uint32_t>(Serializer<ElemType>::type_id); |
| if (!type_id_matches(elem_type_info->type_id, expected)) { |
| ctx.set_error( |
| Error::type_mismatch(elem_type_info->type_id, expected)); |
| return std::forward_list<T, Alloc>(); |
| } |
| } |
| |
| // Fast path: no tracking, no nulls, elements have declared type |
| if (!track_ref && !has_null && is_same_type) { |
| for (uint32_t i = 0; i < length; ++i) { |
| if (FORY_PREDICT_FALSE(ctx.has_error())) { |
| break; |
| } |
| auto elem = Serializer<T>::read(ctx, RefMode::None, false); |
| temp.push_back(std::move(elem)); |
| } |
| } else { |
| // General path: handle HAS_NULL and/or TRACKING_REF |
| for (uint32_t i = 0; i < length; ++i) { |
| if (FORY_PREDICT_FALSE(ctx.has_error())) { |
| break; |
| } |
| if (track_ref) { |
| auto elem = Serializer<T>::read(ctx, RefMode::Tracking, false); |
| temp.push_back(std::move(elem)); |
| } else if (has_null) { |
| bool has_value_elem = read_null_only_flag(ctx, RefMode::NullOnly); |
| if (!has_value_elem) { |
| temp.emplace_back(); |
| } else { |
| if constexpr (is_nullable_v<T>) { |
| using Inner = nullable_element_t<T>; |
| auto inner = Serializer<Inner>::read(ctx, RefMode::None, false); |
| temp.emplace_back(std::move(inner)); |
| } else { |
| auto elem = Serializer<T>::read(ctx, RefMode::None, false); |
| temp.push_back(std::move(elem)); |
| } |
| } |
| } else { |
| auto elem = Serializer<T>::read(ctx, RefMode::None, false); |
| temp.push_back(std::move(elem)); |
| } |
| } |
| } |
| } |
| |
| // Build forward_list in reverse order using push_front |
| std::forward_list<T, Alloc> result; |
| for (auto it = temp.rbegin(); it != temp.rend(); ++it) { |
| result.push_front(std::move(*it)); |
| } |
| return result; |
| } |
| |
| static inline void write(const std::forward_list<T, Alloc> &lst, |
| WriteContext &ctx, RefMode ref_mode, bool write_type, |
| bool has_generics = false) { |
| // Write ref flag if requested |
| write_not_null_ref_flag(ctx, ref_mode); |
| |
| // Write type info if requested |
| if (write_type) { |
| ctx.write_varuint32(static_cast<uint32_t>(type_id)); |
| } |
| |
| write_data_generic(lst, ctx, has_generics); |
| } |
| |
| static inline void write_data(const std::forward_list<T, Alloc> &lst, |
| WriteContext &ctx) { |
| // forward_list doesn't have size(), so we need to count elements first |
| uint32_t size = 0; |
| for (const auto &elem : lst) { |
| (void)elem; |
| ++size; |
| } |
| ctx.write_varuint32(size); |
| for (const auto &elem : lst) { |
| Serializer<T>::write_data(elem, ctx); |
| } |
| } |
| |
| static inline void write_data_generic(const std::forward_list<T, Alloc> &lst, |
| WriteContext &ctx, bool has_generics) { |
| // Convert to vector first for efficient writing (forward_list has no size) |
| std::vector<std::reference_wrapper<const T>> temp; |
| for (const auto &elem : lst) { |
| temp.push_back(std::cref(elem)); |
| } |
| |
| // Write length |
| ctx.write_varuint32(static_cast<uint32_t>(temp.size())); |
| |
| if (temp.empty()) { |
| return; |
| } |
| |
| // Dispatch to fast or slow path based on element type characteristics |
| constexpr bool is_fast_path = !is_polymorphic_v<T> && !is_shared_ref_v<T>; |
| |
| if constexpr (is_fast_path) { |
| // Check for null elements |
| bool has_null = false; |
| if constexpr (is_nullable_v<T>) { |
| for (const auto &elem_ref : temp) { |
| if (is_null_value(elem_ref.get())) { |
| has_null = true; |
| break; |
| } |
| } |
| } |
| |
| // Build header bitmap |
| uint8_t bitmap = COLL_IS_SAME_TYPE; |
| if (has_null) { |
| bitmap |= COLL_HAS_NULL; |
| } |
| |
| // Determine if element type is declared |
| using ElemType = nullable_element_t<T>; |
| bool is_elem_declared = |
| has_generics && !need_type_for_collection_elem<ElemType>(); |
| if (is_elem_declared) { |
| bitmap |= COLL_DECL_ELEMENT_TYPE; |
| } |
| |
| // Write header |
| ctx.write_uint8(bitmap); |
| |
| // Write element type info if not declared |
| if (!is_elem_declared) { |
| Serializer<ElemType>::write_type_info(ctx); |
| } |
| |
| // Write elements |
| if constexpr (is_nullable_v<T>) { |
| using Inner = nullable_element_t<T>; |
| if (has_null) { |
| for (const auto &elem_ref : temp) { |
| const auto &elem = elem_ref.get(); |
| if (is_null_value(elem)) { |
| ctx.write_int8(NULL_FLAG); |
| } else { |
| ctx.write_int8(NOT_NULL_VALUE_FLAG); |
| if (is_elem_declared) { |
| Serializer<Inner>::write_data(deref_nullable(elem), ctx); |
| } else { |
| Serializer<Inner>::write(deref_nullable(elem), ctx, |
| RefMode::None, false); |
| } |
| } |
| } |
| } else { |
| for (const auto &elem_ref : temp) { |
| const auto &elem = elem_ref.get(); |
| if (is_elem_declared) { |
| Serializer<Inner>::write_data(deref_nullable(elem), ctx); |
| } else { |
| Serializer<Inner>::write(deref_nullable(elem), ctx, RefMode::None, |
| false); |
| } |
| } |
| } |
| } else { |
| for (const auto &elem_ref : temp) { |
| const auto &elem = elem_ref.get(); |
| if (is_elem_declared) { |
| if constexpr (is_generic_type_v<T>) { |
| Serializer<T>::write_data_generic(elem, ctx, true); |
| } else { |
| Serializer<T>::write_data(elem, ctx); |
| } |
| } else { |
| Serializer<T>::write(elem, ctx, RefMode::None, false); |
| } |
| } |
| } |
| } else { |
| // Slow path for polymorphic or shared-ref elements |
| constexpr bool elem_is_polymorphic = is_polymorphic_v<T>; |
| constexpr bool elem_is_shared_ref = is_shared_ref_v<T>; |
| |
| using ElemType = nullable_element_t<T>; |
| bool is_elem_declared = |
| has_generics && !need_type_for_collection_elem<ElemType>(); |
| |
| // Scan collection to determine header flags |
| bool has_null = false; |
| bool is_same_type = true; |
| std::type_index first_type{typeid(void)}; |
| bool first_type_set = false; |
| |
| for (const auto &elem_ref : temp) { |
| const auto &elem = elem_ref.get(); |
| // Check for nulls |
| if constexpr (is_nullable_v<T>) { |
| if (is_null_value(elem)) { |
| has_null = true; |
| continue; |
| } |
| } |
| // Check runtime types for polymorphic elements |
| if constexpr (elem_is_polymorphic) { |
| if (is_same_type) { |
| auto concrete_id = get_concrete_type_id(elem); |
| if (!first_type_set) { |
| first_type = concrete_id; |
| first_type_set = true; |
| } else if (concrete_id != first_type) { |
| is_same_type = false; |
| } |
| } |
| } |
| } |
| |
| // If all polymorphic elements are null, treat as heterogeneous |
| if constexpr (elem_is_polymorphic) { |
| if (is_same_type && !first_type_set) { |
| is_same_type = false; |
| } |
| } |
| |
| // Build header bitmap |
| uint8_t bitmap = 0; |
| if (has_null) { |
| bitmap |= COLL_HAS_NULL; |
| } |
| if (is_elem_declared && !elem_is_polymorphic) { |
| bitmap |= COLL_DECL_ELEMENT_TYPE; |
| } |
| if (is_same_type) { |
| bitmap |= COLL_IS_SAME_TYPE; |
| } |
| // Only set TRACKING_REF if element is shared ref AND global ref tracking |
| // is enabled |
| if constexpr (elem_is_shared_ref) { |
| if (ctx.track_ref()) { |
| bitmap |= COLL_TRACKING_REF; |
| } |
| } |
| |
| // Write header |
| ctx.write_uint8(bitmap); |
| |
| // Write element type info if IS_SAME_TYPE && !IS_DECL_ELEMENT_TYPE |
| if (is_same_type && !(bitmap & COLL_DECL_ELEMENT_TYPE)) { |
| if constexpr (elem_is_polymorphic) { |
| ctx.write_any_typeinfo(static_cast<uint32_t>(TypeId::UNKNOWN), |
| first_type); |
| } else { |
| Serializer<ElemType>::write_type_info(ctx); |
| } |
| } |
| |
| // Write elements |
| if (is_same_type) { |
| if (!has_null) { |
| if constexpr (elem_is_shared_ref) { |
| for (const auto &elem_ref : temp) { |
| Serializer<T>::write(elem_ref.get(), ctx, RefMode::NullOnly, |
| false, has_generics); |
| } |
| } else { |
| for (const auto &elem_ref : temp) { |
| const auto &elem = elem_ref.get(); |
| if constexpr (is_nullable_v<T>) { |
| using Inner = nullable_element_t<T>; |
| Serializer<Inner>::write_data(deref_nullable(elem), ctx); |
| } else { |
| if constexpr (is_generic_type_v<T>) { |
| Serializer<T>::write_data_generic(elem, ctx, has_generics); |
| } else { |
| Serializer<T>::write_data(elem, ctx); |
| } |
| } |
| } |
| } |
| } else { |
| for (const auto &elem_ref : temp) { |
| Serializer<T>::write(elem_ref.get(), ctx, RefMode::NullOnly, false, |
| has_generics); |
| } |
| } |
| } else { |
| if (!has_null) { |
| if constexpr (elem_is_shared_ref) { |
| for (const auto &elem_ref : temp) { |
| Serializer<T>::write(elem_ref.get(), ctx, RefMode::NullOnly, true, |
| has_generics); |
| } |
| } else { |
| for (const auto &elem_ref : temp) { |
| Serializer<T>::write(elem_ref.get(), ctx, RefMode::None, true, |
| has_generics); |
| } |
| } |
| } else { |
| for (const auto &elem_ref : temp) { |
| Serializer<T>::write(elem_ref.get(), ctx, RefMode::NullOnly, true, |
| has_generics); |
| } |
| } |
| } |
| } |
| } |
| |
| static inline std::forward_list<T, Alloc> |
| read_with_type_info(ReadContext &ctx, RefMode ref_mode, |
| const TypeInfo &type_info) { |
| return read(ctx, ref_mode, false); |
| } |
| |
| static inline std::forward_list<T, Alloc> read_data(ReadContext &ctx) { |
| uint32_t size = ctx.read_varuint32(ctx.error()); |
| if (FORY_PREDICT_FALSE(ctx.has_error())) { |
| return std::forward_list<T, Alloc>(); |
| } |
| std::vector<T> temp; |
| temp.reserve(size); |
| for (uint32_t i = 0; i < size; ++i) { |
| if (FORY_PREDICT_FALSE(ctx.has_error())) { |
| break; |
| } |
| auto elem = Serializer<T>::read_data(ctx); |
| temp.push_back(std::move(elem)); |
| } |
| // Build forward_list in reverse order |
| std::forward_list<T, Alloc> result; |
| for (auto it = temp.rbegin(); it != temp.rend(); ++it) { |
| result.push_front(std::move(*it)); |
| } |
| return result; |
| } |
| }; |
| |
| // ============================================================================ |
| // std::set serializer |
| // ============================================================================ |
| |
| template <typename T, typename... Args> |
| struct Serializer<std::set<T, Args...>> { |
| static constexpr TypeId type_id = TypeId::SET; |
| |
| static inline void write_type_info(WriteContext &ctx) { |
| ctx.write_varuint32(static_cast<uint32_t>(type_id)); |
| } |
| |
| static inline void read_type_info(ReadContext &ctx) { |
| uint32_t actual = ctx.read_varuint32(ctx.error()); |
| if (FORY_PREDICT_FALSE(ctx.has_error())) { |
| return; |
| } |
| if (!type_id_matches(actual, static_cast<uint32_t>(type_id))) { |
| ctx.set_error( |
| Error::type_mismatch(actual, static_cast<uint32_t>(type_id))); |
| } |
| } |
| |
| static inline void write(const std::set<T, Args...> &set, WriteContext &ctx, |
| RefMode ref_mode, bool write_type, |
| bool has_generics = false) { |
| write_not_null_ref_flag(ctx, ref_mode); |
| |
| if (write_type) { |
| ctx.write_varuint32(static_cast<uint32_t>(type_id)); |
| } |
| |
| write_data_generic(set, ctx, has_generics); |
| } |
| |
| static inline void write_data(const std::set<T, Args...> &set, |
| WriteContext &ctx) { |
| ctx.write_varuint32(static_cast<uint32_t>(set.size())); |
| for (const auto &elem : set) { |
| Serializer<T>::write_data(elem, ctx); |
| } |
| } |
| |
| static inline void write_data_generic(const std::set<T, Args...> &set, |
| WriteContext &ctx, bool has_generics) { |
| // Dispatch to fast or slow path based on element type characteristics |
| constexpr bool is_fast_path = !is_polymorphic_v<T> && !is_shared_ref_v<T>; |
| |
| if constexpr (is_fast_path) { |
| write_collection_data_fast<T>(set, ctx, has_generics); |
| } else { |
| write_collection_data_slow<T>(set, ctx, has_generics); |
| } |
| } |
| |
| static inline std::set<T, Args...> read(ReadContext &ctx, RefMode ref_mode, |
| bool read_type) { |
| bool has_value = read_null_only_flag(ctx, ref_mode); |
| if (ctx.has_error() || !has_value) { |
| return std::set<T, Args...>(); |
| } |
| |
| // Read type info |
| if (read_type) { |
| uint32_t type_id_read = ctx.read_varuint32(ctx.error()); |
| if (FORY_PREDICT_FALSE(ctx.has_error())) { |
| return std::set<T, Args...>(); |
| } |
| if (type_id_read != static_cast<uint32_t>(type_id)) { |
| ctx.set_error( |
| Error::type_mismatch(type_id_read, static_cast<uint32_t>(type_id))); |
| return std::set<T, Args...>(); |
| } |
| } |
| |
| // Read set size |
| uint32_t size = ctx.read_varuint32(ctx.error()); |
| if (FORY_PREDICT_FALSE(ctx.has_error())) { |
| return std::set<T, Args...>(); |
| } |
| // Per xlang spec: header and type_info are omitted when length is 0 |
| if (size == 0) { |
| return std::set<T, Args...>(); |
| } |
| |
| // Dispatch to slow path for polymorphic/shared-ref elements |
| constexpr bool is_slow_path = is_polymorphic_v<T> || is_shared_ref_v<T>; |
| if constexpr (is_slow_path) { |
| return read_collection_data_slow<T, std::set<T, Args...>>(ctx, size); |
| } else { |
| // Fast path for non-polymorphic, non-shared-ref elements |
| |
| // Read elements header bitmap (CollectionFlags) in xlang mode |
| uint8_t bitmap = ctx.read_uint8(ctx.error()); |
| if (FORY_PREDICT_FALSE(ctx.has_error())) { |
| return std::set<T, Args...>(); |
| } |
| bool track_ref = (bitmap & COLL_TRACKING_REF) != 0; |
| bool has_null = (bitmap & COLL_HAS_NULL) != 0; |
| bool is_decl_type = (bitmap & COLL_DECL_ELEMENT_TYPE) != 0; |
| bool is_same_type = (bitmap & COLL_IS_SAME_TYPE) != 0; |
| |
| if (is_same_type && !is_decl_type) { |
| const TypeInfo *elem_type_info = ctx.read_any_typeinfo(ctx.error()); |
| if (FORY_PREDICT_FALSE(ctx.has_error())) { |
| return std::set<T, Args...>(); |
| } |
| uint32_t expected = static_cast<uint32_t>(Serializer<T>::type_id); |
| if (!type_id_matches(elem_type_info->type_id, expected)) { |
| ctx.set_error( |
| Error::type_mismatch(elem_type_info->type_id, expected)); |
| return std::set<T, Args...>(); |
| } |
| } |
| |
| std::set<T, Args...> result; |
| if (!track_ref && !has_null && is_same_type) { |
| for (uint32_t i = 0; i < size; ++i) { |
| if (FORY_PREDICT_FALSE(ctx.has_error())) { |
| return result; |
| } |
| auto elem = Serializer<T>::read(ctx, RefMode::None, false); |
| result.insert(std::move(elem)); |
| } |
| return result; |
| } |
| |
| for (uint32_t i = 0; i < size; ++i) { |
| if (FORY_PREDICT_FALSE(ctx.has_error())) { |
| return result; |
| } |
| if (track_ref) { |
| auto elem = Serializer<T>::read(ctx, RefMode::Tracking, false); |
| result.insert(std::move(elem)); |
| } else if (has_null) { |
| bool has_value_elem = read_null_only_flag(ctx, RefMode::NullOnly); |
| if (has_value_elem) { |
| auto elem = Serializer<T>::read(ctx, RefMode::None, false); |
| result.insert(std::move(elem)); |
| } |
| } else { |
| auto elem = Serializer<T>::read(ctx, RefMode::None, false); |
| result.insert(std::move(elem)); |
| } |
| } |
| return result; |
| } |
| } |
| |
| static inline std::set<T, Args...> |
| read_with_type_info(ReadContext &ctx, RefMode ref_mode, |
| const TypeInfo &type_info) { |
| return read(ctx, ref_mode, false); |
| } |
| |
| static inline std::set<T, Args...> read_data(ReadContext &ctx) { |
| uint32_t size = ctx.read_varuint32(ctx.error()); |
| if (FORY_PREDICT_FALSE(ctx.has_error())) { |
| return std::set<T, Args...>(); |
| } |
| std::set<T, Args...> result; |
| for (uint32_t i = 0; i < size; ++i) { |
| if (FORY_PREDICT_FALSE(ctx.has_error())) { |
| return result; |
| } |
| auto elem = Serializer<T>::read_data(ctx); |
| result.insert(std::move(elem)); |
| } |
| return result; |
| } |
| }; |
| |
| // ============================================================================ |
| // std::unordered_set serializer |
| // ============================================================================ |
| |
| template <typename T, typename... Args> |
| struct Serializer<std::unordered_set<T, Args...>> { |
| static constexpr TypeId type_id = TypeId::SET; |
| |
| static inline void write_type_info(WriteContext &ctx) { |
| ctx.write_varuint32(static_cast<uint32_t>(type_id)); |
| } |
| |
| static inline void read_type_info(ReadContext &ctx) { |
| uint32_t actual = ctx.read_varuint32(ctx.error()); |
| if (FORY_PREDICT_FALSE(ctx.has_error())) { |
| return; |
| } |
| if (!type_id_matches(actual, static_cast<uint32_t>(type_id))) { |
| ctx.set_error( |
| Error::type_mismatch(actual, static_cast<uint32_t>(type_id))); |
| } |
| } |
| |
| static inline void write(const std::unordered_set<T, Args...> &set, |
| WriteContext &ctx, RefMode ref_mode, bool write_type, |
| bool has_generics = false) { |
| write_not_null_ref_flag(ctx, ref_mode); |
| |
| if (write_type) { |
| ctx.write_varuint32(static_cast<uint32_t>(type_id)); |
| } |
| |
| write_data_generic(set, ctx, has_generics); |
| } |
| |
| static inline void write_data(const std::unordered_set<T, Args...> &set, |
| WriteContext &ctx) { |
| ctx.write_varuint32(static_cast<uint32_t>(set.size())); |
| for (const auto &elem : set) { |
| Serializer<T>::write_data(elem, ctx); |
| } |
| } |
| |
| static inline void |
| write_data_generic(const std::unordered_set<T, Args...> &set, |
| WriteContext &ctx, bool has_generics) { |
| // Dispatch to fast or slow path based on element type characteristics |
| constexpr bool is_fast_path = !is_polymorphic_v<T> && !is_shared_ref_v<T>; |
| |
| if constexpr (is_fast_path) { |
| write_collection_data_fast<T>(set, ctx, has_generics); |
| } else { |
| write_collection_data_slow<T>(set, ctx, has_generics); |
| } |
| } |
| |
| static inline std::unordered_set<T, Args...> |
| read(ReadContext &ctx, RefMode ref_mode, bool read_type) { |
| bool has_value = read_null_only_flag(ctx, ref_mode); |
| if (ctx.has_error() || !has_value) { |
| return std::unordered_set<T, Args...>(); |
| } |
| |
| // Read type info |
| if (read_type) { |
| uint32_t type_id_read = ctx.read_varuint32(ctx.error()); |
| if (FORY_PREDICT_FALSE(ctx.has_error())) { |
| return std::unordered_set<T, Args...>(); |
| } |
| if (type_id_read != static_cast<uint32_t>(type_id)) { |
| ctx.set_error( |
| Error::type_mismatch(type_id_read, static_cast<uint32_t>(type_id))); |
| return std::unordered_set<T, Args...>(); |
| } |
| } |
| |
| // Read set size |
| uint32_t size = ctx.read_varuint32(ctx.error()); |
| if (FORY_PREDICT_FALSE(ctx.has_error())) { |
| return std::unordered_set<T, Args...>(); |
| } |
| |
| // Per xlang spec: header and type_info are omitted when length is 0 |
| if (size == 0) { |
| return std::unordered_set<T, Args...>(); |
| } |
| |
| // Dispatch to slow path for polymorphic/shared-ref elements |
| constexpr bool is_slow_path = is_polymorphic_v<T> || is_shared_ref_v<T>; |
| if constexpr (is_slow_path) { |
| return read_collection_data_slow<T, std::unordered_set<T, Args...>>(ctx, |
| size); |
| } else { |
| // Fast path for non-polymorphic, non-shared-ref elements |
| |
| // Read elements header bitmap (CollectionFlags) in xlang mode |
| uint8_t bitmap = ctx.read_uint8(ctx.error()); |
| if (FORY_PREDICT_FALSE(ctx.has_error())) { |
| return std::unordered_set<T, Args...>(); |
| } |
| bool track_ref = (bitmap & COLL_TRACKING_REF) != 0; |
| bool has_null = (bitmap & COLL_HAS_NULL) != 0; |
| bool is_decl_type = (bitmap & COLL_DECL_ELEMENT_TYPE) != 0; |
| bool is_same_type = (bitmap & COLL_IS_SAME_TYPE) != 0; |
| |
| if (is_same_type && !is_decl_type) { |
| const TypeInfo *elem_type_info = ctx.read_any_typeinfo(ctx.error()); |
| if (FORY_PREDICT_FALSE(ctx.has_error())) { |
| return std::unordered_set<T, Args...>(); |
| } |
| uint32_t expected = static_cast<uint32_t>(Serializer<T>::type_id); |
| if (!type_id_matches(elem_type_info->type_id, expected)) { |
| ctx.set_error( |
| Error::type_mismatch(elem_type_info->type_id, expected)); |
| return std::unordered_set<T, Args...>(); |
| } |
| } |
| |
| std::unordered_set<T, Args...> result; |
| result.reserve(size); |
| if (!track_ref && !has_null && is_same_type) { |
| for (uint32_t i = 0; i < size; ++i) { |
| if (FORY_PREDICT_FALSE(ctx.has_error())) { |
| return result; |
| } |
| auto elem = Serializer<T>::read(ctx, RefMode::None, false); |
| result.insert(std::move(elem)); |
| } |
| return result; |
| } |
| |
| for (uint32_t i = 0; i < size; ++i) { |
| if (FORY_PREDICT_FALSE(ctx.has_error())) { |
| return result; |
| } |
| if (track_ref) { |
| auto elem = Serializer<T>::read(ctx, RefMode::Tracking, false); |
| result.insert(std::move(elem)); |
| } else if (has_null) { |
| bool has_value_elem = read_null_only_flag(ctx, RefMode::NullOnly); |
| if (has_value_elem) { |
| auto elem = Serializer<T>::read(ctx, RefMode::None, false); |
| result.insert(std::move(elem)); |
| } |
| } else { |
| auto elem = Serializer<T>::read(ctx, RefMode::None, false); |
| result.insert(std::move(elem)); |
| } |
| } |
| return result; |
| } |
| } |
| |
| static inline std::unordered_set<T, Args...> |
| read_with_type_info(ReadContext &ctx, RefMode ref_mode, |
| const TypeInfo &type_info) { |
| return read(ctx, ref_mode, false); |
| } |
| |
| static inline std::unordered_set<T, Args...> read_data(ReadContext &ctx) { |
| uint32_t size = ctx.read_varuint32(ctx.error()); |
| if (FORY_PREDICT_FALSE(ctx.has_error())) { |
| return std::unordered_set<T, Args...>(); |
| } |
| std::unordered_set<T, Args...> result; |
| result.reserve(size); |
| for (uint32_t i = 0; i < size; ++i) { |
| if (FORY_PREDICT_FALSE(ctx.has_error())) { |
| return result; |
| } |
| auto elem = Serializer<T>::read_data(ctx); |
| result.insert(std::move(elem)); |
| } |
| return result; |
| } |
| }; |
| |
| } // namespace serialization |
| } // namespace fory |