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):
- Producer task writes payload at IndexOfNextItem and then calls AppendIndex
- Consumer task reads payload at IndexOfFirstItem and then calls PopFirstItem
- This is only thread-safe while the buffer is NOT full
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
bInitRetainsBOOLif TRUE, the retain variables are initialized (warm start / cold start)
bInCopyCodeBOOLif TRUE, the instance afterwards gets moved into the copy code (online change)
bufferSizeDINTsize 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
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