mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-31 15:12:06 +00:00 
			
		
		
		
	[event_emitter] Replace unordered_map with vector - saves 2.6KB flash, 2.3x faster
This commit is contained in:
		| @@ -1,5 +1,4 @@ | ||||
| #pragma once | ||||
| #include <unordered_map> | ||||
| #include <vector> | ||||
| #include <functional> | ||||
| #include <limits> | ||||
| @@ -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<typename EvtType, typename... Args> class EventEmitter { | ||||
|  public: | ||||
|   EventEmitterListenerID on(EvtType event, std::function<void(Args...)> 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<EventEmitterListenerID>::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<void(Args...)> callback; | ||||
|   }; | ||||
|  | ||||
|   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(); | ||||
|     while (listeners_[event].count(next_id) > 0) { | ||||
|       next_id = (next_id + 1) % std::numeric_limits<EventEmitterListenerID>::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<EvtType, std::unordered_map<EventEmitterListenerID, std::function<void(Args...)>>> listeners_; | ||||
|   std::vector<EventEntry> events_; | ||||
|   EventEmitterListenerID current_id_ = 0; | ||||
| }; | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user