1
0
mirror of https://github.com/esphome/esphome.git synced 2025-10-18 17:53:47 +01:00

fixed_vector, bluetooth services

This commit is contained in:
J. Nick Koston
2025-10-12 18:21:14 -10:00
parent 4c00861760
commit a9fd0a3b26
7 changed files with 85 additions and 13 deletions

View File

@@ -1519,7 +1519,7 @@ message BluetoothGATTCharacteristic {
repeated uint64 uuid = 1 [(fixed_array_size) = 2, (fixed_array_skip_zero) = true]; repeated uint64 uuid = 1 [(fixed_array_size) = 2, (fixed_array_skip_zero) = true];
uint32 handle = 2; uint32 handle = 2;
uint32 properties = 3; uint32 properties = 3;
repeated BluetoothGATTDescriptor descriptors = 4; repeated BluetoothGATTDescriptor descriptors = 4 [(fixed_vector) = true];
// New field for efficient UUID (v1.12+) // New field for efficient UUID (v1.12+)
// Only one of uuid or short_uuid will be set. // Only one of uuid or short_uuid will be set.
@@ -1531,7 +1531,7 @@ message BluetoothGATTCharacteristic {
message BluetoothGATTService { message BluetoothGATTService {
repeated uint64 uuid = 1 [(fixed_array_size) = 2, (fixed_array_skip_zero) = true]; repeated uint64 uuid = 1 [(fixed_array_size) = 2, (fixed_array_skip_zero) = true];
uint32 handle = 2; uint32 handle = 2;
repeated BluetoothGATTCharacteristic characteristics = 3; repeated BluetoothGATTCharacteristic characteristics = 3 [(fixed_vector) = true];
// New field for efficient UUID (v1.12+) // New field for efficient UUID (v1.12+)
// Only one of uuid or short_uuid will be set. // Only one of uuid or short_uuid will be set.

View File

@@ -64,4 +64,10 @@ extend google.protobuf.FieldOptions {
// This is typically done through methods returning const T& or special accessor // This is typically done through methods returning const T& or special accessor
// methods like get_options() or supported_modes_for_api_(). // methods like get_options() or supported_modes_for_api_().
optional string container_pointer = 50001; optional string container_pointer = 50001;
// fixed_vector: Use FixedVector instead of std::vector for repeated fields
// When set, the repeated field will use FixedVector<T> which requires calling
// init(size) before adding elements. This eliminates std::vector template overhead
// and is ideal when the exact size is known before populating the array.
optional bool fixed_vector = 50013 [default=false];
} }

View File

@@ -1923,7 +1923,7 @@ class BluetoothGATTCharacteristic final : public ProtoMessage {
std::array<uint64_t, 2> uuid{}; std::array<uint64_t, 2> uuid{};
uint32_t handle{0}; uint32_t handle{0};
uint32_t properties{0}; uint32_t properties{0};
std::vector<BluetoothGATTDescriptor> descriptors{}; FixedVector<BluetoothGATTDescriptor> descriptors{};
uint32_t short_uuid{0}; uint32_t short_uuid{0};
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(ProtoSize &size) const override; void calculate_size(ProtoSize &size) const override;
@@ -1937,7 +1937,7 @@ class BluetoothGATTService final : public ProtoMessage {
public: public:
std::array<uint64_t, 2> uuid{}; std::array<uint64_t, 2> uuid{};
uint32_t handle{0}; uint32_t handle{0};
std::vector<BluetoothGATTCharacteristic> characteristics{}; FixedVector<BluetoothGATTCharacteristic> characteristics{};
uint32_t short_uuid{0}; uint32_t short_uuid{0};
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(ProtoSize &size) const override; void calculate_size(ProtoSize &size) const override;

View File

@@ -749,13 +749,29 @@ class ProtoSize {
template<typename MessageType> template<typename MessageType>
inline void add_repeated_message(uint32_t field_id_size, const std::vector<MessageType> &messages) { inline void add_repeated_message(uint32_t field_id_size, const std::vector<MessageType> &messages) {
// Skip if the vector is empty // Skip if the vector is empty
if (messages.empty()) { if (!messages.empty()) {
return; // Use the force version for all messages in the repeated field
for (const auto &message : messages) {
add_message_object_force(field_id_size, message);
}
} }
}
// Use the force version for all messages in the repeated field /**
for (const auto &message : messages) { * @brief Calculates and adds the sizes of all messages in a repeated field to the total message size (FixedVector
add_message_object_force(field_id_size, message); * version)
*
* @tparam MessageType The type of the nested messages in the FixedVector
* @param messages FixedVector of message objects
*/
template<typename MessageType>
inline void add_repeated_message(uint32_t field_id_size, const FixedVector<MessageType> &messages) {
// Skip if the fixed vector is empty
if (!messages.empty()) {
// Use the force version for all messages in the repeated field
for (const auto &message : messages) {
add_message_object_force(field_id_size, message);
}
} }
} }
}; };

View File

@@ -230,8 +230,8 @@ void BluetoothConnection::send_service_for_discovery_() {
service_resp.handle = service_result.start_handle; service_resp.handle = service_result.start_handle;
if (total_char_count > 0) { if (total_char_count > 0) {
// Reserve space and process characteristics // Initialize FixedVector with exact count and process characteristics
service_resp.characteristics.reserve(total_char_count); service_resp.characteristics.init(total_char_count);
uint16_t char_offset = 0; uint16_t char_offset = 0;
esp_gattc_char_elem_t char_result; esp_gattc_char_elem_t char_result;
while (true) { // characteristics while (true) { // characteristics
@@ -275,8 +275,8 @@ void BluetoothConnection::send_service_for_discovery_() {
continue; continue;
} }
// Reserve space and process descriptors // Initialize FixedVector with exact count and process descriptors
characteristic_resp.descriptors.reserve(total_desc_count); characteristic_resp.descriptors.init(total_desc_count);
uint16_t desc_offset = 0; uint16_t desc_offset = 0;
esp_gattc_descr_elem_t desc_result; esp_gattc_descr_elem_t desc_result;
while (true) { // descriptors while (true) { // descriptors

View File

@@ -181,6 +181,31 @@ template<typename T> class FixedVector {
FixedVector(const FixedVector &) = delete; FixedVector(const FixedVector &) = delete;
FixedVector &operator=(const FixedVector &) = delete; FixedVector &operator=(const FixedVector &) = delete;
// Enable move semantics for use in containers
FixedVector(FixedVector &&other) noexcept : data_(other.data_), size_(other.size_), capacity_(other.capacity_) {
other.data_ = nullptr;
other.size_ = 0;
other.capacity_ = 0;
}
FixedVector &operator=(FixedVector &&other) noexcept {
if (this != &other) {
// Delete our current data
if (data_ != nullptr) {
delete[] data_;
}
// Take ownership of other's data
data_ = other.data_;
size_ = other.size_;
capacity_ = other.capacity_;
// Leave other in valid empty state
other.data_ = nullptr;
other.size_ = 0;
other.capacity_ = 0;
}
return *this;
}
// Allocate capacity - can only be called once on empty vector // Allocate capacity - can only be called once on empty vector
void init(size_t n) { void init(size_t n) {
if (data_ == nullptr && n > 0) { if (data_ == nullptr && n > 0) {
@@ -199,12 +224,33 @@ template<typename T> class FixedVector {
} }
} }
/// Construct element in place and return reference
/// Caller must ensure sufficient capacity was allocated via init()
T &emplace_back() {
if (size_ < capacity_) {
return data_[size_++];
}
// Should never happen with proper init() - return last element to avoid crash
return data_[capacity_ - 1];
}
/// Access last element
T &back() { return data_[size_ - 1]; }
const T &back() const { return data_[size_ - 1]; }
size_t size() const { return size_; } size_t size() const { return size_; }
bool empty() const { return size_ == 0; }
/// Access element without bounds checking (matches std::vector behavior) /// Access element without bounds checking (matches std::vector behavior)
/// Caller must ensure index is valid (i < size()) /// Caller must ensure index is valid (i < size())
T &operator[](size_t i) { return data_[i]; } T &operator[](size_t i) { return data_[i]; }
const T &operator[](size_t i) const { return data_[i]; } const T &operator[](size_t i) const { return data_[i]; }
/// Iterators for range-based for loops
T *begin() { return data_; }
T *end() { return data_ + size_; }
const T *begin() const { return data_; }
const T *end() const { return data_ + size_; }
}; };
///@} ///@}

View File

@@ -1415,6 +1415,8 @@ class RepeatedTypeInfo(TypeInfo):
# Check if this is a pointer field by looking for container_pointer option # Check if this is a pointer field by looking for container_pointer option
self._container_type = get_field_opt(field, pb.container_pointer, "") self._container_type = get_field_opt(field, pb.container_pointer, "")
self._use_pointer = bool(self._container_type) self._use_pointer = bool(self._container_type)
# Check if this should use FixedVector instead of std::vector
self._use_fixed_vector = get_field_opt(field, pb.fixed_vector, False)
# For repeated fields, we need to get the base type info # For repeated fields, we need to get the base type info
# but we can't call create_field_type_info as it would cause recursion # but we can't call create_field_type_info as it would cause recursion
@@ -1438,6 +1440,8 @@ class RepeatedTypeInfo(TypeInfo):
if "<" in self._container_type and ">" in self._container_type: if "<" in self._container_type and ">" in self._container_type:
return f"const {self._container_type}*" return f"const {self._container_type}*"
return f"const {self._container_type}<{self._ti.cpp_type}>*" return f"const {self._container_type}<{self._ti.cpp_type}>*"
if self._use_fixed_vector:
return f"FixedVector<{self._ti.cpp_type}>"
return f"std::vector<{self._ti.cpp_type}>" return f"std::vector<{self._ti.cpp_type}>"
@property @property