You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
o3de/Code/CryEngine/CryCommon/RingBuffer.h

274 lines
8.3 KiB
C++

/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
// Original file Copyright (C), Crytek, 1999-2015.
#pragma once
// A fixed-size type-safe ring buffer (ie, fixed-size double-ended queue).
// Note: It's possible to add support for iterators, indexing if needed.
template<typename T, size_t N, typename I = uint32>
class CRingBuffer
{
static_assert(std::is_integral<I>::value && std::is_unsigned<I>::value, "I is not unsigned integral type");
static_assert(N != 0 && N <= I(-1), "N is not a valid value (or I is too small)");
enum : I
{
kPowerOf2 = (N & (N - 1)) == 0,
kMaxSize = static_cast<I>(N),
};
public:
typedef T value_type;
typedef T& reference;
typedef const T& const_reference;
typedef T* pointer;
typedef const T* const_pointer;
typedef I size_type;
// Constructs an empty ring buffer.
CRingBuffer()
: m_begin(0)
, m_count(0)
{}
// Destroy a ring buffer.
~CRingBuffer()
{
clear();
}
// Retrieve the size of the collection.
size_type size() const
{
return m_count;
}
// Retrieve the maximum size of the collection.
size_type max_size() const
{
return kMaxSize;
}
// Test if the collection is empty.
bool empty() const
{
return m_count == 0;
}
// Test if the collection is full.
bool full() const
{
return m_count == kMaxSize;
}
// Get the front-most item of the collection.
// If the collection is empty, the behavior is undefined.
reference front()
{
CRY_ASSERT_MESSAGE(m_count != 0, "Container is empty");
return *ptr(m_begin);
}
// Get the front-most item of the collection.
// If the collection is empty, the behavior is undefined.
const_reference front() const
{
CRY_ASSERT_MESSAGE(m_count != 0, "Container is empty");
return *ptr(m_begin);
}
// Get the back-most item of the collection.
// If the collection is empty, the behavior is undefined.
reference back()
{
CRY_ASSERT_MESSAGE(m_count != 0, "Container is empty");
return *ptr(wrap(m_begin + m_count - 1));
}
// Get the back-most item of the collection.
// If the collection is empty, the behavior is undefined.
const_reference back() const
{
CRY_ASSERT_MESSAGE(m_count != 0, "Container is empty");
return *ptr(wrap(m_begin + m_count - 1));
}
// Adds an item to the front of the collection.
// In case the collection is full, the function returns false and the collection remains unmodified.
template<typename X>
bool push_front(X&& value)
{
static_assert(std::is_constructible<T, X&&>::value, "T cannot be constructed from the given type");
if (full())
{
return false;
}
const I index = decrement(m_begin);
::new(static_cast<void*>(ptr(index)))T(std::forward<X>(value));
m_begin = index;
++m_count;
return true;
}
// Adds an item to the front of the collection.
// In case the collection is full, the function overwrites the last item in the collection.
template<typename X>
void push_front_overwrite(X&& value)
{
static_assert(std::is_constructible<T, X&&>::value, "T cannot be constructed from the given type");
const I index = decrement(m_begin);
if (full())
{
ptr(index)->~T();
--m_count;
}
::new(static_cast<void*>(ptr(index)))T(std::forward<X>(value));
m_begin = index;
++m_count;
}
// Removes an item from the front of the collection.
// If the collection is empty, the behavior is undefined.
void pop_front()
{
CRY_ASSERT_MESSAGE(m_count != 0, "Container is empty");
ptr(m_begin)->~T();
m_begin = increment(m_begin);
--m_count;
}
// Attempts to remove an item from the front of the collection, and assigns it to 'value'.
// Returns true if an item was removed, false if the collection was empty (and 'value' remains unmodified).
bool try_pop_front(T& value)
{
if (m_count != 0)
{
T* const pItem = ptr(m_begin);
value = std::move(*pItem);
pItem->~T();
m_begin = increment(m_begin);
--m_count;
return true;
}
return false;
}
// Adds an item to the back of the collection.
// In case the collection is full, the function returns false and the collection remains unmodified.
template<typename X>
bool push_back(X&& value)
{
static_assert(std::is_constructible<T, X&&>::value, "T cannot be constructed from the given type");
if (full())
{
return false;
}
const I index = wrap(m_begin + m_count);
::new(static_cast<void*>(ptr(index)))T(std::forward<X>(value));
++m_count;
return true;
}
// Adds an item to the back of the collection.
// In case the collection is full, the function overwrites the first item in the collection.
template<typename X>
void push_back_overwrite(X&& value)
{
static_assert(std::is_constructible<T, X&&>::value, "T cannot be constructed from the given type");
const I index = wrap(m_begin + m_count);
if (full())
{
ptr(index)->~T();
m_begin = increment(index);
--m_count;
}
::new(static_cast<void*>(ptr(index)))T(std::forward<X>(value));
++m_count;
}
// Removes an item from the back of the collection.
// If the collection is empty, the behavior is undefined.
void pop_back()
{
CRY_ASSERT_MESSAGE(m_count != 0, "Container is empty");
const I index = wrap(m_begin + m_count - 1);
ptr(index)->~T();
--m_count;
}
// Attempts to remove an item from the back of the collection, and assigns it to 'value'.
// Returns true if an item was removed, false if the collection was empty (and 'value' remains unmodified).
bool try_pop_back(T& value)
{
if (m_count != 0)
{
const I index = wrap(m_begin + m_count - 1);
T* const pItem = ptr(index);
value = std::move(*pItem);
pItem->~T();
--m_count;
return true;
}
return false;
}
// Destroy all items in a ring buffer.
void clear()
{
size_type index = m_begin;
for (size_type i = 0; i < m_count; ++i, index = increment(index))
{
ptr(index)->~T();
}
m_begin = 0;
m_count = 0;
}
private:
// Decrements a given index, wrapping it around N.
static size_type decrement(size_type index)
{
return kPowerOf2 ? ((index - 1) & (kMaxSize - 1)) : index ? index - 1 : kMaxSize - 1;
}
// Increments a given index, wrapping it around N.
static size_type increment(size_type index)
{
++index;
return kPowerOf2 ? index & (kMaxSize - 1) : index == kMaxSize ? 0 : index;
}
// Wraps an index, which has a maximum value of 2N-1.
static size_type wrap(size_type index)
{
return kPowerOf2 ? index & (kMaxSize - 1) : index >= kMaxSize ? index - kMaxSize : index;
}
// Obtain pointer to raw storage at given index.
pointer ptr(size_type index)
{
return reinterpret_cast<pointer>(&m_storage) + index;
}
// Obtain pointer to raw storage at given index.
const_pointer ptr(size_type index) const
{
return reinterpret_cast<const_pointer>(&m_storage) + index;
}
// No copy/assign supported.
CRingBuffer(const CRingBuffer&);
void operator=(const CRingBuffer&);
size_type m_begin, m_count;
typename std::aligned_storage<sizeof(T)* N, std::alignment_of<T>::value>::type m_storage;
};