The pre-sleep scan of all monitored sockets was added to preserve
select() semantics by checking for pending data before sleeping.
However, this is unnecessary with the FreeRTOS task notification
approach:
- xTaskNotifyGive from the lwip callback persists until consumed
by ulTaskNotifyTake, so notifications received while the task
is running (not sleeping) are not lost.
- The only case the scan caught was intentionally undrained sockets
(e.g., API's MAX_MESSAGES_PER_LOOP=5 throttle). Adding up to
16ms (loop_interval) latency before re-checking undrained data
is the desired behavior — waking immediately would defeat the
purpose of the throttle which exists to let other components run.
This removes N volatile cross-core reads (one per monitored socket)
from every loop iteration.
Each platform defines exactly one OTA backend subclass (marked final),
so using concrete types in unique_ptr eliminates virtual dispatch overhead.
- Remove OTABackend base class - no longer needed since all consumers
use concrete types directly
- Move make_ota_backend() declaration to each concrete backend header
with concrete return type
- Add ota_backend_factory.h convenience header for consumers
- Use concrete unique_ptr types in ota_esphome, http_request, and
web_server consumers
Each platform defines exactly one OTA backend subclass (marked final),
so using concrete types in unique_ptr eliminates virtual dispatch overhead.
- Move make_ota_backend() declaration from base header to each concrete
backend header with concrete return type
- Add ota_backend_factory.h convenience header for consumers
- Use concrete unique_ptr types in ota_esphome, http_request, and
web_server consumers
When only one API protocol is configured (plaintext-only or noise-only),
use the concrete frame helper type in unique_ptr instead of the base
class. Since both APIPlaintextFrameHelper and APINoiseFrameHelper are
marked final, the compiler can devirtualize all virtual calls
(read_packet, write_protobuf_packet, loop, etc.), eliminating vtable
dispatch overhead in the hot APIConnection::loop() path.
When both protocols are enabled (encryption key set with plaintext
fallback), the polymorphic base pointer is used as before.
Move is_connected() to the header as an inline method that returns a
cached bool field. The previous implementation called
wifi_sta_connect_status_() on every invocation, which makes SDK calls
on ESP8266 (wifi_station_get_connect_status) and RP2040
(cyw43_wifi_link_status + WiFi.status), preventing inlining and adding
overhead for the many callers that check it every loop iteration
(network::is_connected, API server, MQTT, status sensor, etc.).
The cached state is updated once per loop() after wifi_loop_() processes
platform events. Internal call sites that need a live SDK query
(STA_CONNECTED loss detection, RP2040 can_proceed) use the new
is_connected_() private method directly.
In debug builds (HAS_PROTO_MESSAGE_DUMP), skip dump logging for
SubscribeLogsResponse (recursive logging risk) and CameraImageResponse
(high-frequency image data noise). This matches the base branch behavior
where both bypassed dump logging via direct send_message_impl() calls.
The previous millis()-based timing had insufficient resolution. Most
components complete their loop() in microseconds, but millis() only
has 1ms granularity. Components taking <1ms would show either 0ms or
1ms depending on whether a millisecond boundary happened to tick over
during execution — essentially random noise rather than useful data.
Switch to self-timed micros() per guard (only when USE_RUNTIME_STATS
is compiled in — zero cost in production builds). Track internally in
microseconds, display in milliseconds with fractional precision.
Use uint64_t for total_time_us_ to avoid overflow (uint32_t would wrap
after ~10 hours at typical loop rates).