[api] Elide empty message construction in protobuf dispatch (#13871)

This commit is contained in:
J. Nick Koston
2026-02-09 03:24:45 -06:00
committed by GitHub
parent 756f1c6b7e
commit 140ec0639c
5 changed files with 117 additions and 145 deletions
+30 -23
View File
@@ -2270,10 +2270,13 @@ SOURCE_NAMES = {
SOURCE_CLIENT: "SOURCE_CLIENT",
}
RECEIVE_CASES: dict[int, tuple[str, str | None]] = {}
RECEIVE_CASES: dict[int, tuple[str, str | None, str]] = {}
ifdefs: dict[str, str] = {}
# Track messages with no fields (empty messages) for parameter elision
EMPTY_MESSAGES: set[str] = set()
def get_opt(
desc: descriptor.DescriptorProto,
@@ -2504,26 +2507,26 @@ def build_service_message_type(
# Only add ifdef when we're actually generating content
if ifdef is not None:
hout += f"#ifdef {ifdef}\n"
# Generate receive
# Generate receive handler and switch case
func = f"on_{snake}"
hout += f"virtual void {func}(const {mt.name} &value){{}};\n"
case = ""
case += f"{mt.name} msg;\n"
# Check if this message has any fields (excluding deprecated ones)
has_fields = any(not field.options.deprecated for field in mt.field)
if has_fields:
# Normal case: decode the message
is_empty = not has_fields
if is_empty:
EMPTY_MESSAGES.add(mt.name)
hout += f"virtual void {func}({'' if is_empty else f'const {mt.name} &value'}){{}};\n"
case = ""
if not is_empty:
case += f"{mt.name} msg;\n"
case += "msg.decode(msg_data, msg_size);\n"
else:
# Empty message optimization: skip decode since there are no fields
case += "// Empty message: no decode needed\n"
if log:
case += "#ifdef HAS_PROTO_MESSAGE_DUMP\n"
case += f'this->log_receive_message_(LOG_STR("{func}"), msg);\n'
if is_empty:
case += f'this->log_receive_message_(LOG_STR("{func}"));\n'
else:
case += f'this->log_receive_message_(LOG_STR("{func}"), msg);\n'
case += "#endif\n"
case += f"this->{func}(msg);\n"
case += f"this->{func}({'msg' if not is_empty else ''});\n"
case += "break;"
# Store the message name and ifdef with the case for later use
RECEIVE_CASES[id_] = (case, ifdef, mt.name)
# Only close ifdef if we opened it
@@ -2839,6 +2842,7 @@ static const char *const TAG = "api.service";
hpp += (
" void log_receive_message_(const LogString *name, const ProtoMessage &msg);\n"
)
hpp += " void log_receive_message_(const LogString *name);\n"
hpp += " public:\n"
hpp += "#endif\n\n"
@@ -2862,6 +2866,9 @@ static const char *const TAG = "api.service";
cpp += " DumpBuffer dump_buf;\n"
cpp += ' ESP_LOGVV(TAG, "%s: %s", LOG_STR_ARG(name), msg.dump_to(dump_buf));\n'
cpp += "}\n"
cpp += f"void {class_name}::log_receive_message_(const LogString *name) {{\n"
cpp += ' ESP_LOGVV(TAG, "%s: {}", LOG_STR_ARG(name));\n'
cpp += "}\n"
cpp += "#endif\n\n"
for mt in file.message_type:
@@ -2929,22 +2936,22 @@ static const char *const TAG = "api.service";
hpp_protected += f"#ifdef {ifdef}\n"
cpp += f"#ifdef {ifdef}\n"
hpp_protected += f" void {on_func}(const {inp} &msg) override;\n"
is_empty = inp in EMPTY_MESSAGES
param = "" if is_empty else f"const {inp} &msg"
arg = "" if is_empty else "msg"
# For non-void methods, generate a send_ method instead of return-by-value
hpp_protected += f" void {on_func}({param}) override;\n"
if is_void:
hpp += f" virtual void {func}(const {inp} &msg) = 0;\n"
hpp += f" virtual void {func}({param}) = 0;\n"
else:
hpp += f" virtual bool send_{func}_response(const {inp} &msg) = 0;\n"
hpp += f" virtual bool send_{func}_response({param}) = 0;\n"
cpp += f"void {class_name}::{on_func}(const {inp} &msg) {{\n"
# No authentication check here - it's done in read_message
cpp += f"void {class_name}::{on_func}({param}) {{\n"
body = ""
if is_void:
body += f"this->{func}(msg);\n"
body += f"this->{func}({arg});\n"
else:
body += f"if (!this->send_{func}_response(msg)) {{\n"
body += f"if (!this->send_{func}_response({arg})) {{\n"
body += " this->on_fatal_error();\n"
body += "}\n"