Motion Master 6.0.0-alpha.86
Next-generation motion control software
Loading...
Searching...
No Matches
GameLoop Class Reference

Fixed-period real-time loop. More...

#include <game_loop.h>

Public Member Functions

 GameLoop (mm::node::DeviceManager &deviceManager, std::chrono::microseconds period, int cpuAffinity=-1)
 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.
 
GameLoopoperator= (const GameLoop &)=delete
 Copy assignment is deleted — see copy constructor.
 
void addTask (mm::core::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.
 

Detailed Description

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.

Constructor & Destructor Documentation

◆ GameLoop() [1/2]

GameLoop::GameLoop ( mm::node::DeviceManager deviceManager,
std::chrono::microseconds  period,
int  cpuAffinity = -1 
)

Constructs the loop with the given cycle period.

Parameters
deviceManagerThe device manager the loop enters a cycle on. The loop takes a DeviceManager::CycleGuard around the whole task list each cycle, so a task's own findDevice / findParameter results are valid for its execute() by construction and no task has to take one itself. Must outlive the loop.
periodTime between cycle starts. Typical value: 1000 µs (1 ms).
cpuAffinityCore to pin the RT thread to once run() is called, or a negative value (the default) to leave it unpinned. Set this to an isolcpus core to get that core to itself; see mm::core::setRealtimePriority(). Fixed for the lifetime of the loop — unlike the period, it is a deployment property, not something to retune live.

◆ ~GameLoop()

GameLoop::~GameLoop ( )
default

Destructor. Does not call stop() — the caller is responsible for stopping the loop before destroying it.

◆ GameLoop() [2/2]

GameLoop::GameLoop ( const GameLoop )
delete

Copying is deleted — the loop owns real-time scheduling state that cannot be shared.

Member Function Documentation

◆ addTask()

void GameLoop::addTask ( mm::core::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.

A task runs only while the bus is activated. The loop enters the cycle before calling any task and skips them all when no process image is published, which is the state a rescan or a re-map passes through. So a task never sees a device set being replaced, and a task that wants to compute without a bus cannot do it here.

Parameters
taskNon-owning pointer. The task must outlive every call to run() — its pointer is only dereferenced while the loop is executing, never after run() returns.

◆ executedCycles()

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().

Returns
Executed-cycle count. Reads with relaxed ordering — suitable for diagnostics and logging, not for synchronisation.

◆ health()

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.

◆ operator=()

GameLoop & GameLoop::operator= ( const GameLoop )
delete

Copy assignment is deleted — see copy constructor.

◆ run()

void GameLoop::run ( )

Blocks the calling thread, running one cycle per period until stop() is called.

Calls mm::core::setRealtimePriority() before entering the loop, which raises the calling thread to SCHED_FIFO (POSIX), locks all memory pages (Linux), and pins it to the constructor's cpuAffinity core when one was given (Linux). Every step fails 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); health() reports which of them took.

Note
Call this on the main thread and start all other subsystem threads beforehand — it does not return until the loop stops.

◆ setPeriod()

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.

Parameters
periodNew cycle period. Must be > 0 (validated by the caller).

◆ skippedCycles()

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().

Returns
Skipped-cycle count. Reads with relaxed ordering — suitable for diagnostics, not for synchronisation.

◆ stop()

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.


The documentation for this class was generated from the following files: