Helios Engine 0.1.0
A modular ECS based data-oriented C++23 game engine
 
Loading...
Searching...
No Matches
event_manager.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <helios/core_pch.hpp>
4
9
10#include <cstddef>
11#include <iterator>
12#include <ranges>
13#include <string_view>
14#include <type_traits>
15#include <unordered_map>
16#include <utility>
17#include <vector>
18
19namespace helios::ecs::details {
20
21/**
22 * @brief Metadata for registered events.
23 * @details Stores information about event lifecycle management and registration.
24 */
26 EventTypeId type_id = 0; ///< Unique type identifier for the event
27 std::string_view name; ///< Human-readable name of the event
29 size_t frame_registered = 0; ///< Frame number when event was registered
30};
31
32/**
33 * @brief Manages event lifecycle with double buffering and registration tracking.
34 * @details Implements Bevy-style event management with:
35 * - Double buffering: events persist for one full update cycle
36 * - Explicit registration: events must be registered before use
37 * - Automatic clearing: events are cleared after their lifecycle expires
38 * - Manual control: users can opt-out of auto-clearing for custom events
39 *
40 * Event Lifecycle:
41 * - Frame N: Events written to current queue
42 * - Frame N+1: Events readable from previous queue (after swap)
43 * - Frame N+2: Events cleared from previous queue
44 *
45 * @note Not thread-safe.
46 */
48public:
49 EventManager() = default;
50 EventManager(const EventManager&) = delete;
53
56
57 /**
58 * @brief Clears all events and registration data.
59 */
60 void Clear() noexcept;
61
62 /**
63 * @brief Clears all event queues without removing registration data.
64 * @details Useful for manually clearing events while preserving event types.
65 * Events can still be written/read after calling this method.
66 * Registration metadata and frame counter are preserved.
67 */
69
70 /**
71 * @brief Manually clears events of a specific type from current and previous queues.
72 * @details Should be used for manually-managed events (clear_policy = kManual).
73 * Can also be used as an override for automatic events if needed.
74 * @warning Triggers assertion if event is not registered.
75 * @tparam T Event type
76 */
78 void ManualClear();
79
80 /**
81 * @brief Updates event lifecycle - swaps buffers and clears old events.
82 * @details Should be called at the end of each update cycle.
83 * Clears previous queue, moves current to previous, creates new current queue.
84 */
85 void Update();
86
87 /**
88 * @brief Registers an event type for use.
89 * @details Uses the event's GetClearPolicy() if available, otherwise defaults to kAutomatic.
90 * @warning Triggers assertion if event is already registered.
91 * @tparam T Event type
92 */
94 void RegisterEvent();
95
96 /**
97 * @brief Registers multiple event types for use.
98 * @details Each event uses its own GetClearPolicy() if available.
99 * @warning Triggers assertion if any event is already registered.
100 * @tparam Events Event types to register
101 */
103 requires(sizeof...(Events) > 1)
105 (RegisterEvent<Events>(), ...);
106 }
107
108 /**
109 * @brief Writes a single event to the current queue.
110 * @warning Triggers assertion if event is not registered.
111 * @tparam T Event type
112 * @param event Event to write
113 */
114 template <EventTrait T>
115 void Write(const T& event);
116
117 /**
118 * @brief Writes multiple events to the queue in bulk.
119 * @warning Triggers assertion if event type does not exist.
120 * @tparam R Range of events
121 * @param events Range of events to store
122 */
123 template <std::ranges::sized_range R>
125 void WriteBulk(const R& events);
126
127 /**
128 * @brief Reads all events of a specific type from current and previous queues.
129 * @details Events from the current frame and previous frame are returned,
130 * implementing double buffering for reliable event delivery.
131 * @warning Triggers assertion if event is not registered.
132 * @tparam T Event type
133 * @return Vector containing all events of type T from both buffers
134 */
135 template <EventTrait T>
136 [[nodiscard]] auto Read() const -> std::vector<T>;
137
138 /**
139 * @brief Reads events of a specific type into a provided output iterator.
140 * @details Reads from current and previous queues without intermediate allocations.
141 * @warning Triggers assertion if event is not registered.
142 * @tparam T Event type
143 * @tparam OutIt Output iterator type
144 * @param out Output iterator to write events into
145 */
148 void ReadInto(OutIt out) const;
149
150 /**
151 * @brief Merges events from another EventQueue into the current queue.
152 * @param other Event queue to merge from (typically from SystemLocalStorage)
153 */
154 void Merge(EventQueue& other);
155
156 /**
157 * @brief Checks if an event type is registered.
158 * @tparam T Event type
159 * @return True if event is registered, false otherwise
160 */
163
164 /**
165 * @brief Checks if both queues are empty.
166 * @return True if no events exist in either queue, false otherwise
167 */
168 [[nodiscard]] bool Empty() const noexcept { return current_queue_.Empty() && previous_queue_.Empty(); }
169
170 /**
171 * @brief Checks if events of a specific type exist in either queue.
172 * @tparam T Event type
173 * @return True if events of type T exist, false otherwise
174 */
175 template <EventTrait T>
176 [[nodiscard]] bool HasEvents() const {
177 return current_queue_.HasEvents<T>() || previous_queue_.HasEvents<T>();
178 }
179
180 /**
181 * @brief Gets metadata for a registered event.
182 * @tparam T Event type
183 * @return Pointer to metadata, or nullptr if not registered
184 */
185 template <EventTrait T>
187
188 /**
189 * @brief Gets the current frame number.
190 * @return Current frame counter value
191 */
192 [[nodiscard]] size_t GetCurrentFrame() const noexcept { return current_frame_; }
193
194 /**
195 * @brief Gets the number of registered event types.
196 * @return Count of registered events
197 */
198 [[nodiscard]] size_t RegisteredEventCount() const noexcept { return registered_events_.size(); }
199
200 /**
201 * @brief Gets reference to current event queue (for testing/debugging).
202 * @return Const reference to current queue
203 */
204 [[nodiscard]] const EventQueue& GetCurrentQueue() const noexcept { return current_queue_; }
205
206 /**
207 * @brief Gets reference to previous event queue (for testing/debugging).
208 * @return Const reference to previous queue
209 */
210 [[nodiscard]] const EventQueue& GetPreviousQueue() const noexcept { return previous_queue_; }
211
212private:
213 std::unordered_map<EventTypeId, EventMetadata> registered_events_; ///< Metadata for registered events
214
215 EventQueue current_queue_; ///< Events written in current frame
216 EventQueue previous_queue_; ///< Events from previous frame (readable for double buffering)
217
218 size_t current_frame_ = 0; ///< Current frame counter for lifecycle tracking
219};
220
222 registered_events_.clear();
223 current_queue_.Clear();
224 previous_queue_.Clear();
225 current_frame_ = 0;
226}
227
229 current_queue_.Clear();
230 previous_queue_.Clear();
231}
232
233inline void EventManager::Update() {
234 // Double-queue double buffering implementation with selective clearing:
235 // 1. Selectively clear auto_clear events from previous_queue_ (events that are now 2 frames old)
236 // 2. Merge current queue into previous queue (preserving non-auto_clear events)
237 // 3. Create new empty current queue for next frame
238 //
239 // This ensures:
240 // - auto_clear=true events persist for exactly 1 full update cycle
241 // - auto_clear=false events persist indefinitely until manually cleared
242 //
243 // Frame lifecycle:
244 // - Frame N: Event written to current_queue_
245 // - Frame N+1: Event readable from previous_queue_ (after merge)
246 // - Frame N+2: auto_clear=true events cleared, auto_clear=false events remain
247
248 // Step 1: Clear automatic events from previous queue (events that are 2+ frames old)
249 for (const auto& [type_id, metadata] : registered_events_) {
250 if (metadata.clear_policy == EventClearPolicy::kAutomatic) {
251 previous_queue_.ClearByTypeId(type_id);
252 }
253 }
254
255 // Step 2: Merge current queue into previous queue
256 // This preserves non-auto_clear events from previous queue while adding current frame's events
257 previous_queue_.Merge(current_queue_);
258
259 // Step 3: Clear current queue for next frame (clear each registered type individually)
260 for (const auto& [type_id, metadata] : registered_events_) {
261 current_queue_.ClearByTypeId(type_id);
262 }
263
264 ++current_frame_;
265}
266
267template <EventTrait T>
269 constexpr EventTypeId type_id = EventTypeIdOf<T>();
270 constexpr std::string_view name = EventNameOf<T>();
271 constexpr EventClearPolicy clear_policy = EventClearPolicyOf<T>();
272
273 HELIOS_ASSERT(!registered_events_.contains(type_id), "Failed to register event '{}': Event already registered!",
274 name);
275
276 registered_events_.emplace(
277 type_id, EventMetadata{
278 .type_id = type_id, .name = name, .clear_policy = clear_policy, .frame_registered = current_frame_});
279
280 current_queue_.Register<T>();
281 previous_queue_.Register<T>();
282
283 constexpr std::string_view policy_str = (clear_policy == EventClearPolicy::kAutomatic) ? "automatic" : "manual";
284 HELIOS_DEBUG("Registered event '{}' (clear_policy: {})", name, policy_str);
285}
286
287template <EventTrait T>
288inline void EventManager::Write(const T& event) {
289 HELIOS_ASSERT(registered_events_.contains(EventTypeIdOf<T>()), "Failed to write event '{}': Event is not registered!",
291
292 current_queue_.Write(event);
293}
294
295template <std::ranges::sized_range R>
297inline void EventManager::WriteBulk(const R& events) {
298 using EventType = std::ranges::range_value_t<R>;
299 HELIOS_ASSERT(registered_events_.contains(EventTypeIdOf<EventType>()),
300 "Failed to write bulk event '{}': Event is not registered!", EventNameOf<EventType>());
301
302 current_queue_.WriteBulk(events);
303}
304
305template <EventTrait T>
306inline auto EventManager::Read() const -> std::vector<T> {
307 HELIOS_ASSERT(registered_events_.contains(EventTypeIdOf<T>()), "Failed to read events '{}': Event is not registered!",
309
310 std::vector<T> result;
311 result.reserve(previous_queue_.TypeCount() + current_queue_.TypeCount());
312
313 // Read from previous queue (events from last frame)
314 previous_queue_.ReadInto<T>(std::back_inserter(result));
315
316 // Read from current queue (events from this frame)
317 current_queue_.ReadInto<T>(std::back_inserter(result));
318
319 return result;
320}
321
322template <EventTrait T, typename OutIt>
323 requires std::output_iterator<std::remove_reference_t<OutIt>, T>
324inline void EventManager::ReadInto(OutIt out) const {
325 HELIOS_ASSERT(registered_events_.contains(EventTypeIdOf<T>()),
326 "Failed to read events '{}' into: Event is not registered!", EventNameOf<T>());
327
328 previous_queue_.ReadInto<T>(out);
329 current_queue_.ReadInto<T>(std::move(out));
330}
331
332inline void EventManager::Merge(EventQueue& other) {
333 current_queue_.Merge(other);
334 other.Clear();
335}
336
337template <EventTrait T>
339 HELIOS_ASSERT(registered_events_.contains(EventTypeIdOf<T>()),
340 "Failed to manual clear events '{}': Event is not registered!", EventNameOf<T>());
341
342 // Note: We allow manual clearing for all events (automatic or manual) as an override
343 // This can be useful for debugging or special cases
344
345 current_queue_.Clear<T>();
346 previous_queue_.Clear<T>();
347}
348
349template <EventTrait T>
351 constexpr EventTypeId type_id = EventTypeIdOf<T>();
352 const auto it = registered_events_.find(type_id);
353 return it != registered_events_.end() ? &it->second : nullptr;
354}
355
356template <EventTrait T>
358 constexpr EventTypeId type_id = EventTypeIdOf<T>();
359 return registered_events_.contains(type_id);
360}
361
362} // namespace helios::ecs::details
#define HELIOS_ASSERT(condition,...)
Assertion macro that aborts execution in debug builds.
Definition assert.hpp:140
Manages event lifecycle with double buffering and registration tracking.
void ManualClear()
Manually clears events of a specific type from current and previous queues.
void ReadInto(OutIt out) const
Reads events of a specific type into a provided output iterator.
void Clear() noexcept
Clears all events and registration data.
void ClearAllQueues() noexcept
Clears all event queues without removing registration data.
auto Read() const -> std::vector< T >
Reads all events of a specific type from current and previous queues.
void Update()
Updates event lifecycle - swaps buffers and clears old events.
bool HasEvents() const
Checks if events of a specific type exist in either queue.
const EventQueue & GetPreviousQueue() const noexcept
Gets reference to previous event queue (for testing/debugging).
void RegisterEvents()
Registers multiple event types for use.
const EventQueue & GetCurrentQueue() const noexcept
Gets reference to current event queue (for testing/debugging).
bool IsRegistered() const noexcept
Checks if an event type is registered.
bool Empty() const noexcept
Checks if both queues are empty.
size_t GetCurrentFrame() const noexcept
Gets the current frame number.
EventManager(const EventManager &)=delete
void Write(const T &event)
Writes a single event to the current queue.
const EventMetadata * GetMetadata() const noexcept
Gets metadata for a registered event.
EventManager(EventManager &&) noexcept=default
void WriteBulk(const R &events)
Writes multiple events to the queue in bulk.
void RegisterEvent()
Registers an event type for use.
void Merge(EventQueue &other)
Merges events from another EventQueue into the current queue.
size_t RegisteredEventCount() const noexcept
Gets the number of registered event types.
Queue for managing multiple event types.
void Merge(EventQueue &other)
Merges events from another EventQueue into this one.
void WriteBulk(const R &events)
Writes multiple events to the queue in bulk.
void ClearByTypeId(EventTypeId type_id)
Clears events of a specific type by type ID (runtime).
bool Empty() const noexcept
Checks if the queue is empty.
void Clear() noexcept
Clears all events from the queue and removes registrations.
void Register()
Registers an event type with the queue.
bool HasEvents() const
Checks if events of a specific type exist in the queue.
void ReadInto(OutIt out) const
Reads events of a specific type into a provided output iterator.
void Write(const T &event)
Writes a single event to the queue.
size_t TypeCount() const noexcept
Gets the number of event types stored.
Concept for valid event types.
Definition event.hpp:44
#define HELIOS_DEBUG(...)
Definition logger.hpp:667
size_t EventTypeId
Type ID for events.
Definition event.hpp:18
EventClearPolicy
Policy for event clearing behavior.
Definition event.hpp:23
@ kAutomatic
Events are automatically cleared after double buffer cycle.
BasicQuery< World, Allocator, Components... > Query
Type alias for query with mutable world access.
Definition query.hpp:2481
STL namespace.
Metadata for registered events.
EventClearPolicy clear_policy
Event clearing policy.
size_t frame_registered
Frame number when event was registered.
EventTypeId type_id
Unique type identifier for the event.
std::string_view name
Human-readable name of the event.