slang-list.h
#ifndef SLANG_CORE_LIST_H
#define SLANG_CORE_LIST_H
#include "../../slang.h"
#include "slang-allocator.h"
#include "slang-math.h"
#include "slang-array-view.h"
#include <algorithm>
#include <new>
#include <type_traits>
namespace Slang
{
// List is container of values of a type held consecutively in memory (much like std::vector)
//
// Note that in this implementation, the underlying memory is backed via an allocation of T[capacity]
// This means that all values have to be in a valid state *even if they are not used* (ie indices >= m_count must be valid)
//
// Also note this implementation does not necessarily 'initialize' an element which is no longer used,
// and this may lead to surprising behavior. Say the list contains a single smart pointer, and the last element is removed (say with removeLast).
// The smart pointer will *not* be released. The smart pointer will be released if the that index is used (via say an add) or
// the List goes out of scope.
template<typename T, typename TAllocator = StandardAllocator>
class List
{
private:
static const Index kInitialCount = 16;
public:
typedef List ThisType;
List()
: m_buffer(nullptr), m_count(0), m_capacity(0)
{
}
template<typename... Args>
List(const T& val, Args... args)
: m_buffer(nullptr), m_count(0), m_capacity(0)
{
_init(val, args...);
}
List(const List<T>& list)
: m_buffer(nullptr), m_count(0), m_capacity(0)
{
this->operator=(list);
}
List(List<T>&& list)
: m_buffer(nullptr), m_count(0), m_capacity(0)
{
this->operator=(static_cast<List<T>&&>(list));
}
List(ArrayView<T> view)
: List()
{
addRange(view);
}
static List<T> makeRepeated(const T& val, Index count)
{
List<T> rs;
rs.setCount(count);
for (Index i = 0; i < count; i++)
rs[i] = val;
return rs;
}
~List()
{
_deallocateBuffer();
}
List<T>& operator=(const List<T>& list)
{
clearAndDeallocate();
addRange(list);
return *this;
}
List<T>& operator=(List<T>&& list)
{
// Could just do a swap here, and memory would be freed on rhs dtor
_deallocateBuffer();
m_count = list.m_count;
m_capacity = list.m_capacity;
m_buffer = list.m_buffer;
list.m_buffer = nullptr;
list.m_count = 0;
list.m_capacity = 0;
return *this;
}
const T* begin() const { return m_buffer; }
const T* end() const { return m_buffer + m_count; }
T* begin() { return m_buffer; }
T* end() { return m_buffer + m_count; }
const T& getFirst() const
{
SLANG_ASSERT(m_count > 0);
return m_buffer[0];
}
T& getFirst()
{
SLANG_ASSERT(m_count > 0);
return m_buffer[0];
}
const T& getLast() const
{
SLANG_ASSERT(m_count > 0);
return m_buffer[m_count - 1];
}
T& getLast()
{
SLANG_ASSERT(m_count > 0);
return m_buffer[m_count - 1];
}
void removeLast()
{
SLANG_ASSERT(m_count > 0);
m_count--;
}
inline void swapWith(List<T, TAllocator>& other)
{
T* buffer = m_buffer;
m_buffer = other.m_buffer;
other.m_buffer = buffer;
auto bufferSize = m_capacity;
m_capacity = other.m_capacity;
other.m_capacity = bufferSize;
auto count = m_count;
m_count = other.m_count;
other.m_count = count;
}
T* detachBuffer()
{
T* rs = m_buffer;
m_buffer = nullptr;
m_count = 0;
m_capacity = 0;
return rs;
}
void attachBuffer(T* buffer, Index count, Index capacity)
{
// Can only attach a buffer if there isn't a buffer already associated
SLANG_ASSERT(m_buffer == nullptr);
SLANG_ASSERT(count <= capacity);
m_buffer = buffer;
m_count = count;
m_capacity = capacity;
}
inline ArrayView<T> getArrayView() const
{
return ArrayView<T>(m_buffer, m_count);
}
inline ArrayView<T> getArrayView(Index start, Index count) const
{
SLANG_ASSERT(start >= 0 && count >= 0 && start + count <= m_count);
return ArrayView<T>(m_buffer + start, count);
}
void _maybeReserveForAdd()
{
if (m_capacity <= m_count)
{
Index newBufferSize = kInitialCount;
if (m_capacity)
newBufferSize = (m_capacity << 1);
reserve(newBufferSize);
}
}
void add(T&& obj)
{
_maybeReserveForAdd();
m_buffer[m_count++] = static_cast<T&&>(obj);
}
void add(const T& obj)
{
_maybeReserveForAdd();
m_buffer[m_count++] = obj;
}
Index getCount() const { return m_count; }
Index getCapacity() const { return m_capacity; }
const T* getBuffer() const { return m_buffer; }
T* getBuffer() { return m_buffer; }
bool operator==(const ThisType& rhs) const
{
if (&rhs == this)
{
return true;
}
const Index count = getCount();
if (count != rhs.getCount())
{
return false;
}
for (Index i = 0; i < count; ++i)
{
if ((*this)[i] != rhs[i])
{
return false;
}
}
return true;
}
SLANG_FORCE_INLINE bool operator!=(const ThisType& rhs) const { return !(*this == rhs); }
void insert(Index idx, const T& val) { insertRange(idx, &val, 1); }
void insertRange(Index idx, const T* vals, Index n)
{
if (m_capacity < m_count + n)
{
Index newBufferCount = kInitialCount;
while (newBufferCount < m_count + n)
newBufferCount = newBufferCount << 1;
T* newBuffer = _allocate(newBufferCount);
if (m_capacity)
{
/*if (std::has_trivial_copy_assign<T>::value && std::has_trivial_destructor<T>::value)
{
memcpy(newBuffer, buffer, sizeof(T) * id);
memcpy(newBuffer + id + n, buffer + id, sizeof(T) * (_count - id));
}
else*/
{
for (Index i = 0; i < idx; i++)
newBuffer[i] = m_buffer[i];
for (Index i = idx; i < m_count; i++)
newBuffer[i + n] = T(static_cast<T&&>(m_buffer[i]));
}
_deallocateBuffer();
}
m_buffer = newBuffer;
m_capacity = newBufferCount;
}
else
{
/*if (std::has_trivial_copy_assign<T>::value && std::has_trivial_destructor<T>::value)
memmove(buffer + id + n, buffer + id, sizeof(T) * (_count - id));
else*/
{
for (Index i = m_count; i > idx; i--)
m_buffer[i + n - 1] = static_cast<T&&>(m_buffer[i - 1]);
}
}
/*if (std::has_trivial_copy_assign<T>::value && std::has_trivial_destructor<T>::value)
memcpy(buffer + id, vals, sizeof(T) * n);
else*/
for (Index i = 0; i < n; i++)
m_buffer[idx + i] = vals[i];
m_count += n;
}
void insertRange(Index id, const List<T>& list) { insertRange(id, list.m_buffer, list.m_count); }
void addRange(ArrayView<T> list) { insertRange(m_count, list.getBuffer(), list.getCount()); }
void addRange(const T* vals, Index n) { insertRange(m_count, vals, n); }
void addRange(const List<T>& list) { insertRange(m_count, list.m_buffer, list.m_count); }
void removeRange(Index idx, Index count)
{
SLANG_ASSERT(idx >= 0 && idx <= m_count);
const Index actualDeleteCount = ((idx + count) >= m_count)? (m_count - idx) : count;
for (Index i = idx + actualDeleteCount; i < m_count; i++)
m_buffer[i - actualDeleteCount] = static_cast<T&&>(m_buffer[i]);
m_count -= actualDeleteCount;
}
void removeAt(Index id) { removeRange(id, 1); }
void remove(const T& val)
{
Index idx = indexOf(val);
if (idx != -1)
removeAt(idx);
}
void reverse()
{
for (Index i = 0; i < (m_count >> 1); i++)
{
swapElements(m_buffer, i, m_count - i - 1);
}
}
void fastRemove(const T& val)
{
Index idx = indexOf(val);
if (idx >= 0)
{
fastRemoveAt(idx);
}
}
void fastRemoveAt(Index idx)
{
SLANG_ASSERT(idx >= 0 && idx < m_count);
// We do not test for idx == m_count - 1 (ie the move is to current index). With the assumption that any reasonable move implementation
// tests and ignores this case
if (idx != m_count - 1)
{
m_buffer[idx] = _Move(m_buffer[m_count - 1]);
}
m_count--;
}
void clear() { m_count = 0; }
void clearAndDeallocate()
{
_deallocateBuffer();
m_count = m_capacity = 0;
}
void reserve(Index size)
{
// The cast for this comparison is needed, otherwise some compilers erroneously detect
// the possiblity of a zero sized allocation (possible if m_capacity is assumed to be negative).
if(UIndex(size) > UIndex(m_capacity))
{
T* newBuffer = _allocate(size);
if (m_capacity)
{
/*if (std::has_trivial_copy_assign<T>::value && std::has_trivial_destructor<T>::value)
memcpy(newBuffer, buffer, _count * sizeof(T));
else*/
{
for (Index i = 0; i < m_count; i++)
newBuffer[i] = static_cast<T&&>(m_buffer[i]);
// Default-initialize the remaining elements
for(Index i = m_count; i < size; i++)
{
new(newBuffer + i) T();
}
}
_deallocateBuffer();
}
m_buffer = newBuffer;
m_capacity = size;
}
}
void growToCount(Index count)
{
Index newBufferCount = Index(1) << Math::Log2Ceil((unsigned int)count);
if (m_capacity < newBufferCount)
{
reserve(newBufferCount);
}
m_count = count;
}
void setCount(Index count)
{
reserve(count);
m_count = count;
}
void unsafeShrinkToCount(Index count) { m_count = count; }
void compress()
{
if (m_capacity > m_count && m_count > 0)
{
T* newBuffer = _allocate(m_count);
for (Index i = 0; i < m_count; i++)
newBuffer[i] = static_cast<T&&>(m_buffer[i]);
_deallocateBuffer();
m_buffer = newBuffer;
m_capacity = m_count;
}
}
SLANG_FORCE_INLINE const T& operator [](Index idx) const
{
SLANG_ASSERT(idx >= 0 && idx < m_count);
return m_buffer[idx];
}
SLANG_FORCE_INLINE T& operator [](Index idx)
{
SLANG_ASSERT(idx >= 0 && idx < m_count);
return m_buffer[idx];
}
template<typename Func>
Index findFirstIndex(const Func& predicate) const
{
for (Index i = 0; i < m_count; i++)
{
if (predicate(m_buffer[i]))
return i;
}
return -1;
}
template<typename T2>
Index indexOf(const T2& val) const
{
for (Index i = 0; i < m_count; i++)
{
if (m_buffer[i] == val)
return i;
}
return -1;
}
template<typename Func>
Index findLastIndex(const Func& predicate) const
{
for (Index i = m_count - 1; i >= 0; i--)
{
if (predicate(m_buffer[i]))
return i;
}
return -1;
}
template<typename T2>
Index lastIndexOf(const T2& val) const
{
for (Index i = m_count - 1; i >= 0; i--)
{
if(m_buffer[i] == val)
return i;
}
return -1;
}
bool contains(const T& val) const { return indexOf(val) != Index(-1); }
void sort()
{
sort([](const T& t1, const T& t2){return t1 < t2;});
}
template<typename Comparer>
void sort(Comparer compare)
{
//insertionSort(buffer, 0, _count - 1);
//quickSort(buffer, 0, _count - 1, compare);
std::sort(m_buffer, m_buffer + m_count, compare);
}
void stableSort()
{
stableSort([](const T& t1, const T& t2){return t1 < t2;});
}
template<typename Comparer>
void stableSort(Comparer compare)
{
std::stable_sort(m_buffer, m_buffer + m_count, compare);
}
template <typename IterateFunc>
void forEach(IterateFunc f) const
{
for (Index i = 0; i < m_count; i++)
f(m_buffer[i]);
}
template<typename Comparer>
void quickSort(T* vals, Index startIndex, Index endIndex, Comparer comparer)
{
static const Index kMinQSortSize = 32;
if(startIndex < endIndex)
{
if (endIndex - startIndex < kMinQSortSize)
insertionSort(vals, startIndex, endIndex, comparer);
else
{
Index pivotIndex = (startIndex + endIndex) >> 1;
Index pivotNewIndex = partition(vals, startIndex, endIndex, pivotIndex, comparer);
quickSort(vals, startIndex, pivotNewIndex - 1, comparer);
quickSort(vals, pivotNewIndex + 1, endIndex, comparer);
}
}
}
template<typename Comparer>
Index partition(T* vals, Index left, Index right, Index pivotIndex, Comparer comparer)
{
T pivotValue = vals[pivotIndex];
swapElements(vals, right, pivotIndex);
Index storeIndex = left;
for (Index i = left; i < right; i++)
{
if (comparer(vals[i], pivotValue))
{
swapElements(vals, i, storeIndex);
storeIndex++;
}
}
swapElements(vals, storeIndex, right);
return storeIndex;
}
template<typename Comparer>
void insertionSort(T* vals, Index startIndex, Index endIndex, Comparer comparer)
{
for (Index i = startIndex + 1; i <= endIndex; i++)
{
T insertValue = static_cast<T&&>(vals[i]);
Index insertIndex = i - 1;
while (insertIndex >= startIndex && comparer(insertValue, vals[insertIndex]))
{
vals[insertIndex + 1] = static_cast<T&&>(vals[insertIndex]);
insertIndex--;
}
vals[insertIndex + 1] = static_cast<T&&>(insertValue);
}
}
inline void swapElements(T* vals, Index index1, Index index2)
{
if (index1 != index2)
{
T tmp = static_cast<T&&>(vals[index1]);
vals[index1] = static_cast<T&&>(vals[index2]);
vals[index2] = static_cast<T&&>(tmp);
}
}
template<typename T2, typename Comparer>
Index binarySearch(const T2& obj, Comparer comparer) const
{
Index imin = 0, imax = m_count - 1;
while (imax >= imin)
{
Index imid = imin + ((imax - imin)>>1);
int compareResult = comparer(m_buffer[imid], obj);
if (compareResult == 0)
return imid;
else if (compareResult < 0)
imin = imid + 1;
else
imax = imid - 1;
}
// TODO: The return value on a failed search should be
// the bitwise negation of the index where `obj` should
// be inserted to be in the proper sorted location.
return -1;
}
template<typename T2>
int binarySearch(const T2& obj)
{
return binarySearch(obj,
[](T & curObj, const T2 & thatObj)->int
{
if (curObj < thatObj)
return -1;
else if (curObj == thatObj)
return 0;
else
return 1;
});
}
private:
T* m_buffer; ///< A new T[N] allocated buffer. NOTE! All elements up to capacity are in some valid form for T.
Index m_capacity; ///< The total capacity of elements
Index m_count; ///< The amount of elements
void _deallocateBuffer()
{
if (m_buffer)
{
AllocateMethod<T, TAllocator>::deallocateArray(m_buffer, m_capacity);
m_buffer = nullptr;
}
}
static inline T* _allocate(Index count)
{
return AllocateMethod<T, TAllocator>::allocateArray(count);
}
static void _free(T* buffer, Index count)
{
return AllocateMethod<T, TAllocator>::deallocateArray(buffer, count);
}
template<typename... Args>
void _init(const T& val, Args... args)
{
add(val);
_init(args...);
}
void _init() {}
};
template<typename T>
T calcMin(const List<T>& list)
{
T minVal = list.getFirst();
for (Index i = 1; i < list.getCount(); i++)
if (list[i] < minVal)
minVal = list[i];
return minVal;
}
template<typename T>
T calcMax(const List<T>& list)
{
T maxVal = list.getFirst();
for (Index i = 1; i< list.getCount(); i++)
if (list[i] > maxVal)
maxVal = list[i];
return maxVal;
}
}
#endif