Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
Move ControllerContract next to its only user in controller_dispatch.h,
collapse the contract check into one static_assert, and let codegen
build the tuple with ArrayInitializer after emitting the tuple include.
Share one esphome.h exclusion set between the writer and the clang-tidy
all-headers file so the dispatch header is skipped by both. Add unit
tests for the generated dispatch.
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.
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.
A generated decodable message that lost its decode() would now fail to compile instead of
silently keeping its defaults. The asserts cover exactly the classes that derive from
ProtoDecodableMessage, under one ifdef per run instead of one per line, and the generator
tests pin the inline decode() wrapper, its absence on fixed vector messages, and the
this-> free static body.
Every build below VERY_VERBOSE now checks the base class and each generated decodable
message with std::is_polymorphic_v, so a vtable cannot come back unnoticed.
decode_field() becomes a static per message function and the generated decode() hands it
to the shared loop as a function pointer, so decodable messages carry no vtable and no
vptr store at every construction site. The loop loses the two vtable loads per field. The
protected destructor on ProtoDecodableMessage goes with the virtuals; ProtoMessage keeps
its own guard for the dump builds that still have them.