Motion Master 6.0.0-alpha.50
Next-generation motion control software
Loading...
Searching...
No Matches
mm::node::ProcessDataRing Class Reference

A lock-free circular recorder of the raw process image, one record per RT cycle. More...

#include <process_data_ring.h>

Classes

struct  Record
 One recorded cycle, copied out by a reader. inputs / outputs are the raw IOmap bytes for that cycle (sized to the live image at record time). More...
 

Public Member Functions

 ProcessDataRing ()=default
 
 ~ProcessDataRing ()
 
 ProcessDataRing (const ProcessDataRing &)=delete
 
ProcessDataRingoperator= (const ProcessDataRing &)=delete
 
void allocate (uint32_t inputCap, uint32_t outputCap, size_t capacityCycles)
 (Re)allocates the ring for an image of inputCap / outputCap bytes per direction and capacity cycles, discarding any prior recording (sequence restarts at 0).
 
void clear ()
 Releases the storage and resets to the unallocated state. Idempotent.
 
bool allocated () const
 Whether allocate has been called with a non-zero capacity.
 
void write (uint64_t timestampNs, int wkc, std::span< const uint8_t > inputs, std::span< const uint8_t > outputs)
 Records one cycle. RT, wait-free, single-writer. No-op until allocate.
 
uint64_t head () const
 The producer's next sequence number; head()-1 is the newest recorded cycle, and head()==0 means nothing has been recorded yet.
 
size_t capacity () const
 Number of cycles the ring can hold.
 
uint64_t oldestValidSeq () const
 The oldest sequence number still in the ring: max(0, head - capacity). A cursor below this has been lapped (its data overwritten) and must resync to this value.
 
bool readRecord (uint64_t seq, Record &out) const
 Copies the record for seq into out. Lock-free; retries are the caller's job.
 

Detailed Description

A lock-free circular recorder of the raw process image, one record per RT cycle.

The RT loop appends one record every cycle via write() and never blocks: a record is the whole raw input and output IOmap for that cycle plus its timestamp and working counter. The ring overwrites its oldest record on wrap, so it holds the most recent capacity() cycles — a flight-data recorder, not a drain queue. Any number of non-RT readers consume it through independent read cursors (a read index, not a tail): a reader never frees a slot and never gates the producer, so the RT loop overwrites unconditionally and may lap a slow reader. That is allowed and detected, not a bug — a lapped reader sees readRecord return false and resyncs from oldestValidSeq().

It is the single RT-written source for both the live monitoring stream (each monitoring holds a cursor and ships every record in [cursor, head)) and point reads of the freshest value (read head()-1) — one write per cycle, and one record holds both directions from the same cycle.

Sequencing: each record carries an absolute cycle sequence number; slot = seq % capacity. head() is the producer's monotonic next-seq (it never wraps). The per-slot sequence word is stored after the payload with release ordering — its publication is what makes a record readable — and a reader re-checks it after copying to detect a write that raced the copy (a per-slot sequence re-check). The RT write() is wait-free: a few memcpy plus two release stores.

Single writer (the RT loop), many concurrent readers. Not copyable or movable.

Constructor & Destructor Documentation

◆ ProcessDataRing() [1/2]

mm::node::ProcessDataRing::ProcessDataRing ( )
default

◆ ~ProcessDataRing()

mm::node::ProcessDataRing::~ProcessDataRing ( )

◆ ProcessDataRing() [2/2]

mm::node::ProcessDataRing::ProcessDataRing ( const ProcessDataRing )
delete

Member Function Documentation

◆ allocate()

void mm::node::ProcessDataRing::allocate ( uint32_t  inputCap,
uint32_t  outputCap,
size_t  capacityCycles 
)

(Re)allocates the ring for an image of inputCap / outputCap bytes per direction and capacity cycles, discarding any prior recording (sequence restarts at 0).

Called from DeviceManager::configureProcessData once the process-image sizes are known — a layout-changing re-map re-allocates, because records under the old layout are undecodable under a new one. The storage is best-effort mlock'd on POSIX so the RT write() never page-faults (process-wide mlockall already covers it when RT scheduling is in effect; this is belt-and-suspenders and silently does nothing if locking is not permitted).

◆ allocated()

bool mm::node::ProcessDataRing::allocated ( ) const
inline

Whether allocate has been called with a non-zero capacity.

◆ capacity()

size_t mm::node::ProcessDataRing::capacity ( ) const
inline

Number of cycles the ring can hold.

◆ clear()

void mm::node::ProcessDataRing::clear ( )

Releases the storage and resets to the unallocated state. Idempotent.

◆ head()

uint64_t mm::node::ProcessDataRing::head ( ) const
inline

The producer's next sequence number; head()-1 is the newest recorded cycle, and head()==0 means nothing has been recorded yet.

◆ oldestValidSeq()

uint64_t mm::node::ProcessDataRing::oldestValidSeq ( ) const

The oldest sequence number still in the ring: max(0, head - capacity). A cursor below this has been lapped (its data overwritten) and must resync to this value.

◆ operator=()

ProcessDataRing & mm::node::ProcessDataRing::operator= ( const ProcessDataRing )
delete

◆ readRecord()

bool mm::node::ProcessDataRing::readRecord ( uint64_t  seq,
Record out 
) const

Copies the record for seq into out. Lock-free; retries are the caller's job.

Returns
true if seq is present and was copied without a concurrent overwrite; false if seq is not in the ring (never written, already overwritten) or the copy raced the producer (a torn read — the caller may retry or skip the cycle).

◆ write()

void mm::node::ProcessDataRing::write ( uint64_t  timestampNs,
int  wkc,
std::span< const uint8_t >  inputs,
std::span< const uint8_t >  outputs 
)

Records one cycle. RT, wait-free, single-writer. No-op until allocate.

inputs / outputs are clamped to the per-direction capacities the ring was allocated with (they match the live image exactly in normal use).


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