| 4173 | /// Both paths pre-check bounds and update reader_index once at the end. |
| 4174 | template <typename T, size_t... Indices> |
| 4175 | void read_struct_fields_impl(T &obj, ReadContext &ctx, |
| 4176 | std::index_sequence<Indices...>) { |
| 4177 | using Helpers = CompileTimeFieldHelpers<T>; |
| 4178 | constexpr size_t fixed_count = Helpers::leading_fixed_count; |
| 4179 | constexpr size_t fixed_bytes = Helpers::leading_fixed_size_bytes; |
| 4180 | constexpr size_t varint_count = Helpers::varint_count; |
| 4181 | constexpr size_t total_count = sizeof...(Indices); |
| 4182 | |
| 4183 | // FAST PATH: When compatible=false, use optimized batch reading |
| 4184 | if (!ctx.is_compatible()) { |
| 4185 | Buffer &buffer = ctx.buffer(); |
| 4186 | |
| 4187 | // Phase 1: Read leading fixed-size primitives if any |
| 4188 | if constexpr (fixed_count > 0 && fixed_bytes > 0) { |
| 4189 | if (FORY_PREDICT_FALSE(!buffer.ensure_readable( |
| 4190 | static_cast<uint32_t>(fixed_bytes), ctx.error()))) { |
| 4191 | return; |
| 4192 | } |
| 4193 | // Fast read fixed-size primitives |
| 4194 | read_fixed_primitive_fields<T>(obj, buffer, |
| 4195 | std::make_index_sequence<fixed_count>{}); |
| 4196 | } |
| 4197 | |
| 4198 | // Phase 2: Read consecutive varint primitives (int32, int64) if any |
| 4199 | // Note: varint bounds checking is done per-byte during reading since |
| 4200 | // varint lengths are variable (actual size << max possible size) |
| 4201 | if constexpr (varint_count > 0) { |
| 4202 | if (FORY_PREDICT_FALSE(buffer.has_input_stream())) { |
| 4203 | // Stream-backed buffers may not have all varint bytes materialized yet. |
| 4204 | // Fall back to per-field readers that propagate stream read errors. |
| 4205 | read_remaining_fields<T, fixed_count, total_count>(obj, ctx); |
| 4206 | return; |
| 4207 | } |
| 4208 | // Track offset locally for batch varint reading |
| 4209 | uint32_t offset = buffer.reader_index(); |
| 4210 | // Fast read varint primitives (bounds checking happens in |
| 4211 | // get_var_uint32/64) |
| 4212 | read_varint_primitive_fields<T, fixed_count>( |
| 4213 | obj, buffer, offset, std::make_index_sequence<varint_count>{}); |
| 4214 | // Update reader_index once after all varints |
| 4215 | buffer.reader_index(offset); |
| 4216 | } |
| 4217 | |
| 4218 | // Phase 3: Read remaining fields (if any) with normal path |
| 4219 | constexpr size_t fast_count = fixed_count + varint_count; |
| 4220 | if constexpr (fast_count < total_count) { |
| 4221 | read_remaining_fields<T, fast_count, total_count>(obj, ctx); |
| 4222 | } |
| 4223 | return; |
| 4224 | } |
| 4225 | |
| 4226 | // NORMAL PATH: compatible mode - all fields need full serialization |
| 4227 | (read_field_at_sorted_position<T, Indices>(obj, ctx), ...); |
| 4228 | } |
| 4229 | |
| 4230 | /// Read struct fields in sorted order using the fast primitive paths. |
| 4231 | /// Used when compatible mode is enabled but the remote schema matches locally. |
no test coverage detected