From a632a7e6441e5990e1452b49ac52aa926e501816 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 28 Feb 2026 16:35:27 -1000 Subject: [PATCH] [core] Deduplicate ControllerRegistry notify dispatch loop Each notify_*_update method had an identical loop that only differed in the vtable offset for the virtual call. Extract the shared loop into a single noinline notify() function and have each wrapper pass a small trampoline lambda that dispatches to the correct Controller virtual method. Before: 8 copies of the loop at ~56 bytes each = ~448 bytes After: 1 shared loop (56 B) + 8 wrappers (16 B) + 8 trampolines (8 B) = ~248 bytes Saves ~200 bytes of flash. --- esphome/core/controller_registry.cpp | 15 +++++++++------ esphome/core/controller_registry.h | 15 +++++++++++++++ 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/esphome/core/controller_registry.cpp b/esphome/core/controller_registry.cpp index 13b505e8e95..67b964d4aa5 100644 --- a/esphome/core/controller_registry.cpp +++ b/esphome/core/controller_registry.cpp @@ -10,20 +10,23 @@ StaticVector ControllerRegistry::controll void ControllerRegistry::register_controller(Controller *controller) { controllers.push_back(controller); } +void ControllerRegistry::notify(void *obj, DispatchFunc dispatch) { + for (auto *controller : controllers) { + dispatch(controller, obj); + } +} + // Macro for standard registry notification dispatch - calls on__update() +// Each wrapper passes a small trampoline lambda that calls the correct virtual method. #define CONTROLLER_REGISTRY_NOTIFY(entity_type, entity_name) \ void ControllerRegistry::notify_##entity_name##_update(entity_type *obj) { /* NOLINT(bugprone-macro-parentheses) */ \ - for (auto *controller : controllers) { \ - controller->on_##entity_name##_update(obj); \ - } \ + notify(obj, [](Controller *c, void *o) { c->on_##entity_name##_update(static_cast(o)); }); \ } // Macro for entities where controller method has no "_update" suffix (Event, Update) #define CONTROLLER_REGISTRY_NOTIFY_NO_UPDATE_SUFFIX(entity_type, entity_name) \ void ControllerRegistry::notify_##entity_name(entity_type *obj) { /* NOLINT(bugprone-macro-parentheses) */ \ - for (auto *controller : controllers) { \ - controller->on_##entity_name(obj); \ - } \ + notify(obj, [](Controller *c, void *o) { c->on_##entity_name(static_cast(o)); }); \ } #ifdef USE_BINARY_SENSOR diff --git a/esphome/core/controller_registry.h b/esphome/core/controller_registry.h index d6452d8827e..0642aacdce4 100644 --- a/esphome/core/controller_registry.h +++ b/esphome/core/controller_registry.h @@ -162,6 +162,21 @@ class ControllerRegistry { */ static void register_controller(Controller *controller); + /** Type-erased dispatch function pointer. + * + * Each notify method passes a small trampoline that calls the + * correct virtual method on Controller. The shared notify() loop + * iterates controllers once, calling the trampoline for each. + */ + using DispatchFunc = void (*)(Controller *, void *); + + /** Shared dispatch loop - iterates controllers and calls dispatch for each. + * + * Marked noinline to ensure only one copy of the loop exists in flash, + * rather than being duplicated into each notify_*_update wrapper. + */ + static void __attribute__((noinline)) notify(void *obj, DispatchFunc dispatch); + #ifdef USE_BINARY_SENSOR static void notify_binary_sensor_update(binary_sensor::BinarySensor *obj); #endif