/* SPDX-FileCopyrightText: 2024 Blender Authors * * SPDX-License-Identifier: GPL-2.0-or-later */ #pragma once #include #include "BLI_linear_allocator.hh" #include "BLI_struct_equality_utils.hh" #include "BLI_utility_mixins.hh" namespace blender::linear_allocator { /** * The list is a linked list of segments containing multiple elements. The capacity of each segment * is a template parameter because that removes the need to store it for every segment. */ template struct ChunkedListSegment { /** Pointer to the next segment in the list. */ ChunkedListSegment *next = nullptr; /** * Number of constructed elements in this segment. The constructed elements are always at the * beginning of the array below. */ int64_t size = 0; /** * The memory that actually contains the values in the end. The values are constructed and * destructed by higher level code. */ std::array, Capacity> values; }; /** * This is a special purpose container data structure that can be used to efficiently gather many * elements into many (small) lists for later retrieval. Insertion order is *not* maintained. * * To use this data structure, one has to have a separate #LinearAllocator which is passed to the * `append` function. This allows the same allocator to be used by many lists. Passing it into the * append function also removes the need to store the allocator pointer in every list. * * It is an improvement over #Vector because it does not require any reallocations. #VectorList * could also be used to overcome the reallocation issue. * * This data structure is also an improvement over #VectorList because: * - It has a much lower memory footprint when empty. * - Allows using a #LinearAllocator for all allocations, without storing the pointer to it in * every vector. * - It wastes less memory due to over-allocations. */ template class ChunkedList : NonCopyable { private: using Segment = ChunkedListSegment; Segment *current_segment_ = nullptr; public: ChunkedList() = default; ChunkedList(ChunkedList &&other) { current_segment_ = other.current_segment_; other.current_segment_ = nullptr; } ~ChunkedList() { /* This code assumes that the #ChunkedListSegment does not have to be destructed if the * contained type is trivially destructible. */ static_assert(std::is_trivially_destructible_v>); if constexpr (!std::is_trivially_destructible_v) { for (Segment *segment = current_segment_; segment; segment = segment->next) { for (const int64_t i : IndexRange(segment->size)) { T &value = *segment->values[i]; std::destroy_at(&value); } } } } ChunkedList &operator=(ChunkedList &&other) { if (this == &other) { return *this; } std::destroy_at(this); new (this) ChunkedList(std::move(other)); return *this; } /** * Add an element to the list. The insertion order is not maintained. The given allocator is used * to allocate any extra memory that may be needed. */ void append(LinearAllocator<> &allocator, const T &value) { this->append_as(allocator, value); } void append(LinearAllocator<> &allocator, T &&value) { this->append_as(allocator, std::move(value)); } template void append_as(LinearAllocator<> &allocator, Args &&...args) { if (current_segment_ == nullptr || current_segment_->size == SegmentCapacity) { /* Allocate a new segment if necessary. */ static_assert(std::is_trivially_destructible_v); Segment *new_segment = allocator.construct().release(); new_segment->next = current_segment_; current_segment_ = new_segment; } T *value = &*current_segment_->values[current_segment_->size++]; new (value) T(std::forward(args)...); } class ConstIterator { private: const Segment *segment_ = nullptr; int64_t index_ = 0; public: ConstIterator(const Segment *segment, int64_t index = 0) : segment_(segment), index_(index) {} ConstIterator &operator++() { index_++; if (index_ == segment_->size) { segment_ = segment_->next; index_ = 0; } return *this; } const T &operator*() const { return *segment_->values[index_]; } BLI_STRUCT_EQUALITY_OPERATORS_2(ConstIterator, segment_, index_) }; class MutableIterator { private: Segment *segment_ = nullptr; int64_t index_ = 0; public: MutableIterator(Segment *segment, int64_t index = 0) : segment_(segment), index_(index) {} MutableIterator &operator++() { index_++; if (index_ == segment_->size) { segment_ = segment_->next; index_ = 0; } return *this; } T &operator*() { return *segment_->values[index_]; } BLI_STRUCT_EQUALITY_OPERATORS_2(MutableIterator, segment_, index_) }; ConstIterator begin() const { return ConstIterator(current_segment_, 0); } ConstIterator end() const { return ConstIterator(nullptr, 0); } MutableIterator begin() { return MutableIterator(current_segment_, 0); } MutableIterator end() { return MutableIterator(nullptr, 0); } }; } // namespace blender::linear_allocator