From eeff69d50bc65d1faa51d5f5b4d3503e13c08f87 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 26 Sep 2025 10:14:34 -0500 Subject: [PATCH] [event_emitter] Replace unordered_map with vector - saves 2.6KB flash, 2.3x faster --- .../components/event_emitter/event_emitter.h | 97 +++++++++++++++---- 1 file changed, 76 insertions(+), 21 deletions(-) diff --git a/esphome/components/event_emitter/event_emitter.h b/esphome/components/event_emitter/event_emitter.h index 3876a2cc14..a810187922 100644 --- a/esphome/components/event_emitter/event_emitter.h +++ b/esphome/components/event_emitter/event_emitter.h @@ -1,5 +1,4 @@ #pragma once -#include #include #include #include @@ -10,6 +9,7 @@ namespace esphome { namespace event_emitter { using EventEmitterListenerID = uint32_t; +static constexpr EventEmitterListenerID INVALID_LISTENER_ID = 0; void raise_event_emitter_full_error(); // EventEmitter class that can emit events with a specific name (it is highly recommended to use an enum class for this) @@ -17,45 +17,100 @@ void raise_event_emitter_full_error(); template class EventEmitter { public: EventEmitterListenerID on(EvtType event, std::function listener) { - EventEmitterListenerID listener_id = get_next_id_(event); - listeners_[event][listener_id] = listener; + EventEmitterListenerID listener_id = get_next_id_(); + + // Find or create event entry + EventEntry *entry = find_or_create_event_(event); + entry->listeners.push_back({listener_id, listener}); + return listener_id; } void off(EvtType event, EventEmitterListenerID id) { - if (listeners_.count(event) == 0) + EventEntry *entry = find_event_(event); + if (entry == nullptr) 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: void emit_(EvtType event, Args... args) { - if (listeners_.count(event) == 0) + EventEntry *entry = find_event_(event); + if (entry == nullptr) 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) { - // Check if the map is full - if (listeners_[event].size() == std::numeric_limits::max()) { - // Raise an error if the map is full - raise_event_emitter_full_error(); - off(event, 0); - return 0; + private: + struct Listener { + EventEmitterListenerID id; + std::function callback; + }; + + struct EventEntry { + EvtType event; + std::vector 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::max(); - while (listeners_[event].count(next_id) > 0) { - next_id = (next_id + 1) % std::numeric_limits::max(); + return nullptr; + } + + 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; return current_id_; } - private: - std::unordered_map>> listeners_; + std::vector events_; EventEmitterListenerID current_id_ = 0; };