Apply the same linker wrap technique used on ESP32 (PR #14362) to RP2040.
ESPHome logging uses snprintf/vsnprintf, not libc printf, so the FILE*-based
printf path (_vfprintf_r) is dead code at runtime.
The stubs redirect printf/vprintf/fprintf through vsnprintf + fwrite,
allowing the linker to GC _vfprintf_r (~8.9 KB).
Apply the same linker wrap technique used on ESP32 (PR #14362) to ESP8266.
ESPHome logging uses ets_printf, not libc printf, so the FILE*-based printf
path is dead code. The stubs redirect through vsnprintf + fwrite, allowing
the linker to GC _vfprintf_r.
Savings are smaller than ESP32 (~900 bytes vs ~11 KB) because ESP8266's
newlib printf is more modular, but ESP8266 has much less flash headroom
so every byte counts.
The scheduler was already managing SchedulerItem lifecycle explicitly
through its object pool (recycle_item_main_loop_ / get_item_from_pool_locked_).
The unique_ptr wrapper added overhead (11 destructor call sites on the hot path)
without providing safety — if a lifecycle path was missed, the unique_ptr would
silently delete the item and cause needless heap allocations instead of pool reuse.
Replace unique_ptr<SchedulerItem, SchedulerItemDeleter> with raw SchedulerItem*
throughout. Every item is now explicitly recycled to the pool or deleted via
delete_item_(). This eliminates all 11 unique_ptr destructor calls from the hot
path and saves ~256 bytes of firmware.
Add debug leak detection under ESPHOME_DEBUG_SCHEDULER: a live-item counter
verified at the end of every call() cycle asserts that all allocated items are
accounted for in items_, to_add_, defer_queue_, or the pool. This turns silent
heap churn from missed lifecycle management into an immediate assert failure
caught by integration tests.
Also moves the retry-cancelled check before item allocation in set_timer_common_
to avoid needless alloc+delete on the cold retry path, and fixes a thread-safety
issue where recycle_item_main_loop_ (main-loop-only) was called from
set_timer_common_ which can run on non-main-loop threads.
Enable debug_scheduler: true in all 18 scheduler integration test fixtures.
Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
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)