diff --git a/applications/test/CircularBuffer/Make/files b/applications/test/CircularBuffer/Make/files
new file mode 100644
index 0000000000..72687d6670
--- /dev/null
+++ b/applications/test/CircularBuffer/Make/files
@@ -0,0 +1,3 @@
+Test-CircularBuffer.C
+
+EXE = $(FOAM_USER_APPBIN)/Test-CircularBuffer
diff --git a/applications/test/CircularBuffer/Make/options b/applications/test/CircularBuffer/Make/options
new file mode 100644
index 0000000000..18e6fe47af
--- /dev/null
+++ b/applications/test/CircularBuffer/Make/options
@@ -0,0 +1,2 @@
+/* EXE_INC = */
+/* EXE_LIBS = */
diff --git a/applications/test/CircularBuffer/Test-CircularBuffer.C b/applications/test/CircularBuffer/Test-CircularBuffer.C
new file mode 100644
index 0000000000..eed9b26ed1
--- /dev/null
+++ b/applications/test/CircularBuffer/Test-CircularBuffer.C
@@ -0,0 +1,136 @@
+/*---------------------------------------------------------------------------*\
+ ========= |
+ \\ / F ield | OpenFOAM: The Open Source CFD Toolbox
+ \\ / O peration |
+ \\ / A nd | www.openfoam.com
+ \\/ M anipulation |
+-------------------------------------------------------------------------------
+ Copyright (C) 2022 OpenCFD Ltd.
+-------------------------------------------------------------------------------
+License
+ This file is part of OpenFOAM.
+
+ OpenFOAM is free software: you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with OpenFOAM. If not, see .
+
+Application
+ Test-CircularBuffer
+
+Description
+ Basic tests for CircularBuffer behaviour and characteristics
+
+\*---------------------------------------------------------------------------*/
+
+#include "argList.H"
+#include "ListOps.H"
+#include "CircularBuffer.H"
+#include "StringStream.H"
+#include "FlatOutput.H"
+
+using namespace Foam;
+
+template
+inline Ostream& report
+(
+ const CircularBuffer& buf,
+ bool debugOutput = true
+)
+{
+ buf.writeList(Info, 0);
+ if (debugOutput)
+ {
+ Info<< " : ";
+ buf.info(Info);
+ }
+ else
+ {
+ Info<< nl;
+ }
+ return Info;
+}
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+int main(int argc, char *argv[])
+{
+ CircularBuffer buf1(1); report(buf1);
+ buf1.append(10); report(buf1);
+
+ Info<< buf1.range_one() << nl;
+
+ buf1.append(20); report(buf1);
+ buf1.append(30); report(buf1);
+ buf1.push_back(40); report(buf1);
+ buf1.push_front(-50); report(buf1);
+ buf1.append(60); report(buf1);
+ buf1.append(labelList({70,80,90})); report(buf1);
+
+ Info<< nl << "access: " << buf1 << nl;
+
+ Info<< buf1[-12] << nl;
+
+ Info<< "found: " << buf1.found(40) << nl;
+ buf1.appendUniq(100); report(buf1);
+
+ buf1 = Zero; report(buf1);
+
+ buf1 = 500; report(buf1);
+
+ while (buf1.size() > 2)
+ {
+ (void) buf1.pop_front();
+ }
+ report(buf1);
+
+ buf1.append(identity(5)); report(buf1);
+
+ buf1.info(Info);
+ Info<< buf1 << nl;
+
+ CircularBuffer buf2(15);
+ report(buf2);
+
+ buf2 = std::move(buf1);
+ Info<< "buf1: "; report(buf1);
+ Info<< "buf2: "; report(buf2);
+
+ Info<< "for-range:";
+ for (const label val : buf2)
+ {
+ Info<< ' ' << val;
+ }
+ Info<< endl;
+
+ {
+ auto iter = buf2.cbegin();
+ auto endIter = buf2.cend();
+
+ Info<< "iterated:";
+ while (iter != endIter)
+ {
+ Info<< ' ' << *(++iter);
+ }
+ Info<< endl;
+ }
+
+ Info<< "normal: " << flatOutput(buf2) << nl;
+ buf2.reverse();
+ Info<< "reverse: " << flatOutput(buf2) << nl;
+
+ Info<< nl << "End\n" << endl;
+
+ return 0;
+}
+
+
+// ************************************************************************* //
diff --git a/src/OpenFOAM/containers/Buffers/CircularBuffer.C b/src/OpenFOAM/containers/Buffers/CircularBuffer.C
new file mode 100644
index 0000000000..c068e91ec2
--- /dev/null
+++ b/src/OpenFOAM/containers/Buffers/CircularBuffer.C
@@ -0,0 +1,143 @@
+/*---------------------------------------------------------------------------*\
+ ========= |
+ \\ / F ield | OpenFOAM: The Open Source CFD Toolbox
+ \\ / O peration |
+ \\ / A nd | www.openfoam.com
+ \\/ M anipulation |
+-------------------------------------------------------------------------------
+ Copyright (C) 2022 OpenCFD Ltd.
+-------------------------------------------------------------------------------
+License
+ This file is part of OpenFOAM.
+
+ OpenFOAM is free software: you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with OpenFOAM. If not, see .
+
+\*---------------------------------------------------------------------------*/
+
+// * * * * * * * * * * * * * Private Member Functions * * * * * * * * * * * //
+
+template
+void Foam::CircularBuffer::doReserve
+(
+ const bool nocopy,
+ const label len
+)
+{
+ if (storage_.size() < len)
+ {
+ // Increase capacity (doubling)
+ const label newCapacity =
+ max(min_size(), max(len+1, label(2*storage_.size())));
+
+ if (nocopy || empty())
+ {
+ // Simple - no content to preserve
+
+ clear(); // Reset begin/end
+ storage_.resize_nocopy(newCapacity);
+ }
+ else
+ {
+ // Preserve content
+ const labelRange range1 = range_one();
+ const labelRange range2 = range_two();
+
+ List old(newCapacity);
+ storage_.swap(old);
+ begin_ = 0;
+ end_ = 0;
+
+ for (const label i : range1)
+ {
+ storage_[end_++] = std::move(old[i]);
+ }
+ for (const label i : range2)
+ {
+ storage_[end_++] = std::move(old[i]);
+ }
+ }
+ }
+}
+
+
+// * * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * //
+
+template
+Foam::SubList Foam::CircularBuffer::array_one()
+{
+ const label len = size_one();
+ return (len ? storage_.slice(begin_, len) : SubList());
+}
+
+
+template
+Foam::SubList Foam::CircularBuffer::array_two()
+{
+ const label len = size_two();
+ return (len ? storage_.slice(0, len) : SubList());
+}
+
+
+template
+const Foam::SubList Foam::CircularBuffer::array_one() const
+{
+ const label len = size_one();
+ return (len ? storage_.slice(begin_, len) : SubList());
+}
+
+
+template
+const Foam::SubList Foam::CircularBuffer::array_two() const
+{
+ const label len = size_two();
+ return (len ? storage_.slice(0, len) : SubList());
+}
+
+
+template
+Foam::label Foam::CircularBuffer::find(const T& val, label pos) const
+{
+ label i = -1;
+
+ const auto list1 = this->array_one();
+
+ if (pos < list1.size())
+ {
+ i = list1.find(val, pos);
+ }
+
+ if (i < 0)
+ {
+ // Not found - search the second list
+ return this->array_two().find(val, 0);
+ }
+
+ return i;
+}
+
+
+template
+void Foam::CircularBuffer::reverse()
+{
+ const label n = this->size();
+ const label nBy2 = n/2;
+
+ for (label i = 0; i < nBy2; ++i)
+ {
+ Foam::Swap(operator[](i), operator[](n-1-i));
+ }
+}
+
+
+// ************************************************************************* //
diff --git a/src/OpenFOAM/containers/Buffers/CircularBuffer.H b/src/OpenFOAM/containers/Buffers/CircularBuffer.H
new file mode 100644
index 0000000000..3ec106dfce
--- /dev/null
+++ b/src/OpenFOAM/containers/Buffers/CircularBuffer.H
@@ -0,0 +1,518 @@
+/*---------------------------------------------------------------------------*\
+ ========= |
+ \\ / F ield | OpenFOAM: The Open Source CFD Toolbox
+ \\ / O peration |
+ \\ / A nd | www.openfoam.com
+ \\/ M anipulation |
+-------------------------------------------------------------------------------
+ Copyright (C) 2022 OpenCFD Ltd.
+-------------------------------------------------------------------------------
+License
+ This file is part of OpenFOAM.
+
+ OpenFOAM is free software: you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with OpenFOAM. If not, see .
+
+Class
+ Foam::CircularBuffer
+
+Description
+ A simple list of objects of type \ that is intended to be used
+ as a circular buffer (eg, a FIFO) when the alloc/free overhead
+ associated with a linked-list approach is to be avoided.
+
+ The internal storage is addressed by independent begin/end markers.
+ - The %begin marker points to the \em front.
+ - The %end marker is a one-past the \em back.
+ .
+ This results in a variety ofr different possible buffer states:
+ -# \em empty (\em begin == \em end)
+
+ -# \em simple/linear (\em begin \< \em end) has no wrapping:
+ \verbatim
+ |.|.|.|a|b|c|d|.|.|.|
+ beg ___^
+ end ___________^
+ \endverbatim
+
+ -# \em split (\em begin \> \em end):
+ \verbatim
+ |f|g|h|i|.|.|.|a|b|c|d|e|
+ end _____^
+ beg ___________^
+ \endverbatim
+ .
+
+ The methods range_one(), range_two() return the internal indexing and
+ the methods array_one(), array_two() provide direct access to the
+ internal contents.
+
+ When filling the buffer, the internal storage will be resized
+ (doubling strategy) as required. When this occurs, the new list
+ will be linearized with \em begin = 0.
+
+ Simultaneously when filling, the storage buffer will be over-allocated
+ to avoid ambiguity when (\em begin == \em end), which represents an
+ \em %empty buffer and not a \em %full buffer. Eg,
+ \verbatim
+ |c|d|.|a|b|
+ end _^
+ beg ___^
+ \endverbatim
+ after appending one more, it would be incorrect to simply fill
+ the available space:
+ \verbatim
+ |c|d|e|a|b|
+ end ___^ WRONG : would represent empty!
+ beg ___^
+ \endverbatim
+ the storage is instead increased (doubled) and rebalanced before
+ the append occurs (old capacity 5, new capacity 10):
+ \verbatim
+ |a|b|c|d|e|.|.|.|.|.|
+ _^_ beg
+ end _______^
+ \endverbatim
+
+SourceFiles
+ CircularBuffer.C
+ CircularBufferI.H
+ CircularBufferIO.C
+
+\*---------------------------------------------------------------------------*/
+
+#ifndef Foam_CircularBuffer_H
+#define Foam_CircularBuffer_H
+
+#include "labelRange.H"
+#include "List.H"
+#include "SubList.H"
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+namespace Foam
+{
+
+// Forward Declarations
+template class CircularBuffer;
+
+template
+Istream& operator>>(Istream& is, CircularBuffer& list);
+
+template
+Ostream& operator<<(Ostream& os, const CircularBuffer& list);
+
+
+/*---------------------------------------------------------------------------*\
+ Class CircularBuffer Declaration
+\*---------------------------------------------------------------------------*/
+
+template
+class CircularBuffer
+{
+ // Private Data
+
+ //- The allocated buffer storage
+ List storage_;
+
+ //- The first readable element
+ label begin_;
+
+ //- One past last writable element
+ label end_;
+
+
+ // Private Member Functions
+
+ //- Map the logical location to the buffer location
+ inline label toGlobal(const label i) const;
+
+ //- Length of array one
+ inline label size_one() const noexcept;
+
+ //- Length of array two
+ inline label size_two() const noexcept;
+
+ //- Reserve allocation space for at least this size.
+ // Never shrinks the allocated size, use setCapacity() for that.
+ // The 'nocopy' option will not attempt to recover old content
+ void doReserve(const bool nocopy, const label len);
+
+ //- Copy all list contents
+ template
+ inline void copyList(const OtherListType& rhs);
+
+
+public:
+
+ // STL type definitions
+
+ //- The value type the list contains
+ typedef T value_type;
+
+ //- The pointer type for non-const access to value_type items
+ typedef T* pointer;
+
+ //- The pointer type for const access to value_type items
+ typedef const T* const_pointer;
+
+ //- The type used for storing into value_type objects
+ typedef T& reference;
+
+ //- The type used for reading from constant value_type objects
+ typedef const T& const_reference;
+
+ //- The type to represent the size of a buffer
+ typedef label size_type;
+
+ //- The difference between iterator objects
+ typedef label difference_type;
+
+ //- Forward iterator with const access
+ class const_iterator;
+
+
+ // Constructors
+
+ //- Default construct, empty buffer without allocation
+ inline constexpr CircularBuffer() noexcept;
+
+ //- Construct an empty buffer with given reserve size
+ inline explicit CircularBuffer(const label len);
+
+ //- Copy construct
+ inline CircularBuffer(const CircularBuffer& list);
+
+ //- Move construct
+ inline CircularBuffer(CircularBuffer&& list);
+
+ //- Construct from Istream - uses readList
+ explicit CircularBuffer(Istream& is);
+
+
+ // Member Functions
+
+ // Characteristics
+
+ //- Lower capacity limit
+ static constexpr label min_size() noexcept { return 16; }
+
+ //- Size of the underlying storage.
+ inline label capacity() const noexcept;
+
+ //- Empty or exhausted buffer
+ inline bool empty() const noexcept;
+
+ //- The current number of buffer items
+ inline label size() const noexcept;
+
+
+ // Internal Access
+
+ //- The nominal space available to fill.
+ //- Subtract 1 for the number to append before re-balancing is needed.
+ inline label space() const noexcept;
+
+ //- The addressing range covered by array_one()
+ inline labelRange range_one() const noexcept;
+
+ //- The addressing range covered by array_two()
+ inline labelRange range_two() const noexcept;
+
+ //- The contents of the first internal array
+ SubList array_one();
+
+ //- The contents of the first internal array
+ SubList array_two();
+
+ //- The contents of the second internal array
+ const SubList array_one() const;
+
+ //- The contents of the second internal array
+ const SubList array_two() const;
+
+
+
+ // Access
+
+ //- Access the first element (front). Requires !empty().
+ T& first();
+
+ //- Access the last element (back). Requires !empty().
+ T& last();
+
+ //- Const access to the first element (front). Requires !empty().
+ const T& first() const;
+
+ //- Const access to the last element (back). Requires !empty().
+ const T& last() const;
+
+
+ // Sizing
+
+ //- Reserve allocation space for at least this size, allocating new
+ //- space if required and \em retaining old content.
+ // Never shrinks.
+ inline void reserve(const label len);
+
+ //- Reserve allocation space for at least this size, allocating new
+ //- space if required \em without retaining old content.
+ // Never shrinks.
+ inline void reserve_nocopy(const label len);
+
+ //- Clear the addressed buffer, does not change allocation
+ inline void clear() noexcept;
+
+ //- Clear the buffer and delete storage.
+ inline void clearStorage();
+
+ //- Swap content, independent of sizing parameter
+ inline void swap(CircularBuffer& other);
+
+
+ // Search
+
+ //- Find index of the first occurrence of the value.
+ // Any occurrences before the start pos are ignored.
+ // Linear search.
+ // \return position in list or -1 if not found.
+ label find(const T& val, label pos = 0) const;
+
+ //- True if the value if found in the list.
+ // Any occurrences before the start pos are ignored.
+ // Linear search.
+ // \return true if found.
+ inline bool found(const T& val, label pos = 0) const;
+
+
+ // Stack-like Operations
+
+ //- Copy prepend an element to the front of the buffer
+ inline void push_front(const T& val);
+
+ //- Move prepend an element to the front of the buffer
+ inline void push_front(T&& val);
+
+ //- Copy append an element to the end of the buffer
+ inline void push_back(const T& val);
+
+ //- Move Append an element to the end of the buffer
+ inline void push_back(T&& val);
+
+ //- Shrink by moving the front of the buffer 1 or more times
+ inline void pop_front(label n = 1);
+
+ //- Shrink by moving the end of the buffer 1 or more times
+ inline void pop_back(label n = 1);
+
+ //- Copy append an element to the end of the buffer
+ void append(const T& val) { this->push_back(val); }
+
+ //- Move append an element to the end of the buffer
+ void append(T&& val) { this->push_back(std::move(val)); }
+
+ //- Copy append multiple elements the end of the buffer
+ inline void append(const UList& list);
+
+ //- Copy append IndirectList elements the end of the buffer
+ template
+ inline void append(const IndirectListBase& list);
+
+ //- Append an element if not already in the buffer.
+ // \return the change in the buffer length
+ inline label appendUniq(const T& val);
+
+
+ // Other Operations
+
+ //- Reverse the buffer order, swapping elements
+ void reverse();
+
+
+ // Member Operators
+
+ //- Non-const access to an element in the list.
+ // The index is allowed to wrap in both directions
+ inline T& operator[](const label i);
+
+ //- Const access to an element in the list
+ // The index is allowed to wrap in both directions
+ inline const T& operator[](const label i) const;
+
+ //- Copy construct
+ inline void operator=(const CircularBuffer& list);
+
+ //- Move construct
+ inline void operator=(CircularBuffer&& list);
+
+ //- Assign all addressed elements to the given value
+ inline void operator=(const T& val);
+
+ //- Assignment of all entries to zero
+ inline void operator=(const Foam::zero);
+
+ //- Deep copy values from a list of the addressed elements
+ inline void operator=(const UList& rhs);
+
+ //- Deep copy values from a list of the addressed elements
+ template
+ inline void operator=(const IndirectListBase& rhs);
+
+
+ // IOstream Operators
+
+ //- Print information
+ Ostream& info(Ostream& os) const;
+
+ //- Write List, with line-breaks in ASCII when length exceeds shortLen.
+ // Using '0' suppresses line-breaks entirely.
+ Istream& readList(Istream& is);
+
+ //- Write List, with line-breaks in ASCII when length exceeds shortLen.
+ // Using '0' suppresses line-breaks entirely.
+ Ostream& writeList(Ostream& os, const label shortLen=0) const;
+
+ //- Use the readList() method to read contents from Istream.
+ friend Istream& operator>>
+ (
+ Istream& is,
+ CircularBuffer& list
+ );
+
+ //- Write to Ostream
+ friend Ostream& operator<<
+ (
+ Ostream& os,
+ const CircularBuffer& list
+ );
+
+
+ // Iterators
+
+ //- A simple forward const iterator for a circular buffer
+ class const_iterator
+ {
+ const CircularBuffer* container_;
+ label iter_;
+
+ public:
+
+ using difference_type = label;
+ using value_type = const T;
+ using pointer = const T*;
+ using reference = const T&;
+ using iterator_category = std::forward_iterator_tag;
+
+ const_iterator(const const_iterator&) = default;
+ const_iterator& operator=(const const_iterator&) = default;
+
+ const_iterator
+ (
+ const CircularBuffer* buffer,
+ label i
+ )
+ :
+ container_(buffer),
+ iter_(i)
+ {}
+
+ reference operator*() const
+ {
+ return (*container_)[iter_];
+ }
+
+ const_iterator& operator++()
+ {
+ ++iter_;
+ return *this;
+ }
+
+ const_iterator operator++(int)
+ {
+ auto old(*this);
+ ++iter_;
+ return old;
+ }
+
+ bool operator==(const const_iterator& rhs) const
+ {
+ return iter_ == rhs.iter_;
+ }
+
+ bool operator!=(const const_iterator& rhs) const
+ {
+ return iter_ != rhs.iter_;
+ }
+ };
+
+
+ // Iterator (const)
+
+ //- Return a const_iterator at begin of buffer
+ inline const_iterator cbegin() const
+ {
+ return const_iterator(this, 0);
+ }
+
+ //- Return a const_iterator at end of buffer
+ inline const_iterator cend() const
+ {
+ return const_iterator(this, this->size());
+ }
+
+ //- Return a const_iterator at begin of buffer
+ inline const_iterator begin() const { return cbegin(); }
+
+ //- Return a const_iterator at end of buffer
+ inline const_iterator end() const { return cend(); }
+};
+
+
+// * * * * * * * * * * * * * * * IOstream Operators * * * * * * * * * * * * //
+
+//- Read List contents from Istream
+template
+Istream& operator>>(Istream& is, CircularBuffer& list)
+{
+ return list.readList(is);
+}
+
+
+//- Write List to Ostream, as per UList::writeList() with default length.
+template
+Ostream& operator<<(Ostream& os, const CircularBuffer& list)
+{
+ return list.writeList(os);
+}
+
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+} // End namespace Foam
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+#include "CircularBufferI.H"
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+#ifdef NoRepository
+ #include "CircularBuffer.C"
+ #include "CircularBufferIO.C"
+#endif
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+#endif
+
+// ************************************************************************* //
diff --git a/src/OpenFOAM/containers/Buffers/CircularBufferI.H b/src/OpenFOAM/containers/Buffers/CircularBufferI.H
new file mode 100644
index 0000000000..8f4b46f964
--- /dev/null
+++ b/src/OpenFOAM/containers/Buffers/CircularBufferI.H
@@ -0,0 +1,556 @@
+/*---------------------------------------------------------------------------*\
+ ========= |
+ \\ / F ield | OpenFOAM: The Open Source CFD Toolbox
+ \\ / O peration |
+ \\ / A nd | www.openfoam.com
+ \\/ M anipulation |
+-------------------------------------------------------------------------------
+ Copyright (C) 2022 OpenCFD Ltd.
+-------------------------------------------------------------------------------
+License
+ This file is part of OpenFOAM.
+
+ OpenFOAM is free software: you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with OpenFOAM. If not, see .
+
+\*---------------------------------------------------------------------------*/
+
+// * * * * * * * * * * * * * Private Member Functions * * * * * * * * * * * //
+
+template
+inline Foam::label Foam::CircularBuffer::toGlobal(label i) const
+{
+ const label len = this->size();
+
+ if (!len)
+ {
+ // Bounds error
+ return -1;
+ }
+ else if (i < 0)
+ {
+ // Wrap any number of times
+ while (i < 0) i += len;
+ }
+ else
+ {
+ // Wrap any number of times
+ while (i >= len) i -= len;
+ }
+
+ i += begin_;
+
+ if (i >= storage_.size())
+ {
+ i -= storage_.size();
+ }
+
+ return i;
+}
+
+
+template
+inline Foam::label Foam::CircularBuffer::size_one() const noexcept
+{
+ return
+ (
+ (end_ >= begin_)
+ ? (end_ - begin_)
+ : (storage_.size() - begin_)
+ );
+}
+
+
+template
+inline Foam::label Foam::CircularBuffer::size_two() const noexcept
+{
+ return
+ (
+ (end_ && end_ < begin_)
+ ? end_
+ : static_cast(0)
+ );
+}
+
+
+template
+template
+inline void Foam::CircularBuffer::copyList(const OtherListType& rhs)
+{
+ this->clear();
+
+ const label len = rhs.size();
+
+ if (len)
+ {
+ reserve(len + 1);
+
+ // Never overfilled, simply write at end_ (one-past position)
+
+ // - after clear(), begin_ and end_ are both 0
+
+ for (label i = 0; i < len; ++i)
+ {
+ storage_[end_] = rhs[i]; // copy element
+ ++end_;
+ }
+ }
+}
+
+
+// * * * * * * * * * * * * * * * * Constructors * * * * * * * * * * * * * * //
+
+template
+inline constexpr Foam::CircularBuffer::CircularBuffer() noexcept
+:
+ storage_(),
+ begin_(0),
+ end_(0)
+{}
+
+
+template
+inline Foam::CircularBuffer::CircularBuffer(const label len)
+:
+ storage_(max(min_size(), len + 1)),
+ begin_(0),
+ end_(0)
+{}
+
+
+template
+inline Foam::CircularBuffer::CircularBuffer
+(
+ const CircularBuffer& list
+)
+:
+ storage_(list.storage_),
+ begin_(list.begin_),
+ end_(list.end_)
+{}
+
+
+template
+inline Foam::CircularBuffer::CircularBuffer
+(
+ CircularBuffer&& list
+)
+:
+ storage_(std::move(list.storage_)),
+ begin_(list.begin_),
+ end_(list.end_)
+{
+ list.begin_ = 0;
+ list.end_ = 0;
+}
+
+
+// * * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * //
+
+template
+inline Foam::label Foam::CircularBuffer::capacity() const noexcept
+{
+ return storage_.size();
+}
+
+
+template
+inline bool Foam::CircularBuffer::empty() const noexcept
+{
+ return storage_.empty() || (begin_ == end_);
+}
+
+
+template
+inline Foam::label Foam::CircularBuffer::size() const noexcept
+{
+ const label diff(end_ - begin_);
+
+ if (diff < 0)
+ {
+ return (storage_.size() + diff);
+ }
+
+ return diff;
+}
+
+
+template
+inline Foam::label Foam::CircularBuffer::space() const noexcept
+{
+ return (storage_.size() - size());
+}
+
+
+template
+inline Foam::labelRange Foam::CircularBuffer::range_one() const noexcept
+{
+ return
+ (
+ (begin_ == end_)
+ ? labelRange()
+ : labelRange(begin_, this->size_one())
+ );
+}
+
+
+template
+inline Foam::labelRange Foam::CircularBuffer::range_two() const noexcept
+{
+ return labelRange(0, this->size_two());
+}
+
+
+template
+inline void Foam::CircularBuffer::clear() noexcept
+{
+ begin_ = end_ = 0;
+}
+
+
+template
+inline void Foam::CircularBuffer::clearStorage()
+{
+ storage_.clear();
+ begin_ = end_ = 0;
+}
+
+
+template
+inline void Foam::CircularBuffer::swap(CircularBuffer& other)
+{
+ if (this == &other)
+ {
+ return; // Self-swap is a no-op
+ }
+
+ // Swap storage and addressing
+ storage_.swap(other.storage_);
+ std::swap(begin_, other.begin_);
+ std::swap(end_, other.end_);
+}
+
+
+template
+inline void Foam::CircularBuffer::reserve(const label len)
+{
+ this->doReserve(false, len);
+}
+
+
+template
+inline void Foam::CircularBuffer::reserve_nocopy(const label len)
+{
+ this->doReserve(true, len);
+}
+
+
+template
+inline bool Foam::CircularBuffer::found(const T& val, label pos) const
+{
+ return (this->find(val, pos) >= 0);
+}
+
+
+template
+inline T& Foam::CircularBuffer::first()
+{
+ if (empty())
+ {
+ FatalErrorInFunction << "Buffer is empty" << abort(FatalError);
+ }
+
+ return storage_[begin_];
+}
+
+
+template
+inline const T& Foam::CircularBuffer::first() const
+{
+ if (empty())
+ {
+ FatalErrorInFunction << "Buffer is empty" << abort(FatalError);
+ }
+
+ return storage_[begin_];
+}
+
+
+template
+inline T& Foam::CircularBuffer::last()
+{
+ if (empty())
+ {
+ FatalErrorInFunction << "Buffer is empty" << abort(FatalError);
+ }
+
+ return storage_.rcValue(end_);
+}
+
+
+template
+inline const T& Foam::CircularBuffer::last() const
+{
+ if (empty())
+ {
+ FatalErrorInFunction << "Buffer is empty" << abort(FatalError);
+ }
+
+ return storage_.rcValue(end_);
+}
+
+
+template
+inline void Foam::CircularBuffer::push_front(const T& val)
+{
+ reserve(size() + 2);
+
+ // Never overfilled. Move begin and write
+
+ begin_ = storage_.rcIndex(begin_);
+ storage_[begin_] = val; // copy assign element
+}
+
+
+template
+inline void Foam::CircularBuffer::push_front(T&& val)
+{
+ reserve(size() + 2);
+
+ // Never overfilled. Move begin and write
+
+ begin_ = storage_.rcIndex(begin_);
+ storage_[begin_] = std::move(val); // move assign element
+}
+
+
+template
+inline void Foam::CircularBuffer::push_back(const T& val)
+{
+ reserve(size() + 2);
+
+ // Never overfilled, simply write at end_ (one-past position)
+
+ storage_[end_] = val; // copy assign element
+ end_ = storage_.fcIndex(end_);
+}
+
+
+template
+inline void Foam::CircularBuffer::push_back(T&& val)
+{
+ reserve(size() + 2);
+
+ // Never overfilled, simply write at end_ (one-past position)
+
+ storage_[end_] = std::move(val); // move assign element
+ end_ = storage_.fcIndex(end_);
+}
+
+
+template
+inline void Foam::CircularBuffer::pop_front(label n)
+{
+ if (n >= size())
+ {
+ begin_ = end_;
+ }
+ else
+ {
+ while (n-- > 0)
+ {
+ begin_ = storage_.fcIndex(begin_);
+ }
+ }
+}
+
+
+template
+inline void Foam::CircularBuffer::pop_back(label n)
+{
+ if (n >= size())
+ {
+ end_ = begin_;
+ }
+ else
+ {
+ while (n-- > 0)
+ {
+ end_ = storage_.rcIndex(end_);
+ }
+ }
+}
+
+
+template
+inline Foam::label Foam::CircularBuffer::appendUniq(const T& val)
+{
+ if (this->found(val))
+ {
+ return 0;
+ }
+ else
+ {
+ this->push_back(val);
+ return 1; // Increased list length by one
+ }
+}
+
+
+template
+inline void Foam::CircularBuffer::append(const UList& rhs)
+{
+ const label len = rhs.size();
+
+ if (len)
+ {
+ reserve(size() + len + 1);
+
+ // Never overfilled, simply write at end_ (one-past position)
+
+ for (label i = 0; i < len; ++i)
+ {
+ storage_[end_] = rhs[i]; // copy element
+ end_ = storage_.fcIndex(end_);
+ }
+ }
+}
+
+
+template
+template
+inline void Foam::CircularBuffer::append
+(
+ const IndirectListBase& rhs
+)
+{
+ const label len = rhs.size();
+
+ if (len)
+ {
+ reserve(size() + len + 1);
+
+ // Never overfilled, simply write at end_ (one-past position)
+
+ for (label i = 0; i < len; ++i)
+ {
+ storage_[end_] = rhs[i]; // copy element
+ end_ = storage_.fcIndex(end_);
+ }
+ }
+}
+
+
+// * * * * * * * * * * * * * * * Member Operators * * * * * * * * * * * * * //
+
+template
+inline T& Foam::CircularBuffer::operator[](label i)
+{
+ const label idx = this->toGlobal(i);
+ return storage_[idx];
+}
+
+
+template
+inline const T& Foam::CircularBuffer::operator[](label i) const
+{
+ const label idx = this->toGlobal(i);
+ return storage_[idx];
+}
+
+
+template
+inline void Foam::CircularBuffer::operator=(const CircularBuffer& rhs)
+{
+ if (this == &rhs)
+ {
+ return; // Self-assignment is a no-op
+ }
+
+ this->clear();
+
+ const auto list1 = rhs.array_one();
+ const auto list2 = rhs.array_two();
+ const label len = (list1.size() + list2.size());
+
+ if (len)
+ {
+ reserve(len + 1);
+
+ // Never overfilled, simply write at end_ (one-past position)
+
+ // - after clear(), begin_ and end_ are both 0
+
+ for (const T& val : list1)
+ {
+ storage_[end_] = val;
+ ++end_;
+ }
+
+ for (const T& val : list2)
+ {
+ storage_[end_] = val;
+ ++end_;
+ }
+ }
+}
+
+
+template
+inline void Foam::CircularBuffer::operator=(CircularBuffer&& rhs)
+{
+ if (this == &rhs)
+ {
+ return; // Self-assignment is a no-op
+ }
+
+ this->clearStorage();
+ this->swap(rhs);
+}
+
+
+template
+inline void Foam::CircularBuffer::operator=(const T& val)
+{
+ this->array_one() = val;
+ this->array_two() = val;
+}
+
+
+template
+inline void Foam::CircularBuffer::operator=(const Foam::zero)
+{
+ this->array_one() = Zero;
+ this->array_two() = Zero;
+}
+
+
+template
+inline void Foam::CircularBuffer::operator=(const UList& rhs)
+{
+ this->copyList(rhs);
+}
+
+
+template
+template
+inline void Foam::CircularBuffer::operator=
+(
+ const IndirectListBase& rhs
+)
+{
+ this->copyList(rhs);
+}
+
+
+// ************************************************************************* //
diff --git a/src/OpenFOAM/containers/Buffers/CircularBufferIO.C b/src/OpenFOAM/containers/Buffers/CircularBufferIO.C
new file mode 100644
index 0000000000..78f60f6386
--- /dev/null
+++ b/src/OpenFOAM/containers/Buffers/CircularBufferIO.C
@@ -0,0 +1,258 @@
+/*---------------------------------------------------------------------------*\
+ ========= |
+ \\ / F ield | OpenFOAM: The Open Source CFD Toolbox
+ \\ / O peration |
+ \\ / A nd | www.openfoam.com
+ \\/ M anipulation |
+-------------------------------------------------------------------------------
+ Copyright (C) 2022 OpenCFD Ltd.
+-------------------------------------------------------------------------------
+License
+ This file is part of OpenFOAM.
+
+ OpenFOAM is free software: you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with OpenFOAM. If not, see .
+
+\*---------------------------------------------------------------------------*/
+
+#include "List.H"
+#include "Istream.H"
+#include "contiguous.H"
+
+// * * * * * * * * * * * * * * * * Constructors * * * * * * * * * * * * * * //
+
+template
+Foam::CircularBuffer::CircularBuffer(Istream& is)
+{
+ this->readList(is);
+}
+
+
+// * * * * * * * * * * * * * * * IOstream Operators * * * * * * * * * * * * //
+
+template
+Foam::Ostream& Foam::CircularBuffer::info(Ostream& os) const
+{
+ os << "size=" << size() << '/' << capacity()
+ << " begin=" << begin_
+ << " end=" << end_
+ /// << " one=" << this->range_one() << this->array_one()
+ /// << " two=" << this->range_two() << this->array_two()
+ << nl;
+
+ return os;
+}
+
+
+template
+Foam::Istream& Foam::CircularBuffer::readList(Istream& is)
+{
+ // Clear list
+ storage_.clear();
+ begin_ = 0;
+ end_ = 0;
+
+ // More work than it should be. We avoid completely filled buffers!
+
+ is.fatalCheck(FUNCTION_NAME);
+
+ token tok(is);
+
+ is.fatalCheck
+ (
+ "CircularBuffer::readList(Istream&) : "
+ "reading first token"
+ );
+
+ if (tok.isCompound())
+ {
+ // Compound: simply transfer contents
+
+ storage_.transfer
+ (
+ dynamicCast>>
+ (
+ tok.transferCompoundToken(is)
+ )
+ );
+
+ end_ = storage_.size();
+ if (end_)
+ {
+ // Resize larger to avoid full buffer
+ storage_.resize(end_ + min_size());
+ }
+ }
+ else if (tok.isLabel())
+ {
+ // Label: could be int(..), int{...} or just a plain '0'
+
+ const label len = tok.labelToken();
+
+ end_ = len;
+ if (end_)
+ {
+ // Resize larger to avoid full buffer
+ storage_.resize(end_ + min_size());
+ }
+
+ // Dispatch to UList reading...
+
+ UList list(storage_.data(), end_);
+
+ is.putBack(tok);
+ list.readList(is);
+ }
+ else if (tok.isPunctuation(token::BEGIN_LIST))
+ {
+ // "(...)" : read as SLList and transfer contents
+
+ is.putBack(tok); // Putback the opening bracket
+ SLList sll(is); // Read as singly-linked list
+
+ const label len = sll.size();
+
+ end_ = len;
+ if (end_)
+ {
+ // Resize larger to avoid full buffer
+ storage_.resize(end_ + min_size());
+
+ // Move assign each list element
+ for (label i = 0; i < len; ++i)
+ {
+ storage_[i] = std::move(sll.removeHead());
+ }
+ }
+ }
+ else
+ {
+ FatalIOErrorInFunction(is)
+ << "incorrect first token, expected or '(', found "
+ << tok.info() << nl
+ << exit(FatalIOError);
+ }
+
+ return is;
+}
+
+
+template
+Foam::Ostream& Foam::CircularBuffer::writeList
+(
+ Ostream& os,
+ const label shortLen
+) const
+{
+ const label len = this->size();
+ const auto list1 = this->array_one();
+ const auto list2 = this->array_two();
+
+ #ifdef FULLDEBUG
+ if (len != (list1.size() + list2.size()))
+ {
+ FatalErrorInFunction
+ << "Size check failed"
+ << abort(FatalError);
+ }
+ #endif
+
+ if (os.format() == IOstream::BINARY && is_contiguous::value)
+ {
+ // Binary and contiguous
+
+ os << nl << len << nl;
+
+ if (len)
+ {
+ // The TOTAL number of bytes to be written.
+ // - possibly add start delimiter
+ // This is much like IndirectListBase output
+
+ os.beginRawWrite(len*sizeof(T));
+
+ if (!list1.empty())
+ {
+ os.writeRaw(list1.cdata_bytes(), list1.size_bytes());
+ }
+ if (!list2.empty())
+ {
+ os.writeRaw(list2.cdata_bytes(), list2.size_bytes());
+ }
+
+ // End delimiter and/or cleanup.
+ os.endRawWrite();
+ }
+ }
+ else if
+ (
+ (len <= 1 || !shortLen)
+ ||
+ (
+ (len <= shortLen)
+ &&
+ (
+ is_contiguous::value
+ || Detail::ListPolicy::no_linebreak::value
+ )
+ )
+ )
+ {
+ // Single-line output
+
+ // Size and start delimiter
+ os << len << token::BEGIN_LIST;
+
+ // Contents
+ label i = 0;
+ for (const T& val : list1)
+ {
+ if (i++) os << token::SPACE;
+ os << val;
+ }
+ for (const T& val : list2)
+ {
+ if (i++) os << token::SPACE;
+ os << val;
+ }
+
+ // End delimiter
+ os << token::END_LIST;
+ }
+ else
+ {
+ // Multi-line output
+
+ // Size and start delimiter
+ os << nl << len << nl << token::BEGIN_LIST << nl;
+
+ // Contents
+ for (const T& val : list1)
+ {
+ os << val << nl;
+ }
+ for (const T& val : list2)
+ {
+ os << val << nl;
+ }
+
+ // End delimiter
+ os << token::END_LIST << nl;
+ }
+
+ os.check(FUNCTION_NAME);
+ return os;
+}
+
+
+// ************************************************************************* //