Table of Contents

RingBuffer

Namespace
ZCore
FUNCTION_BLOCK ABSTRACT RingBuffer

This functionblock serves as the main building block for all kinds of RingBuffers in the Zeugwerk Framework. Since Structured Text (ST) does not offer any kind of Generics it is pretty common in ST to duplicate code for different Datatypes (or write code generation tools to do this automatically). The RingBuffer capsules the logic that has to be implemented for every RingBuffer, e.g. it has all the logic to add elements to the buffer or move elements out of the buffer if it is full.

The RingBuffer is an abstract function block and thus, has to be extended in order to use the functionality it provides. Some common implemetentations for specific datatypes are RingBufferInt and RingBufferLreal. Use these functionblocks as a template when implementating a custom RingBuffer.

Note

A RingBuffer assumes that the actual buffer is constructed in the way shown in the code example. ìndex=0 contains an actual, read and writeable instance. There needs to be one more element in the databuffer than the RingBuffer actually manages. The additional element is used to distinguish a full buffer from an empty buffer.

Important

Thread-safe usage is limited to a single producer and a single consumer (SPSC):

Thread-safety overview:

  • AppendIndex: thread-safe in SPSC while buffer is not full
  • PopFirstItem: thread-safe in SPSC while buffer is not full
  • Size, IndexOfItem, Empty, Full: read-only snapshots, not atomic across multiple calls
  • Clear: not thread-safe with concurrent producer or consumer access
  • PopLastItem: not thread-safe for SPSC producer-consumer usage

If full-buffer behavior is required under concurrency, external synchronization is required. In practice, producer code usually encapsulates this sequence in a dedicated Append... method of the concrete ringbuffer type (for example Append in RingBufferLreal), so callers do not manually access IndexOfNextItem and AppendIndex.

Example (thread-safe SPSC usage):

// Producer task
nextIdx := ringBuffer.IndexOfNextItem();
dataBuffer[nextIdx] := newValue; // write payload first
ringBuffer.AppendIndex();         // publish as last step

// Consumer task
IF NOT ringBuffer.Empty THEN
  firstIdx := ringBuffer.IndexOfFirstItem();
  value := dataBuffer[firstIdx];  // read payload first
  ringBuffer.PopFirstItem();      // consume as last step
END_IF

Multiple writer tasks can be used if they are serialized via IMutex:

// writer task
IF _mutex.Lock(THIS^) THEN
  ringBuffer.Append(newValue);
  _mutex.Unlock(THIS^);
END_IF

This keeps writer-side access effectively single-writer.

Constructor

FB_init

METHOD FB_init (
 [input] bInitRetains : BOOL,
 [input] bInCopyCode : BOOL,
 [input] bufferSize : DINT) : BOOL

Initializes the RingBuffer at construction time of the object by providing the bufferSize. It also inidializes the pointers of head and tail with an internally stored variable.

Inputs

bInitRetains BOOL

if TRUE, the retain variables are initialized (warm start / cold start)

bInCopyCode BOOL

if TRUE, the instance afterwards gets moved into the copy code (online change)

bufferSize DINT

size of the managable buffer. The actual databuffer needs to be one element larger than bufferSize, i.e. data : ARRAY[0..bufferSize] OF ???

Returns

BOOL

Properties

Empty

PROPERTY Empty : BOOL

Returns TRUE if no item is in the ringbuffer. It is the same as calling

ringbuffer.IndexOfFirstItem() = ringbuffer.IndexOfNextItem()

the read and write index are only equal to eachother if the buffer is empty.

Property Value

BOOL

Full

PROPERTY Full : BOOL

returns TRUE if the RingBuffer is completely filled. Adding a new item under this condition will remove the oldest item.

Property Value

BOOL

Size

PROPERTY Size : DINT

Returns the number of items in the RingBuffer

Property Value

DINT

Methods

AppendIndex

METHOD PROTECTED AppendIndex ()

This method can be used to add an item to the Ringbuffer. It takes care of incrementing write and read buffers, respectively. When adding new items to an actual RingBuffer (i.e. RingBufferLreal (Append)) to common pattern is to write into the databuffer at the IndexOfNextItem index and then call AppendIndex for notifying the RingBuffer about a new item. If the buffer is full, appending a new item drops the oldest item by advancing IndexOfFirstItem.

Note

Under concurrent SPSC usage this method is thread-safe only while the buffer is not full.

Clear

METHOD Clear ()

Clears the RingBuffer by setting the write and read index to zero. Data in the buffer will not be cleared, but it can not be accessed by using the RingBuffers methods anymore.

IndexOfFirstItem

METHOD IndexOfFirstItem () : DINT

Returns the head index of the RingBuffer - this represents the first item that has been added to the buffer (the index for the oldest item)

Returns

DINT

IndexOfItem

METHOD IndexOfItem (
 [input] pos : DINT) : DINT

Returns the index of an item in the databuffer. For pos=0 this method returns the same value as IndexOfFirstItem. Remember, this is not the first item in the buffer, its the first item between head and tail. This method can be used to iterate over all items in the buffer which are currently not processed. The following example shows this:

FOR i:=0 TO ringbuffer.Size()-1
DO
  value = _databuffer[ringbuffer.IndexOfItem(i)]
END_FOR

Inputs

pos DINT

pos=0 refers to the first item in the buffer

Returns

DINT

IndexOfNextItem

METHOD PROTECTED IndexOfNextItem () : DINT

returns the tail index of the RingBuffer. This is the index at which the next item gets appended. The item with this index is not valid for reading from.

Returns

DINT

PopFirstItem

METHOD PopFirstItem () : BOOL

removes the oldest item that has been added to the buffer. The method returns FALSE if no item was removed (Because it was empty) and returns TRUE if the item was successfully removed from the buffer.

Note

This method is intended to be called by the consumer task in SPSC usage.

Returns

BOOL

PopLastItem

METHOD PopLastItem () : BOOL

removes the newest item that has been added to the buffer. The method returns FALSE if no item was removed (Because it was empty) and returns TRUE if the item was successfully removed from the buffer.

Warning

This method modifies the write index and is not thread-safe for SPSC producer-consumer usage.

Returns

BOOL

SetLogicPtrs

METHOD SetLogicPtrs (
 [input] indexOfFirstPtr : POINTER TO DINT,
 [input] indexOfNextPtr : POINTER TO DINT)

This method can be used to remap the internal logic pointers that are used for read and write access, respectively. This is useful if the buffer should be hold persistent in the memory, because in this case read- and writebuffer have to be persistent as well. Also, if external objects should be able to "see" when an entry is added to the buffer.

Note

This method should only be called directly after initializing the ringbuffer and before adding any entries to the buffer. Already added entries will be lost or mixed up

Inputs

indexOfFirstPtr POINTER TO DINT
indexOfNextPtr POINTER TO DINT