Fold dev's ProgmemStringRef struct into the class and share the progmem short string body

This commit is contained in:
J. Nick Koston
2026-10-12 16:53:49 -10:00
parent 00f8e89f28
commit 0e8a3dfb6f
3 changed files with 16 additions and 37 deletions
+1 -8
View File
@@ -410,7 +410,7 @@ class ProtoEncode {
[[nodiscard]] static inline uint8_t *encode_short_string_force(uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM,
uint8_t tag, const StringRef &ref) {
#ifdef USE_ESP8266
// memcpy_P reads RAM too, so RAM strings share the progmem copy
// memcpy_P reads RAM too, and sharing the progmem copy is smaller than an inline memcpy
return encode_short_progmem_string_force(pos PROTO_ENCODE_DEBUG_ARG, tag, ProgmemStringRef(ref));
#else
#ifdef ESPHOME_DEBUG_API
@@ -424,7 +424,6 @@ class ProtoEncode {
#endif
}
/// encode_short_string_force for a string that may be in flash (PROGMEM).
#ifdef USE_ESP8266
[[nodiscard]] static inline uint8_t *encode_short_progmem_string_force(
uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM, uint8_t tag, const ProgmemStringRef &ref) {
#ifdef ESPHOME_DEBUG_API
@@ -436,12 +435,6 @@ class ProtoEncode {
progmem_memcpy(pos + 2, ref.progmem_ptr(), ref.size());
return pos + 2 + ref.size();
}
#else
[[nodiscard]] static inline uint8_t *encode_short_progmem_string_force(
uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM, uint8_t tag, const ProgmemStringRef &ref) {
return encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, tag, ref.ram_ref());
}
#endif
/// Write a precomputed tag byte + 32-bit value. Outlined on embedded: one copy beats inline stores per field.
[[nodiscard]] static PROTO_OUTLINE_FOR_SIZE uint8_t *write_tag_and_fixed32(
uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM, uint8_t tag, uint32_t value) {
+10 -19
View File
@@ -190,24 +190,6 @@ inline bool operator!=(const StringRef &lhs, const __FlashStringHelper *rhs) { r
inline bool operator!=(const __FlashStringHelper *lhs, const StringRef &rhs) { return !(rhs == lhs); }
/// Codegen use only: a flash string literal with its byte length, compared without a runtime strlen.
struct ProgmemStringRef {
PGM_P str;
size_t len;
};
inline bool operator==(const std::string &lhs, ProgmemStringRef rhs) {
return lhs.size() == rhs.len && (rhs.len == 0 || memcmp_P(lhs.data(), rhs.str, rhs.len) == 0);
}
inline bool operator!=(const std::string &lhs, ProgmemStringRef rhs) { return !(lhs == rhs); }
inline bool operator==(const StringRef &lhs, ProgmemStringRef rhs) {
// A StringRef may be {nullptr, 0}, and memcmp_P with a null pointer is undefined even for 0 bytes
return lhs.size() == rhs.len && (rhs.len == 0 || memcmp_P(lhs.c_str(), rhs.str, rhs.len) == 0);
}
inline bool operator!=(const StringRef &lhs, ProgmemStringRef rhs) { return !(lhs == rhs); }
#endif // USE_ESP8266
inline bool operator<(const StringRef &lhs, const StringRef &rhs) {
@@ -271,7 +253,9 @@ class ProgmemStringRef {
bool equals(const StringRef &other) const {
#ifdef USE_ESP8266
return other.size() == this->ref_.size() && memcmp_P(other.c_str(), this->ref_.c_str(), other.size()) == 0;
// A StringRef may be {nullptr, 0}, and memcmp_P with a null pointer is undefined even for 0 bytes
return other.size() == this->ref_.size() &&
(other.empty() || memcmp_P(other.c_str(), this->ref_.c_str(), other.size()) == 0);
#else
return other == this->ref_;
#endif
@@ -293,6 +277,13 @@ class ProgmemStringRef {
StringRef ref_;
};
#ifdef USE_ESP8266
inline bool operator==(const StringRef &lhs, const ProgmemStringRef &rhs) { return rhs.equals(lhs); }
inline bool operator!=(const StringRef &lhs, const ProgmemStringRef &rhs) { return !rhs.equals(lhs); }
inline bool operator==(const std::string &lhs, const ProgmemStringRef &rhs) { return rhs.equals(StringRef(lhs)); }
inline bool operator!=(const std::string &lhs, const ProgmemStringRef &rhs) { return !rhs.equals(StringRef(lhs)); }
#endif
// String conversion functions for ADL compatibility (allows stoi(x) where x is StringRef)
// Must be in esphome namespace for ADL to find them. Uses strtol/strtod directly to avoid heap allocation.
namespace internal {
+5 -10
View File
@@ -1158,6 +1158,11 @@ class PointerToBufferTypeBase(TypeInfo):
"""Get the wire type for this field."""
return WireType.LENGTH_DELIMITED # Uses wire type 2
@property
def progmem(self) -> bool:
"""Whether the data is in flash, so encode and dump copy it with progmem_memcpy."""
return get_field_opt(self._field, pb.progmem, False)
def get_estimated_size(self) -> int:
# field ID + length varint + typical data (assume small for pointer fields)
return self.calculate_field_id_size() + 2 + 16
@@ -1171,11 +1176,6 @@ class PointerToBytesBufferType(PointerToBufferTypeBase):
reference_type = "const uint8_t*"
const_reference_type = "const uint8_t*"
@property
def progmem(self) -> bool:
"""Whether the data is in flash, so encode and dump copy it with progmem_memcpy."""
return get_field_opt(self._field, pb.progmem, False)
@property
def public_content(self) -> list[str]:
# Use uint16_t for length - max packet size is well below 65535
@@ -1253,11 +1253,6 @@ class PointerToStringBufferType(PointerToBufferTypeBase):
one zero fill. Any encode path that copies unconditionally must check this."""
return not self._needs_decode and not self.force
@property
def progmem(self) -> bool:
"""Whether the string is in flash, so encode and dump copy it with progmem_memcpy."""
return get_field_opt(self._field, pb.progmem, False)
@property
def public_content(self) -> list[str]:
if self.progmem: