Skip to main content

GameLoop.ixx File

Central orchestrator for the game update cycle. 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...

namespacegameloop

Central game loop orchestration module. More...

Classes Index

classGameLoop

Central orchestrator for the game update cycle. More...

Macro Definitions Index

#defineHELIOS_LOG_SCOPE   "helios::engine::runtime::gameloop::GameLoop"

Description

Central orchestrator for the game update cycle.

Macro Definitions

HELIOS_LOG_SCOPE

#define HELIOS_LOG_SCOPE   "helios::engine::runtime::gameloop::GameLoop"

Definition at line 44 of file GameLoop.ixx.

44#define HELIOS_LOG_SCOPE "helios::engine::runtime::gameloop::GameLoop"

File Listing

The file content with the documentation metadata removed is:

1/**
2 * @file GameLoop.ixx
3 * @brief Central orchestrator for the game update cycle.
4 */
5module;
6
7#include <cassert>
8#include <memory>
9#include <vector>
10#include <span>
11
12export module helios.engine.runtime.gameloop.GameLoop;
13
14import helios.engine.runtime.world.GameWorld;
15
16import helios.engine.runtime.world.UpdateContext;
17import helios.util.log.Logger;
18import helios.util.log.LogManager;
19
20import helios.engine.ecs;
21
22import helios.engine.state.Bindings;
23import helios.engine.runtime.messaging.command.EngineCommandBuffer;
24
25import helios.engine.runtime.messaging.event.GameLoopEventBus;
26
27import helios.engine.state.Bindings;
28
29import helios.engine.mechanics.gamestate.types;
30import helios.engine.mechanics.match.types;
31
32import helios.engine.runtime.gameloop.CommitPoint;
33import helios.engine.runtime.gameloop.Phase;
34
35import helios.engine.runtime.world.Manager;
36
37import helios.input.InputSnapshot;
38import helios.rendering.ViewportSnapshot;
39
40import helios.engine.runtime.gameloop.PassCommitListener;
41
43
44#define HELIOS_LOG_SCOPE "helios::engine::runtime::gameloop::GameLoop"
46
47 /**
48 * @brief Central orchestrator for the game update cycle.
49 *
50 * @details
51 * The GameLoop manages the execution of game systems across three distinct phases:
52 * Pre, Main, and Post. Each phase can contain multiple passes, and each pass can
53 * have a configurable commit point for fine-grained synchronization control.
54 *
55 * ## Ownership
56 *
57 * The GameLoop owns:
58 * - **Three phases** (Pre, Main, Post), each containing passes with registered systems.
59 * - **Three event buses** for different propagation scopes:
60 * - `phaseEventBus_`: Events readable in the next phase.
61 * - `passEventBus_`: Events readable in subsequent passes (within the same phase).
62 * - `frameEventBus_`: Events readable in the next frame.
63 *
64 * The EngineCommandBuffer and Managers are owned by the GameWorld's
65 * ResourceRegistry and accessed via UpdateContext during commit points.
66 *
67 * ## Commit Points
68 *
69 * Commit points allow systems to specify when commands should be flushed, managers
70 * should process their requests, and pass-level events should be synchronized.
71 * This enables deterministic ordering and fine-grained control over the update cycle.
72 *
73 * ## Frame Lifecycle
74 *
75 * ```
76 * ┌─────────────────────────────────────────────────────────────┐
77 * │ FRAME N │
78 * ├─────────────────┬─────────────────┬─────────────────────────┤
79 * │ PRE PHASE │ MAIN PHASE │ POST PHASE │
80 * │ (Input, Cmd) │ (Physics, AI) │ (Cleanup, Sync) │
81 * ├─────────────────┼─────────────────┼─────────────────────────┤
82 * │ phaseCommit() │ phaseCommit() │ phaseCommit() │
83 * │ │ │ frameEventBus_.swap() │
84 * └─────────────────┴─────────────────┴─────────────────────────┘
85 * ```
86 *
87 * @see Phase
88 * @see Pass
89 * @see CommitPoint
90 * @see ResourceRegistry
91 * @see EngineCommandBuffer
92 * @see PassCommitListener
93 */
95
96
97 /**
98 * @brief Flag indicating whether init() has been called.
99 *
100 * Used to assert that init() is called exactly once before the first update()
101 * and to prevent multiple initializations.
102 */
103 bool initialized_ = false;
104 protected:
105
106
107
108 /**
109 * @brief The logger used with this GameLoop instance.
110 */
113
114
115 /**
116 * @brief The pre-update phase, executed before main gameplay logic.
117 */
119
120 /**
121 * @brief The main update phase for core gameplay systems.
122 */
124
125 /**
126 * @brief The post-update phase for cleanup and synchronization.
127 */
129
130
131 /**
132 * @brief Event bus for phase-level event propagation.
133 *
134 * Events pushed via `UpdateContext::pushPhase()` are buffered here
135 * and become readable in the next phase via `UpdateContext::readPhase()`.
136 * The buffer swap occurs in phaseCommit().
137 *
138 * @see UpdateContext::pushPhase()
139 * @see UpdateContext::readPhase()
140 */
142
143 /**
144 * @brief Event bus for pass-level event propagation.
145 *
146 * Events pushed via `UpdateContext::pushPass()` are buffered here
147 * and become readable in subsequent passes via `UpdateContext::readPass()`.
148 * The buffer swap occurs when a pass has a commit point.
149 *
150 * @see UpdateContext::pushPass()
151 * @see UpdateContext::readPass()
152 * @see Pass::addCommitPoint()
153 */
155
156 /**
157 * @brief Event bus for frame-level event propagation.
158 *
159 * Events pushed via `UpdateContext::pushFrame()` are buffered here
160 * and become readable in the next frame via `UpdateContext::readFrame()`.
161 * The buffer swap occurs at the end of the Post phase.
162 *
163 * Frame-level events persist across all phases within a frame and are
164 * useful for cross-frame communication (e.g., collision events that
165 * should be processed in the next frame).
166 *
167 * @see UpdateContext::pushFrame()
168 * @see UpdateContext::readFrame()
169 */
171
172 /**
173 * @brief Accumulated total time since the first frame, in seconds.
174 */
175 float totalTime_ = 0.0f;
176
177
178 /**
179 * @brief Commits pass-level state based on the specified CommitPoint flags.
180 *
181 * @details Called after each pass that has a commit point configured. The
182 * CommitPoint flags determine which synchronization actions are performed:
183 *
184 * - **PassEvents:** Swaps pass event bus buffers, making events pushed via
185 * `UpdateContext::pushPass()` readable in subsequent passes.
186 * - **FlushCommands:** Executes pending commands from the CommandBuffer.
187 * - **FlushManagers:** Processes manager requests (e.g., spawning from pools).
188 *
189 * The order is: FlushCommands → FlushManagers → PassEvents.
190 * Commands must be flushed before managers to ensure spawn requests are
191 * generated before being processed.
192 *
193 * @param commitPoint The flags specifying which actions to perform.
194 * @param gameWorld The game world where the commit occured.
195 * @param updateContext The current update context.
196 *
197 * @see CommitPoint
198 * @see Pass::addCommitPoint()
199 * @see UpdateContext::pushPass()
200 * @see UpdateContext::readPass()
201 */
203 const CommitPoint commitPoint,
204 GameWorld& gameWorld,
205 UpdateContext& updateContext) noexcept override {
206
207 // commands must be executed before Managers
209 gameWorld.flushCommandBuffers(updateContext);
210 }
211
213 gameWorld.flushManagers(updateContext);
214 }
215
216 // managers might create pass events
217 if ((commitPoint & CommitPoint::PassEvents) == CommitPoint::PassEvents) {
218 passEventBus_.swapBuffers();
219 }
220
221 }
222
223 /**
224 * @brief Commits phase-level events and flushes commands and managers.
225 *
226 * Called after each phase completes. This method:
227 *
228 * 1 Clears pass event buffers for the new phase.
229 * 2 Flushes the command buffer, executing deferred commands.
230 * 3 Flushes managers, allowing to process any request generated by the commands.
231 * 4 Swaps phase event bus buffers, making events readable in the next phase.
232
233 * @param gameWorld Reference to the game world.
234 * @param updateContext The current update context.
235 *
236 * @see UpdateContext::pushPhase()
237 * @see UpdateContext::readPhase()
238 */
240 GameWorld& gameWorld,
241 UpdateContext& updateContext) {
242
243 passEventBus_.clearAll();
244
245 // command buffers generate requests for managers, so this comes first
246 gameWorld.flushCommandBuffers(updateContext);
247
248 // managers process requests
249 gameWorld.flushManagers(updateContext);
250
251 // make sure flushed managers make their events available to the phase event bus
252 phaseEventBus_.swapBuffers();
253 }
254
255
256 public:
257
258
259 /**
260 * @brief Default constructor.
261 *
262 * @details Creates a GameLoop with empty phases. Systems must be added
263 * to the phases via `phase(PhaseType).addPass().addSystem<T>()` before
264 * calling `init()`.
265 */
266 GameLoop() = default;
267
268 /**
269 * @brief Returns a reference to the specified phase.
270 *
271 * @param phaseType The type of phase to retrieve (Pre, Main, or Post).
272 *
273 * @return Reference to the requested Phase.
274 */
276
277 switch (phaseType) {
279 return prePhase_;
280 break;
282 return mainPhase_;
283 break;
285 return postPhase_;
286 break;
287 }
288
289 std::unreachable();
290
291 }
292
293
294 /**
295 * @brief Initializes the GameLoop and all registered phases and passes.
296 *
297 * @details Iterates through all phases (Pre, Main, Post) and calls their
298 * `init()` methods, which in turn initialize all registered passes and
299 * systems. Systems receive a reference to the GameWorld for component
300 * queries and entity access.
301 *
302 * Must be called exactly once before the first `update()` call.
303 *
304 * @param gameWorld Reference to the game world to initialize with.
305 *
306 * @pre Must not have been called before (asserts on multiple calls).
307 *
308 * @see Phase::init()
309 * @see Pass::init()
310 * @see System::init()
311 */
312 void init(GameWorld& gameWorld) {
313
314 assert(!initialized_ && "init() already called");
315
319 switch (phase) {
321 prePhase_.init(gameWorld);
322 prePhase_.addPassCommitListener(this);
323 break;
325 mainPhase_.init(gameWorld);
326 mainPhase_.addPassCommitListener(this);
327 break;
329 postPhase_.init(gameWorld);
330 postPhase_.addPassCommitListener(this);
331 break;
332 }
333 }
334
335 initialized_ = true;
336 }
337
338 /**
339 * @brief Executes one full frame update across all phases.
340 *
341 * @details Iterates through Pre, Main, and Post phases, updating all registered
342 * systems and committing events and commands after each phase. The frame lifecycle:
343 *
344 * 1 **Pre Phase:** Input processing, command generation, preparation
345 * 2 **Main Phase:** Core gameplay logic, physics, collision detection
346 * 3 **Post Phase:** Cleanup, synchronization, rendering preparation
347 *
348 * After each phase, `phaseCommit()` is called to:
349 * - Swap phase event buffers (events become readable in next phase)
350 * - Clear pass event buffers
351 * - Flush command buffer
352 * - Flush managers
353 *
354 * After the Post phase, the frame event bus is swapped, making frame-level
355 * events readable in the next frame.
356 *
357 * @param gameWorld Reference to the game world.
358 * @param deltaTime Time elapsed since the last frame in seconds.
359 * @param inputSnapshot Snapshot of the current input state.
360 * @param viewportSnapshots Snapshots of viewports registered with an id.
361 *
362 * @pre init() must have been called before the first update.
363 *
364 * @see Phase
365 * @see phaseCommit()
366 * @see UpdateContext
367 */
368 void update(
369 GameWorld& gameWorld,
370 float deltaTime,
371 const helios::input::InputSnapshot& inputSnapshot,
372 std::span<const helios::rendering::ViewportSnapshot> viewportSnapshots
373 ) noexcept {
374
375 assert(initialized_ && "GameLoop not initialized");
376
377 totalTime_ += deltaTime;
378
379 auto updateContext = UpdateContext(
380 gameWorld.resourceRegistry(),
381 helios::engine::ecs::EntityResolver(&gameWorld.entityManager()),
382 gameWorld.session(),
383 deltaTime,
388 inputSnapshot,
389 viewportSnapshots,
390 gameWorld.level()
391 );
392
393 auto& session = gameWorld.session();
394
395 // gameloop phases
399
400 switch (phase) {
402 prePhase_.update(gameWorld, updateContext);
403 phaseCommit(gameWorld, updateContext);
404
405 break;
407 mainPhase_.update(gameWorld, updateContext);
408 phaseCommit(gameWorld, updateContext);
409
410 break;
412 postPhase_.update(gameWorld, updateContext);
413 phaseCommit(gameWorld, updateContext);
414 frameEventBus_.swapBuffers();
415 break;
416 }
417
418
419 }
420 }
421
422
423
424 };
425
426}
427

Generated via doxygen2docusaurus 2.0.0 by Doxygen 1.15.0.