This repository has been archived on 2023-10-09. You can view files and clone it, but cannot push or open issues or pull requests.
Files
blender-archive/source/blender/functions/FN_generic_array.hh
Hans Goudey b42ce0c54c Functions: Generic array data structure
Sometimes it's useful to pass around a set of values with a generic
type. The virtual array data structures allow this, but they don't
have logical ownership. My initial use case for this is as a return
type for the functions that interpolate curve attributes to evaluated
points, but a need for this data structure has come up in a few other
places as well. It also reduced the need for templates.

Differential Revision: https://developer.blender.org/D11103
2021-10-14 11:06:18 -05:00

271 lines
6.5 KiB
C++

/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#pragma once
/** \file
* \ingroup fn
*
* This is a generic counterpart to #blender::Array, used when the type is not known at runtime.
*
* `GArray` should generally only be used for passing data around in dynamic contexts.
* It does not support a few things that #blender::Array supports:
* - Small object optimization / inline buffer.
* - Exception safety and various more specific constructors.
*/
#include "BLI_allocator.hh"
#include "FN_cpp_type.hh"
#include "FN_generic_span.hh"
namespace blender::fn {
template<
/**
* The allocator used by this array. Should rarely be changed, except when you don't want that
* MEM_* functions are used internally.
*/
typename Allocator = GuardedAllocator>
class GArray {
protected:
/** The type of the data in the array, will be null after the array is default constructed,
* but a value should be assigned before any other interaction with the array. */
const CPPType *type_ = nullptr;
void *data_ = nullptr;
int64_t size_ = 0;
Allocator allocator_;
public:
/**
* The default constructor creates an empty array, the only situation in which the type is
* allowed to be null. This default constructor exists so `GArray` can be used in containers,
* but the type should be supplied before doing anything else to the array.
*/
GArray(Allocator allocator = {}) noexcept : allocator_(allocator)
{
}
GArray(NoExceptConstructor, Allocator allocator = {}) noexcept : GArray(allocator)
{
}
/**
* Create and allocate a new array, with elements default constructed
* (which does not do anything for trivial types).
*/
GArray(const CPPType &type, int64_t size, Allocator allocator = {}) : GArray(type, allocator)
{
BLI_assert(size >= 0);
size_ = size;
data_ = this->allocate(size_);
type_->default_construct_n(data_, size_);
}
/**
* Create an empty array with just a type.
*/
GArray(const CPPType &type, Allocator allocator = {}) : GArray(allocator)
{
type_ = &type;
}
/**
* Take ownership of a buffer with a provided size. The buffer should be
* allocated with the same allocator provided to the constructor.
*/
GArray(const CPPType &type, void *buffer, int64_t size, Allocator allocator = {})
: GArray(type, allocator)
{
BLI_assert(size >= 0);
BLI_assert(buffer != nullptr || size == 0);
BLI_assert(type_->pointer_has_valid_alignment(buffer));
data_ = buffer;
size_ = size;
}
/**
* Create an array by copying values from a generic span.
*/
GArray(const GSpan span, Allocator allocator = {}) : GArray(span.type(), span.size(), allocator)
{
if (span.data() != nullptr) {
BLI_assert(span.size() != 0);
/* Use copy assign rather than construct since the memory is already initialized. */
type_->copy_assign_n(span.data(), data_, size_);
}
}
/**
* Create an array by copying values from another generic array.
*/
GArray(const GArray &other) : GArray(other.as_span(), other.allocator())
{
}
/**
* Create an array by taking ownership of another array's data, clearing the data in the other.
*/
GArray(GArray &&other) : GArray(other.type(), other.data(), other.size(), other.allocator())
{
other.data_ = nullptr;
other.size_ = 0;
}
~GArray()
{
if (data_ != nullptr) {
type_->destruct_n(data_, size_);
this->deallocate(data_);
}
}
GArray &operator=(const GArray &other)
{
return copy_assign_container(*this, other);
}
GArray &operator=(GArray &&other)
{
return move_assign_container(*this, std::move(other));
}
const CPPType &type() const
{
BLI_assert(type_ != nullptr);
return *type_;
}
bool is_empty() const
{
return size_ == 0;
}
/**
* Return the number of elements in the array (not the size in bytes).
*/
int64_t size() const
{
return size_;
}
/**
* Get a pointer to the beginning of the array.
*/
const void *data() const
{
return data_;
}
void *data()
{
return data_;
}
const void *operator[](int64_t index) const
{
BLI_assert(index < size_);
return POINTER_OFFSET(data_, type_->size() * index);
}
void *operator[](int64_t index)
{
BLI_assert(index < size_);
return POINTER_OFFSET(data_, type_->size() * index);
}
operator GSpan() const
{
BLI_assert(type_ != nullptr);
return GSpan(*type_, data_, size_);
}
operator GMutableSpan()
{
BLI_assert(type_ != nullptr);
return GMutableSpan(*type_, data_, size_);
}
GSpan as_span() const
{
return *this;
}
GMutableSpan as_mutable_span()
{
return *this;
}
/**
* Access the allocator used by this array.
*/
Allocator &allocator()
{
return allocator_;
}
const Allocator &allocator() const
{
return allocator_;
}
/**
* Destruct values and create a new array of the given size. The values in the new array are
* default constructed.
*/
void reinitialize(const int64_t new_size)
{
BLI_assert(new_size >= 0);
int64_t old_size = size_;
type_->destruct_n(data_, size_);
size_ = 0;
if (new_size <= old_size) {
type_->default_construct_n(data_, new_size);
}
else {
void *new_data = this->allocate(new_size);
try {
type_->default_construct_n(new_data, new_size);
}
catch (...) {
this->deallocate(new_data);
throw;
}
this->deallocate(data_);
data_ = new_data;
}
size_ = new_size;
}
private:
void *allocate(int64_t size)
{
const int64_t item_size = type_->size();
const int64_t alignment = type_->alignment();
return allocator_.allocate(static_cast<size_t>(size) * item_size, alignment, AT);
}
void deallocate(void *ptr)
{
allocator_.deallocate(ptr);
}
};
} // namespace blender::fn