1
0
mirror of https://github.com/esphome/esphome.git synced 2025-09-06 13:22:19 +01:00

Merge branch 'zero_copy' into integration

This commit is contained in:
J. Nick Koston
2025-07-20 20:18:35 -10:00
8 changed files with 172 additions and 77 deletions

View File

@@ -313,7 +313,11 @@ def validate_field_type(field_type: int, field_name: str = "") -> None:
)
def create_field_type_info(field: descriptor.FieldDescriptorProto) -> TypeInfo:
def create_field_type_info(
field: descriptor.FieldDescriptorProto,
needs_decode: bool = True,
needs_encode: bool = True,
) -> TypeInfo:
"""Create the appropriate TypeInfo instance for a field, handling repeated fields and custom options."""
if field.label == 3: # repeated
return RepeatedTypeInfo(field)
@@ -325,6 +329,10 @@ def create_field_type_info(field: descriptor.FieldDescriptorProto) -> TypeInfo:
):
return FixedArrayBytesType(field, fixed_size)
# Special handling for bytes fields
if field.type == 12:
return BytesType(field, needs_decode, needs_encode)
validate_field_type(field.type, field.name)
return TYPE_INFO[field.type](field)
@@ -589,20 +597,88 @@ class BytesType(TypeInfo):
default_value = ""
reference_type = "std::string &"
const_reference_type = "const std::string &"
decode_length = "value.as_string()"
encode_func = "encode_bytes"
wire_type = WireType.LENGTH_DELIMITED # Uses wire type 2
def __init__(
self,
field: descriptor.FieldDescriptorProto,
needs_decode: bool = True,
needs_encode: bool = True,
) -> None:
super().__init__(field)
self.needs_decode = needs_decode
self.needs_encode = needs_encode
@property
def public_content(self) -> list[str]:
content = []
# Add pointer/length fields if message needs encoding
if self.needs_encode:
content.extend(
[
f"const uint8_t* {self.field_name}_ptr_{{nullptr}};",
f"size_t {self.field_name}_len_{{0}};",
]
)
# Add std::string storage if message needs decoding
if self.needs_decode:
content.append(f"std::string {self.field_name}{{}};")
# Add setter method if message needs encoding
if self.needs_encode:
content.extend(
[
f"void set_{self.field_name}(const uint8_t* data, size_t len) {{",
f" this->{self.field_name}_ptr_ = data;",
f" this->{self.field_name}_len_ = len;",
"}",
]
)
return content
@property
def decode_length_content(self) -> str:
if not self.needs_decode:
return "" # No decode needed for SOURCE_SERVER messages
# Decode into storage only - pointer/length are only needed for encoding
return (
f"case {self.number}:\n"
f" this->{self.field_name} = value.as_string();\n"
f" break;"
)
@property
def encode_content(self) -> str:
return f"buffer.encode_bytes({self.number}, reinterpret_cast<const uint8_t*>(this->{self.field_name}.data()), this->{self.field_name}.size());"
return f"buffer.encode_bytes({self.number}, this->{self.field_name}_ptr_, this->{self.field_name}_len_);"
def dump(self, name: str) -> str:
o = f"out.append(format_hex_pretty({name}));"
return o
ptr_dump = f"format_hex_pretty(this->{self.field_name}_ptr_, this->{self.field_name}_len_)"
str_dump = f"format_hex_pretty(reinterpret_cast<const uint8_t*>(this->{self.field_name}.data()), this->{self.field_name}.size())"
# For SOURCE_CLIENT only, always use std::string
if not self.needs_encode:
return f"out.append({str_dump});"
# For SOURCE_SERVER, always use pointer/length
if not self.needs_decode:
return f"out.append({ptr_dump});"
# For SOURCE_BOTH, check if pointer is set (sending) or use string (received)
return (
f"if (this->{self.field_name}_ptr_ != nullptr) {{\n"
f" out.append({ptr_dump});\n"
f" }} else {{\n"
f" out.append({str_dump});\n"
f" }}"
)
def get_size_calculation(self, name: str, force: bool = False) -> str:
return self._get_simple_size_calculation(name, force, "add_string_field")
return f"ProtoSize::add_bytes_field(total_size, {self.calculate_field_id_size()}, this->{self.field_name}_len_);"
def get_estimated_size(self) -> int:
return self.calculate_field_id_size() + 8 # field ID + 8 bytes typical bytes
@@ -1260,7 +1336,7 @@ def build_message_type(
if field.options.deprecated:
continue
ti = create_field_type_info(field)
ti = create_field_type_info(field, needs_decode, needs_encode)
# Skip field declarations for fields that are in the base class
# but include their encode/decode logic
@@ -1575,10 +1651,20 @@ def build_base_class(
public_content = []
protected_content = []
# Determine if any message using this base class needs decoding/encoding
needs_decode = any(
message_source_map.get(msg.name, SOURCE_BOTH) in (SOURCE_BOTH, SOURCE_CLIENT)
for msg in messages
)
needs_encode = any(
message_source_map.get(msg.name, SOURCE_BOTH) in (SOURCE_BOTH, SOURCE_SERVER)
for msg in messages
)
# For base classes, we only declare the fields but don't handle encode/decode
# The derived classes will handle encoding/decoding with their specific field numbers
for field in common_fields:
ti = create_field_type_info(field)
ti = create_field_type_info(field, needs_decode, needs_encode)
# Get field_ifdef if it's consistent across all messages
field_ifdef = get_common_field_ifdef(field.name, messages)
@@ -1589,12 +1675,6 @@ def build_base_class(
if ti.public_content:
public_content.extend(wrap_with_ifdef(ti.public_content, field_ifdef))
# Determine if any message using this base class needs decoding
needs_decode = any(
message_source_map.get(msg.name, SOURCE_BOTH) in (SOURCE_BOTH, SOURCE_CLIENT)
for msg in messages
)
# Build header
parent_class = "ProtoDecodableMessage" if needs_decode else "ProtoMessage"
out = f"class {base_class_name} : public {parent_class} {{\n"