DeferredEvent contains two pointers (void* + function pointer), which
are already naturally 4-byte aligned on all ESPHome targets. The struct
is 8 bytes with no padding regardless of packed.
The packed attribute forces the compiler to use byte-by-byte loads and
stores instead of word-aligned access, bloating deq_push_back_with_dedup_
from 163 to 317 bytes due to shift/mask/or sequences for every field
access.
The packed attribute was added in #7538 likely to guarantee the struct
stayed at 8 bytes, but this is already the case without it since both
fields are pointer-sized.
Replace SmallBufferWithHeapFallback with a fixed stack buffer sized
to the physical storage limit. No single preference can exceed the
storage it resides in, so heap allocation is never needed.
ESP8266: buffer sized to max(flash_storage, rtc_normal_region) —
128 words (512B) with restore_from_flash, 96 words (384B) without.
RP2040: buffer sized to flash storage (512B).
Eliminates new[]/delete[] codegen from save()/load() virtual methods.
After clamp_and_log_if_invalid() clamps the value in-place, the
LightColorValues setter's clamp() is guaranteed to be a no-op. For 5
of 9 fields the compiler was inlining the setter's clamp, generating
~18 bytes of redundant float compare + conditional move per field.
Use friend access to assign directly to LightColorValues fields,
bypassing the setter. Also apply the same optimization to
normalize_color() where division by max_value guarantees results
stay in [0,1].
After clamp_and_log_if_invalid() clamps the value in-place, the
LightColorValues setter's clamp() is guaranteed to be a no-op. For 5
of 9 fields the compiler was inlining the setter's clamp, generating
~18 bytes of redundant float compare + conditional move per field.
Use friend access to assign directly to LightColorValues fields,
bypassing the setter. Saves ~204 bytes of flash on ESP32.
Previously ready() always returned true on ESP8266/RP2040, causing
every socket to be checked on every loop iteration even when no data
was available.
Override ready() in LWIPRawImpl to check rx_buf_/rx_closed_/pcb_ state,
and in LWIPRawListenImpl to check accepted_socket_count_. This uses
existing fields so no extra memory is needed per socket.
Keep ready() virtual only on the non-select path (ESP8266/RP2040) so
the select()-based path (ESP32) retains the non-virtual optimization
from the previous commit.
On ESP8266 and RP2040, MDNS.update() was called every loop iteration
(~120 Hz) but only manages timer-driven probe/announce state machines.
Incoming mDNS packets are handled independently via the lwIP onRx UDP
callback and are unaffected by update() frequency.
Replace the loop() override with set_interval() at 50ms. This removes
the component from the loop list entirely via has_overridden_loop(),
eliminating all per-iteration overhead including virtual dispatch.
The 50ms interval provides sufficient resolution for all internal
timers (shortest is 250ms probe interval per RFC 6762 Section 8.1).
On ESP8266 and RP2040, MDNS.update() is called every loop iteration
(~120 Hz) but only manages timer-driven probe/announce state machines.
Incoming mDNS packets are handled independently via the lwIP onRx UDP
callback and are unaffected by update() frequency.
The shortest internal timer is the 250ms probe interval (RFC 6762).
Throttling to 50ms provides sufficient resolution while reducing CPU
overhead by ~84% (from ~360ms to ~60ms per 60s measurement period).
Move fd_, closed_, and loop_monitored_ fields from BSD/LWIP socket
implementations to the base Socket class. Since only one socket
implementation is active per build, these can be non-virtual.
Make Socket::ready() and get_fd() non-virtual, eliminating vtable
dispatch on every main loop iteration. Inline is_socket_ready via
friendship for the fast path while keeping the public API with
bounds checking for external callers.
Saves ~316 bytes of flash on ESP32-IDF builds.