mirror of
https://github.com/esphome/esphome.git
synced 2025-10-04 02:52:22 +01:00
[event_emitter] Replace unordered_map with vector - saves 2.6KB flash, 2.3x faster (#10900)
This commit is contained in:
@@ -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
|
|
@@ -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 = this->get_next_id_();
|
||||||
listeners_[event][listener_id] = listener;
|
|
||||||
|
// Find or create event entry
|
||||||
|
EventEntry *entry = this->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 = this->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()) {
|
||||||
|
this->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 = this->find_event_(event);
|
||||||
|
if (entry == nullptr)
|
||||||
return;
|
return;
|
||||||
for (const auto &listener : listeners_[event]) {
|
|
||||||
listener.second(args...);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
EventEmitterListenerID get_next_id_(EvtType event) {
|
// Call all listeners for this event
|
||||||
// Check if the map is full
|
for (const auto &listener : entry->listeners) {
|
||||||
if (listeners_[event].size() == std::numeric_limits<EventEmitterListenerID>::max()) {
|
listener.callback(args...);
|
||||||
// Raise an error if the map is full
|
|
||||||
raise_event_emitter_full_error();
|
|
||||||
off(event, 0);
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
// Get the next ID for the given event.
|
|
||||||
EventEmitterListenerID next_id = (current_id_ + 1) % std::numeric_limits<EventEmitterListenerID>::max();
|
|
||||||
while (listeners_[event].count(next_id) > 0) {
|
|
||||||
next_id = (next_id + 1) % std::numeric_limits<EventEmitterListenerID>::max();
|
|
||||||
}
|
|
||||||
current_id_ = next_id;
|
|
||||||
return current_id_;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::unordered_map<EvtType, std::unordered_map<EventEmitterListenerID, std::function<void(Args...)>>> listeners_;
|
struct Listener {
|
||||||
|
EventEmitterListenerID id;
|
||||||
|
std::function<void(Args...)> callback;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct EventEntry {
|
||||||
|
EvtType event;
|
||||||
|
std::vector<Listener> 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<EventEntry> events_;
|
||||||
EventEmitterListenerID current_id_ = 0;
|
EventEmitterListenerID current_id_ = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user