Apply the same inline fast-path / noinline slow-path split to
ProtoWriteBuffer::encode_varint_raw that was done for ProtoSize::varint.
For values < 128 (field tags, small field values, short lengths), the
single-byte write is now inlined at each call site instead of going
through a full function call. The multi-byte loop is outlined into
encode_varint_raw_slow_().
Also add [[likely]] to both varint fast paths (ProtoSize::varint and
encode_varint_raw) to hint branch prediction.
- Add explicit #include "api_buffer.h" in api_server.h
- Remove stale "swap trick" comment in api_frame_helper.h
- Document that exact-size allocation is intentional (no growth factor)
- Clarify debug_check_bounds_ scope to protobuf write path only
Replace std::vector<uint8_t> with a minimal APIBuffer class for the
shared protobuf write buffer, frame helper receive buffer (rx_buf_),
and noise handshake prologue buffer.
std::vector::resize() zero-fills new bytes via memset. Every byte is
immediately overwritten by the protobuf encoder or socket reads,
making the zero-fill pure waste. APIBuffer skips zero-initialization
on resize() and uses make_unique_for_overwrite where available.
Also removes a dead write_raw_ template overload from api_frame_helper.h.
- Move APIBuffer (renamed from ProtoByteBuffer) to api_buffer.h/cpp
- Use APIBuffer for rx_buf_ (frame helper receive buffer) and
prologue_ (noise handshake buffer) to skip zero-fill on resize
- Remove dead write_raw_ template overload and unused <vector> include
Add inject_to_rx_buffer_delayed() to uart_mock which stages bytes
that aren't visible to available() until the delay elapses. This
simulates USB packet delivery latency.
The test uses a 40ms delay which is:
- Greater than the old ~2ms timeout (fails without fix)
- Less than the new 50ms fallback timeout (passes with fix)
- Add test fixture simulating USB UART (no rx_full_threshold set)
with a 20ms gap between response chunks
- Make rx_full_threshold optional in uart_mock
- Extract MODBUS_BITS_PER_CHAR and MS_PER_SEC constexprs
The rx_full_threshold is only meaningful for ESP32 native UARTs where
it controls the hardware FIFO interrupt threshold. On other platforms
(USB UART, Arduino, etc.) it was left at the default of 1, causing
the long_rx_buffer_delay_ms calculation to produce a tiny value (~1-2ms).
This caused false timeouts on partial responses when data arrives in
USB packets with inherent USB-level latency, leading to cascading
CRC failures as the remaining bytes were parsed as garbage.
Change the default rx_full_threshold to 0 (unset sentinel) and use
50ms when unset, matching the previous hardcoded timeout behavior.
The "driver" shim component (providing driver/i2s.h) was excluded by
default in #13623 to reduce compile time. When use_legacy is true,
the i2s_audio component needs this shim for the legacy I2S API headers.
Ensure EnumType.encode_content passes force=true to the encode
function when the force field option is set, matching all other
TypeInfo subclasses for consistency and safety.
Fixed-width types (float, fixed32, sfixed32, fixed64, sfixed64)
now handle force=true by emitting a compile-time constant size
instead of the zero-checked calc_ call.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add a `force` proto field option that generates `_force` variants of
calc_ and encode methods, skipping the zero/empty check. Applied to
BluetoothLERawAdvertisement fields that are almost always non-default
(address, rssi, data) to eliminate dead branches on the BLE proxy
hot path.
On-device benchmarks show calculate_size improved from 12,649 ns to
10,982 ns per 12-advertisement batch (-13.2%) with only +8 bytes flash.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The constexpr path and the noinline slow path shared the same
if/else cascade for values >= 128. Extract into a private
constexpr ESPHOME_ALWAYS_INLINE helper used by both.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Static methods use lower_snake_case without trailing underscore.
The trailing underscore convention is for member fields only.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The ESPHOME_ALWAYS_INLINE on these caused significant flash bloat in
cold calculate_size() callers (DeviceInfoResponse +117B, HelloResponse
+63B, ListEntitiesEventResponse +79B) while only benefiting the BLE
hot path marginally. Let the compiler decide when to inline these.
The varint() fast path remains force-inlined as that eliminates the
most expensive indirect calls on the hot path.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Split ProtoSize::varint() into an always-inlined fast path (value < 128)
and a noinline slow path, eliminating indirect function calls on the BLE
advertisement encode hot path. Also force-inline calc_uint32() and
calc_length() so the compiler fully inlines size calculations per
advertisement instead of emitting out-of-line calls.
Verified via Xtensa disassembly: eliminates ~80 indirect function calls
per 16-advertisement batch flush (3 calc + varint per advertisement).
Total hot path code reduced from 24 to 21 functions.