From a5bd718fc9c00717c93e2dcc7443bbbf4af6ff09 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 21 Mar 2026 20:51:28 -1000 Subject: [PATCH] [api] Make ProtoDecodableMessage::decode() non-virtual decode() is never called polymorphically - all call sites in read_message_() use concrete types. The only indirect call site was decode_to_message(), which also always knows the concrete type. Convert decode_to_message() to a template so the concrete type is preserved, allowing decode() to be non-virtual. The two classes that override decode() (ExecuteServiceArgument, ExecuteServiceRequest) now hide the base method, which works since all calls use concrete types. This removes one vtable slot (4 bytes) from each decodable message class vtable, saving ~148 bytes of flash. --- esphome/components/api/api_pb2.h | 4 ++-- esphome/components/api/proto.h | 22 +++++++--------------- script/api_protobuf/api_protobuf.py | 2 +- 3 files changed, 10 insertions(+), 18 deletions(-) diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index a4ee0adb8b..86289a28d6 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -1253,7 +1253,7 @@ class ExecuteServiceArgument final : public ProtoDecodableMessage { FixedVector int_array{}; FixedVector float_array{}; FixedVector string_array{}; - void decode(const uint8_t *buffer, size_t length) override; + void decode(const uint8_t *buffer, size_t length); #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; #endif @@ -1278,7 +1278,7 @@ class ExecuteServiceRequest final : public ProtoDecodableMessage { #ifdef USE_API_USER_DEFINED_ACTION_RESPONSES bool return_response{false}; #endif - void decode(const uint8_t *buffer, size_t length) override; + void decode(const uint8_t *buffer, size_t length); #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; #endif diff --git a/esphome/components/api/proto.h b/esphome/components/api/proto.h index cc1d1f1549..a63f53adb4 100644 --- a/esphome/components/api/proto.h +++ b/esphome/components/api/proto.h @@ -152,8 +152,7 @@ class ProtoVarInt { #endif }; -// Forward declarations for decode_to_message and related encoding helpers -class ProtoDecodableMessage; +// Forward declarations for encoding helpers class ProtoMessage; class ProtoSize; @@ -166,16 +165,9 @@ class ProtoLengthDelimited { const uint8_t *data() const { return this->value_; } size_t size() const { return this->length_; } - /** - * Decode the length-delimited data into an existing ProtoDecodableMessage instance. - * - * This method allows decoding without templates, enabling use in contexts - * where the message type is not known at compile time. The ProtoDecodableMessage's - * decode() method will be called with the raw data and length. - * - * @param msg The ProtoDecodableMessage instance to decode into - */ - void decode_to_message(ProtoDecodableMessage &msg) const; + /// Decode the length-delimited data into a message instance. + /// Template preserves concrete type so decode() resolves statically. + template void decode_to_message(T &msg) const; protected: const uint8_t *const value_; @@ -454,7 +446,7 @@ class ProtoMessage { // Base class for messages that support decoding class ProtoDecodableMessage : public ProtoMessage { public: - virtual void decode(const uint8_t *buffer, size_t length); + void decode(const uint8_t *buffer, size_t length); /** * Count occurrences of a repeated field in a protobuf buffer. @@ -690,8 +682,8 @@ template inline void ProtoWriteBuffer::encode_optional_sub_message(u this->encode_optional_sub_message(field_id, value.calculate_size(), &value, &proto_encode_msg); } -// Implementation of decode_to_message - must be after ProtoDecodableMessage is defined -inline void ProtoLengthDelimited::decode_to_message(ProtoDecodableMessage &msg) const { +// Template decode_to_message - preserves concrete type so decode() resolves statically +template void ProtoLengthDelimited::decode_to_message(T &msg) const { msg.decode(this->value_, this->length_); } diff --git a/script/api_protobuf/api_protobuf.py b/script/api_protobuf/api_protobuf.py index e4e0632542..1e0e872270 100755 --- a/script/api_protobuf/api_protobuf.py +++ b/script/api_protobuf/api_protobuf.py @@ -2262,7 +2262,7 @@ def build_message_type( o += "}\n" cpp += o # Generate the decode() declaration in header (public method) - prot = "void decode(const uint8_t *buffer, size_t length) override;" + prot = "void decode(const uint8_t *buffer, size_t length);" public_content.append(prot) # Only generate encode method if this message needs encoding and has fields