Skip to main content

TypedCommandBuffer.ixx File

Compile-time typed command buffer with handler routing. More...

Included Headers

Namespaces Index

namespacehelios
namespaceengine

Main engine module aggregating core infrastructure and game systems. More...

namespaceruntime

Runtime infrastructure for game execution and lifecycle orchestration. More...

namespacemessaging

Communication infrastructure for commands and events. More...

namespacecommand

Compile-time typed command buffering and handler routing. More...

Classes Index

classTypedCommandBuffer<CommandTypes>

Compile-time typed command buffer with per-type queues and handler routing. More...

Description

Compile-time typed command buffer with handler routing.

File Listing

The file content with the documentation metadata removed is:

1/**
2 * @file TypedCommandBuffer.ixx
3 * @brief Compile-time typed command buffer with handler routing.
4 */
5module;
6
7#include <cassert>
8#include <tuple>
9#include <vector>
10#include <utility>
11
12export module helios.engine.runtime.messaging.command.TypedCommandBuffer;
13
14import helios.engine.state.components;
15
16import helios.engine.runtime.world.GameWorld;
17import helios.engine.runtime.world.UpdateContext;
18
19import helios.engine.runtime.messaging.command.CommandBuffer;
20
21import helios.engine.state.commands.DelayedStateCommand;
22
23import helios.engine.mechanics.timing.TimerManager;
24import helios.engine.mechanics.timing.types;
25
30
32
33 /**
34 * @brief Concept constraining commands that can self-execute.
35 *
36 * @details A command satisfies ExecutableCommand if it provides a
37 * noexcept `execute(UpdateContext&)` method. Commands that do not
38 * satisfy this concept must have a registered handler.
39 *
40 * @tparam Cmd The command type to check.
41 */
42 template<typename Cmd>
43 concept ExecutableCommand = requires(Cmd const& c, UpdateContext& updateContext) {
44 {c.execute(updateContext) } noexcept;
45 };
46
47 /**
48 * @brief Concept constraining commands that carry a timer gate.
49 *
50 * @details A command satisfies DelayedCommandLike if it provides a
51 * noexcept `gameTimerId()` accessor returning a GameTimerId. Such
52 * commands are held in a scratch queue until their associated timer
53 * reaches `TimerState::Finished`.
54 *
55 * @tparam Cmd The command type to check.
56 */
57 template<typename Cmd>
58 concept DelayedCommandLike = requires(Cmd const& c) {
59 {c.gameTimerId() } noexcept;
60 };
61
62 /**
63 * @brief Compile-time typed command buffer with per-type queues and handler routing.
64 *
65 * @details TypedCommandBuffer stores commands in separate `std::vector` queues,
66 * one per command type, packed into a `std::tuple`. This provides:
67 *
68 * - **Zero-overhead dispatch:** Command types are known at compile time,
69 * eliminating virtual dispatch for queue access.
70 * - **Deterministic ordering:** Commands are flushed in the order of the
71 * template parameter list, ensuring reproducible execution.
72 * - **Handler-or-execute routing:** During flush, each command is either
73 * routed to a registered handler or executed directly via
74 * its `execute()` method (if it satisfies ExecutableCommand).
75 *
76 * ## Flush Routing
77 *
78 * For each command type in the parameter pack:
79 * 1 If a handler for `Cmd` is registered → `handler.submit(cmd)`
80 * 2 Else if `Cmd` satisfies `ExecutableCommand` → `cmd.execute(ctx)`
81 * 3 Else → assertion failure (misconfiguration)
82 *
83 * @tparam CommandTypes The command types this buffer manages.
84 *
85 * @see CommandBuffer
86 * @see CommandHandlerRegistry
87 * @see EngineCommandBuffer
88 * @see ExecutableCommand
89 */
90 template <typename... CommandTypes>
92
93 /**
94 * @brief Per-type command queues stored as a tuple of vectors.
95 */
96 std::tuple<std::vector<CommandTypes>...> commandQueues_;
97
98 /**
99 * @brief Scratch queues for timer-gated commands surviving a flush cycle.
100 *
101 * @details Commands satisfying DelayedCommandLike whose timer has
102 * not yet finished are moved here during flush and swapped back
103 * into the primary queues afterwards.
104 */
105 std::tuple<std::vector<CommandTypes>...> delayedQueues_;
106
107 /**
108 * @brief Returns the queue for a specific command type.
109 *
110 * @tparam CommandType The command type.
111 *
112 * @return Reference to the command queue.
113 */
114 template<typename CommandType>
115 std::vector<CommandType>& commandQueue() noexcept {
116 return std::get<std::vector<CommandType>>(commandQueues_);
117 }
118
119 /**
120 * @brief Returns the delayed scratch queue for a specific command type.
121 *
122 * @tparam CommandType The command type.
123 *
124 * @return Reference to the delayed scratch queue.
125 */
126 template<typename CommandType>
127 std::vector<CommandType>& delayedQueue() noexcept {
128 return std::get<std::vector<CommandType>>(delayedQueues_);
129 }
130
131 /**
132 * @brief Determines whether a delayed command should be deferred.
133 *
134 * @details Returns true when the timer is still running - i.e. its
135 * state is neither Finished nor Undefined.
136 *
137 * @param state The current timer state.
138 *
139 * @return True if the command must remain in the scratch queue.
140 */
141 [[nodiscard]] bool shouldDelayCommand(const TimerState state) const noexcept {
142 return state == TimerState::Running;
143 }
144
145 /**
146 * @brief Determines whether a delayed command should be discarded.
147 *
148 * @details Returns true when the timer is not in state Running or Finished..
149 *
150 * @param state The current timer state.
151 *
152 * @return True if the command should be discarded.
153 */
154 [[nodiscard]] bool shouldDiscardCommand(const TimerState state) const noexcept {
156 }
157
158 /**
159 * @brief Determines whether a delayed command is ready for dispatch.
160 *
161 * @param state The current timer state.
162 *
163 * @return True if the associated timer has finished.
164 */
165 [[nodiscard]] bool isDelayedCommandReady(const TimerState state) const noexcept {
167 }
168
169 /**
170 * @brief Flushes a single command queue.
171 *
172 * @details Processing follows two branches depending on whether a
173 * handler is registered for `CommandType`:
174 *
175 * **Handler registered** (`CommandHandlerRegistry::has<CommandType>()`):
176 * - Each command is forwarded via `commandHandlerRegistry.submit<CommandType>(cmd)`.
177 *
178 * **No handler registered** (fallback):
179 * - The command must satisfy `ExecutableCommand`; otherwise an
180 * assertion fires at runtime.
181 * - Each command is dispatched via `cmd.execute(updateContext)`.
182 *
183 * In both branches, if `CommandType` satisfies `DelayedCommandLike`,
184 * an additional timer check is performed per command:
185 *
186 * 1 The associated `GameTimer` is looked up via the command's
187 * `gameTimerId()`.
188 * 2 If the timer is still running (`shouldDelayCommand` returns
189 * true), the command is moved into the scratch queue and
190 * survives the current flush cycle.
191 * 3 If the timer has finished (`isDelayedCommandReady` returns
192 * true), the command is dispatched normally.
193 * 4 If the timer state is `Undefined` (e.g. timer was removed),
194 * the command is silently dropped.
195 *
196 * After all commands have been processed the primary queue is
197 * cleared, the scratch queue contents are swapped back in as
198 * the new primary queue, and the scratch queue is cleared.
199 *
200 * @tparam CommandType The command type to flush.
201 *
202 * @param gameWorld The game world providing the CommandHandlerRegistry
203 * and TimerManager.
204 * @param updateContext The current frame's update context.
205 */
206 template<typename CommandType>
207 void flushCommandQueue(GameWorld& gameWorld, UpdateContext& updateContext) noexcept {
208
209 auto& timerManager = gameWorld.manager<TimerManager>();
210
211 auto& queue = commandQueue<CommandType>();
212 auto& delayed = delayedQueue<CommandType>();
213 delayed.clear();
214
215 if (queue.empty()) {
216 return;
217 }
218
219 auto& commandHandlerRegistry = gameWorld.commandHandlerRegistry();
220
221 if (commandHandlerRegistry.has<CommandType>()) {
222
223 for (auto& cmd : queue) {
224 if constexpr (DelayedCommandLike<CommandType>) {
225 auto* gameTimer = timerManager.gameTimer(cmd.gameTimerId());
226 if (!gameTimer) {
227 assert(gameTimer && "Unexpected null game timer");
228 commandHandlerRegistry.submit<CommandType>(cmd);
229 continue;
230 }
231
232 if (shouldDelayCommand(gameTimer->state())) {
233 delayed.push_back(std::move(cmd));
234 } else if (isDelayedCommandReady(gameTimer->state())) {
235 commandHandlerRegistry.submit<CommandType>(cmd);
236 } else if (shouldDiscardCommand(gameTimer->state())) {
237 // cancelled? Discard! intentionally noop
238 }
239 } else {
240 commandHandlerRegistry.submit<CommandType>(cmd);
241 }
242 }
243
244
245 } else {
246 if constexpr (ExecutableCommand<CommandType>) {
247
248 for (auto& cmd : queue) {
249 if constexpr (DelayedCommandLike<CommandType>) {
250 auto* gameTimer = timerManager.gameTimer(cmd.gameTimerId());
251 if (!gameTimer) {
252 assert(gameTimer && "Unexpected null game timer");
253 cmd.execute(updateContext);
254 continue;
255 }
256
257 if (shouldDelayCommand(gameTimer->state())) {
258 delayed.push_back(std::move(cmd));
259 } else if (isDelayedCommandReady(gameTimer->state())) {
260 cmd.execute(updateContext);
261 } else if (shouldDiscardCommand(gameTimer->state())) {
262 // cancelled? Discard! intentionally noop
263 }
264 } else {
265 cmd.execute(updateContext);
266 }
267
268 }
269
270 } else {
271 assert(false && "Command type is not executable");
272 }
273
274 }
275
276 queue.clear();
277 queue.swap(delayed);
278 delayed.clear();
279 }
280
281 public:
282
283 /**
284 * @brief Enqueues a command of the specified type.
285 *
286 * @tparam T The command type. Must be one of the CommandTypes.
287 * @tparam Args Constructor argument types.
288 *
289 * @param args Arguments forwarded to the command constructor.
290 */
291 template<typename T, typename... Args>
292 void add(Args&&... args) {
293 auto& queue = std::get<std::vector<T>>(commandQueues_);
294 queue.emplace_back(std::forward<Args>(args)...);
295 }
296
297 /**
298 * @brief Discards all queued commands without executing them.
299 */
300 void clear() noexcept {
301 std::apply([](auto&... queue) { (queue.clear(), ...); }, commandQueues_);
302 }
303
304 /**
305 * @brief Flushes all command queues in template parameter order.
306 *
307 * @details Iterates through each command type using a fold expression,
308 * flushing queues in the order specified by the template parameters.
309 *
310 * @param gameWorld The game world for which the queue should be flushed.
311 * @param updateContext The current frame's update context.
312 */
313 void flush(GameWorld& gameWorld, UpdateContext& updateContext) noexcept {
314 (flushCommandQueue<CommandTypes>(gameWorld, updateContext), ...);
315 }
316
317
318 };
319
320
321}

Generated via doxygen2docusaurus 2.0.0 by Doxygen 1.15.0.