[core] Move the controller dispatch into controller_dispatch.h

Code generation now emits one function returning the registered
controllers as a tuple plus an include; the header defines the
ControllerRegistry notify functions with a fold expression and checks
ControllerContract for every tuple element.
This commit is contained in:
J. Nick Koston
2026-09-24 17:03:03 +01:00
parent d731ddefc9
commit 4b9906c5c9
3 changed files with 50 additions and 20 deletions
+5 -20
View File
@@ -675,34 +675,19 @@ async def _add_platform_defines() -> None:
@coroutine_with_priority(CoroPriority.FINAL)
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.
# controller_dispatch.h defines ControllerRegistry::notify_*() as direct
# calls on the controllers returned by esphome_controllers().
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)
entries = ", ".join(str(var) 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_"
f"static auto esphome_controllers() {{ return std::tuple{{{entries}}}; }}"
)
)
cg.add_global(cg.RawStatement('#include "esphome/core/controller_dispatch.h"'))
@coroutine_with_priority(CoroPriority.FINAL)
+41
View File
@@ -0,0 +1,41 @@
#pragma once
// Included once by the generated main.cpp, after it returns the registered controllers as a tuple:
//
// static auto esphome_controllers() { return std::tuple{api_apiserver_id, web_server_webserver_id}; }
// #include "esphome/core/controller_dispatch.h"
//
// Defines ControllerRegistry::notify_*() as direct calls on those controllers. Excluded from
// esphome.h so nothing else includes it.
#include "esphome/core/controller_registry.h"
#ifdef USE_CONTROLLER_REGISTRY
#include <tuple>
#include <type_traits>
namespace esphome {
template<typename... Controllers> constexpr bool controllers_satisfy_contract(std::tuple<Controllers...> *) {
static_assert((ControllerContract<std::remove_pointer_t<Controllers>> && ...),
"A registered controller is missing an on_*_update() callback for an entity type in this build "
"(esphome/core/controller_registry.h)");
return true;
}
static_assert(controllers_satisfy_contract(static_cast<decltype(esphome_controllers()) *>(nullptr)));
// 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) \
void ControllerRegistry::notify_##callback(type *obj) { \
std::apply([obj](auto *...controller) { (controller->on_##callback(obj), ...); }, esphome_controllers()); \
}
#include "esphome/core/entity_types.h"
#undef ENTITY_TYPE_
#undef ENTITY_CONTROLLER_TYPE_
// NOLINTEND(bugprone-macro-parentheses)
} // namespace esphome
#endif // USE_CONTROLLER_REGISTRY
+4
View File
@@ -211,6 +211,7 @@ VERSION_H_TARGET = "esphome/core/version.h"
BUILD_INFO_DATA_H_TARGET = "esphome/core/build_info_data.h"
BUILD_INFO_DATA_CPP_TARGET = "esphome/core/build_info_data.cpp"
ENTITY_TYPES_H_TARGET = "esphome/core/entity_types.h"
CONTROLLER_DISPATCH_H_TARGET = "esphome/core/controller_dispatch.h"
ESPHOME_README_TXT = """
THIS DIRECTORY IS AUTO-GENERATED, DO NOT MODIFY
@@ -243,6 +244,9 @@ def copy_src_tree():
# new component is loaded by a consumer.
esphome_h_exclude = {
Path(ENTITY_TYPES_H_TARGET),
Path(
CONTROLLER_DISPATCH_H_TARGET
), # included by main.cpp once ESPHOME_CONTROLLERS is defined
Path(
"esphome/core/ring_buffer.h"
), # moved to components/ring_buffer/, removed in 2026.11.0