Core: VectorList improvements
- Add a custom Iterator, so it can be iterated as a 1D list. - Add missing functions like `first`, `is_empty`, `clear`, and subscript operator. - Add a `size_` member variable for faster `size` calls. - Add compile-time asserts to ensure the Capacity sizes are valid. - Add unit tests. See #138947 for the motivation behind this. Pull Request: https://projects.blender.org/blender/blender/pulls/139102
This commit is contained in:
@@ -128,12 +128,8 @@ struct UVPrimitiveLookup {
|
||||
|
||||
uint64_t uv_island_index = 0;
|
||||
for (uv_islands::UVIsland &uv_island : uv_islands.islands) {
|
||||
for (VectorList<uv_islands::UVPrimitive>::UsedVector &uv_primitives :
|
||||
uv_island.uv_primitives)
|
||||
{
|
||||
for (uv_islands::UVPrimitive &uv_primitive : uv_primitives) {
|
||||
lookup[uv_primitive.primitive_i].append_as(Entry(&uv_primitive, uv_island_index));
|
||||
}
|
||||
for (uv_islands::UVPrimitive &uv_primitive : uv_island.uv_primitives) {
|
||||
lookup[uv_primitive.primitive_i].append_as(Entry(&uv_primitive, uv_island_index));
|
||||
}
|
||||
uv_island_index++;
|
||||
}
|
||||
|
||||
@@ -386,11 +386,9 @@ void UVIsland::append(const UVPrimitive &primitive)
|
||||
|
||||
bool UVIsland::has_shared_edge(const UVPrimitive &primitive) const
|
||||
{
|
||||
for (const VectorList<UVPrimitive>::UsedVector &prims : uv_primitives) {
|
||||
for (const UVPrimitive &prim : prims) {
|
||||
if (prim.has_shared_edge(primitive)) {
|
||||
return true;
|
||||
}
|
||||
for (const UVPrimitive &prim : uv_primitives) {
|
||||
if (prim.has_shared_edge(primitive)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
@@ -398,11 +396,9 @@ bool UVIsland::has_shared_edge(const UVPrimitive &primitive) const
|
||||
|
||||
bool UVIsland::has_shared_edge(const MeshData &mesh_data, const int primitive_i) const
|
||||
{
|
||||
for (const VectorList<UVPrimitive>::UsedVector &primitives : uv_primitives) {
|
||||
for (const UVPrimitive &prim : primitives) {
|
||||
if (prim.has_shared_edge(mesh_data, primitive_i)) {
|
||||
return true;
|
||||
}
|
||||
for (const UVPrimitive &prim : uv_primitives) {
|
||||
if (prim.has_shared_edge(mesh_data, primitive_i)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
@@ -410,11 +406,9 @@ bool UVIsland::has_shared_edge(const MeshData &mesh_data, const int primitive_i)
|
||||
|
||||
void UVIsland::extend_border(const UVPrimitive &primitive)
|
||||
{
|
||||
for (const VectorList<UVPrimitive>::UsedVector &primitives : uv_primitives) {
|
||||
for (const UVPrimitive &prim : primitives) {
|
||||
if (prim.has_shared_edge(primitive)) {
|
||||
this->append(primitive);
|
||||
}
|
||||
for (const UVPrimitive &prim : uv_primitives) {
|
||||
if (prim.has_shared_edge(primitive)) {
|
||||
this->append(primitive);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -446,12 +440,10 @@ void UVIsland::extract_borders()
|
||||
{
|
||||
/* Lookup all borders of the island. */
|
||||
Vector<UVBorderEdge> edges;
|
||||
for (VectorList<UVPrimitive>::UsedVector &prims : uv_primitives) {
|
||||
for (UVPrimitive &prim : prims) {
|
||||
for (UVEdge *edge : prim.edges) {
|
||||
if (edge->is_border_edge()) {
|
||||
edges.append(UVBorderEdge(edge, &prim));
|
||||
}
|
||||
for (UVPrimitive &prim : uv_primitives) {
|
||||
for (UVEdge *edge : prim.edges) {
|
||||
if (edge->is_border_edge()) {
|
||||
edges.append(UVBorderEdge(edge, &prim));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1029,11 +1021,9 @@ static void extend_at_vert(const MeshData &mesh_data,
|
||||
/* Marks vertices that can be extended. Only vertices that are part of a border can be extended. */
|
||||
static void reset_extendability_flags(UVIsland &island)
|
||||
{
|
||||
for (VectorList<UVVertex>::UsedVector &uv_vertices : island.uv_vertices) {
|
||||
for (UVVertex &uv_vertex : uv_vertices) {
|
||||
uv_vertex.flags.is_border = false;
|
||||
uv_vertex.flags.is_extended = false;
|
||||
}
|
||||
for (UVVertex &uv_vertex : island.uv_vertices) {
|
||||
uv_vertex.flags.is_border = false;
|
||||
uv_vertex.flags.is_extended = false;
|
||||
}
|
||||
for (const UVBorder &border : island.borders) {
|
||||
for (const UVBorderEdge &border_edge : border.edges) {
|
||||
@@ -1090,32 +1080,28 @@ void UVIsland::print_debug(const MeshData &mesh_data) const
|
||||
ss << "uvisland_edges = []\n";
|
||||
|
||||
ss << "uvisland_faces = [\n";
|
||||
for (const VectorList<UVPrimitive>::UsedVector &uvprimitives : uv_primitives) {
|
||||
for (const UVPrimitive &uvprimitive : uvprimitives) {
|
||||
ss << " [" << uvprimitive.edges[0]->vertices[0]->vertex << ", "
|
||||
<< uvprimitive.edges[0]->vertices[1]->vertex << ", "
|
||||
<< uvprimitive
|
||||
.get_other_uv_vertex(uvprimitive.edges[0]->vertices[0],
|
||||
uvprimitive.edges[0]->vertices[1])
|
||||
->vertex
|
||||
<< "],\n";
|
||||
}
|
||||
for (const UVPrimitive &uvprimitive : uv_primitives) {
|
||||
ss << " [" << uvprimitive.edges[0]->vertices[0]->vertex << ", "
|
||||
<< uvprimitive.edges[0]->vertices[1]->vertex << ", "
|
||||
<< uvprimitive
|
||||
.get_other_uv_vertex(uvprimitive.edges[0]->vertices[0],
|
||||
uvprimitive.edges[0]->vertices[1])
|
||||
->vertex
|
||||
<< "],\n";
|
||||
}
|
||||
ss << "]\n";
|
||||
|
||||
ss << "uvisland_uvs = [\n";
|
||||
for (const VectorList<UVPrimitive>::UsedVector &uvprimitives : uv_primitives) {
|
||||
for (const UVPrimitive &uvprimitive : uvprimitives) {
|
||||
float2 uv = uvprimitive.edges[0]->vertices[0]->uv;
|
||||
ss << " " << uv.x << ", " << uv.y << ",\n";
|
||||
uv = uvprimitive.edges[0]->vertices[1]->uv;
|
||||
ss << " " << uv.x << ", " << uv.y << ",\n";
|
||||
uv = uvprimitive
|
||||
.get_other_uv_vertex(uvprimitive.edges[0]->vertices[0],
|
||||
uvprimitive.edges[0]->vertices[1])
|
||||
->uv;
|
||||
ss << " " << uv.x << ", " << uv.y << ",\n";
|
||||
}
|
||||
for (const UVPrimitive &uvprimitive : uv_primitives) {
|
||||
float2 uv = uvprimitive.edges[0]->vertices[0]->uv;
|
||||
ss << " " << uv.x << ", " << uv.y << ",\n";
|
||||
uv = uvprimitive.edges[0]->vertices[1]->uv;
|
||||
ss << " " << uv.x << ", " << uv.y << ",\n";
|
||||
uv = uvprimitive
|
||||
.get_other_uv_vertex(uvprimitive.edges[0]->vertices[0],
|
||||
uvprimitive.edges[0]->vertices[1])
|
||||
->uv;
|
||||
ss << " " << uv.x << ", " << uv.y << ",\n";
|
||||
}
|
||||
ss << "]\n";
|
||||
|
||||
@@ -1518,39 +1504,37 @@ static void add_uv_island(const MeshData &mesh_data,
|
||||
const UVIsland &uv_island,
|
||||
int16_t island_index)
|
||||
{
|
||||
for (const VectorList<UVPrimitive>::UsedVector &uv_primitives : uv_island.uv_primitives) {
|
||||
for (const UVPrimitive &uv_primitive : uv_primitives) {
|
||||
const int3 &tri = mesh_data.corner_tris[uv_primitive.primitive_i];
|
||||
for (const UVPrimitive &uv_primitive : uv_island.uv_primitives) {
|
||||
const int3 &tri = mesh_data.corner_tris[uv_primitive.primitive_i];
|
||||
|
||||
rctf uv_bounds = primitive_uv_bounds(tri, mesh_data.uv_map);
|
||||
rcti buffer_bounds;
|
||||
buffer_bounds.xmin = max_ii(
|
||||
floor((uv_bounds.xmin - tile.udim_offset.x) * tile.mask_resolution.x), 0);
|
||||
buffer_bounds.xmax = min_ii(
|
||||
ceil((uv_bounds.xmax - tile.udim_offset.x) * tile.mask_resolution.x),
|
||||
tile.mask_resolution.x - 1);
|
||||
buffer_bounds.ymin = max_ii(
|
||||
floor((uv_bounds.ymin - tile.udim_offset.y) * tile.mask_resolution.y), 0);
|
||||
buffer_bounds.ymax = min_ii(
|
||||
ceil((uv_bounds.ymax - tile.udim_offset.y) * tile.mask_resolution.y),
|
||||
tile.mask_resolution.y - 1);
|
||||
rctf uv_bounds = primitive_uv_bounds(tri, mesh_data.uv_map);
|
||||
rcti buffer_bounds;
|
||||
buffer_bounds.xmin = max_ii(
|
||||
floor((uv_bounds.xmin - tile.udim_offset.x) * tile.mask_resolution.x), 0);
|
||||
buffer_bounds.xmax = min_ii(
|
||||
ceil((uv_bounds.xmax - tile.udim_offset.x) * tile.mask_resolution.x),
|
||||
tile.mask_resolution.x - 1);
|
||||
buffer_bounds.ymin = max_ii(
|
||||
floor((uv_bounds.ymin - tile.udim_offset.y) * tile.mask_resolution.y), 0);
|
||||
buffer_bounds.ymax = min_ii(
|
||||
ceil((uv_bounds.ymax - tile.udim_offset.y) * tile.mask_resolution.y),
|
||||
tile.mask_resolution.y - 1);
|
||||
|
||||
for (int y = buffer_bounds.ymin; y < buffer_bounds.ymax + 1; y++) {
|
||||
for (int x = buffer_bounds.xmin; x < buffer_bounds.xmax + 1; x++) {
|
||||
float2 uv(float(x) / tile.mask_resolution.x, float(y) / tile.mask_resolution.y);
|
||||
float3 weights;
|
||||
barycentric_weights_v2(mesh_data.uv_map[tri[0]],
|
||||
mesh_data.uv_map[tri[1]],
|
||||
mesh_data.uv_map[tri[2]],
|
||||
uv + tile.udim_offset,
|
||||
weights);
|
||||
if (!barycentric_inside_triangle_v2(weights)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
uint64_t offset = tile.mask_resolution.x * y + x;
|
||||
tile.mask[offset] = island_index;
|
||||
for (int y = buffer_bounds.ymin; y < buffer_bounds.ymax + 1; y++) {
|
||||
for (int x = buffer_bounds.xmin; x < buffer_bounds.xmax + 1; x++) {
|
||||
float2 uv(float(x) / tile.mask_resolution.x, float(y) / tile.mask_resolution.y);
|
||||
float3 weights;
|
||||
barycentric_weights_v2(mesh_data.uv_map[tri[0]],
|
||||
mesh_data.uv_map[tri[1]],
|
||||
mesh_data.uv_map[tri[2]],
|
||||
uv + tile.udim_offset,
|
||||
weights);
|
||||
if (!barycentric_inside_triangle_v2(weights)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
uint64_t offset = tile.mask_resolution.x * y + x;
|
||||
tile.mask[offset] = island_index;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,10 @@
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
#include "BLI_math_bits.h"
|
||||
#include "BLI_utildefines.h"
|
||||
#include "BLI_vector.hh"
|
||||
|
||||
namespace blender {
|
||||
@@ -28,83 +31,190 @@ namespace blender {
|
||||
*
|
||||
* When a VectorList reserved memory is full it will allocate memory for the new items, breaking
|
||||
* the sequential access. Within each allocated memory block the elements are ordered sequentially.
|
||||
*
|
||||
* Indexing has some overhead compared to a Vector or an Array, but it still has constant time
|
||||
* access.
|
||||
*/
|
||||
template<typename T, int64_t CapacityStart = 32, int64_t CapacitySoftLimit = 4096>
|
||||
class VectorList {
|
||||
public:
|
||||
using UsedVector = Vector<T, 0>;
|
||||
template<typename T, int64_t CapacityStart = 32, int64_t CapacityMax = 4096> class VectorList {
|
||||
using SelfT = VectorList<T, CapacityStart, CapacityMax>;
|
||||
using VectorT = Vector<T, 0>;
|
||||
|
||||
private:
|
||||
/**
|
||||
* Contains the individual vectors. There must always be at least one vector
|
||||
*/
|
||||
Vector<UsedVector> vectors_;
|
||||
static_assert(is_power_of_2(CapacityStart));
|
||||
static_assert(is_power_of_2(CapacityMax));
|
||||
static_assert(CapacityStart <= CapacityMax);
|
||||
|
||||
/* Contains the individual vectors. There must always be at least one vector. */
|
||||
Vector<VectorT> vectors_;
|
||||
/* Number of vectors in use. */
|
||||
int64_t used_vectors_ = 0;
|
||||
/* Total element count accross all vectors_. */
|
||||
int64_t size_ = 0;
|
||||
|
||||
public:
|
||||
VectorList()
|
||||
{
|
||||
this->append_vector();
|
||||
used_vectors_ = 1;
|
||||
}
|
||||
|
||||
VectorList(VectorList &&other) noexcept
|
||||
{
|
||||
vectors_ = std::move(other.vectors_);
|
||||
used_vectors_ = other.used_vectors_;
|
||||
size_ = other.size_;
|
||||
other.clear_and_shrink();
|
||||
}
|
||||
|
||||
VectorList &operator=(VectorList &&other)
|
||||
{
|
||||
return move_assign_container(*this, std::move(other));
|
||||
}
|
||||
|
||||
/* Insert a new element at the end of the VectorList. */
|
||||
void append(const T &value)
|
||||
{
|
||||
this->append_as(value);
|
||||
}
|
||||
|
||||
/* Insert a new element at the end of the VectorList. */
|
||||
void append(T &&value)
|
||||
{
|
||||
this->append_as(std::move(value));
|
||||
}
|
||||
|
||||
/* This is similar to `std::vector::emplace_back`. */
|
||||
template<typename ForwardT> void append_as(ForwardT &&value)
|
||||
{
|
||||
UsedVector &vector = this->ensure_space_for_one();
|
||||
VectorT &vector = this->ensure_space_for_one();
|
||||
vector.append_unchecked_as(std::forward<ForwardT>(value));
|
||||
size_++;
|
||||
}
|
||||
|
||||
UsedVector *begin()
|
||||
/**
|
||||
* Return a reference to the first element in the VectorList.
|
||||
* This invokes undefined behavior when the VectorList is empty.
|
||||
*/
|
||||
T &first()
|
||||
{
|
||||
return vectors_.begin();
|
||||
}
|
||||
|
||||
UsedVector *end()
|
||||
{
|
||||
return vectors_.end();
|
||||
}
|
||||
|
||||
const UsedVector *begin() const
|
||||
{
|
||||
return vectors_.begin();
|
||||
}
|
||||
|
||||
const UsedVector *end() const
|
||||
{
|
||||
return vectors_.end();
|
||||
BLI_assert(size() > 0);
|
||||
return vectors_.first().first();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a reference to the last element in the VectorList.
|
||||
* This invokes undefined behavior when the VectorList is empty.
|
||||
*/
|
||||
T &last()
|
||||
{
|
||||
return vectors_.last().last();
|
||||
BLI_assert(size() > 0);
|
||||
return vectors_[used_vectors_ - 1].last();
|
||||
}
|
||||
|
||||
/* Return how many values are currently stored in the VectorList. */
|
||||
int64_t size() const
|
||||
{
|
||||
int64_t result = 0;
|
||||
for (const UsedVector &vector : *this) {
|
||||
result += vector.size();
|
||||
return size_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true when the VectorList contains no elements, otherwise false.
|
||||
*
|
||||
* This is the same as std::vector::empty.
|
||||
*/
|
||||
bool is_empty() const
|
||||
{
|
||||
return size_ == 0;
|
||||
}
|
||||
|
||||
/* Afterwards the VectorList has 0 elements, but will still have memory to be refilled again. */
|
||||
void clear()
|
||||
{
|
||||
for (VectorT &vector : vectors_) {
|
||||
vector.clear();
|
||||
}
|
||||
return result;
|
||||
used_vectors_ = 1;
|
||||
size_ = 0;
|
||||
}
|
||||
|
||||
/* Afterwards the VectorList has 0 elements and the Vectors allocated memory will be freed. */
|
||||
void clear_and_shrink()
|
||||
{
|
||||
vectors_.clear();
|
||||
this->append_vector();
|
||||
used_vectors_ = 1;
|
||||
size_ = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value at the given index.
|
||||
* This invokes undefined behavior when the index is out of bounds.
|
||||
*/
|
||||
const T &operator[](int64_t index) const
|
||||
{
|
||||
std::pair<int64_t, int64_t> index_pair = this->global_index_to_index_pair(index);
|
||||
return vectors_[index_pair.first][index_pair.second];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value at the given index.
|
||||
* This invokes undefined behavior when the index is out of bounds.
|
||||
*/
|
||||
T &operator[](int64_t index)
|
||||
{
|
||||
std::pair<int64_t, int64_t> index_pair = this->global_index_to_index_pair(index);
|
||||
return vectors_[index_pair.first][index_pair.second];
|
||||
}
|
||||
|
||||
private:
|
||||
UsedVector &ensure_space_for_one()
|
||||
/**
|
||||
* Convert a global index into a Vector index and Element index pair.
|
||||
* We use the fact that vector sizes increase geometrically to compute this in constant time.
|
||||
* https://en.wikipedia.org/wiki/Geometric_progression
|
||||
*/
|
||||
std::pair<int64_t, int64_t> global_index_to_index_pair(int64_t index) const
|
||||
{
|
||||
UsedVector &vector = vectors_.last();
|
||||
if (LIKELY(!vector.is_at_capacity())) {
|
||||
return vector;
|
||||
BLI_assert(index >= 0);
|
||||
BLI_assert(index < this->size());
|
||||
|
||||
auto log2 = [](int64_t value) -> int64_t {
|
||||
return 31 - bitscan_reverse_uint(uint32_t(value));
|
||||
};
|
||||
auto geometric_sum = [](int64_t index) -> int64_t {
|
||||
return CapacityStart * ((2 << index) - 1);
|
||||
};
|
||||
auto index_from_sum = [log2](int64_t sum) -> int64_t {
|
||||
return log2((sum / CapacityStart) + 1);
|
||||
};
|
||||
|
||||
static const int64_t start_log2 = log2(CapacityStart);
|
||||
static const int64_t end_log2 = log2(CapacityMax);
|
||||
/* The number of vectors until CapacityMax size is reached. */
|
||||
static const int64_t geometric_steps = end_log2 - start_log2 + 1;
|
||||
/* The number of elements until CapacityMax size is reached. */
|
||||
static const int64_t geometric_total = geometric_sum(geometric_steps - 1);
|
||||
|
||||
int64_t index_a, index_b;
|
||||
if (index < geometric_total) {
|
||||
index_a = index_from_sum(index);
|
||||
index_b = index_a > 0 ? index - geometric_sum(index_a - 1) : index;
|
||||
}
|
||||
this->append_vector();
|
||||
return vectors_.last();
|
||||
else {
|
||||
int64_t linear_start = index - geometric_total;
|
||||
index_a = geometric_steps + linear_start / CapacityMax;
|
||||
index_b = linear_start % CapacityMax;
|
||||
}
|
||||
return {index_a, index_b};
|
||||
}
|
||||
|
||||
VectorT &ensure_space_for_one()
|
||||
{
|
||||
if (vectors_[used_vectors_ - 1].is_at_capacity()) {
|
||||
if (used_vectors_ == vectors_.size()) {
|
||||
this->append_vector();
|
||||
}
|
||||
used_vectors_++;
|
||||
}
|
||||
return vectors_[used_vectors_ - 1];
|
||||
}
|
||||
|
||||
void append_vector()
|
||||
@@ -119,7 +229,74 @@ class VectorList {
|
||||
if (vectors_.is_empty()) {
|
||||
return CapacityStart;
|
||||
}
|
||||
return std::min(vectors_.last().capacity() * 2, CapacitySoftLimit);
|
||||
return std::min(vectors_.last().capacity() * 2, CapacityMax);
|
||||
}
|
||||
|
||||
template<typename IterableT, typename ElemT> struct Iterator {
|
||||
IterableT &vector_list;
|
||||
int64_t index_a = 0;
|
||||
int64_t index_b = 0;
|
||||
|
||||
Iterator(IterableT &vector_list, int64_t index_a = 0, int64_t index_b = 0)
|
||||
: vector_list(vector_list), index_a(index_a), index_b(index_b)
|
||||
{
|
||||
}
|
||||
|
||||
ElemT &operator*() const
|
||||
{
|
||||
return vector_list.vectors_[index_a][index_b];
|
||||
}
|
||||
|
||||
Iterator &operator++()
|
||||
{
|
||||
if (vector_list.vectors_[index_a].size() == index_b + 1) {
|
||||
if (index_a + 1 == vector_list.vectors_.size()) {
|
||||
/* Reached the end. */
|
||||
index_b++;
|
||||
}
|
||||
else {
|
||||
index_a++;
|
||||
index_b = 0;
|
||||
}
|
||||
}
|
||||
else {
|
||||
index_b++;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool operator==(const Iterator &other) const
|
||||
{
|
||||
BLI_assert(&other.vector_list == &vector_list);
|
||||
return other.index_a == index_a && other.index_b == index_b;
|
||||
}
|
||||
|
||||
bool operator!=(const Iterator &other) const
|
||||
{
|
||||
return !(other == *this);
|
||||
}
|
||||
};
|
||||
|
||||
using MutIterator = Iterator<SelfT, T>;
|
||||
using ConstIterator = Iterator<const SelfT, const T>;
|
||||
|
||||
public:
|
||||
MutIterator begin()
|
||||
{
|
||||
return MutIterator(*this, 0, 0);
|
||||
}
|
||||
MutIterator end()
|
||||
{
|
||||
return MutIterator(*this, used_vectors_ - 1, vectors_[used_vectors_ - 1].size());
|
||||
}
|
||||
|
||||
ConstIterator begin() const
|
||||
{
|
||||
return ConstIterator(*this, 0, 0);
|
||||
}
|
||||
ConstIterator end() const
|
||||
{
|
||||
return ConstIterator(*this, used_vectors_ - 1, vectors_[used_vectors_ - 1].size());
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -603,6 +603,7 @@ if(WITH_GTESTS)
|
||||
tests/BLI_unique_sorted_indices_test.cc
|
||||
tests/BLI_utildefines_test.cc
|
||||
tests/BLI_uuid_test.cc
|
||||
tests/BLI_vector_list_test.cc
|
||||
tests/BLI_vector_set_test.cc
|
||||
tests/BLI_vector_test.cc
|
||||
tests/BLI_virtual_array_test.cc
|
||||
|
||||
250
source/blender/blenlib/tests/BLI_vector_list_test.cc
Normal file
250
source/blender/blenlib/tests/BLI_vector_list_test.cc
Normal file
@@ -0,0 +1,250 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 */
|
||||
|
||||
#include "BLI_exception_safety_test_utils.hh"
|
||||
#include "BLI_vector_list.hh"
|
||||
#include "testing/testing.h"
|
||||
|
||||
#include "BLI_strict_flags.h" /* IWYU pragma: keep. Keep last. */
|
||||
|
||||
namespace blender::tests {
|
||||
|
||||
TEST(vectorlist, DefaultConstructor)
|
||||
{
|
||||
VectorList<int> vec;
|
||||
EXPECT_EQ(vec.size(), 0);
|
||||
}
|
||||
|
||||
TEST(vectorlist, MoveConstructor)
|
||||
{
|
||||
VectorList<int> vec1;
|
||||
vec1.append(1);
|
||||
vec1.append(2);
|
||||
vec1.append(3);
|
||||
vec1.append(4);
|
||||
VectorList<int> vec2(std::move(vec1));
|
||||
|
||||
EXPECT_EQ(vec1.size(), 0); /* NOLINT: bugprone-use-after-move */
|
||||
EXPECT_EQ(vec2.size(), 4);
|
||||
EXPECT_EQ(vec2[0], 1);
|
||||
EXPECT_EQ(vec2[1], 2);
|
||||
EXPECT_EQ(vec2[2], 3);
|
||||
EXPECT_EQ(vec2[3], 4);
|
||||
}
|
||||
|
||||
TEST(vectorlist, MoveOperator)
|
||||
{
|
||||
VectorList<int> vec1;
|
||||
vec1.append(1);
|
||||
vec1.append(2);
|
||||
vec1.append(3);
|
||||
vec1.append(4);
|
||||
VectorList<int> vec2;
|
||||
vec2 = std::move(vec1);
|
||||
|
||||
EXPECT_EQ(vec1.size(), 0); /* NOLINT: bugprone-use-after-move */
|
||||
EXPECT_EQ(vec2.size(), 4);
|
||||
EXPECT_EQ(vec2[0], 1);
|
||||
EXPECT_EQ(vec2[1], 2);
|
||||
EXPECT_EQ(vec2[2], 3);
|
||||
EXPECT_EQ(vec2[3], 4);
|
||||
}
|
||||
|
||||
TEST(vectorlist, Append)
|
||||
{
|
||||
VectorList<int> vec;
|
||||
vec.append(3);
|
||||
vec.append(6);
|
||||
vec.append(7);
|
||||
EXPECT_EQ(vec.size(), 3);
|
||||
EXPECT_EQ(vec[0], 3);
|
||||
EXPECT_EQ(vec[1], 6);
|
||||
EXPECT_EQ(vec[2], 7);
|
||||
}
|
||||
|
||||
TEST(vectorlist, Iterator)
|
||||
{
|
||||
VectorList<int> vec;
|
||||
vec.append(1);
|
||||
vec.append(4);
|
||||
vec.append(9);
|
||||
vec.append(16);
|
||||
int i = 1;
|
||||
for (int value : vec) {
|
||||
EXPECT_EQ(value, i * i);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
TEST(vectorlist, ConstIterator)
|
||||
{
|
||||
VectorList<int> vec;
|
||||
vec.append(1);
|
||||
vec.append(4);
|
||||
vec.append(9);
|
||||
vec.append(16);
|
||||
const VectorList<int> &const_ref = vec;
|
||||
int i = 1;
|
||||
for (int value : const_ref) {
|
||||
EXPECT_EQ(value, i * i);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
TEST(vectorlist, LimitIterator)
|
||||
{
|
||||
VectorList<int, 8, 128> vec;
|
||||
for (int64_t i : IndexRange(1024)) {
|
||||
vec.append(int(i));
|
||||
}
|
||||
int i = 0;
|
||||
for (int value : vec) {
|
||||
EXPECT_EQ(value, i);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
TEST(vectorlist, LimitIndexing)
|
||||
{
|
||||
VectorList<int, 8, 128> vec;
|
||||
for (int64_t i : IndexRange(1024)) {
|
||||
vec.append(int(i));
|
||||
}
|
||||
for (int64_t i : IndexRange(1024)) {
|
||||
EXPECT_EQ(vec[i], i);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(vectorlist, ConstLimitIndexing)
|
||||
{
|
||||
VectorList<int, 8, 128> vec;
|
||||
for (int64_t i : IndexRange(1024)) {
|
||||
vec.append(int(i));
|
||||
}
|
||||
const VectorList<int, 8, 128> &const_ref = vec;
|
||||
for (int64_t i : IndexRange(1024)) {
|
||||
EXPECT_EQ(const_ref[i], i);
|
||||
}
|
||||
}
|
||||
|
||||
static VectorList<int> return_by_value_helper()
|
||||
{
|
||||
VectorList<int> vec;
|
||||
vec.append(3);
|
||||
vec.append(5);
|
||||
vec.append(1);
|
||||
return vec;
|
||||
}
|
||||
|
||||
TEST(vectorlist, ReturnByValue)
|
||||
{
|
||||
VectorList<int> vec = return_by_value_helper();
|
||||
EXPECT_EQ(vec.size(), 3);
|
||||
EXPECT_EQ(vec[0], 3);
|
||||
EXPECT_EQ(vec[1], 5);
|
||||
EXPECT_EQ(vec[2], 1);
|
||||
}
|
||||
|
||||
TEST(vectorlist, IsEmpty)
|
||||
{
|
||||
VectorList<int> vec;
|
||||
EXPECT_TRUE(vec.is_empty());
|
||||
vec.append(1);
|
||||
EXPECT_FALSE(vec.is_empty());
|
||||
vec.clear();
|
||||
EXPECT_TRUE(vec.is_empty());
|
||||
}
|
||||
|
||||
TEST(vectorlist, First)
|
||||
{
|
||||
VectorList<int> vec;
|
||||
vec.append(3);
|
||||
vec.append(5);
|
||||
vec.append(7);
|
||||
EXPECT_EQ(vec.first(), 3);
|
||||
}
|
||||
|
||||
TEST(vectorlist, Last)
|
||||
{
|
||||
VectorList<int> vec;
|
||||
vec.append(3);
|
||||
vec.append(5);
|
||||
vec.append(7);
|
||||
EXPECT_EQ(vec.last(), 7);
|
||||
}
|
||||
|
||||
class TypeConstructMock {
|
||||
public:
|
||||
bool default_constructed = false;
|
||||
bool copy_constructed = false;
|
||||
bool move_constructed = false;
|
||||
bool copy_assigned = false;
|
||||
bool move_assigned = false;
|
||||
|
||||
TypeConstructMock() : default_constructed(true) {}
|
||||
|
||||
TypeConstructMock(const TypeConstructMock & /*other*/) : copy_constructed(true) {}
|
||||
|
||||
TypeConstructMock(TypeConstructMock && /*other*/) noexcept : move_constructed(true) {}
|
||||
|
||||
TypeConstructMock &operator=(const TypeConstructMock &other)
|
||||
{
|
||||
if (this == &other) {
|
||||
return *this;
|
||||
}
|
||||
|
||||
copy_assigned = true;
|
||||
return *this;
|
||||
}
|
||||
|
||||
TypeConstructMock &operator=(TypeConstructMock &&other) noexcept
|
||||
{
|
||||
if (this == &other) {
|
||||
return *this;
|
||||
}
|
||||
|
||||
move_assigned = true;
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
TEST(vectorlist, AppendCallsCopyConstructor)
|
||||
{
|
||||
VectorList<TypeConstructMock> vec;
|
||||
TypeConstructMock value;
|
||||
vec.append(value);
|
||||
EXPECT_TRUE(vec[0].copy_constructed);
|
||||
}
|
||||
|
||||
TEST(vectorlist, AppendCallsMoveConstructor)
|
||||
{
|
||||
VectorList<TypeConstructMock> vec;
|
||||
vec.append(TypeConstructMock());
|
||||
EXPECT_TRUE(vec[0].move_constructed);
|
||||
}
|
||||
|
||||
TEST(vectorlist, OveralignedValues)
|
||||
{
|
||||
VectorList<AlignedBuffer<1, 512>> vec;
|
||||
for (int i = 0; i < 100; i++) {
|
||||
vec.append({});
|
||||
EXPECT_EQ(uintptr_t(&vec.last()) % 512, 0);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(vectorlist, AppendExceptions)
|
||||
{
|
||||
VectorList<ExceptionThrower> vec;
|
||||
vec.append({});
|
||||
vec.append({});
|
||||
ExceptionThrower *ptr1 = &vec.last();
|
||||
ExceptionThrower value;
|
||||
value.throw_during_copy = true;
|
||||
EXPECT_ANY_THROW({ vec.append(value); });
|
||||
EXPECT_EQ(vec.size(), 2);
|
||||
ExceptionThrower *ptr2 = &vec.last();
|
||||
EXPECT_EQ(ptr1, ptr2);
|
||||
}
|
||||
|
||||
} // namespace blender::tests
|
||||
Reference in New Issue
Block a user