On ESP8266 (GCC 10.3), std::vector::push_back/emplace_back emit
separate _M_realloc_insert functions even when called from only
one site. Adding __attribute__((flatten)) inlines the realloc
path, saving the out-of-line function overhead.
Changes:
- wifi: Move set_sta_priority from header to .cpp (eliminates
duplicate instantiation at 2 call sites) and add flatten
- web_server_base: Flatten add_handler (single push_back site)
- api: Flatten accept_new_connections_ (single emplace_back site)
Saves 160 bytes of flash on ESP8266.
On ESP8266 (GCC 10.3), std::vector::push_back() emits a separate
_M_realloc_insert<DeferredEvent> function (198 bytes) that is only
called from one site. Adding __attribute__((flatten)) forces the
compiler to inline it, eliminating the out-of-line function's
prologue/epilogue and call overhead.
This matches what GCC 14.2 (ESP-IDF toolchain) already does
naturally for the equivalent code in web_server_idf.cpp.
Saves 80 bytes of flash on ESP8266.
Extract the keepalive ping sending logic from APIConnection::loop()
into a separate noinline send_keepalive_ping_() method. This code
only fires once per keepalive interval (~60s) making it cold relative
to the ~111 Hz loop rate. Moving it out reduces loop() from 337 to
258 bytes, keeping the hot path smaller and more cache-friendly.
- Add #include <type_traits> to both .cpp and .h for static_asserts
- Clarify CompactString comment: explicitly note it is not trivially
copyable, and that memcpy safety relies on validated layout property
- Use memcpy_fn indirection to suppress both GCC -Wclass-memaccess
and clang-tidy bugprone-undefined-memory-manipulation without
platform-specific pragma guards
Replace copy-assignment with raw memcpy in the WiFi scan result
insertion sort. Copy assignment on WiFiScanResult calls
CompactString's destructor then placement-new for every shift,
which means delete[]/new[] per shift for heap-allocated SSIDs.
With 70+ networks visible (e.g., during captive portal transition
showing full scan results), this caused event loop blocking from
hundreds of heap allocations in a tight loop on an 80MHz ESP8266.
This optimization is safe because we're permuting elements within
the same array - each slot is overwritten exactly once, so no
ownership duplication occurs. CompactString stores either inline
data or a heap pointer, never a self-referential pointer (unlike
libstdc++ std::string SSO). This was made possible by PR#13472
which replaced std::string with CompactString.
Static asserts guard the memcpy safety assumptions at compile time.
Confirmed on real device: event loop blocking during captive portal
transition is eliminated and WiFi connection is slightly faster.
Add @warning comments to all format_hex_pretty() overloads in helpers.h
pointing to the buffer-based format_hex_pretty_to() alternatives.
Deprecate with ESPDEPRECATED (removal 2026.9.0):
- EthernetComponent::get_eth_mac_address_pretty()
-> use get_eth_mac_address_pretty_into_buffer()
- AsyncWebServerRequest::url()
-> use url_to()
Neither deprecated method has internal callers.
wifi_ssid() returns std::string which allocates on the heap.
All internal callers have already been migrated to the
buffer-based wifi_ssid_to() alternative. Mark as deprecated
with a 6-month removal window (2026.9.0).
bLength includes the 2-byte descriptor header, so the character count
is (bLength - 2) / 2, not bLength / 2. The old loop read one wData
entry past the actual string data. Also guard bLength < 2.
Extract the USB_CLIENT_OPEN state handling into handle_open_state_() to
keep loop() hot path small (~112 B vs 440 B before). Replace
get_descriptor_string() std::string return with caller-provided
fixed-size buffer via std::span to eliminate heap allocation during
device connection.
Remove the FreeRTOS mutex (lock_) that wrapped all ESP-IDF UART driver
calls. A codebase audit confirmed all UART wrapper methods (read_array,
write_array, peek_byte, available, flush) are called exclusively from
the cooperative main loop. The lock was protecting single-threaded code
from itself and added 4 unnecessary semaphore operations per read_array
call on the hot path.
Fix a race condition in rx_event_task_func where uart_flush_input() was
called from a separate FreeRTOS task without taking the lock, racing
with read_array() on the main loop. This could destroy ring buffer data
mid-read, causing data loss. The flush was also unnecessary since the
ESP-IDF driver self-heals the buffer-full condition: uart_read_bytes()
internally calls uart_check_buf_full() which moves stashed FIFO bytes
back into the ring buffer and re-enables RX interrupts.
Move the event queue allocation behind USE_UART_WAKE_LOOP_ON_RX. When
the feature is disabled, uart_driver_install is called with queue_size=0
and uart_queue=NULL (the documented API for skipping the event queue).
This saves ~320 bytes of RAM per UART instance and reduces ISR overhead
since the driver skips xQueueSendFromISR when no queue exists.
Add thread safety documentation to the class and rx_event_task.
The regex matches `scanf (` in comments too since `\s*\(` matches the
space before the parenthesized size note.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Move `packet$` pattern from `wifi_config` to `mdns_lib` category.
The `packet$` static buffer is the mDNS packet assembly buffer used
by ESP-IDF's mDNS component, not a WiFi config symbol. This was
causing 1,460 B of mDNS RAM to appear under wifi_config in
ethernet-only builds.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Skip emitting the "icon" key in set_json_id() when USE_ENTITY_ICON
is not defined. This avoids the ArduinoJson .set() call overhead and
sending empty "icon":"" in every JSON response for builds without icons.