1
0
mirror of https://github.com/esphome/esphome.git synced 2025-09-29 08:32:26 +01:00

Merge branch 'integration' into memory_api

This commit is contained in:
J. Nick Koston
2025-09-26 10:18:10 -05:00
5 changed files with 84 additions and 45 deletions

View File

@@ -313,10 +313,8 @@ void BLECharacteristic::remove_client_from_notify_list_(uint16_t conn_id) {
// for the common case by swapping with the last element and popping // for the common case by swapping with the last element and popping
for (size_t i = 0; i < this->clients_to_notify_.size(); i++) { for (size_t i = 0; i < this->clients_to_notify_.size(); i++) {
if (this->clients_to_notify_[i].conn_id == conn_id) { if (this->clients_to_notify_[i].conn_id == conn_id) {
// Swap with last element and pop // Swap with last element and pop (safe even when i is the last element)
if (i != this->clients_to_notify_.size() - 1) {
this->clients_to_notify_[i] = this->clients_to_notify_.back(); this->clients_to_notify_[i] = this->clients_to_notify_.back();
}
this->clients_to_notify_.pop_back(); this->clients_to_notify_.pop_back();
return; return;
} }

View File

@@ -83,10 +83,8 @@ void BLECharacteristicSetValueActionManager::remove_listener_(BLECharacteristic
// Since we typically have very few listeners, optimize by swapping with back and popping // Since we typically have very few listeners, optimize by swapping with back and popping
for (size_t i = 0; i < this->listeners_.size(); i++) { for (size_t i = 0; i < this->listeners_.size(); i++) {
if (this->listeners_[i].characteristic == characteristic) { if (this->listeners_[i].characteristic == characteristic) {
// Swap with last element and pop // Swap with last element and pop (safe even when i is the last element)
if (i != this->listeners_.size() - 1) {
this->listeners_[i] = this->listeners_.back(); this->listeners_[i] = this->listeners_.back();
}
this->listeners_.pop_back(); this->listeners_.pop_back();
return; return;
} }

View File

@@ -20,6 +20,9 @@ namespace esp32_ble_server_automations {
using namespace esp32_ble; using namespace esp32_ble;
using namespace event_emitter; using namespace event_emitter;
// Invalid listener ID constant - 0 is used as sentinel value in EventEmitter
static constexpr EventEmitterListenerID INVALID_LISTENER_ID = 0;
class BLETriggers { class BLETriggers {
public: public:
static Trigger<std::vector<uint8_t>, uint16_t> *create_characteristic_on_write_trigger( static Trigger<std::vector<uint8_t>, uint16_t> *create_characteristic_on_write_trigger(
@@ -50,7 +53,7 @@ class BLECharacteristicSetValueActionManager
return entry.listener_id; return entry.listener_id;
} }
} }
return 0; return INVALID_LISTENER_ID;
} }
void emit_pre_notify(BLECharacteristic *characteristic) { void emit_pre_notify(BLECharacteristic *characteristic) {
this->emit_(BLECharacteristicSetValueActionEvt::PRE_NOTIFY, characteristic); this->emit_(BLECharacteristicSetValueActionEvt::PRE_NOTIFY, characteristic);

View File

@@ -1,14 +0,0 @@
#include "event_emitter.h"
namespace esphome {
namespace event_emitter {
static const char *const TAG = "event_emitter";
void raise_event_emitter_full_error() {
ESP_LOGE(TAG, "EventEmitter has reached the maximum number of listeners for event");
ESP_LOGW(TAG, "Removing listener to make space for new listener");
}
} // namespace event_emitter
} // namespace esphome

View File

@@ -1,5 +1,4 @@
#pragma once #pragma once
#include <unordered_map>
#include <vector> #include <vector>
#include <functional> #include <functional>
#include <limits> #include <limits>
@@ -10,52 +9,107 @@ namespace esphome {
namespace event_emitter { namespace event_emitter {
using EventEmitterListenerID = uint32_t; using EventEmitterListenerID = uint32_t;
void raise_event_emitter_full_error(); static constexpr EventEmitterListenerID INVALID_LISTENER_ID = 0;
// EventEmitter class that can emit events with a specific name (it is highly recommended to use an enum class for this) // EventEmitter class that can emit events with a specific name (it is highly recommended to use an enum class for this)
// and a list of arguments. Supports multiple listeners for each event. // and a list of arguments. Supports multiple listeners for each event.
template<typename EvtType, typename... Args> class EventEmitter { template<typename EvtType, typename... Args> class EventEmitter {
public: public:
EventEmitterListenerID on(EvtType event, std::function<void(Args...)> listener) { EventEmitterListenerID on(EvtType event, std::function<void(Args...)> listener) {
EventEmitterListenerID listener_id = get_next_id_(event); EventEmitterListenerID listener_id = get_next_id_();
listeners_[event][listener_id] = listener;
// Find or create event entry
EventEntry *entry = find_or_create_event_(event);
entry->listeners.push_back({listener_id, listener});
return listener_id; return listener_id;
} }
void off(EvtType event, EventEmitterListenerID id) { void off(EvtType event, EventEmitterListenerID id) {
if (listeners_.count(event) == 0) EventEntry *entry = find_event_(event);
if (entry == nullptr)
return; return;
listeners_[event].erase(id);
// Remove listener with given id
for (auto it = entry->listeners.begin(); it != entry->listeners.end(); ++it) {
if (it->id == id) {
// Swap with last and pop for efficient removal
*it = entry->listeners.back();
entry->listeners.pop_back();
// Remove event entry if no more listeners
if (entry->listeners.empty()) {
remove_event_(event);
}
return;
}
}
} }
protected: protected:
void emit_(EvtType event, Args... args) { void emit_(EvtType event, Args... args) {
if (listeners_.count(event) == 0) EventEntry *entry = find_event_(event);
if (entry == nullptr)
return; return;
for (const auto &listener : listeners_[event]) {
listener.second(args...); // Call all listeners for this event
for (const auto &listener : entry->listeners) {
listener.callback(args...);
} }
} }
EventEmitterListenerID get_next_id_(EvtType event) { private:
// Check if the map is full struct Listener {
if (listeners_[event].size() == std::numeric_limits<EventEmitterListenerID>::max()) { EventEmitterListenerID id;
// Raise an error if the map is full std::function<void(Args...)> callback;
raise_event_emitter_full_error(); };
off(event, 0);
return 0; struct EventEntry {
EvtType event;
std::vector<Listener> listeners;
};
EventEntry *find_event_(EvtType event) {
for (auto &entry : events_) {
if (entry.event == event) {
return &entry;
} }
// Get the next ID for the given event. }
EventEmitterListenerID next_id = (current_id_ + 1) % std::numeric_limits<EventEmitterListenerID>::max(); return nullptr;
while (listeners_[event].count(next_id) > 0) { }
next_id = (next_id + 1) % std::numeric_limits<EventEmitterListenerID>::max();
EventEntry *find_or_create_event_(EvtType event) {
EventEntry *entry = find_event_(event);
if (entry != nullptr)
return entry;
// Create new event entry
events_.push_back({event, {}});
return &events_.back();
}
void remove_event_(EvtType event) {
for (auto it = events_.begin(); it != events_.end(); ++it) {
if (it->event == event) {
// Swap with last and pop
*it = events_.back();
events_.pop_back();
return;
}
}
}
EventEmitterListenerID get_next_id_() {
// Simple incrementing ID, wrapping around at max
EventEmitterListenerID next_id = (current_id_ + 1);
if (next_id == 0) { // Skip 0 as it's often used as "invalid"
next_id = 1;
} }
current_id_ = next_id; current_id_ = next_id;
return current_id_; return current_id_;
} }
private: std::vector<EventEntry> events_;
std::unordered_map<EvtType, std::unordered_map<EventEmitterListenerID, std::function<void(Args...)>>> listeners_;
EventEmitterListenerID current_id_ = 0; EventEmitterListenerID current_id_ = 0;
}; };