#ifndef SLANG_CORE_LIST_H #define SLANG_CORE_LIST_H #include "slang-allocator.h" #include "slang-array-view.h" #include "slang-math.h" #include "slang.h" #include #include #include 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 class List { private: static const Index kInitialCount = 16; public: typedef List ThisType; List() : m_buffer(nullptr), m_count(0), m_capacity(0) { } template List(const T& val, Args... args) : m_buffer(nullptr), m_count(0), m_capacity(0) { _init(val, args...); } List(const List& list) : m_buffer(nullptr), m_count(0), m_capacity(0) { this->operator=(list); } List(List&& list) : m_buffer(nullptr), m_count(0), m_capacity(0) { this->operator=(static_cast&&>(list)); } List(ArrayView view) : List() { addRange(view); } static List makeRepeated(const T& val, Index count) { List rs; rs.setCount(count); for (Index i = 0; i < count; i++) rs[i] = val; return rs; } ~List() { _deallocateBuffer(); } List& operator=(const List& list) { clearAndDeallocate(); addRange(list); return *this; } List& operator=(List&& 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& 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 getArrayView() const { return ArrayView(m_buffer, m_count); } inline ArrayView getArrayView(Index start, Index count) const { SLANG_ASSERT(start >= 0 && count >= 0 && start + count <= m_count); return ArrayView(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(obj); } void add(const T& obj) { _maybeReserveForAdd(); m_buffer[m_count++] = obj; } Index getCount() const { return m_count; } Index getCapacity() const { return m_capacity; } template Index countIf(Predicate predicate) const { Index count = 0; for (Index i = 0; i < getCount(); ++i) { if (predicate((*this)[i])) count++; } return count; } 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::value && std::has_trivial_destructor::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(m_buffer[i])); } _deallocateBuffer(); } m_buffer = newBuffer; m_capacity = newBufferCount; } else { /*if (std::has_trivial_copy_assign::value && std::has_trivial_destructor::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(m_buffer[i - 1]); } } /*if (std::has_trivial_copy_assign::value && std::has_trivial_destructor::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& list) { insertRange(id, list.m_buffer, list.m_count); } void addRange(ArrayView list) { insertRange(m_count, list.getBuffer(), list.getCount()); } void addRange(const T* vals, Index n) { insertRange(m_count, vals, n); } void addRange(const List& 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(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::value && std::has_trivial_destructor::value) memcpy(newBuffer, buffer, _count * sizeof(T)); else*/ { for (Index i = 0; i < m_count; i++) newBuffer[i] = static_cast(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(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 Index findFirstIndex(const Func& predicate) const { for (Index i = 0; i < m_count; i++) { if (predicate(m_buffer[i])) return i; } return -1; } template Index indexOf(const T2& val) const { for (Index i = 0; i < m_count; i++) { if (m_buffer[i] == val) return i; } return -1; } template 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 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 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 void stableSort(Comparer compare) { std::stable_sort(m_buffer, m_buffer + m_count, compare); } template void forEach(IterateFunc f) const { for (Index i = 0; i < m_count; i++) f(m_buffer[i]); } template 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 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 void insertionSort(T* vals, Index startIndex, Index endIndex, Comparer comparer) { for (Index i = startIndex + 1; i <= endIndex; i++) { T insertValue = static_cast(vals[i]); Index insertIndex = i - 1; while (insertIndex >= startIndex && comparer(insertValue, vals[insertIndex])) { vals[insertIndex + 1] = static_cast(vals[insertIndex]); insertIndex--; } vals[insertIndex + 1] = static_cast(insertValue); } } inline static void swapElements(T* vals, Index index1, Index index2) { if (index1 != index2) { T tmp = static_cast(vals[index1]); vals[index1] = static_cast(vals[index2]); vals[index2] = static_cast(tmp); } } inline void swapElements(Index index1, Index index2) { swapElements(m_buffer, index1, index2); } template 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 Index binarySearch(const T2& obj) const { 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::deallocateArray(m_buffer, m_capacity); m_buffer = nullptr; } } static inline T* _allocate(Index count) { return AllocateMethod::allocateArray(count); } static void _free(T* buffer, Index count) { return AllocateMethod::deallocateArray(buffer, count); } template void _init(const T& val, Args... args) { add(val); _init(args...); } void _init() {} }; template T calcMin(const List& list) { T minVal = list.getFirst(); for (Index i = 1; i < list.getCount(); i++) if (list[i] < minVal) minVal = list[i]; return minVal; } template T calcMax(const List& list) { T maxVal = list.getFirst(); for (Index i = 1; i < list.getCount(); i++) if (list[i] > maxVal) maxVal = list[i]; return maxVal; } } // namespace Slang #endif