Callback::create() can store callables inline (no heap allocation)
when they fit in sizeof(void*) — i.e., a single [this] capture on
32-bit platforms. Many automation triggers captured [this, parent]
in their callback lambdas, doubling the capture size and forcing
heap allocation.
Store the parent/state pointer as a class member and capture only
[this] in the lambda. This trades 4 bytes of member storage for
avoiding a permanent heap allocation per callback registration.
Components changed: cover, valve, lock, fan, media_player, datetime,
display_menu_base, graphical_display_menu, esp32_improv, mqtt_fan.
- Add std::launder to reinterpret_cast in inline callback path for
formal C++20 correctness (generates identical code)
- Fix static_assert comment: sizeof equality ensures round-trip through
uintptr_t, not absence of trap representations per se
- Document that call() is only valid on Callbacks from create()
Convert rotary_encoder register_listener, canbus add_callback,
sdl add_key_listener, and udp add_listener from std::function to
templates to avoid the heap-allocation fallback path.
MQTT backend set_on_* methods are virtual and cannot be templatized.
Pass wrapping lambdas directly to CallbackManager::add() instead of
first wrapping in std::function. The lambdas capture [this, callback]
(two pointers) so they hit the heap path either way, but this avoids
double-wrapping and the extra std::function overhead.
Also fix "deallaction" typo.
Convert haier, nextion, microphone, ezo, uart, speaker, zigbee,
and hlk_fm22x callback methods that were missed by the initial
conversion (they used add_*_callback instead of add_on_*_callback).
Use (*ptr)(args...) instead of ptr->operator()(args...) so the
inline path works correctly for function pointers, which are
trivially copyable and pointer-sized but lack operator().
Replace char-by-char loops with __builtin_memcpy in the inline
Callback storage path. Generates identical code but is cleaner
and avoids clang-analyzer false positives without NOLINT annotations.
Convert all remaining component add_on_*_callback methods from
std::function to templates so lambdas flow through without wrapping.
This avoids a heap allocation regression where passing std::function
through Callback::create would heap-allocate the std::function itself.
Also add NOLINT annotations for clang-analyzer false positive on
byte-wise copy in Callback::create.
Replace std::function with a lightweight type-erased Callback struct
(8 bytes on 32-bit vs 16 for std::function) in CallbackManager and
LazyCallbackManager. Using C++20 if constexpr, small trivially-copyable
callables like [this] lambdas (81% of all callback registrations) are
stored inline in the function pointer context without heap allocation.
This eliminates std::function null checks (and __throw_bad_function_call)
from all entity callback paths, and templatizes add_on_*_callback methods
across all entity types so lambdas flow through without being wrapped in
std::function first.
Also converts EntityStateBase from hand-rolled nullable CallbackManager
pointers to LazyCallbackManager for consistency.
Measured flash savings:
- ESP8266 simple: -160 B
- ESP8266 ratgdo: -592 B
- ESP32 IDF (large): -456 B
- ESP32 IDF (minimal): -212 B
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>