|
Motion Master 6.0.0-alpha.50
Next-generation motion control software
|
Fixed-period real-time loop. More...
#include <game_loop.h>
Public Member Functions | |
| GameLoop (std::chrono::microseconds period) | |
| Constructs the loop with the given cycle period. | |
| ~GameLoop ()=default | |
| Destructor. Does not call stop() — the caller is responsible for stopping the loop before destroying it. | |
| GameLoop (const GameLoop &)=delete | |
| Copying is deleted — the loop owns real-time scheduling state that cannot be shared. | |
| GameLoop & | operator= (const GameLoop &)=delete |
| Copy assignment is deleted — see copy constructor. | |
| void | addTask (CyclicTask *task) |
| Registers a task to be executed every cycle. | |
| void | run () |
| Blocks the calling thread, running one cycle per period until stop() is called. | |
| void | stop () |
| Signals the loop to stop after the current cycle completes. | |
| void | setPeriod (std::chrono::microseconds period) |
| Changes the cycle period while the loop is running. | |
| uint64_t | executedCycles () const |
| Returns the number of cycles actually executed since run() was called. | |
| uint64_t | skippedCycles () const |
| Returns the total number of cycles skipped since run() was called. | |
| GameLoopHealth | health () const |
| Returns a snapshot of real-time loop health for diagnostics. | |
Fixed-period real-time loop.
The caller drives the loop by blocking the calling thread in run(). The main thread becomes the RT thread — no separate thread is created. All other subsystems should start their own threads before calling run(), and stop them after run() returns.
Each cycle waits for the next absolute deadline via CyclicTimer, then executes the loop body. The period is set at construction and may be retimed while running via setPeriod() (see below).
Shutdown is cooperative: stop() sets an atomic flag that run() checks after each cycle completes. The loop exits cleanly within one period.
|
explicit |
Constructs the loop with the given cycle period.
| period | Time between cycle starts. Typical value: 1000 µs (1 ms). |
|
default |
Destructor. Does not call stop() — the caller is responsible for stopping the loop before destroying it.
|
delete |
Copying is deleted — the loop owns real-time scheduling state that cannot be shared.
| void GameLoop::addTask | ( | CyclicTask * | task | ) |
Registers a task to be executed every cycle.
Tasks are called in registration order after each timer tick. Must be called before run() — not safe to call concurrently with a running loop.
| uint64_t GameLoop::executedCycles | ( | ) | const |
Returns the number of cycles actually executed since run() was called.
This counts loop iterations (calls to a task's execute()), which is distinct from grid periods elapsed: a stall that skips N cycles advances this by 1 (one iteration ran) while wall-clock time advanced by 1 + N periods. CycleContext::elapsed = executedCycles() + skippedCycles().
| GameLoopHealth GameLoop::health | ( | ) | const |
Returns a snapshot of real-time loop health for diagnostics.
Reads the counters, task-execution aggregates, and RT-scheduling flags with relaxed ordering, and computes the cumulative achievedHz from uptime. Safe to call from any thread; the dynamic fields are zero before run() starts.
Copy assignment is deleted — see copy constructor.
| void GameLoop::run | ( | ) |
Blocks the calling thread, running one cycle per period until stop() is called.
On Linux, raises the calling thread to SCHED_FIFO priority and locks all memory pages before entering the loop. Both steps fail gracefully with a warning when the process lacks the required privileges (e.g. not run as root and no CAP_SYS_NICE / CAP_IPC_LOCK).
| void GameLoop::setPeriod | ( | std::chrono::microseconds | period | ) |
Changes the cycle period while the loop is running.
Stores the new period; the running loop picks it up on its next iteration and re-anchors the CyclicTimer's deadline grid to that instant (see CyclicTimer::setPeriod). Takes effect within one cycle. Safe to call from any thread — the store is a relaxed atomic and the RT loop is the only reader. Also updates the period reported by health().
Applying a change also starts a fresh health epoch: the loop resets its cumulative counters (executed/skipped cycles, task-time max/avg) and re-anchors the achievedHz baseline to that instant, so health() reflects only the new period. Without this the old, worse figures would linger and mask the improvement the change was meant to produce.
Changing the period only re-times the master cadence; it does not touch the process-data recorder ring or any drive-side watchdog. Raising the period toward a drive's PDO/SM watchdog window can fault that drive — the caller is responsible for choosing a sane value.
| period | New cycle period. Must be > 0 (validated by the caller). |
| uint64_t GameLoop::skippedCycles | ( | ) | const |
Returns the total number of cycles skipped since run() was called.
When a deadline is already past (an overrun or a scheduling stall — the latter routine on a non-RT OS), the timer skips the backlog and re-syncs to the grid rather than running missed cycles back-to-back (which would flood the bus with stale process data). This is the running total of cycles skipped that way — a nonzero, growing value means the loop is not meeting its period. Read via health() (GET /api/game-loop, the console Game Loop page). CycleContext::elapsed = executedCycles() + skippedCycles().
| void GameLoop::stop | ( | ) |
Signals the loop to stop after the current cycle completes.
Safe to call from a signal handler. run() returns within one period.