tornavis/source/blender/blenlib/BLI_linear_allocator_chunke...

191 lines
5.3 KiB
C++

/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include <array>
#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<typename T, int64_t Capacity> 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<TypedBuffer<T>, 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<typename T, int64_t SegmentCapacity = 4> class ChunkedList : NonCopyable {
private:
using Segment = ChunkedListSegment<T, SegmentCapacity>;
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<ChunkedListSegment<int, 4>>);
if constexpr (!std::is_trivially_destructible_v<T>) {
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<typename... Args> 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>);
Segment *new_segment = allocator.construct<Segment>().release();
new_segment->next = current_segment_;
current_segment_ = new_segment;
}
T *value = &*current_segment_->values[current_segment_->size++];
new (value) T(std::forward<Args>(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