From 39d5cbc74a2284fe52d98b8d7e7b7f7b3a30cc48 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 5 Oct 2025 15:20:40 -0500 Subject: [PATCH] [esp32_ble_server] Replace EventEmitter with direct callbacks to reduce memory usage (#10946) --- CODEOWNERS | 1 - .../components/esp32_ble_server/__init__.py | 2 +- .../esp32_ble_server/ble_characteristic.cpp | 17 +-- .../esp32_ble_server/ble_characteristic.h | 30 ++--- .../esp32_ble_server/ble_descriptor.cpp | 7 +- .../esp32_ble_server/ble_descriptor.h | 22 ++-- .../esp32_ble_server/ble_server.cpp | 12 +- .../components/esp32_ble_server/ble_server.h | 35 ++++-- .../ble_server_automations.cpp | 37 ++---- .../esp32_ble_server/ble_server_automations.h | 42 ++----- .../esp32_improv/esp32_improv_component.cpp | 14 +-- esphome/components/event_emitter/__init__.py | 5 - .../components/event_emitter/event_emitter.h | 117 ------------------ 13 files changed, 107 insertions(+), 234 deletions(-) delete mode 100644 esphome/components/event_emitter/__init__.py delete mode 100644 esphome/components/event_emitter/event_emitter.h diff --git a/CODEOWNERS b/CODEOWNERS index 3747acd2b5..0b9935faf7 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -160,7 +160,6 @@ esphome/components/esp_ldo/* @clydebarrow esphome/components/espnow/* @jesserockz esphome/components/ethernet_info/* @gtjadsonsantos esphome/components/event/* @nohat -esphome/components/event_emitter/* @Rapsssito esphome/components/exposure_notifications/* @OttoWinter esphome/components/ezo/* @ssieb esphome/components/ezo_pmp/* @carlos-sarmiento diff --git a/esphome/components/esp32_ble_server/__init__.py b/esphome/components/esp32_ble_server/__init__.py index 9eab9647b3..10fa09fcc3 100644 --- a/esphome/components/esp32_ble_server/__init__.py +++ b/esphome/components/esp32_ble_server/__init__.py @@ -26,7 +26,7 @@ from esphome.const import ( from esphome.core import CORE from esphome.schema_extractors import SCHEMA_EXTRACT -AUTO_LOAD = ["esp32_ble", "bytebuffer", "event_emitter"] +AUTO_LOAD = ["esp32_ble", "bytebuffer"] CODEOWNERS = ["@jesserockz", "@clydebarrow", "@Rapsssito"] DEPENDENCIES = ["esp32"] DOMAIN = "esp32_ble_server" diff --git a/esphome/components/esp32_ble_server/ble_characteristic.cpp b/esphome/components/esp32_ble_server/ble_characteristic.cpp index fabcc75321..d485d9fe2d 100644 --- a/esphome/components/esp32_ble_server/ble_characteristic.cpp +++ b/esphome/components/esp32_ble_server/ble_characteristic.cpp @@ -73,7 +73,7 @@ void BLECharacteristic::notify() { void BLECharacteristic::add_descriptor(BLEDescriptor *descriptor) { // If the descriptor is the CCCD descriptor, listen to its write event to know if the client wants to be notified if (descriptor->get_uuid() == ESPBTUUID::from_uint16(ESP_GATT_UUID_CHAR_CLIENT_CONFIG)) { - descriptor->on(BLEDescriptorEvt::VectorEvt::ON_WRITE, [this](const std::vector &value, uint16_t conn_id) { + descriptor->on_write([this](std::span value, uint16_t conn_id) { if (value.size() != 2) return; uint16_t cccd = encode_uint16(value[1], value[0]); @@ -208,8 +208,9 @@ void BLECharacteristic::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt if (!param->read.need_rsp) break; // For some reason you can request a read but not want a response - this->EventEmitter::emit_(BLECharacteristicEvt::EmptyEvt::ON_READ, - param->read.conn_id); + if (this->on_read_callback_) { + (*this->on_read_callback_)(param->read.conn_id); + } uint16_t max_offset = 22; @@ -277,8 +278,9 @@ void BLECharacteristic::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt } if (!param->write.is_prep) { - this->EventEmitter, uint16_t>::emit_( - BLECharacteristicEvt::VectorEvt::ON_WRITE, this->value_, param->write.conn_id); + if (this->on_write_callback_) { + (*this->on_write_callback_)(this->value_, param->write.conn_id); + } } break; @@ -289,8 +291,9 @@ void BLECharacteristic::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt break; this->write_event_ = false; if (param->exec_write.exec_write_flag == ESP_GATT_PREP_WRITE_EXEC) { - this->EventEmitter, uint16_t>::emit_( - BLECharacteristicEvt::VectorEvt::ON_WRITE, this->value_, param->exec_write.conn_id); + if (this->on_write_callback_) { + (*this->on_write_callback_)(this->value_, param->exec_write.conn_id); + } } esp_err_t err = esp_ble_gatts_send_response(gatts_if, param->write.conn_id, param->write.trans_id, ESP_GATT_OK, nullptr); diff --git a/esphome/components/esp32_ble_server/ble_characteristic.h b/esphome/components/esp32_ble_server/ble_characteristic.h index 97b3af2a21..4a29683f41 100644 --- a/esphome/components/esp32_ble_server/ble_characteristic.h +++ b/esphome/components/esp32_ble_server/ble_characteristic.h @@ -2,10 +2,12 @@ #include "ble_descriptor.h" #include "esphome/components/esp32_ble/ble_uuid.h" -#include "esphome/components/event_emitter/event_emitter.h" #include "esphome/components/bytebuffer/bytebuffer.h" #include +#include +#include +#include #ifdef USE_ESP32 @@ -22,22 +24,10 @@ namespace esp32_ble_server { using namespace esp32_ble; using namespace bytebuffer; -using namespace event_emitter; class BLEService; -namespace BLECharacteristicEvt { -enum VectorEvt { - ON_WRITE, -}; - -enum EmptyEvt { - ON_READ, -}; -} // namespace BLECharacteristicEvt - -class BLECharacteristic : public EventEmitter, uint16_t>, - public EventEmitter { +class BLECharacteristic { public: BLECharacteristic(ESPBTUUID uuid, uint32_t properties); ~BLECharacteristic(); @@ -76,6 +66,15 @@ class BLECharacteristic : public EventEmitter, uint16_t)> &&callback) { + this->on_write_callback_ = + std::make_unique, uint16_t)>>(std::move(callback)); + } + void on_read(std::function &&callback) { + this->on_read_callback_ = std::make_unique>(std::move(callback)); + } + protected: bool write_event_{false}; BLEService *service_{}; @@ -98,6 +97,9 @@ class BLECharacteristic : public EventEmitter, uint16_t)>> on_write_callback_; + std::unique_ptr> on_read_callback_; + esp_gatt_perm_t permissions_ = ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE; enum State : uint8_t { diff --git a/esphome/components/esp32_ble_server/ble_descriptor.cpp b/esphome/components/esp32_ble_server/ble_descriptor.cpp index afbe579513..16941cca0f 100644 --- a/esphome/components/esp32_ble_server/ble_descriptor.cpp +++ b/esphome/components/esp32_ble_server/ble_descriptor.cpp @@ -74,9 +74,10 @@ void BLEDescriptor::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_ break; this->value_.attr_len = param->write.len; memcpy(this->value_.attr_value, param->write.value, param->write.len); - this->emit_(BLEDescriptorEvt::VectorEvt::ON_WRITE, - std::vector(param->write.value, param->write.value + param->write.len), - param->write.conn_id); + if (this->on_write_callback_) { + (*this->on_write_callback_)(std::span(param->write.value, param->write.len), + param->write.conn_id); + } break; } default: diff --git a/esphome/components/esp32_ble_server/ble_descriptor.h b/esphome/components/esp32_ble_server/ble_descriptor.h index 8d3c22c5a1..425462a316 100644 --- a/esphome/components/esp32_ble_server/ble_descriptor.h +++ b/esphome/components/esp32_ble_server/ble_descriptor.h @@ -1,30 +1,26 @@ #pragma once #include "esphome/components/esp32_ble/ble_uuid.h" -#include "esphome/components/event_emitter/event_emitter.h" #include "esphome/components/bytebuffer/bytebuffer.h" #ifdef USE_ESP32 #include #include +#include +#include +#include namespace esphome { namespace esp32_ble_server { using namespace esp32_ble; using namespace bytebuffer; -using namespace event_emitter; class BLECharacteristic; -namespace BLEDescriptorEvt { -enum VectorEvt { - ON_WRITE, -}; -} // namespace BLEDescriptorEvt - -class BLEDescriptor : public EventEmitter, uint16_t> { +// Base class for BLE descriptors +class BLEDescriptor { public: BLEDescriptor(ESPBTUUID uuid, uint16_t max_len = 100, bool read = true, bool write = true); virtual ~BLEDescriptor(); @@ -39,6 +35,12 @@ class BLEDescriptor : public EventEmitterstate_ == CREATED; } bool is_failed() { return this->state_ == FAILED; } + // Direct callback registration - only allocates when callback is set + void on_write(std::function, uint16_t)> &&callback) { + this->on_write_callback_ = + std::make_unique, uint16_t)>>(std::move(callback)); + } + protected: BLECharacteristic *characteristic_{nullptr}; ESPBTUUID uuid_; @@ -46,6 +48,8 @@ class BLEDescriptor : public EventEmitter, uint16_t)>> on_write_callback_; + esp_gatt_perm_t permissions_{}; enum State : uint8_t { diff --git a/esphome/components/esp32_ble_server/ble_server.cpp b/esphome/components/esp32_ble_server/ble_server.cpp index 89299bb417..942be7e597 100644 --- a/esphome/components/esp32_ble_server/ble_server.cpp +++ b/esphome/components/esp32_ble_server/ble_server.cpp @@ -147,20 +147,28 @@ BLEService *BLEServer::get_service(ESPBTUUID uuid, uint8_t inst_id) { return nullptr; } +void BLEServer::dispatch_callbacks_(CallbackType type, uint16_t conn_id) { + for (auto &entry : this->callbacks_) { + if (entry.type == type) { + entry.callback(conn_id); + } + } +} + void BLEServer::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param) { switch (event) { case ESP_GATTS_CONNECT_EVT: { ESP_LOGD(TAG, "BLE Client connected"); this->add_client_(param->connect.conn_id); - this->emit_(BLEServerEvt::EmptyEvt::ON_CONNECT, param->connect.conn_id); + this->dispatch_callbacks_(CallbackType::ON_CONNECT, param->connect.conn_id); break; } case ESP_GATTS_DISCONNECT_EVT: { ESP_LOGD(TAG, "BLE Client disconnected"); this->remove_client_(param->disconnect.conn_id); this->parent_->advertising_start(); - this->emit_(BLEServerEvt::EmptyEvt::ON_DISCONNECT, param->disconnect.conn_id); + this->dispatch_callbacks_(CallbackType::ON_DISCONNECT, param->disconnect.conn_id); break; } case ESP_GATTS_REG_EVT: { diff --git a/esphome/components/esp32_ble_server/ble_server.h b/esphome/components/esp32_ble_server/ble_server.h index b5973ed099..48005b1346 100644 --- a/esphome/components/esp32_ble_server/ble_server.h +++ b/esphome/components/esp32_ble_server/ble_server.h @@ -13,6 +13,7 @@ #include #include #include +#include #ifdef USE_ESP32 @@ -24,18 +25,7 @@ namespace esp32_ble_server { using namespace esp32_ble; using namespace bytebuffer; -namespace BLEServerEvt { -enum EmptyEvt { - ON_CONNECT, - ON_DISCONNECT, -}; -} // namespace BLEServerEvt - -class BLEServer : public Component, - public GATTsEventHandler, - public BLEStatusEventHandler, - public Parented, - public EventEmitter { +class BLEServer : public Component, public GATTsEventHandler, public BLEStatusEventHandler, public Parented { public: void setup() override; void loop() override; @@ -65,7 +55,25 @@ class BLEServer : public Component, void ble_before_disabled_event_handler() override; + // Direct callback registration - supports multiple callbacks + void on_connect(std::function &&callback) { + this->callbacks_.push_back({CallbackType::ON_CONNECT, std::move(callback)}); + } + void on_disconnect(std::function &&callback) { + this->callbacks_.push_back({CallbackType::ON_DISCONNECT, std::move(callback)}); + } + protected: + enum class CallbackType : uint8_t { + ON_CONNECT, + ON_DISCONNECT, + }; + + struct CallbackEntry { + CallbackType type; + std::function callback; + }; + struct ServiceEntry { ESPBTUUID uuid; uint8_t inst_id; @@ -76,6 +84,9 @@ class BLEServer : public Component, void add_client_(uint16_t conn_id) { this->clients_.insert(conn_id); } void remove_client_(uint16_t conn_id) { this->clients_.erase(conn_id); } + void dispatch_callbacks_(CallbackType type, uint16_t conn_id); + + std::vector callbacks_; std::vector manufacturer_data_{}; esp_gatt_if_t gatts_if_{0}; diff --git a/esphome/components/esp32_ble_server/ble_server_automations.cpp b/esphome/components/esp32_ble_server/ble_server_automations.cpp index 67e00a9bfe..0761de994a 100644 --- a/esphome/components/esp32_ble_server/ble_server_automations.cpp +++ b/esphome/components/esp32_ble_server/ble_server_automations.cpp @@ -14,9 +14,10 @@ Trigger, uint16_t> *BLETriggers::create_characteristic_on_w BLECharacteristic *characteristic) { Trigger, uint16_t> *on_write_trigger = // NOLINT(cppcoreguidelines-owning-memory) new Trigger, uint16_t>(); - characteristic->EventEmitter, uint16_t>::on( - BLECharacteristicEvt::VectorEvt::ON_WRITE, - [on_write_trigger](const std::vector &data, uint16_t id) { on_write_trigger->trigger(data, id); }); + characteristic->on_write([on_write_trigger](std::span data, uint16_t id) { + // Convert span to vector for trigger + on_write_trigger->trigger(std::vector(data.begin(), data.end()), id); + }); return on_write_trigger; } #endif @@ -25,9 +26,10 @@ Trigger, uint16_t> *BLETriggers::create_characteristic_on_w Trigger, uint16_t> *BLETriggers::create_descriptor_on_write_trigger(BLEDescriptor *descriptor) { Trigger, uint16_t> *on_write_trigger = // NOLINT(cppcoreguidelines-owning-memory) new Trigger, uint16_t>(); - descriptor->on( - BLEDescriptorEvt::VectorEvt::ON_WRITE, - [on_write_trigger](const std::vector &data, uint16_t id) { on_write_trigger->trigger(data, id); }); + descriptor->on_write([on_write_trigger](std::span data, uint16_t id) { + // Convert span to vector for trigger + on_write_trigger->trigger(std::vector(data.begin(), data.end()), id); + }); return on_write_trigger; } #endif @@ -35,8 +37,7 @@ Trigger, uint16_t> *BLETriggers::create_descriptor_on_write #ifdef USE_ESP32_BLE_SERVER_ON_CONNECT Trigger *BLETriggers::create_server_on_connect_trigger(BLEServer *server) { Trigger *on_connect_trigger = new Trigger(); // NOLINT(cppcoreguidelines-owning-memory) - server->on(BLEServerEvt::EmptyEvt::ON_CONNECT, - [on_connect_trigger](uint16_t conn_id) { on_connect_trigger->trigger(conn_id); }); + server->on_connect([on_connect_trigger](uint16_t conn_id) { on_connect_trigger->trigger(conn_id); }); return on_connect_trigger; } #endif @@ -44,38 +45,22 @@ Trigger *BLETriggers::create_server_on_connect_trigger(BLEServer *serv #ifdef USE_ESP32_BLE_SERVER_ON_DISCONNECT Trigger *BLETriggers::create_server_on_disconnect_trigger(BLEServer *server) { Trigger *on_disconnect_trigger = new Trigger(); // NOLINT(cppcoreguidelines-owning-memory) - server->on(BLEServerEvt::EmptyEvt::ON_DISCONNECT, - [on_disconnect_trigger](uint16_t conn_id) { on_disconnect_trigger->trigger(conn_id); }); + server->on_disconnect([on_disconnect_trigger](uint16_t conn_id) { on_disconnect_trigger->trigger(conn_id); }); return on_disconnect_trigger; } #endif #ifdef USE_ESP32_BLE_SERVER_SET_VALUE_ACTION void BLECharacteristicSetValueActionManager::set_listener(BLECharacteristic *characteristic, - EventEmitterListenerID listener_id, const std::function &pre_notify_listener) { // Find and remove existing listener for this characteristic auto *existing = this->find_listener_(characteristic); if (existing != nullptr) { - // Remove the previous listener - characteristic->EventEmitter::off(BLECharacteristicEvt::EmptyEvt::ON_READ, - existing->listener_id); - // Remove the pre-notify listener - this->off(BLECharacteristicSetValueActionEvt::PRE_NOTIFY, existing->pre_notify_listener_id); // Remove from vector this->remove_listener_(characteristic); } - // Create a new listener for the pre-notify event - EventEmitterListenerID pre_notify_listener_id = - this->on(BLECharacteristicSetValueActionEvt::PRE_NOTIFY, - [pre_notify_listener, characteristic](const BLECharacteristic *evt_characteristic) { - // Only call the pre-notify listener if the characteristic is the one we are interested in - if (characteristic == evt_characteristic) { - pre_notify_listener(); - } - }); // Save the entry to the vector - this->listeners_.push_back({characteristic, listener_id, pre_notify_listener_id}); + this->listeners_.push_back({characteristic, pre_notify_listener}); } BLECharacteristicSetValueActionManager::ListenerEntry *BLECharacteristicSetValueActionManager::find_listener_( diff --git a/esphome/components/esp32_ble_server/ble_server_automations.h b/esphome/components/esp32_ble_server/ble_server_automations.h index 8fcb5842c3..543b1153fc 100644 --- a/esphome/components/esp32_ble_server/ble_server_automations.h +++ b/esphome/components/esp32_ble_server/ble_server_automations.h @@ -4,7 +4,6 @@ #include "ble_characteristic.h" #include "ble_descriptor.h" -#include "esphome/components/event_emitter/event_emitter.h" #include "esphome/core/automation.h" #include @@ -18,10 +17,6 @@ namespace esp32_ble_server { namespace esp32_ble_server_automations { using namespace esp32_ble; -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 { public: @@ -41,38 +36,29 @@ class BLETriggers { }; #ifdef USE_ESP32_BLE_SERVER_SET_VALUE_ACTION -enum BLECharacteristicSetValueActionEvt { - PRE_NOTIFY, -}; - // Class to make sure only one BLECharacteristicSetValueAction is active at a time for each characteristic -class BLECharacteristicSetValueActionManager - : public EventEmitter { +class BLECharacteristicSetValueActionManager { public: // Singleton pattern static BLECharacteristicSetValueActionManager *get_instance() { static BLECharacteristicSetValueActionManager instance; return &instance; } - void set_listener(BLECharacteristic *characteristic, EventEmitterListenerID listener_id, - const std::function &pre_notify_listener); - EventEmitterListenerID get_listener(BLECharacteristic *characteristic) { + void set_listener(BLECharacteristic *characteristic, const std::function &pre_notify_listener); + bool has_listener(BLECharacteristic *characteristic) { return this->find_listener_(characteristic) != nullptr; } + void emit_pre_notify(BLECharacteristic *characteristic) { for (const auto &entry : this->listeners_) { if (entry.characteristic == characteristic) { - return entry.listener_id; + entry.pre_notify_listener(); + break; } } - return INVALID_LISTENER_ID; - } - void emit_pre_notify(BLECharacteristic *characteristic) { - this->emit_(BLECharacteristicSetValueActionEvt::PRE_NOTIFY, characteristic); } private: struct ListenerEntry { BLECharacteristic *characteristic; - EventEmitterListenerID listener_id; - EventEmitterListenerID pre_notify_listener_id; + std::function pre_notify_listener; }; std::vector listeners_; @@ -87,24 +73,22 @@ template class BLECharacteristicSetValueAction : public Actionset_buffer(buffer.get_data()); } void play(Ts... x) override { // If the listener is already set, do nothing - if (BLECharacteristicSetValueActionManager::get_instance()->get_listener(this->parent_) == this->listener_id_) + if (BLECharacteristicSetValueActionManager::get_instance()->has_listener(this->parent_)) return; // Set initial value this->parent_->set_value(this->buffer_.value(x...)); // Set the listener for read events - this->listener_id_ = this->parent_->EventEmitter::on( - BLECharacteristicEvt::EmptyEvt::ON_READ, [this, x...](uint16_t id) { - // Set the value of the characteristic every time it is read - this->parent_->set_value(this->buffer_.value(x...)); - }); + this->parent_->on_read([this, x...](uint16_t id) { + // Set the value of the characteristic every time it is read + this->parent_->set_value(this->buffer_.value(x...)); + }); // Set the listener in the global manager so only one BLECharacteristicSetValueAction is set for each characteristic BLECharacteristicSetValueActionManager::get_instance()->set_listener( - this->parent_, this->listener_id_, [this, x...]() { this->parent_->set_value(this->buffer_.value(x...)); }); + this->parent_, [this, x...]() { this->parent_->set_value(this->buffer_.value(x...)); }); } protected: BLECharacteristic *parent_; - EventEmitterListenerID listener_id_; }; #endif // USE_ESP32_BLE_SERVER_SET_VALUE_ACTION diff --git a/esphome/components/esp32_improv/esp32_improv_component.cpp b/esphome/components/esp32_improv/esp32_improv_component.cpp index ca08ff0cca..f773083890 100644 --- a/esphome/components/esp32_improv/esp32_improv_component.cpp +++ b/esphome/components/esp32_improv/esp32_improv_component.cpp @@ -38,8 +38,7 @@ void ESP32ImprovComponent::setup() { }); } #endif - global_ble_server->on(BLEServerEvt::EmptyEvt::ON_DISCONNECT, - [this](uint16_t conn_id) { this->set_error_(improv::ERROR_NONE); }); + global_ble_server->on_disconnect([this](uint16_t conn_id) { this->set_error_(improv::ERROR_NONE); }); // Start with loop disabled - will be enabled by start() when needed this->disable_loop(); @@ -57,12 +56,11 @@ void ESP32ImprovComponent::setup_characteristics() { this->error_->add_descriptor(error_descriptor); this->rpc_ = this->service_->create_characteristic(improv::RPC_COMMAND_UUID, BLECharacteristic::PROPERTY_WRITE); - this->rpc_->EventEmitter, uint16_t>::on( - BLECharacteristicEvt::VectorEvt::ON_WRITE, [this](const std::vector &data, uint16_t id) { - if (!data.empty()) { - this->incoming_data_.insert(this->incoming_data_.end(), data.begin(), data.end()); - } - }); + this->rpc_->on_write([this](std::span data, uint16_t id) { + if (!data.empty()) { + this->incoming_data_.insert(this->incoming_data_.end(), data.begin(), data.end()); + } + }); BLEDescriptor *rpc_descriptor = new BLE2902(); this->rpc_->add_descriptor(rpc_descriptor); diff --git a/esphome/components/event_emitter/__init__.py b/esphome/components/event_emitter/__init__.py deleted file mode 100644 index fcbbf26f02..0000000000 --- a/esphome/components/event_emitter/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -CODEOWNERS = ["@Rapsssito"] - -# Allows event_emitter to be configured in yaml, to allow use of the C++ api. - -CONFIG_SCHEMA = {} diff --git a/esphome/components/event_emitter/event_emitter.h b/esphome/components/event_emitter/event_emitter.h deleted file mode 100644 index 74afde03c0..0000000000 --- a/esphome/components/event_emitter/event_emitter.h +++ /dev/null @@ -1,117 +0,0 @@ -#pragma once -#include -#include -#include - -#include "esphome/core/log.h" - -namespace esphome { -namespace event_emitter { - -using EventEmitterListenerID = uint32_t; -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) -// and a list of arguments. Supports multiple listeners for each event. -template class EventEmitter { - public: - EventEmitterListenerID on(EvtType event, std::function listener) { - EventEmitterListenerID listener_id = this->get_next_id_(); - - // Find or create event entry - EventEntry *entry = this->find_or_create_event_(event); - entry->listeners.push_back({listener_id, listener}); - - return listener_id; - } - - void off(EvtType event, EventEmitterListenerID id) { - EventEntry *entry = this->find_event_(event); - if (entry == nullptr) - return; - - // 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()) { - this->remove_event_(event); - } - return; - } - } - } - - protected: - void emit_(EvtType event, Args... args) { - EventEntry *entry = this->find_event_(event); - if (entry == nullptr) - return; - - // Call all listeners for this event - for (const auto &listener : entry->listeners) { - listener.callback(args...); - } - } - - private: - struct Listener { - EventEmitterListenerID id; - std::function callback; - }; - - struct EventEntry { - EvtType event; - std::vector listeners; - }; - - EventEmitterListenerID get_next_id_() { - // Simple incrementing ID, wrapping around at max - EventEmitterListenerID next_id = (this->current_id_ + 1); - if (next_id == INVALID_LISTENER_ID) { - next_id = 1; - } - this->current_id_ = next_id; - return this->current_id_; - } - - EventEntry *find_event_(EvtType event) { - for (auto &entry : this->events_) { - if (entry.event == event) { - return &entry; - } - } - return nullptr; - } - - EventEntry *find_or_create_event_(EvtType event) { - EventEntry *entry = this->find_event_(event); - if (entry != nullptr) - return entry; - - // Create new event entry - this->events_.push_back({event, {}}); - return &this->events_.back(); - } - - void remove_event_(EvtType event) { - for (auto it = this->events_.begin(); it != this->events_.end(); ++it) { - if (it->event == event) { - // Swap with last and pop - *it = this->events_.back(); - this->events_.pop_back(); - return; - } - } - } - - std::vector events_; - EventEmitterListenerID current_id_ = 0; -}; - -} // namespace event_emitter -} // namespace esphome