[core] Dispatch controller updates through generated direct calls

APIServer and WebServer inherited both Component and Controller, so each
on_*_update override reached through the registry needed a non-virtual
thunk to adjust this to the secondary base, and every publish iterated a
StaticVector of Controller pointers with an indirect call.

Drop the Controller base class and the runtime list. ControllerRegistry
only declares notify_*; code generation defines each one in main.cpp as
direct calls on the registered controllers through the entity_types.h
X-macro. A ControllerContract concept with a generated static_assert per
controller reports a missing callback clearly.

On an ESP32 IDF config with api, web_server and four entity types this
saves 256 B of flash and 24 B of RAM.
This commit is contained in:
J. Nick Koston
2026-09-24 16:55:18 +01:00
parent f8e8295088
commit d731ddefc9
19 changed files with 114 additions and 163 deletions
-1
View File
@@ -64,7 +64,6 @@ from esphome.cpp_types import ( # noqa: F401
Application,
Component,
ComponentPtr,
Controller,
EntityBase,
EntityCategory,
ESPTime,
+2 -3
View File
@@ -81,7 +81,7 @@ def AUTO_LOAD(config: ConfigType) -> list[str]:
api_ns = cg.esphome_ns.namespace("api")
APIServer = api_ns.class_("APIServer", cg.Component, cg.Controller)
APIServer = api_ns.class_("APIServer", cg.Component)
HomeAssistantServiceCallAction = api_ns.class_(
"HomeAssistantServiceCallAction", automation.Action
)
@@ -462,8 +462,7 @@ async def to_code(config: ConfigType) -> None:
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
# Track controller registration for StaticVector sizing
CORE.register_controller()
CORE.register_controller(var)
# Request a log listener slot for API log streaming
request_log_listener()
-3
View File
@@ -5,7 +5,6 @@
#include "api_connection.h"
#include "esphome/components/network/util.h"
#include "esphome/core/application.h"
#include "esphome/core/controller_registry.h"
#include "esphome/core/defines.h"
#include "esphome/core/hal.h"
#include "esphome/core/log.h"
@@ -61,8 +60,6 @@ void APIServer::socket_failed_(const LogString *msg) {
}
void APIServer::setup() {
ControllerRegistry::register_controller(this);
#ifdef USE_API_NOISE
// Always reserve the slot: flash preferences are positional on esp8266, so
// a yaml key build must keep the layout of a runtime key build
+22 -24
View File
@@ -14,7 +14,6 @@
#include "esphome/components/socket/socket.h"
#include "esphome/core/automation.h"
#include "esphome/core/component.h"
#include "esphome/core/controller.h"
#include "esphome/core/log.h"
#include "esphome/core/string_ref.h"
#ifdef USE_PROVISIONING
@@ -49,8 +48,7 @@ struct SavedNoisePsk {
bool load_saved_noise_psk(noise::psk_t &out);
#endif
class APIServer final : public Component,
public Controller
class APIServer final : public Component
#ifdef USE_CAMERA
,
public camera::CameraListener
@@ -94,58 +92,58 @@ class APIServer final : public Component,
void handle_disconnect(APIConnection *conn);
#ifdef USE_BINARY_SENSOR
void on_binary_sensor_update(binary_sensor::BinarySensor *obj) override;
void on_binary_sensor_update(binary_sensor::BinarySensor *obj);
#endif
#ifdef USE_COVER
void on_cover_update(cover::Cover *obj) override;
void on_cover_update(cover::Cover *obj);
#endif
#ifdef USE_FAN
void on_fan_update(fan::Fan *obj) override;
void on_fan_update(fan::Fan *obj);
#endif
#ifdef USE_LIGHT
void on_light_update(light::LightState *obj) override;
void on_light_update(light::LightState *obj);
#endif
#ifdef USE_SENSOR
void on_sensor_update(sensor::Sensor *obj) override;
void on_sensor_update(sensor::Sensor *obj);
#endif
#ifdef USE_SWITCH
void on_switch_update(switch_::Switch *obj) override;
void on_switch_update(switch_::Switch *obj);
#endif
#ifdef USE_TEXT_SENSOR
void on_text_sensor_update(text_sensor::TextSensor *obj) override;
void on_text_sensor_update(text_sensor::TextSensor *obj);
#endif
#ifdef USE_CLIMATE
void on_climate_update(climate::Climate *obj) override;
void on_climate_update(climate::Climate *obj);
#endif
#ifdef USE_NUMBER
void on_number_update(number::Number *obj) override;
void on_number_update(number::Number *obj);
#endif
#ifdef USE_DATETIME_DATE
void on_date_update(datetime::DateEntity *obj) override;
void on_date_update(datetime::DateEntity *obj);
#endif
#ifdef USE_DATETIME_TIME
void on_time_update(datetime::TimeEntity *obj) override;
void on_time_update(datetime::TimeEntity *obj);
#endif
#ifdef USE_DATETIME_DATETIME
void on_datetime_update(datetime::DateTimeEntity *obj) override;
void on_datetime_update(datetime::DateTimeEntity *obj);
#endif
#ifdef USE_TEXT
void on_text_update(text::Text *obj) override;
void on_text_update(text::Text *obj);
#endif
#ifdef USE_SELECT
void on_select_update(select::Select *obj) override;
void on_select_update(select::Select *obj);
#endif
#ifdef USE_LOCK
void on_lock_update(lock::Lock *obj) override;
void on_lock_update(lock::Lock *obj);
#endif
#ifdef USE_VALVE
void on_valve_update(valve::Valve *obj) override;
void on_valve_update(valve::Valve *obj);
#endif
#ifdef USE_MEDIA_PLAYER
void on_media_player_update(media_player::MediaPlayer *obj) override;
void on_media_player_update(media_player::MediaPlayer *obj);
#endif
#ifdef USE_WATER_HEATER
void on_water_heater_update(water_heater::WaterHeater *obj) override;
void on_water_heater_update(water_heater::WaterHeater *obj);
#endif
#ifdef USE_API_HOMEASSISTANT_SERVICES
void send_homeassistant_action(const HomeassistantActionRequest &call);
@@ -188,13 +186,13 @@ class APIServer final : public Component,
#endif
#ifdef USE_ALARM_CONTROL_PANEL
void on_alarm_control_panel_update(alarm_control_panel::AlarmControlPanel *obj) override;
void on_alarm_control_panel_update(alarm_control_panel::AlarmControlPanel *obj);
#endif
#ifdef USE_EVENT
void on_event(event::Event *obj) override;
void on_event(event::Event *obj);
#endif
#ifdef USE_UPDATE
void on_update(update::UpdateEntity *obj) override;
void on_update(update::UpdateEntity *obj);
#endif
#ifdef USE_ZWAVE_PROXY
void on_zwave_proxy_request(const ZWaveProxyRequest &msg);
-1
View File
@@ -4,7 +4,6 @@
#ifdef USE_API
#include "esphome/core/component.h"
#include "esphome/core/component_iterator.h"
#include "esphome/core/controller.h"
namespace esphome::api {
class APIConnection;
@@ -6,8 +6,8 @@
#include "esphome/components/web_server_base/web_server_base.h"
#include "esphome/core/component.h"
#include "esphome/core/controller.h"
#include "esphome/core/entity_base.h"
#include "esphome/core/entity_includes.h"
#include "esphome/core/progmem.h"
#ifdef USE_CLIMATE
#include "esphome/core/log.h"
+2 -3
View File
@@ -62,7 +62,7 @@ DEFAULT_PORT = 80
web_server_ns = cg.esphome_ns.namespace("web_server")
WebServer = web_server_ns.class_("WebServer", cg.Component, cg.Controller)
WebServer = web_server_ns.class_("WebServer", cg.Component)
sorting_groups = {}
@@ -378,8 +378,7 @@ async def to_code(config: ConfigType) -> None:
var = cg.new_Pvariable(config[CONF_ID], paren)
await cg.register_component(var, config)
# Track controller registration for StaticVector sizing
CORE.register_controller()
CORE.register_controller(var)
version = config[CONF_VERSION]
@@ -5,7 +5,6 @@
#include "esphome/components/network/util.h"
#include "esphome/core/application.h"
#include "esphome/core/defines.h"
#include "esphome/core/controller_registry.h"
#include "esphome/core/entity_base.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
@@ -365,7 +364,6 @@ json::SerializationBuffer<> WebServer::get_config_json() {
}
void WebServer::setup() {
ControllerRegistry::register_controller(this);
this->base_->init();
#ifdef USE_LOGGER
+25 -22
View File
@@ -6,7 +6,6 @@
#include "esphome/components/web_server_base/web_server_base.h"
#ifdef USE_WEBSERVER
#include "esphome/core/component.h"
#include "esphome/core/controller.h"
#include "esphome/core/entity_base.h"
#include "esphome/core/progmem.h"
#ifdef USE_LOGGER
@@ -187,7 +186,7 @@ class DeferredUpdateEventSourceList final : public std::list<DeferredUpdateEvent
* under the '/light/...', '/sensor/...', ... URLs. A full documentation for this API
* can be found under https://esphome.io/web-api/.
*/
class WebServer final : public Controller, public Component, public AsyncWebHandler {
class WebServer final : public Component, public AsyncWebHandler {
#if !defined(USE_ESP32) && defined(USE_ARDUINO)
friend class DeferredUpdateEventSourceList;
#endif
@@ -292,7 +291,7 @@ class WebServer final : public Controller, public Component, public AsyncWebHand
#endif
#ifdef USE_SENSOR
void on_sensor_update(sensor::Sensor *obj) override;
void on_sensor_update(sensor::Sensor *obj);
/// Handle a sensor request under '/sensor/<id>'.
void handle_sensor_request(AsyncWebServerRequest *request, const UrlMatch &match);
@@ -301,7 +300,7 @@ class WebServer final : public Controller, public Component, public AsyncWebHand
#endif
#ifdef USE_SWITCH
void on_switch_update(switch_::Switch *obj) override;
void on_switch_update(switch_::Switch *obj);
/// Handle a switch request under '/switch/<id>/</turn_on/turn_off/toggle>'.
void handle_switch_request(AsyncWebServerRequest *request, const UrlMatch &match);
@@ -319,7 +318,7 @@ class WebServer final : public Controller, public Component, public AsyncWebHand
#endif
#ifdef USE_BINARY_SENSOR
void on_binary_sensor_update(binary_sensor::BinarySensor *obj) override;
void on_binary_sensor_update(binary_sensor::BinarySensor *obj);
/// Handle a binary sensor request under '/binary_sensor/<id>'.
void handle_binary_sensor_request(AsyncWebServerRequest *request, const UrlMatch &match);
@@ -329,7 +328,7 @@ class WebServer final : public Controller, public Component, public AsyncWebHand
#endif
#ifdef USE_FAN
void on_fan_update(fan::Fan *obj) override;
void on_fan_update(fan::Fan *obj);
/// Handle a fan request under '/fan/<id>/</turn_on/turn_off/toggle>'.
void handle_fan_request(AsyncWebServerRequest *request, const UrlMatch &match);
@@ -339,7 +338,7 @@ class WebServer final : public Controller, public Component, public AsyncWebHand
#endif
#ifdef USE_LIGHT
void on_light_update(light::LightState *obj) override;
void on_light_update(light::LightState *obj);
/// Handle a light request under '/light/<id>/</turn_on/turn_off/toggle>'.
void handle_light_request(AsyncWebServerRequest *request, const UrlMatch &match);
@@ -349,7 +348,7 @@ class WebServer final : public Controller, public Component, public AsyncWebHand
#endif
#ifdef USE_TEXT_SENSOR
void on_text_sensor_update(text_sensor::TextSensor *obj) override;
void on_text_sensor_update(text_sensor::TextSensor *obj);
/// Handle a text sensor request under '/text_sensor/<id>'.
void handle_text_sensor_request(AsyncWebServerRequest *request, const UrlMatch &match);
@@ -359,7 +358,7 @@ class WebServer final : public Controller, public Component, public AsyncWebHand
#endif
#ifdef USE_COVER
void on_cover_update(cover::Cover *obj) override;
void on_cover_update(cover::Cover *obj);
/// Handle a cover request under '/cover/<id>/<open/close/stop/set>'.
void handle_cover_request(AsyncWebServerRequest *request, const UrlMatch &match);
@@ -369,7 +368,7 @@ class WebServer final : public Controller, public Component, public AsyncWebHand
#endif
#ifdef USE_NUMBER
void on_number_update(number::Number *obj) override;
void on_number_update(number::Number *obj);
/// Handle a number request under '/number/<id>'.
void handle_number_request(AsyncWebServerRequest *request, const UrlMatch &match);
@@ -378,7 +377,7 @@ class WebServer final : public Controller, public Component, public AsyncWebHand
#endif
#ifdef USE_DATETIME_DATE
void on_date_update(datetime::DateEntity *obj) override;
void on_date_update(datetime::DateEntity *obj);
/// Handle a date request under '/date/<id>'.
void handle_date_request(AsyncWebServerRequest *request, const UrlMatch &match);
@@ -387,7 +386,7 @@ class WebServer final : public Controller, public Component, public AsyncWebHand
#endif
#ifdef USE_DATETIME_TIME
void on_time_update(datetime::TimeEntity *obj) override;
void on_time_update(datetime::TimeEntity *obj);
/// Handle a time request under '/time/<id>'.
void handle_time_request(AsyncWebServerRequest *request, const UrlMatch &match);
@@ -396,7 +395,7 @@ class WebServer final : public Controller, public Component, public AsyncWebHand
#endif
#ifdef USE_DATETIME_DATETIME
void on_datetime_update(datetime::DateTimeEntity *obj) override;
void on_datetime_update(datetime::DateTimeEntity *obj);
/// Handle a datetime request under '/datetime/<id>'.
void handle_datetime_request(AsyncWebServerRequest *request, const UrlMatch &match);
@@ -405,7 +404,7 @@ class WebServer final : public Controller, public Component, public AsyncWebHand
#endif
#ifdef USE_TEXT
void on_text_update(text::Text *obj) override;
void on_text_update(text::Text *obj);
/// Handle a text input request under '/text/<id>'.
void handle_text_request(AsyncWebServerRequest *request, const UrlMatch &match);
@@ -414,7 +413,7 @@ class WebServer final : public Controller, public Component, public AsyncWebHand
#endif
#ifdef USE_SELECT
void on_select_update(select::Select *obj) override;
void on_select_update(select::Select *obj);
/// Handle a select request under '/select/<id>'.
void handle_select_request(AsyncWebServerRequest *request, const UrlMatch &match);
@@ -423,7 +422,7 @@ class WebServer final : public Controller, public Component, public AsyncWebHand
#endif
#ifdef USE_CLIMATE
void on_climate_update(climate::Climate *obj) override;
void on_climate_update(climate::Climate *obj);
/// Handle a climate request under '/climate/<id>'.
void handle_climate_request(AsyncWebServerRequest *request, const UrlMatch &match);
@@ -432,7 +431,7 @@ class WebServer final : public Controller, public Component, public AsyncWebHand
#endif
#ifdef USE_LOCK
void on_lock_update(lock::Lock *obj) override;
void on_lock_update(lock::Lock *obj);
/// Handle a lock request under '/lock/<id>/</lock/unlock/open>'.
void handle_lock_request(AsyncWebServerRequest *request, const UrlMatch &match);
@@ -442,7 +441,7 @@ class WebServer final : public Controller, public Component, public AsyncWebHand
#endif
#ifdef USE_VALVE
void on_valve_update(valve::Valve *obj) override;
void on_valve_update(valve::Valve *obj);
/// Handle a valve request under '/valve/<id>/<open/close/stop/set>'.
void handle_valve_request(AsyncWebServerRequest *request, const UrlMatch &match);
@@ -452,7 +451,7 @@ class WebServer final : public Controller, public Component, public AsyncWebHand
#endif
#ifdef USE_ALARM_CONTROL_PANEL
void on_alarm_control_panel_update(alarm_control_panel::AlarmControlPanel *obj) override;
void on_alarm_control_panel_update(alarm_control_panel::AlarmControlPanel *obj);
/// Handle a alarm_control_panel request under '/alarm_control_panel/<id>'.
void handle_alarm_control_panel_request(AsyncWebServerRequest *request, const UrlMatch &match);
@@ -462,7 +461,7 @@ class WebServer final : public Controller, public Component, public AsyncWebHand
#endif
#ifdef USE_WATER_HEATER
void on_water_heater_update(water_heater::WaterHeater *obj) override;
void on_water_heater_update(water_heater::WaterHeater *obj);
/// Handle a water_heater request under '/water_heater/<id>/<mode/set>'.
void handle_water_heater_request(AsyncWebServerRequest *request, const UrlMatch &match);
@@ -484,8 +483,12 @@ class WebServer final : public Controller, public Component, public AsyncWebHand
static json::SerializationBuffer<> radio_frequency_all_json_generator(WebServer *web_server, void *source);
#endif
#ifdef USE_MEDIA_PLAYER
void on_media_player_update(media_player::MediaPlayer *obj) {}
#endif
#ifdef USE_EVENT
void on_event(event::Event *obj) override;
void on_event(event::Event *obj);
static json::SerializationBuffer<> event_state_json_generator(WebServer *web_server, void *source);
static json::SerializationBuffer<> event_all_json_generator(WebServer *web_server, void *source);
@@ -495,7 +498,7 @@ class WebServer final : public Controller, public Component, public AsyncWebHand
#endif
#ifdef USE_UPDATE
void on_update(update::UpdateEntity *obj) override;
void on_update(update::UpdateEntity *obj);
/// Handle a update request under '/update/<id>'.
void handle_update_request(AsyncWebServerRequest *request, const UrlMatch &match);
+10 -6
View File
@@ -52,8 +52,8 @@ if TYPE_CHECKING:
_LOGGER = logging.getLogger(__name__)
# Key for tracking controller count in CORE.data for ControllerRegistry StaticVector sizing
KEY_CONTROLLER_REGISTRY_COUNT = "controller_registry_count"
# Key for the controllers (APIServer, WebServer) that receive entity state updates
KEY_CONTROLLER_REGISTRY_CONTROLLERS = "controller_registry_controllers"
# CORE.data key for the "is_rp2040 deprecation warning already fired this
# run" flag. Mirrors the ``cv.only_on_rp2040`` dedupe pattern; cleared
@@ -1209,10 +1209,14 @@ class EsphomeCore:
if not self.platform_counts[platform_name]:
self.platform_counts[platform_name] = 1
def register_controller(self) -> None:
"""Track registration of a Controller for ControllerRegistry StaticVector sizing."""
controller_count = self.data.setdefault(KEY_CONTROLLER_REGISTRY_COUNT, 0)
self.data[KEY_CONTROLLER_REGISTRY_COUNT] = controller_count + 1
def register_controller(self, controller: "MockObj") -> None:
"""Register a controller that receives every entity state update.
Code generation defines the ControllerRegistry notify functions in
main.cpp as direct calls on each registered controller, so the C++
class only needs the on_*_update methods, not a base class.
"""
self.data.setdefault(KEY_CONTROLLER_REGISTRY_CONTROLLERS, []).append(controller)
@property
def cpp_main_section(self):
+1 -1
View File
@@ -1,7 +1,7 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/core/controller.h"
#include "esphome/core/entity_includes.h"
#include "esphome/core/helpers.h"
#ifdef USE_CAMERA
+31 -8
View File
@@ -46,7 +46,7 @@ from esphome.const import (
)
from esphome.core import (
CORE,
KEY_CONTROLLER_REGISTRY_COUNT,
KEY_CONTROLLER_REGISTRY_CONTROLLERS,
CoroPriority,
coroutine_with_priority,
)
@@ -674,12 +674,35 @@ async def _add_platform_defines() -> None:
@coroutine_with_priority(CoroPriority.FINAL)
async def _add_controller_registry_define() -> None:
# Generate StaticVector size for ControllerRegistry
controller_count = CORE.data.get(KEY_CONTROLLER_REGISTRY_COUNT, 0)
if controller_count > 0:
cg.add_define("USE_CONTROLLER_REGISTRY")
cg.add_define("CONTROLLER_REGISTRY_MAX", controller_count)
async def _add_controller_registry_dispatch() -> None:
# Define ControllerRegistry::notify_*() in main.cpp as direct calls on each
# registered controller. entity_types.h expands one definition per entity
# type that is compiled in, so only the callbacks in use are emitted.
controllers = CORE.data.get(KEY_CONTROLLER_REGISTRY_CONTROLLERS)
if not controllers:
return
cg.add_define("USE_CONTROLLER_REGISTRY")
for var in controllers:
cg.add_global(
cg.RawStatement(
f"static_assert(ControllerContract<std::remove_pointer_t<decltype({var})>>, "
f'"{var} is missing an on_*_update() callback for an entity type in this build '
'(esphome/core/controller_registry.h)");'
)
)
calls = " \\\n".join(f" {var}->on_##callback(obj);" for var in controllers)
cg.add_global(
cg.RawStatement(
"#define ENTITY_TYPE_(type, singular, plural, count, upper)\n"
"#define ENTITY_CONTROLLER_TYPE_(type, singular, plural, count, upper, callback) \\\n"
" void ControllerRegistry::notify_##callback(type *obj) { \\\n"
f"{calls} \\\n"
" }\n"
'#include "esphome/core/entity_types.h"\n'
"#undef ENTITY_TYPE_\n"
"#undef ENTITY_CONTROLLER_TYPE_"
)
)
@coroutine_with_priority(CoroPriority.FINAL)
@@ -755,7 +778,7 @@ async def to_code(config: ConfigType) -> None:
)
CORE.add_job(_add_platform_defines)
CORE.add_job(_add_controller_registry_define)
CORE.add_job(_add_controller_registry_dispatch)
CORE.add_job(_add_looping_components)
CORE.add_job(_add_automations, config)
-19
View File
@@ -1,19 +0,0 @@
#pragma once
#include "esphome/core/entity_includes.h"
namespace esphome {
class Controller {
public:
// Controller virtual methods (generated from entity_types.h)
// NOLINTBEGIN(bugprone-macro-parentheses)
#define ENTITY_TYPE_(type, singular, plural, count, upper) // no controller callback
#define ENTITY_CONTROLLER_TYPE_(type, singular, plural, count, upper, callback) virtual void on_##callback(type *obj){};
#include "esphome/core/entity_types.h"
#undef ENTITY_TYPE_
#undef ENTITY_CONTROLLER_TYPE_
// NOLINTEND(bugprone-macro-parentheses)
};
} // namespace esphome
-11
View File
@@ -1,11 +0,0 @@
#include "esphome/core/controller_registry.h"
#ifdef USE_CONTROLLER_REGISTRY
namespace esphome {
StaticVector<Controller *, CONTROLLER_REGISTRY_MAX> ControllerRegistry::controllers;
} // namespace esphome
#endif // USE_CONTROLLER_REGISTRY
+20 -53
View File
@@ -5,37 +5,34 @@
#ifdef USE_CONTROLLER_REGISTRY
#include "esphome/core/entity_includes.h"
#include "esphome/core/helpers.h"
namespace esphome {
class Controller;
/// A controller provides a plain on_*_update() member for every entity type in the build.
/// Checked by a static_assert in the generated main.cpp for each registered controller.
// NOLINTBEGIN(bugprone-macro-parentheses)
template<typename T>
concept ControllerContract = requires(T &controller) {
controller;
#define ENTITY_TYPE_(type, singular, plural, count, upper) // no controller callback
#define ENTITY_CONTROLLER_TYPE_(type, singular, plural, count, upper, callback) \
controller.on_##callback(static_cast<type *>(nullptr));
#include "esphome/core/entity_types.h"
#undef ENTITY_TYPE_
#undef ENTITY_CONTROLLER_TYPE_
};
// NOLINTEND(bugprone-macro-parentheses)
/** Global registry for Controllers to receive entity state updates.
/** Fan-out of entity state updates to the controllers (APIServer, WebServer).
*
* This singleton registry allows Controllers (APIServer, WebServer) to receive
* entity state change notifications without storing per-entity callbacks.
*
* Instead of each entity maintaining controller callbacks (32 bytes overhead per entity),
* entities call ControllerRegistry::notify_*_update() which iterates the small list
* of registered controllers (typically 2: API and WebServer).
*
* Each notify method directly iterates controllers and calls the virtual method,
* avoiding function pointer indirection for minimal dispatch overhead.
*
* Memory savings: 32 bytes per entity (2 controllers × 16 bytes std::function overhead)
* Typical config (25 entities): ~780 bytes saved
* Large config (80 entities): ~2,540 bytes saved
* Entities call ControllerRegistry::notify_*_update() instead of holding
* per-entity controller callbacks. The notify functions are only declared here;
* code generation defines them in main.cpp as direct calls on each controller
* that registered through CORE.register_controller(), so there is no virtual
* dispatch, no controller base class and no runtime list of controllers.
*/
class ControllerRegistry {
public:
/** Register a controller to receive entity state updates.
*
* Controllers should call this in their setup() method.
* Typically only APIServer and WebServer register.
*/
static void register_controller(Controller *controller) { controllers.push_back(controller); }
// Notify method declarations (generated from entity_types.h)
// NOLINTBEGIN(bugprone-macro-parentheses)
#define ENTITY_TYPE_(type, singular, plural, count, upper) // no controller callback
@@ -45,38 +42,8 @@ class ControllerRegistry {
#undef ENTITY_TYPE_
#undef ENTITY_CONTROLLER_TYPE_
// NOLINTEND(bugprone-macro-parentheses)
protected:
static StaticVector<Controller *, CONTROLLER_REGISTRY_MAX> controllers;
};
} // namespace esphome
// Include controller.h AFTER the class definition so notify methods can be
// defined inline. This is safe because controller_registry.h is only ever
// included from .cpp files, never from other headers.
#include "esphome/core/controller.h"
namespace esphome {
// Inline notify methods — each is a tiny loop over 1-2 controllers.
// Defining them here (rather than in controller_registry.cpp) allows the
// compiler to inline them into the single call site in each entity's
// notify_frontend_(), eliminating an unnecessary function-call frame.
// NOLINTBEGIN(bugprone-macro-parentheses)
#define ENTITY_TYPE_(type, singular, plural, count, upper) // no controller callback
#define ENTITY_CONTROLLER_TYPE_(type, singular, plural, count, upper, callback) \
inline void ControllerRegistry::notify_##callback(type *obj) { \
for (auto *controller : controllers) { \
controller->on_##callback(obj); \
} \
}
#include "esphome/core/entity_types.h"
#undef ENTITY_TYPE_
#undef ENTITY_CONTROLLER_TYPE_
// NOLINTEND(bugprone-macro-parentheses)
} // namespace esphome
#endif // USE_CONTROLLER_REGISTRY
-1
View File
@@ -629,7 +629,6 @@
#define USE_DASHBOARD_IMPORT
// Default counts for static analysis
#define CONTROLLER_REGISTRY_MAX 2
#define ESPHOME_AREA_COUNT 10
#define ESPHOME_COMPONENT_COUNT 50
#define ESPHOME_DEVICE_COUNT 10
-1
View File
@@ -37,7 +37,6 @@ optional = global_ns.namespace("std").class_("optional")
arduino_json_ns = global_ns.namespace("ArduinoJson")
JsonObject = arduino_json_ns.class_("JsonObject")
JsonObjectConst = arduino_json_ns.class_("JsonObjectConst")
Controller = esphome_ns.class_("Controller")
GPIOPin = esphome_ns.class_("GPIOPin")
InternalGPIOPin = esphome_ns.class_("InternalGPIOPin", GPIOPin)
gpio_ns = esphome_ns.namespace("gpio")
-2
View File
@@ -1,5 +1,4 @@
import esphome.codegen as cg
from esphome.core import CORE
from tests.testing_helpers import ComponentManifestOverride
@@ -12,6 +11,5 @@ def override_manifest(manifest: ComponentManifestOverride) -> None:
cg.add_define("API_MAX_SEND_QUEUE", 8)
cg.add_define("MAX_API_CONNECTIONS", 1)
cg.add_define("USE_SOCKET_IMPL_BSD_SOCKETS")
CORE.register_controller() # api_server registers with the controller registry
manifest.to_code = to_code_testing
-1
View File
@@ -70,7 +70,6 @@ from esphome.cpp_generator import _extract_component_ns
"arduino_json_ns",
"JsonObject",
"JsonObjectConst",
"Controller",
"GPIOPin",
),
)