Helios Engine
A modular ECS based data-oriented C++23 game engine framework
Loading...
Searching...
No Matches
queue.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <helios/assert.hpp>
7
8#include <algorithm>
9#include <memory_resource>
10#include <ranges>
11#include <span>
12#include <type_traits>
13
14namespace helios::ecs {
15
16/**
17 * @brief Queue for managing multiple message types using type-erased contiguous
18 * storage.
19 * @details Messages are stored in a `MultiTypeBuffer`, which provides per-type
20 * contiguous storage.
21 * @note Not thread-safe.
22 * @tparam Allocator Allocator type for internal storage (default:
23 * `std::allocator<std::byte>`)
24 */
25template <typename Allocator = std::allocator<std::byte>>
27private:
28 using MessageStorage =
30 Allocator>;
31
32public:
35
36 constexpr MessageQueue() = default;
37
38 /**
39 * @brief Constructs an `MessageQueue` with a custom allocator.
40 * @param alloc Allocator instance
41 */
42 explicit constexpr MessageQueue(const allocator_type& alloc)
43 : messages_(alloc) {}
44
45 /**
46 * @brief Constructs a `MessageQueue` from a PMR memory resource.
47 * @details Enabled only when `allocator_type` is constructible from
48 * `std::pmr::memory_resource*`.
49 * @param resource Memory resource used to construct allocator
50 */
51 explicit constexpr MessageQueue(std::pmr::memory_resource* resource)
52 requires std::constructible_from<allocator_type, std::pmr::memory_resource*>
53 : MessageQueue(allocator_type{resource}) {}
54
55 MessageQueue(std::nullptr_t) = delete;
56
57 constexpr MessageQueue(const MessageQueue&) = default;
58 constexpr MessageQueue(MessageQueue&&) noexcept = default;
59 constexpr ~MessageQueue() = default;
60
61 constexpr MessageQueue& operator=(const MessageQueue&) = default;
62 constexpr MessageQueue& operator=(MessageQueue&&) noexcept = default;
63
64 /**
65 * @brief Registers an message type with the queue.
66 * @tparam T Message type
67 */
68 template <MessageTrait T>
69 constexpr void Register() {
70 messages_.template Ensure<T>();
71 }
72
73 /**
74 * @brief Ensures storage exists for the given runtime type index.
75 * @details Used when the concrete type is not available at call-site (e.g.,
76 * during Update re-registration). If the type index is already registered,
77 * this is a no-op.
78 * @param type_index Runtime type index
79 */
80 constexpr void Register(MessageTypeIndex type_index) {
81 messages_.Ensure(type_index);
82 }
83
84 /// @brief Clears all messages from the queue maintaining all registered
85 /// types.
86 constexpr void ClearAll() noexcept { messages_.ClearAll(); }
87
88 /**
89 * @brief Clears messages of a specific type.
90 * @tparam T Message type
91 */
92 template <MessageTrait T>
93 constexpr void Clear() noexcept {
94 messages_.template Clear<T>();
95 }
96
97 /**
98 * @brief Clears messages of a specific type by runtime type index.
99 * @param type_index Type index of messages to clear
100 */
101 constexpr void Clear(MessageTypeIndex type_index) noexcept {
102 messages_.Clear(type_index);
103 }
104
105 /// @brief Resets the queue by clearing all messages and unregistering all
106 /// types.
107 constexpr void ResetAll() noexcept { messages_.ResetAll(); }
108
109 /**
110 * @brief Resets a specific message type by clearing its messages and
111 * unregistering the type.
112 * @tparam T Message type
113 */
114 template <MessageTrait T>
115 constexpr void Reset() noexcept {
116 messages_.template Reset<T>();
117 }
118
119 /**
120 * @brief Merges messages from another MessageQueue into this one.
121 * @details For each type in `other`, messages are appended to the
122 * corresponding storage in this queue. After merging, `other` is left in a
123 * valid but empty state.
124 * @tparam OtherAllocator Allocator template of the other queue
125 * @param other MessageQueue to merge from
126 */
127 template <typename OtherAllocator>
128 constexpr void Merge(const MessageQueue<OtherAllocator>& other) {
129 messages_.Merge(other.messages_);
130 }
131
132 /**
133 * @brief Merges messages from another MessageQueue into this one.
134 * @details Rvalue overload that consumes the source queue.
135 * @tparam OtherAllocator Allocator template of the other queue
136 * @param other MessageQueue to merge from
137 */
138 template <typename OtherAllocator>
139 constexpr void Merge(MessageQueue<OtherAllocator>&& other) {
140 messages_.Merge(std::move(other.messages_));
141 }
142
143 /**
144 * @brief Enqueues an message into the queue.
145 * @warning Triggers assertion if message type is not registered.
146 * @tparam T Message type
147 * @param message Message to enqueue
148 */
149 template <MessageTrait T>
150 void Enqueue(T&& message);
151
152 /**
153 * @brief Enqueues multiple messages in bulk.
154 * @warning Triggers assertion if message type is not registered.
155 * @tparam R Range type
156 * @param messages Range of messages to enqueue
157 */
158 template <std::ranges::input_range R>
160 void EnqueueBulk(R&& messages);
161
162 /**
163 * @brief Removes messages at the given sorted indices for a specific message
164 * type.
165 * @warning Triggers assertion if message type is not registered.
166 * @tparam T Message type
167 * @param sorted_indices Span of sorted, unique global indices to remove
168 */
169 template <MessageTrait T>
170 void RemoveIndices(std::span<const size_type> sorted_indices) {
171 RemoveIndices(MessageTypeIndex::From<T>(), sorted_indices);
172 }
173
174 /**
175 * @brief Removes messages at the given sorted indices for a specific type.
176 * @details Erases messages at the provided sorted, deduplicated indices from
177 * back to front to avoid index invalidation. No additional allocation is
178 * performed.
179 * @warning Triggers assertion if type_index is not registered.
180 * @param type_index Type index of the message type
181 * @param sorted_indices Span of sorted, unique global indices to remove
182 */
184 std::span<const size_type> sorted_indices);
185
186 /**
187 * @brief Swaps the contents of this queue with another.
188 * @param other Another MessageQueue to swap with
189 */
190 constexpr void Swap(MessageQueue& other) noexcept {
191 messages_.Swap(other.messages_);
192 }
193
194 /**
195 * @brief Swaps the contents of two message queues.
196 * @param lhs First MessageQueue
197 * @param rhs Second MessageQueue
198 */
199 friend constexpr void swap(MessageQueue& lhs, MessageQueue& rhs) noexcept {
200 lhs.Swap(rhs);
201 }
202
203 /**
204 * @brief Checks if an message type is registered.
205 * @tparam T Message type
206 * @return True if the message type is registered, false otherwise
207 */
208 template <MessageTrait T>
209 [[nodiscard]] constexpr bool IsRegistered() const noexcept {
210 return messages_.template Contains<T>();
211 }
212
213 /**
214 * @brief Checks if an message type is registered by runtime type index.
215 * @param type_index Type index to check
216 * @return True if the message type is registered, false otherwise
217 */
218 [[nodiscard]] constexpr bool IsRegistered(
219 MessageTypeIndex type_index) const noexcept {
220 return messages_.Contains(type_index);
221 }
222
223 /**
224 * @brief Checks if any messages exist in the queue across all types.
225 * @return True if at least one message exists, false otherwise
226 */
227 [[nodiscard]] constexpr bool HasMessages() const noexcept {
228 return !messages_.EmptyAll();
229 }
230
231 /**
232 * @brief Checks if messages of a specific type exist in the queue.
233 * @tparam T Message type
234 * @return True if messages of type T exist, false otherwise or if the type is
235 * not registered
236 */
237 template <MessageTrait T>
238 [[nodiscard]] constexpr bool HasMessages() const noexcept {
239 return !messages_.template Empty<T>();
240 }
241
242 /**
243 * @brief Gets a const span of all messages of a specific type.
244 * @tparam T Message type
245 * @return Span of const messages, or empty span if type is not registered or
246 * has no messages
247 */
248 template <MessageTrait T>
249 [[nodiscard]] constexpr auto Messages() const noexcept -> std::span<const T> {
250 const auto* ptr = messages_.template TryGet<T>();
251 return ptr ? ptr->template Data<T>() : std::span<const T>{};
252 }
253
254 /**
255 * @brief Gets a mutable span of all messages of a specific type.
256 * @tparam T Message type
257 * @return Span of messages, or empty span if type is not registered or has no
258 * messages
259 */
260 template <MessageTrait T>
261 [[nodiscard]] constexpr auto Messages() noexcept -> std::span<T> {
262 auto* ptr = messages_.template TryGet<T>();
263 return ptr ? ptr->template Data<T>() : std::span<T>{};
264 }
265
266 /**
267 * @brief Gets the number of message types stored.
268 * @return Number of distinct message types
269 */
270 [[nodiscard]] constexpr size_type TypeCount() const noexcept {
271 return messages_.TypeCount();
272 }
273
274 /**
275 * @brief Gets the total number of messages stored across all types.
276 * @return Total message count or 0 if no types are registered
277 */
278 [[nodiscard]] constexpr size_type MessageCount() const noexcept {
279 return messages_.Size();
280 }
281
282 /**
283 * @brief Gets the total number of messages stored for a specific message
284 * type.
285 * @tparam T Message type
286 * @return Total message count or 0 if the type is not registered
287 */
288 template <MessageTrait T>
289 [[nodiscard]] constexpr size_type MessageCount() const noexcept {
290 return messages_.template Size<T>();
291 }
292
293 /**
294 * @brief Gets the number of messages for a type by runtime type index.
295 * @param type_index Type index to query
296 * @return Number of messages, or 0 if the type is not registered
297 */
298 [[nodiscard]] constexpr size_type MessageCount(
299 MessageTypeIndex type_index) const noexcept {
300 return messages_.Size(type_index);
301 }
302
303private:
304 template <typename OtherAllocator>
305 friend class MessageQueue;
306
307 MessageStorage messages_; ///< Storage for messages of different types
308};
309
310template <typename Allocator>
311template <MessageTrait T>
312inline void MessageQueue<Allocator>::Enqueue(T&& message) {
313 HELIOS_ASSERT(IsRegistered<std::remove_cvref_t<T>>(),
314 "Message type '{}' is not registered!",
315 MessageNameOf<std::remove_cvref_t<T>>());
316 messages_.template Get<std::remove_cvref_t<T>>()
317 .template EmplaceBack<std::remove_cvref_t<T>>(std::forward<T>(message));
318}
319
320template <typename Allocator>
321template <std::ranges::input_range R>
323inline void MessageQueue<Allocator>::EnqueueBulk(R&& messages) {
324 using T = std::ranges::range_value_t<R>;
325 HELIOS_ASSERT(IsRegistered<T>(), "Message type '{}' is not registered!",
327 messages_.template Get<T>().AppendRange(std::forward<R>(messages));
328}
329
330template <typename Allocator>
332 MessageTypeIndex type_index, std::span<const size_type> sorted_indices) {
333 if (sorted_indices.empty()) [[likely]] {
334 return;
335 }
336
337 HELIOS_ASSERT(IsRegistered(type_index),
338 "Message type index is not registered!");
339
340 auto& buffer = messages_.Get(type_index);
341 if (buffer.Empty()) [[unlikely]] {
342 return;
343 }
344
345 // Erase contiguous ranges from back to front to preserve preceding indices.
346 auto idx = sorted_indices.size();
347 while (idx > 0) {
348 --idx;
349 auto range_end = sorted_indices[idx] + 1;
350 auto range_start = sorted_indices[idx];
351
352 // Coalesce consecutive indices into a single range.
353 while (idx > 0 && sorted_indices[idx - 1] == range_start - 1) {
354 --idx;
355 range_start = sorted_indices[idx];
356 }
357
358 buffer.Erase(range_start, range_end);
359 }
360}
361
364
365} // namespace helios::ecs
#define HELIOS_ASSERT(condition,...)
Assertion macro that aborts execution in debug builds.
Definition assert.hpp:259
Generic type-indexed map that stores one Storage instance per registered type key.
Queue for managing multiple message types using type-erased contiguous storage.
Definition queue.hpp:26
constexpr size_type MessageCount() const noexcept
Gets the total number of messages stored across all types.
Definition queue.hpp:278
void EnqueueBulk(R &&messages)
Enqueues multiple messages in bulk.
Definition queue.hpp:323
constexpr size_type MessageCount() const noexcept
Gets the total number of messages stored for a specific message type.
Definition queue.hpp:289
constexpr bool HasMessages() const noexcept
Checks if messages of a specific type exist in the queue.
Definition queue.hpp:238
constexpr bool HasMessages() const noexcept
Checks if any messages exist in the queue across all types.
Definition queue.hpp:227
constexpr void Register(MessageTypeIndex type_index)
Ensures storage exists for the given runtime type index.
Definition queue.hpp:80
friend class MessageQueue
Definition queue.hpp:305
constexpr MessageQueue()=default
constexpr size_type TypeCount() const noexcept
Gets the number of message types stored.
Definition queue.hpp:270
constexpr auto Messages() const noexcept -> std::span< const T >
Gets a const span of all messages of a specific type.
Definition queue.hpp:249
void RemoveIndices(std::span< const size_type > sorted_indices)
Removes messages at the given sorted indices for a specific message type.
Definition queue.hpp:170
constexpr void Reset() noexcept
Resets a specific message type by clearing its messages and unregistering the type.
Definition queue.hpp:115
constexpr MessageQueue(const allocator_type &alloc)
Constructs an MessageQueue with a custom allocator.
Definition queue.hpp:42
constexpr void ResetAll() noexcept
Resets the queue by clearing all messages and unregistering all types.
Definition queue.hpp:107
MessageQueue(std::nullptr_t)=delete
constexpr size_type MessageCount(MessageTypeIndex type_index) const noexcept
Gets the number of messages for a type by runtime type index.
Definition queue.hpp:298
constexpr void Merge(const MessageQueue< OtherAllocator > &other)
Merges messages from another MessageQueue into this one.
Definition queue.hpp:128
constexpr void Clear(MessageTypeIndex type_index) noexcept
Clears messages of a specific type by runtime type index.
Definition queue.hpp:101
constexpr MessageQueue(const MessageQueue &)=default
MessageStorage::size_type size_type
Definition queue.hpp:33
void RemoveIndices(MessageTypeIndex type_index, std::span< const size_type > sorted_indices)
Removes messages at the given sorted indices for a specific type.
Definition queue.hpp:331
void Enqueue(T &&message)
Enqueues an message into the queue.
Definition queue.hpp:312
constexpr void Merge(MessageQueue< OtherAllocator > &&other)
Merges messages from another MessageQueue into this one.
Definition queue.hpp:139
constexpr auto Messages() noexcept -> std::span< T >
Gets a mutable span of all messages of a specific type.
Definition queue.hpp:261
constexpr void Swap(MessageQueue &other) noexcept
Swaps the contents of this queue with another.
Definition queue.hpp:190
MessageStorage::allocator_type allocator_type
Definition queue.hpp:34
constexpr bool IsRegistered(MessageTypeIndex type_index) const noexcept
Checks if an message type is registered by runtime type index.
Definition queue.hpp:218
constexpr void ClearAll() noexcept
Clears all messages from the queue maintaining all registered types.
Definition queue.hpp:86
constexpr MessageQueue(MessageQueue &&) noexcept=default
constexpr bool IsRegistered() const noexcept
Checks if an message type is registered.
Definition queue.hpp:209
constexpr MessageQueue(std::pmr::memory_resource *resource)
Constructs a MessageQueue from a PMR memory resource.
Definition queue.hpp:51
friend constexpr void swap(MessageQueue &lhs, MessageQueue &rhs) noexcept
Swaps the contents of two message queues.
Definition queue.hpp:199
constexpr void Clear() noexcept
Clears messages of a specific type.
Definition queue.hpp:93
static constexpr TypeIndex From() noexcept
Constructs a TypeIndex from a type T.
Concept for valid message types.
Definition message.hpp:29
MessageQueue< std::pmr::polymorphic_allocator< std::byte > > PmrMessageQueue
Definition queue.hpp:362
utils::TypeIndex MessageTypeIndex
Type index for messages.
Definition message.hpp:14
constexpr std::string_view MessageNameOf() noexcept
Gets the name of an message.
Definition message.hpp:97
STL namespace.