Add Chromium-only Blender WebEngine parity work

This commit is contained in:
mes123456
2026-08-12 04:47:48 -04:00
commit 9fd26010f6
18225 changed files with 11622124 additions and 0 deletions

View File

@@ -0,0 +1,19 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bli
*
* Defines alloca and utility macro BLI_array_alloca
*/
/* BLI_array_alloca / alloca */
#include <cstdlib>
#include <type_traits> /* IWYU pragma: keep */
#define BLI_array_alloca(arr, realsize) \
(std::remove_reference_t<decltype(arr)>)alloca(sizeof(*arr) * (realsize))

View File

@@ -0,0 +1,135 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bli
*
* An `Allocator` can allocate and deallocate memory. It is used by containers such as
* Vector. The allocators defined in this file do not work with standard library
* containers such as std::vector.
*
* Every allocator has to implement two methods:
* void *allocate(size_t size, size_t alignment, const char *name);
* void deallocate(void *ptr);
*
* We don't use the std::allocator interface, because it does more than is really necessary for an
* allocator and has some other quirks. It mixes the concepts of allocation and construction. It is
* essentially forced to be a template, even though the allocator should not care about the type.
* Also see http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2271.html#std_allocator. Some
* of these aspects have been improved in new versions of C++, so we might have to reevaluate the
* strategy later on.
*
* The allocator interface dictated by this file is very simplistic, but for now that is all we
* need. More complexity can be added when it seems necessary.
*/
#include <algorithm>
#include <cstdlib>
#include "MEM_guardedalloc.h"
#include "BLI_utildefines.h"
namespace blender {
/**
* Use Blender's guarded allocator (aka MEM_*). This should always be used except there is a
* good reason not to use it.
*/
class GuardedAllocator {
public:
void *allocate(size_t size, size_t alignment, const char *name)
{
/* Should we use MEM_new_uninitialized, when alignment is small? If yes, how small must
* alignment be? */
return MEM_new_uninitialized_aligned(size, alignment, name);
}
void *allocate_zero(size_t size, size_t alignment, const char *name)
{
if (alignment > MEM_MIN_CPP_ALIGNMENT) {
/* There is no version of calloc with a specific alignment argument. */
void *ptr = this->allocate(size, alignment, name);
memset(ptr, 0, size);
return ptr;
}
return MEM_new_zeroed(size, name);
}
void deallocate(void *ptr)
{
MEM_delete_void(ptr);
}
};
/**
* Like #GuardedAllocator, but makes sure each allocation has a minimum alignment. One use case is
* reusing an allocation between multiple types that have different alignment requirements. The
* default alignment template parameter should be large enough for any type in practice.
*/
template<size_t Alignment = 64ul> class GuardedAlignedAllocator {
public:
static constexpr size_t min_alignment = Alignment;
void *allocate(size_t size, size_t alignment, const char *name)
{
return MEM_new_uninitialized_aligned(size, std::max(alignment, min_alignment), name);
}
void *allocate_zero(size_t size, size_t alignment, const char *name)
{
if (std::max(alignment, Alignment) > MEM_MIN_CPP_ALIGNMENT) {
/* There is no version of calloc with a specific alignment argument. */
void *ptr = this->allocate(size, alignment, name);
memset(ptr, 0, size);
return ptr;
}
return MEM_new_zeroed(size, name);
}
void deallocate(void *ptr)
{
MEM_delete_void(ptr);
}
};
/**
* This is a wrapper around malloc/free. Only use this when the GuardedAllocator cannot be
* used. This can be the case when the allocated memory might live longer than Blender's
* allocator. For example, when the memory is owned by a static variable.
*/
class RawAllocator {
private:
struct MemHead {
int offset;
};
public:
void *allocate(size_t size, size_t alignment, const char * /*name*/)
{
BLI_assert(is_power_of_2(int(alignment)));
void *ptr = malloc(size + alignment + sizeof(MemHead));
void *used_ptr = reinterpret_cast<void *>(
uintptr_t(POINTER_OFFSET(ptr, alignment + sizeof(MemHead))) & ~(uintptr_t(alignment) - 1));
int offset = int(intptr_t(used_ptr) - intptr_t(ptr));
BLI_assert(offset >= int(sizeof(MemHead)));
(static_cast<MemHead *>(used_ptr) - 1)->offset = offset;
return used_ptr;
}
void *allocate_zero(size_t size, size_t alignment, const char *name)
{
void *ptr = this->allocate(size, alignment, name);
memset(ptr, 0, size);
return ptr;
}
void deallocate(void *ptr)
{
MemHead *head = static_cast<MemHead *>(ptr) - 1;
int offset = -head->offset;
void *actual_pointer = POINTER_OFFSET(ptr, offset);
free(actual_pointer);
}
};
} // namespace blender

View File

@@ -0,0 +1,397 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bli
*
* A #Any is a type-safe container for single values of any copy constructible type.
* It is similar to #std::any but provides the following two additional features:
* - Adjustable inline buffer capacity and alignment. #std::any has a small inline buffer in most
* implementations as well, but its size is not guaranteed.
* - Can store additional user-defined type information without increasing the stack size of #Any.
*/
#include <algorithm>
#include <utility>
#include "BLI_memory_utils.hh"
namespace blender {
namespace blenlib_detail {
/**
* Contains function pointers that manage the memory in an #Any.
* Additional type specific #ExtraInfo can be embedded here as well.
*/
template<typename ExtraInfo> struct AnyTypeInfo {
/* The pointers are allowed to be null, which means that the implementation is trivial. */
void (*copy_construct)(void *dst, const void *src);
void (*move_construct)(void *dst, void *src);
void (*destruct)(void *src);
const void *(*get)(const void *src);
ExtraInfo extra_info;
};
/**
* Used when #T is stored directly in the inline buffer of the #Any.
*/
template<typename ExtraInfo, typename T>
inline constexpr AnyTypeInfo<ExtraInfo> info_for_inline = {
is_trivially_copy_constructible_extended_v<T> ?
nullptr :
+[](void *dst, const void *src) { new (dst) T(*static_cast<const T *>(src)); },
is_trivially_move_constructible_extended_v<T> ?
nullptr :
+[](void *dst, void *src) { new (dst) T(std::move(*static_cast<T *>(src))); },
is_trivially_destructible_extended_v<T> ?
nullptr :
+[](void *src) { std::destroy_at((static_cast<T *>(src))); },
nullptr,
ExtraInfo::template get<T>()};
/**
* Used when #T can't be stored directly in the inline buffer and is stored in a #std::unique_ptr
* instead. In this scenario, the #std::unique_ptr is stored in the inline buffer.
*/
template<typename T> using Ptr = std::unique_ptr<T>;
template<typename ExtraInfo, typename T>
inline constexpr AnyTypeInfo<ExtraInfo> info_for_unique_ptr = {
[](void *dst, const void *src) {
new (dst) Ptr<T>(new T(**static_cast<const Ptr<T> *>(src)));
},
[](void *dst, void *src) { new (dst) Ptr<T>(new T(std::move(**static_cast<Ptr<T> *>(src)))); },
[](void *src) { std::destroy_at(static_cast<Ptr<T> *>(src)); },
[](const void *src) -> const void * { return &**static_cast<const Ptr<T> *>(src); },
ExtraInfo::template get<T>()};
/**
* Dummy extra info that is used when no additional type information should be stored in the #Any.
*/
struct NoExtraInfo {
template<typename T> static constexpr NoExtraInfo get()
{
return {};
}
};
struct EmptyType {};
} // namespace blenlib_detail
template<
/**
* Either void or a struct that contains data members for additional type information.
* The struct has to have a static `ExtraInfo get<T>()` method that initializes the struct
* based on a type.
*/
typename ExtraInfo = void,
/**
* Size of the inline buffer. This allows types that are small enough to be stored directly
* inside the #Any without an additional allocation.
*/
size_t InlineBufferCapacity = 8,
/**
* Required minimum alignment of the inline buffer. If this is smaller than the alignment
* requirement of a used type, a separate allocation is necessary.
*/
size_t Alignment = 8,
/**
* Depending on the Alignment template parameter, there may be extra padding space that's
* available to store some data. This type is stored after the buffer to use that space.
*/
typename ExtraData = blenlib_detail::EmptyType>
class Any {
private:
/* Makes it possible to use void in the template parameters. */
using RealExtraInfo =
std::conditional_t<std::is_void_v<ExtraInfo>, blenlib_detail::NoExtraInfo, ExtraInfo>;
using Info = blenlib_detail::AnyTypeInfo<RealExtraInfo>;
static constexpr size_t RealInlineBufferCapacity = std::max(InlineBufferCapacity,
sizeof(std::unique_ptr<int>));
/**
* Inline buffer that either contains nothing, the stored value directly, or a #std::unique_ptr
* to the value.
*/
AlignedBuffer<RealInlineBufferCapacity, Alignment> buffer_{};
public:
/** Extra data potentially stored within padding required by the buffer. */
BLI_NO_UNIQUE_ADDRESS ExtraData extra = {};
private:
/**
* Information about the type that is currently stored.
* This is null when the #Any does not contain a value.
*/
const Info *info_ = nullptr;
public:
/** Only copy constructible types can be stored in #Any. */
template<typename T> static constexpr bool is_allowed_v = std::is_copy_constructible_v<T>;
/**
* Checks if the type will be stored in the inline buffer or if it requires a separate
* allocation.
*/
template<typename T>
static constexpr bool is_inline_v = std::is_nothrow_move_constructible_v<T> &&
sizeof(T) <= InlineBufferCapacity && alignof(T) <= Alignment;
/**
* Checks if #T is the same type as this #Any, because in this case the behavior of e.g. the
* assignment operator is different.
*/
template<typename T> static constexpr bool is_same_any_v = std::is_same_v<std::decay_t<T>, Any>;
private:
template<typename T> const Info &get_info() const
{
using DecayT = std::decay_t<T>;
static_assert(is_allowed_v<DecayT>);
if constexpr (is_inline_v<DecayT>) {
return blenlib_detail::template info_for_inline<RealExtraInfo, DecayT>;
}
else {
return blenlib_detail::template info_for_unique_ptr<RealExtraInfo, DecayT>;
}
}
public:
Any() = default;
Any(const Any &other) : extra(other.extra), info_(other.info_)
{
if (info_ != nullptr) {
if (info_->copy_construct != nullptr) {
info_->copy_construct(&buffer_, &other.buffer_);
}
else {
std::copy_n(static_cast<const std::byte *>(other.buffer_.ptr()),
RealInlineBufferCapacity,
static_cast<std::byte *>(buffer_.ptr()));
}
}
}
/**
* \note The #other #Any will not be empty afterwards if it was not before. Just its value is in
* a moved-from state.
*/
Any(Any &&other) noexcept : extra(std::move(other.extra)), info_(other.info_)
{
if (info_ != nullptr) {
if (info_->move_construct != nullptr) {
info_->move_construct(&buffer_, &other.buffer_);
}
else {
std::copy_n(static_cast<const std::byte *>(other.buffer_.ptr()),
RealInlineBufferCapacity,
static_cast<std::byte *>(buffer_.ptr()));
}
}
}
/**
* Constructs a new #Any that contains the given type #T from #args. The #std::in_place_type_t is
* used to disambiguate this and the copy/move constructors.
*/
template<typename T, typename... Args>
explicit Any(std::in_place_type_t<T> /*tag*/, Args &&...args)
{
this->emplace_on_empty<T>(std::forward<Args>(args)...);
}
/**
* Constructs a new #Any that contains the given value.
*/
template<typename T>
Any(T &&value)
requires(!is_same_any_v<T>)
: Any(std::in_place_type<T>, std::forward<T>(value))
{
}
~Any()
{
if (info_ != nullptr) {
if (info_->destruct != nullptr) {
info_->destruct(&buffer_);
}
}
}
/**
* \note Only needed because the template below does not count as copy assignment operator.
*/
Any &operator=(const Any &other)
{
if (this == &other) {
return *this;
}
this->~Any();
new (this) Any(other);
return *this;
}
/** Assign any value to the #Any. */
template<typename T> Any &operator=(T &&other)
{
if constexpr (is_same_any_v<T>) {
if (this == &other) {
return *this;
}
}
this->~Any();
new (this) Any(std::forward<T>(other));
return *this;
}
/** Destruct any existing value to make it empty. */
void reset()
{
if (info_ != nullptr) {
if (info_->destruct != nullptr) {
info_->destruct(&buffer_);
}
}
info_ = nullptr;
}
operator bool() const
{
return this->has_value();
}
bool has_value() const
{
return info_ != nullptr;
}
template<typename T, typename... Args> std::decay_t<T> &emplace(Args &&...args)
{
this->~Any();
new (this) Any(std::in_place_type<T>, std::forward<Args>(args)...);
return this->get<T>();
}
template<typename T, typename... Args> std::decay_t<T> &emplace_on_empty(Args &&...args)
{
BLI_assert(!this->has_value());
using DecayT = std::decay_t<T>;
static_assert(is_allowed_v<DecayT>);
info_ = &this->template get_info<DecayT>();
if constexpr (is_inline_v<DecayT>) {
/* Construct the value directly in the inline buffer. */
DecayT *stored_value = new (&buffer_) DecayT(std::forward<Args>(args)...);
return *stored_value;
}
else {
/* Construct the value in a new allocation and store a #std::unique_ptr to it in the inline
* buffer. */
std::unique_ptr<DecayT> *stored_value = new (&buffer_)
std::unique_ptr<DecayT>(new DecayT(std::forward<Args>(args)...));
return **stored_value;
}
}
/**
* Like #emplace but does *not* actually construct the value. The caller is responsible for
* calling the constructor before the value is used.
*/
template<typename T> void *allocate()
{
this->reset();
return this->allocate_on_empty<T>();
}
/**
* Like #emplace_on_empty but does *not* actually construct the value. The caller is responsible
* for calling the constructor before the value is used.
*/
template<typename T> void *allocate_on_empty()
{
BLI_assert(!this->has_value());
static_assert(is_allowed_v<T>);
info_ = &this->template get_info<T>();
if constexpr (is_inline_v<T>) {
return buffer_.ptr();
}
else {
/* Using raw allocation here. The caller is responsible for constructing the value. */
T *value = static_cast<T *>(::operator new(sizeof(T)));
new (&buffer_) std::unique_ptr<T>(value);
return value;
}
}
/** Return true when the value that is currently stored is a #T. */
template<typename T> bool is() const
{
return info_ == &this->template get_info<T>();
}
/** Get a pointer to the stored value. */
void *get()
{
BLI_assert(info_ != nullptr);
if (info_->get != nullptr) {
return const_cast<void *>(info_->get(&buffer_));
}
return &buffer_;
}
/** Get a pointer to the stored value. */
const void *get() const
{
BLI_assert(info_ != nullptr);
if (info_->get != nullptr) {
return info_->get(&buffer_);
}
return &buffer_;
}
/**
* Get a reference to the stored value. This invokes undefined behavior when #T does not have the
* correct type.
*/
template<typename T> T &get()
{
/* Use const-cast to be able to reuse the const method above. */
return const_cast<T &>(const_cast<const Any *>(this)->get<T>());
}
/**
* Get a reference to the stored value. This invokes undefined behavior when #T does not have the
* correct type.
*/
template<typename T> const T &get() const
{
BLI_assert(this->is<T>());
const void *buffer;
/* Can avoid the `info_->get == nullptr` check because the result is known statically. */
if constexpr (is_inline_v<T>) {
buffer = &buffer_;
}
else {
BLI_assert(info_->get != nullptr);
buffer = info_->get(&buffer_);
}
return *static_cast<const T *>(buffer);
}
/**
* Get extra information that has been stored for the contained type.
*/
const RealExtraInfo &extra_info() const
{
BLI_assert(info_ != nullptr);
return info_->extra_info;
}
};
} // namespace blender

View File

@@ -0,0 +1,178 @@
/* SPDX-FileCopyrightText: 2026 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "BLI_any.hh"
namespace blender {
namespace detail {
template<typename Base> struct AnyDerivedExtraInfo {
Base *(*get_impl)(const void *buffer);
template<typename StorageT> static constexpr AnyDerivedExtraInfo get()
{
/* These are the only allowed types in the #Any. */
static_assert(std::is_base_of_v<Base, StorageT> ||
is_same_any_v<StorageT, Base *, std::shared_ptr<Base>>);
/* Depending on how the implementation is stored in the #Any, a different #get_impl function
* is required. */
if constexpr (std::is_base_of_v<Base, StorageT>) {
return {[](const void *buffer) {
return static_cast<Base *>(const_cast<StorageT *>(static_cast<const StorageT *>(buffer)));
}};
}
else if constexpr (std::is_same_v<StorageT, Base *>) {
return {[](const void *buffer) {
return *const_cast<StorageT *>(static_cast<const StorageT *>(buffer));
}};
}
else if constexpr (std::is_same_v<StorageT, std::shared_ptr<Base>>) {
return {[](const void *buffer) {
return (const_cast<StorageT *>(static_cast<const StorageT *>(buffer)))->get();
}};
}
else {
BLI_assert_unreachable();
return {};
}
}
};
} // namespace detail
/**
* This allows storing or passing around derived classes of a common base class. Typically, this
* always requires allocating the value on the heap and passing it around e.g. as unique_ptr.
* #AnyDerived has small buffer optimization. So if the type is small, it can be stored directly
* without an additional allocation.
*
* If all derived types are known where the type is used, it can be more efficient to use
* std::variant<Derived1, Derived2, ...> instead. #AnyDerived uses type erasure through the use of
* #Any and therefore works even when not all used derived types are known.
*
* This is used extensively for virtual arrays. Each type of virtual array is implemented as
* subclass of #VArrayImpl while #VArray actually stores a specific implementation.
*
* Note: If the value is not stored inline, it's currently stored as a shared_ptr which is shared
* when the AnyDerived is copied. This behavior is fine and efficient for all current uses but it
* may need to be generalized if #AnyDerived is supposed to be used in more places.
*/
template<typename Base, int64_t InlineBufferCapacity = 24> struct AnyDerived {
private:
using Storage = Any<detail::AnyDerivedExtraInfo<Base>, InlineBufferCapacity, alignof(Base)>;
/** Pointer to the currently contained implementation. This may be null. */
Base *impl_ = nullptr;
/**
* Does the memory management for the implementation. It contains one of the following:
* - Inline subclass of T.
* - Non-owning pointer to a T.
* - Shared pointer to a T.
*/
Storage storage_;
public:
AnyDerived() = default;
AnyDerived(const AnyDerived &other) : storage_(other.storage_)
{
impl_ = this->impl_from_storage();
}
AnyDerived(AnyDerived &&other) noexcept : storage_(std::move(other.storage_))
{
impl_ = this->impl_from_storage();
other.storage_.reset();
other.impl_ = nullptr;
}
explicit AnyDerived(Base *impl) : impl_(impl)
{
storage_ = impl_;
}
explicit AnyDerived(std::shared_ptr<Base> impl) : impl_(impl.get())
{
if (impl_) {
storage_ = std::move(impl);
}
}
AnyDerived &operator=(const AnyDerived &other)
{
if (this == &other) {
return *this;
}
storage_ = other.storage_;
impl_ = this->impl_from_storage();
return *this;
}
AnyDerived &operator=(AnyDerived &&other) noexcept
{
if (this == &other) {
return *this;
}
storage_ = std::move(other.storage_);
impl_ = this->impl_from_storage();
other.storage_.reset();
other.impl_ = nullptr;
return *this;
}
template<typename ImplT, typename... Args>
requires std::is_base_of_v<Base, ImplT>
void emplace(Args &&...args)
{
if constexpr (std::is_copy_constructible_v<ImplT> && Storage::template is_inline_v<ImplT>) {
/* Only inline the implementation when it is copyable and when it fits into the inline
* buffer of the storage. */
impl_ = &storage_.template emplace<ImplT>(std::forward<Args>(args)...);
}
else {
/* If it can't be inlined, create a new #std::shared_ptr instead and store that in the
* storage. */
std::shared_ptr<Base> ptr = std::make_shared<ImplT>(std::forward<Args>(args)...);
impl_ = &*ptr;
storage_ = std::move(ptr);
}
}
operator bool() const
{
return impl_ != nullptr;
}
Base &operator*() const
{
return *impl_;
}
Base *operator->() const
{
return impl_;
}
Base *get() const
{
return impl_;
}
protected:
Base *impl_from_storage() const
{
if (!storage_.has_value()) {
return nullptr;
}
return storage_.extra_info().get_impl(storage_.get());
}
};
} // namespace blender

View File

@@ -0,0 +1,72 @@
/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bli
* \brief A general argument parsing module.
*/
#include <stdarg.h> /* For `va_list`. */
#include <stdbool.h>
#include "BLI_compiler_attrs.h"
namespace blender {
struct bArgs;
/**
* Returns the number of extra arguments consumed by the function.
* - 0 is normal value,
* - -1 stops parsing arguments, other negative indicates skip
*/
using BA_ArgCallback = int (*)(int argc, const char **argv, void *data);
struct bArgs *BLI_args_create(int argc, const char **argv);
void BLI_args_destroy(struct bArgs *ba);
using bArgPrintFn = void (*)(void *user_data, const char *format, va_list args);
void BLI_args_printf(struct bArgs *ba, const char *format, ...);
void BLI_args_print_fn_set(struct bArgs *ba,
ATTR_PRINTF_FORMAT(2, 0) bArgPrintFn print_fn,
void *user_data);
/** The pass to use for #BLI_args_add. */
void BLI_args_pass_set(struct bArgs *ba, int current_pass);
/**
* Pass starts at 1, -1 means valid all the time
* short_arg or long_arg can be null to specify no short or long versions
*/
void BLI_args_add(struct bArgs *ba,
const char *short_arg,
const char *long_arg,
const char *doc,
BA_ArgCallback cb,
void *data);
/**
* Short_case and long_case specify if those arguments are case specific
*/
void BLI_args_add_case(struct bArgs *ba,
const char *short_arg,
int short_case,
const char *long_arg,
int long_case,
const char *doc,
BA_ArgCallback cb,
void *data);
void BLI_args_parse(struct bArgs *ba, int pass, BA_ArgCallback default_cb, void *default_data);
void BLI_args_print_arg_doc(struct bArgs *ba, const char *arg);
void BLI_args_print_other_doc(struct bArgs *ba);
bool BLI_args_has_other_doc(const struct bArgs *ba);
void BLI_args_print(const struct bArgs *ba);
} // namespace blender

View File

@@ -0,0 +1,487 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bli
*
* A `Array<T>` is a container for a fixed size array the size of which is NOT known at
* compile time.
*
* If the size is known at compile time, `std::array<T, N>` should be used instead.
*
* Array should usually be used instead of Vector whenever the number of elements
* is known at construction time. Note however, that Array will default construct all
* elements when initialized with the size-constructor. For trivial types, this does nothing. In
* all other cases, this adds overhead.
*
* A main benefit of using Array over Vector is that it expresses the intent of the developer
* better. It indicates that the size of the data structure is not expected to change. Furthermore,
* you can be more certain that an array does not over-allocate.
*
* Array supports small object optimization to improve performance when the size turns out
* to be small at run-time.
*/
#include "BLI_allocator.hh"
#include "BLI_index_range.hh"
#include "BLI_memory_utils.hh"
#include "BLI_span.hh"
#include "BLI_utildefines.h"
namespace blender {
template<
/**
* The type of the values stored in the array.
*/
typename T,
/**
* The number of values that can be stored in the array, without doing a heap allocation.
*/
int64_t InlineBufferCapacity = default_inline_buffer_capacity(sizeof(T)),
/**
* 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 Array {
public:
using value_type = T;
using pointer = T *;
using const_pointer = const T *;
using reference = T &;
using const_reference = const T &;
using iterator = T *;
using const_iterator = const T *;
using size_type = int64_t;
private:
/** The beginning of the array. It might point into the inline buffer. */
T *data_;
/** Number of elements in the array. */
int64_t size_;
/** Used for allocations when the inline buffer is too small. */
BLI_NO_UNIQUE_ADDRESS Allocator allocator_;
/** A placeholder buffer that will remain uninitialized until it is used. */
BLI_NO_UNIQUE_ADDRESS TypedBuffer<T, InlineBufferCapacity> inline_buffer_;
public:
/**
* By default an empty array is created.
*/
Array(Allocator allocator = {}) noexcept : allocator_(allocator)
{
data_ = inline_buffer_;
size_ = 0;
}
Array(NoExceptConstructor, Allocator allocator = {}) noexcept : Array(allocator) {}
/**
* Create a new array that contains copies of all values.
*/
template<typename U>
Array(Span<U> values, Allocator allocator = {})
requires(std::is_convertible_v<U, T>)
: Array(NoExceptConstructor(), allocator)
{
const int64_t size = values.size();
data_ = this->get_buffer_for_size(size);
uninitialized_convert_n<U, T>(values.data(), size, data_);
size_ = size;
}
/**
* Create a new array that contains copies of all values.
*/
template<typename U>
Array(const std::initializer_list<U> &values, Allocator allocator = {})
requires(std::is_convertible_v<U, T>)
: Array(Span<U>(values), allocator)
{
}
Array(const std::initializer_list<T> &values, Allocator allocator = {})
: Array(Span<T>(values), allocator)
{
}
/**
* Create a new array with the given size. All values will be default constructed. For trivial
* types like int, default construction does nothing.
*
* We might want another version of this in the future, that does not do default construction
* even for non-trivial types. This should not be the default though, because one can easily mess
* up when dealing with uninitialized memory.
*/
explicit Array(int64_t size, Allocator allocator = {}) : Array(NoExceptConstructor(), allocator)
{
BLI_assert(size >= 0);
data_ = this->get_buffer_for_size(size);
default_construct_n(data_, size);
size_ = size;
}
/**
* Create a new array with the given size. All values will be initialized by copying the given
* default.
*/
Array(int64_t size, const T &value, Allocator allocator = {})
: Array(NoExceptConstructor(), allocator)
{
BLI_assert(size >= 0);
if (std::is_trivially_copyable_v<T> && value_is_zero(value)) {
data_ = this->get_buffer_for_size(size, true);
}
else {
data_ = this->get_buffer_for_size(size);
#if defined(__GNUC__) && !defined(__clang__)
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Warray-bounds"
#endif
uninitialized_fill_n(data_, size, value);
#if defined(__GNUC__) && !defined(__clang__)
# pragma GCC diagnostic pop
#endif
}
size_ = size;
}
/**
* Create a new array with uninitialized elements. The caller is responsible for constructing the
* elements. Moving, copying or destructing an Array with uninitialized elements invokes
* undefined behavior.
*
* This should be used very rarely. Note, that the normal size-constructor also does not
* initialize the elements when T is trivially constructible. Therefore, it only makes sense to
* use this with non trivially constructible types.
*
* Usage:
* Array<std::string> my_strings(10, NoInitialization());
*/
Array(int64_t size, NoInitialization, Allocator allocator = {})
: Array(NoExceptConstructor(), allocator)
{
BLI_assert(size >= 0);
data_ = this->get_buffer_for_size(size);
size_ = size;
}
Array(const Array &other) : Array(other.as_span(), other.allocator_) {}
Array(Array &&other) noexcept(std::is_nothrow_move_constructible_v<T>)
: Array(NoExceptConstructor(), other.allocator_)
{
if (other.data_ == other.inline_buffer_) {
#if defined(__GNUC__) && !defined(__clang__)
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Warray-bounds"
#endif
uninitialized_relocate_n(other.data_, other.size_, data_);
#if defined(__GNUC__) && !defined(__clang__)
# pragma GCC diagnostic pop
#endif
}
else {
data_ = other.data_;
}
size_ = other.size_;
other.data_ = other.inline_buffer_;
other.size_ = 0;
}
~Array()
{
destruct_n(data_, size_);
this->deallocate_if_not_inline(data_);
}
Array &operator=(const Array &other)
{
return copy_assign_container(*this, other);
}
Array &operator=(Array &&other) noexcept(std::is_nothrow_move_constructible_v<T>)
{
return move_assign_container(*this, std::move(other));
}
T &operator[](int64_t index)
{
BLI_assert(index >= 0);
BLI_assert(index < size_);
return data_[index];
}
const T &operator[](int64_t index) const
{
BLI_assert(index >= 0);
BLI_assert(index < size_);
return data_[index];
}
operator Span<T>() const
{
return Span<T>(data_, size_);
}
operator MutableSpan<T>()
{
return MutableSpan<T>(data_, size_);
}
template<typename U>
operator Span<U>() const
requires(is_span_convertible_pointer_v<T, U>)
{
return Span<U>(data_, size_);
}
template<typename U>
operator MutableSpan<U>()
requires(is_span_convertible_pointer_v<T, U>)
{
return MutableSpan<U>(data_, size_);
}
Span<T> as_span() const
{
return *this;
}
MutableSpan<T> as_mutable_span()
{
return *this;
}
/**
* Returns the number of elements in the array.
*/
int64_t size() const
{
return size_;
}
/**
* Returns true when the number of elements in the array is zero.
*/
bool is_empty() const
{
return size_ == 0;
}
/**
* Copies the given value to every element in the array.
*/
void fill(const T &value) const
{
initialized_fill_n(data_, size_, value);
}
/**
* Return a reference to the first element in the array.
* This invokes undefined behavior when the array is empty.
*/
const T &first() const
{
BLI_assert(size_ > 0);
return *data_;
}
T &first()
{
BLI_assert(size_ > 0);
return *data_;
}
/**
* Return a reference to the nth last element.
* This invokes undefined behavior when the array is too short.
*/
const T &last(const int64_t n = 0) const
{
BLI_assert(n >= 0);
BLI_assert(n < size_);
return *(data_ + size_ - 1 - n);
}
T &last(const int64_t n = 0)
{
BLI_assert(n >= 0);
BLI_assert(n < size_);
return *(data_ + size_ - 1 - n);
}
/**
* Get a pointer to the beginning of the array.
*/
const T *data() const
{
return data_;
}
T *data()
{
return data_;
}
const T *begin() const
{
return data_;
}
const T *end() const
{
return data_ + size_;
}
T *begin()
{
return data_;
}
T *end()
{
return data_ + size_;
}
std::reverse_iterator<T *> rbegin()
{
return std::reverse_iterator<T *>(this->end());
}
std::reverse_iterator<T *> rend()
{
return std::reverse_iterator<T *>(this->begin());
}
std::reverse_iterator<const T *> rbegin() const
{
return std::reverse_iterator<T *>(this->end());
}
std::reverse_iterator<const T *> rend() const
{
return std::reverse_iterator<T *>(this->begin());
}
/**
* Get an index range containing all valid indices for this array.
*/
IndexRange index_range() const
{
return IndexRange(size_);
}
uint64_t hash() const
{
return this->as_span().hash();
}
static uint64_t hash_as(const Span<T> values)
{
return values.hash();
}
friend bool operator==(const Array &a, const Array &b)
{
return a.as_span() == b.as_span();
}
friend bool operator!=(const Array &a, const Array &b)
{
return !(a == b);
}
/**
* Sets the size to zero. This should only be used when you have manually destructed all elements
* in the array beforehand. Use with care.
*/
void clear_without_destruct()
{
size_ = 0;
}
/**
* Access the allocator used by this array.
*/
Allocator &allocator()
{
return allocator_;
}
const Allocator &allocator() const
{
return allocator_;
}
/**
* Get the value of the InlineBufferCapacity template argument. This is the number of elements
* that can be stored without doing an allocation.
*/
static int64_t inline_buffer_capacity()
{
return InlineBufferCapacity;
}
/**
* 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_;
destruct_n(data_, size_);
size_ = 0;
if (new_size <= old_size) {
default_construct_n(data_, new_size);
}
else {
T *new_data = this->get_buffer_for_size(new_size, false);
try {
default_construct_n(new_data, new_size);
}
catch (...) {
this->deallocate_if_not_inline(new_data);
throw;
}
this->deallocate_if_not_inline(data_);
data_ = new_data;
}
size_ = new_size;
}
private:
T *get_buffer_for_size(int64_t size, const bool zero = false)
{
if (size <= InlineBufferCapacity) {
if (zero) {
if constexpr (InlineBufferCapacity > 0) {
memset(static_cast<void *>(inline_buffer_), 0, size * sizeof(T));
}
}
return inline_buffer_;
}
return this->allocate(size, zero);
}
T *allocate(int64_t size, const bool zero)
{
if (zero) {
return static_cast<T *>(allocator_.allocate_zero(size_t(size) * sizeof(T), alignof(T), AT));
}
return static_cast<T *>(allocator_.allocate(size_t(size) * sizeof(T), alignof(T), AT));
}
void deallocate_if_not_inline(T *ptr)
{
if (ptr != inline_buffer_) {
allocator_.deallocate(ptr);
}
}
};
} // namespace blender

View File

@@ -0,0 +1,89 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bli
*/
#pragma once
#include <optional>
#include "BLI_array.hh"
#include "BLI_implicit_sharing_ptr.hh"
#include "BLI_virtual_array.hh"
namespace blender {
/**
* Remembers the values in an array and allows checking if another array in the future has exactly
* the same values. This is useful for checking if the topology of a mesh has changed.
*
* If possible, this class makes use of implicit-sharing to avoid creating unnecessary copies of
* the data. This also allows detecting that the array is not changed in constant time in common
* cases.
*/
template<typename T> class ArrayState {
private:
/**
* The actual values in the remembered array. This may point to data owned by #sharing_info_ or
* #cached_values_.
*/
Span<T> values_;
/** (Shared) ownership of the array in case it supports implicit-sharing. */
ImplicitSharingPtr<> sharing_info_;
/** Fallback-copy in the case when the array could not be shared. */
std::optional<Array<T, 0>> cached_values_;
public:
ArrayState() = default;
ArrayState(const VArray<T> &values, const ImplicitSharingInfo *sharing_info)
{
if (values.is_span() && sharing_info) {
/* Don't create a copy of the array and just take shared ownership. */
values_ = values.get_internal_span();
sharing_info->add_user();
sharing_info_ = ImplicitSharingPtr(sharing_info);
return;
}
/* Create a copy of the array because sharing is not possible. */
cached_values_.emplace(values.size(), NoInitialization{});
values.materialize_to_uninitialized(*cached_values_);
values_ = *cached_values_;
}
/**
* True when the remembered array does not contain any values.
*/
bool is_empty() const
{
return values_.is_empty();
}
/**
* True when the remembered array contains the same values as the given array.
* This is O(1) in the case when the array was shared and has not been modified.
* If determining equality in constant time is not possible, the method falls back to comparing
* the values individually which will take O(n) time.
*/
bool same_as(const VArray<T> &other_values, const ImplicitSharingInfo *other_sharing_info) const
{
if (sharing_info_ && other_sharing_info) {
if (sharing_info_ == other_sharing_info) {
/* The data is still shared. */
return true;
}
}
if (values_.size() != other_values.size()) {
/* The arrays can't be the same if their sizes differ. */
return false;
}
/* Need to actually compare all elements. */
VArraySpan<T> other_values_span(other_values);
return values_ == other_values_span;
}
};
} // namespace blender

View File

@@ -0,0 +1,131 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bli
* \brief Efficient in-memory storage of multiple similar arrays.
*/
#include "BLI_sys_types.h"
namespace blender {
struct BArrayState;
struct BArrayStore;
/**
* Create a new array store, which can store any number of arrays
* as long as their stride matches.
*
* \param stride: `sizeof()` each element,
*
* \note while a stride of `1` will always work,
* its less efficient since duplicate chunks of memory will be searched
* at positions unaligned with the array data.
*
* \param chunk_count: Number of elements to split each chunk into.
* - A small value increases the ability to de-duplicate chunks,
* but adds overhead by increasing the number of chunks to look up when searching for duplicates,
* as well as some overhead constructing the original array again, with more calls to `memcpy`.
* - Larger values reduce the *book keeping* overhead,
* but increase the chance a small,
* isolated change will cause a larger amount of data to be duplicated.
*
* \return A new array store, to be freed with #BLI_array_store_destroy.
*/
BArrayStore *BLI_array_store_create(unsigned int stride, unsigned int chunk_count);
/**
* Free the #BArrayStore, including all states and chunks.
*/
void BLI_array_store_destroy(BArrayStore *bs);
/**
* Clear all contents, allowing reuse of \a bs.
*/
void BLI_array_store_clear(BArrayStore *bs);
/**
* Find the memory used by all states (expanded & real).
*
* \return the total amount of memory that would be used by getting the arrays for all states.
*/
size_t BLI_array_store_calc_size_expanded_get(const BArrayStore *bs);
/**
* \return the amount of memory used by all #BChunk.data
* (duplicate chunks are only counted once).
*/
size_t BLI_array_store_calc_size_compacted_get(const BArrayStore *bs);
/**
* \param data: Data used to create
* \param state_reference: The state to use as a reference when adding the new state,
* typically this is the previous state,
* however it can be any previously created state from this \a bs.
*
* \return The new state,
* which is used by the caller as a handle to get back the contents of \a data.
* This may be removed using #BLI_array_store_state_remove,
* otherwise it will be removed with #BLI_array_store_destroy.
*/
BArrayState *BLI_array_store_state_add(BArrayStore *bs,
const void *data,
size_t data_len,
const BArrayState *state_reference);
/**
* Remove a state and free any unused #BChunk data.
*
* The states can be freed in any order.
*/
void BLI_array_store_state_remove(BArrayStore *bs, BArrayState *state);
/**
* \return the expanded size of the array,
* use this to know how much memory to allocate #BLI_array_store_state_data_get's argument.
*/
size_t BLI_array_store_state_size_get(const BArrayState *state);
/**
* Fill in existing allocated memory with the contents of \a state.
*/
void BLI_array_store_state_data_get(const BArrayState *state, void *data);
/**
* Allocate an array for \a state and return it.
*/
void *BLI_array_store_state_data_get_alloc(const BArrayState *state, size_t *r_data_len);
/**
* \note Only for tests.
*/
bool BLI_array_store_is_valid(BArrayStore *bs);
/* `array_store_rle.cc` */
/**
* Return a run-length encoded copy of `data_dec`.
*
* \param data_dec: The data to encode.
* \param data_dec_len: The size of the data to encode.
* \param data_enc_extra_size: Allocate extra memory at the beginning of the array.
* - This doesn't impact the value of `r_data_enc_len`.
* - This must be skipped when decoding.
* \param r_data_enc_len: The size of the resulting RLE encoded data.
*/
uint8_t *BLI_array_store_rle_encode(const uint8_t *data_dec,
size_t data_dec_len,
size_t data_enc_extra_size,
size_t *r_data_enc_len);
/**
* Decode a run-length encoded array, writing the result into `data_dec_v`.
*
* \param data_enc: The data to encode (returned by #BLI_array_store_rle_encode).
* \param data_enc_len: The size of `data_enc`.
* \param data_dec_v: The destination for the decoded data to be written to.
* \param data_dec_len: The size of the destination (as passed to #BLI_array_store_rle_encode).
*/
void BLI_array_store_rle_decode(const uint8_t *data_enc,
const size_t data_enc_len,
void *data_dec_v,
const size_t data_dec_len);
} // namespace blender

View File

@@ -0,0 +1,34 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bli
*/
#include <stddef.h>
namespace blender {
struct BArrayStore;
struct BArrayStore_AtSize {
struct BArrayStore **stride_table;
int stride_table_len;
};
struct BArrayStore *BLI_array_store_at_size_ensure(struct BArrayStore_AtSize *bs_stride,
int stride,
int chunk_size);
struct BArrayStore *BLI_array_store_at_size_get(struct BArrayStore_AtSize *bs_stride, int stride);
void BLI_array_store_at_size_clear(struct BArrayStore_AtSize *bs_stride);
void BLI_array_store_at_size_calc_memory_usage(const struct BArrayStore_AtSize *bs_stride,
size_t *r_size_expanded,
size_t *r_size_compacted);
} // namespace blender

View File

@@ -0,0 +1,148 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bli
* \brief Generic array manipulation API.
*/
#include "BLI_compiler_typecheck.h"
#include "BLI_sys_types.h"
namespace blender {
/**
* In-place array reverse.
*
* Access via #BLI_array_reverse
*/
void _bli_array_reverse(void *arr_v, uint arr_len, size_t arr_stride);
#define BLI_array_reverse(arr, arr_len) _bli_array_reverse(arr, arr_len, sizeof(*(arr)))
/**
* In-place array wrap.
* (rotate the array one step forward or backwards).
*
* Access via #BLI_array_wrap
*/
void _bli_array_wrap(void *arr_v, uint arr_len, size_t arr_stride, int dir);
#define BLI_array_wrap(arr, arr_len, dir) _bli_array_wrap(arr, arr_len, sizeof(*(arr)), dir)
/**
* In-place array permute.
* (re-arrange elements based on an array of indices).
*
* Access via #BLI_array_wrap
*/
void _bli_array_permute(
void *arr, uint arr_len, size_t arr_stride, const uint *order, void *arr_temp);
#define BLI_array_permute(arr, arr_len, order) \
_bli_array_permute(arr, arr_len, sizeof(*(arr)), order, NULL)
#define BLI_array_permute_ex(arr, arr_len, order, arr_temp) \
_bli_array_permute(arr, arr_len, sizeof(*(arr)), order, arr_temp)
/**
* In-place array de-duplication of an ordered array.
*
* \return The new length of the array.
*
* Access via #BLI_array_deduplicate_ordered
*/
uint _bli_array_deduplicate_ordered(void *arr, uint arr_len, size_t arr_stride);
#define BLI_array_deduplicate_ordered(arr, arr_len) \
_bli_array_deduplicate_ordered(arr, arr_len, sizeof(*(arr)))
/**
* Find the first index of an item in an array.
*
* Access via #BLI_array_findindex
*
* \note Not efficient, use for error checks/asserts.
*/
int _bli_array_findindex(const void *arr, uint arr_len, size_t arr_stride, const void *p);
#define BLI_array_findindex(arr, arr_len, p) _bli_array_findindex(arr, arr_len, sizeof(*(arr)), p)
/**
* A version of #BLI_array_findindex that searches from the end of the list.
*/
int _bli_array_rfindindex(const void *arr, uint arr_len, size_t arr_stride, const void *p);
#define BLI_array_rfindindex(arr, arr_len, p) \
_bli_array_rfindindex(arr, arr_len, sizeof(*(arr)), p)
void _bli_array_binary_and(
void *arr, const void *arr_a, const void *arr_b, uint arr_len, size_t arr_stride);
#define BLI_array_binary_and(arr, arr_a, arr_b, arr_len) \
(CHECK_TYPE_PAIR_INLINE(*(arr), *(arr_a)), \
CHECK_TYPE_PAIR_INLINE(*(arr), *(arr_b)), \
_bli_array_binary_and(arr, arr_a, arr_b, arr_len, sizeof(*(arr))))
void _bli_array_binary_or(
void *arr, const void *arr_a, const void *arr_b, uint arr_len, size_t arr_stride);
#define BLI_array_binary_or(arr, arr_a, arr_b, arr_len) \
(CHECK_TYPE_PAIR_INLINE(*(arr), *(arr_a)), \
CHECK_TYPE_PAIR_INLINE(*(arr), *(arr_b)), \
_bli_array_binary_or(arr, arr_a, arr_b, arr_len, sizeof(*(arr))))
/**
* Utility function to iterate over contiguous items in an array.
*
* \param use_wrap: Detect contiguous ranges across the first/last points.
* In this case the second index of \a span_step may be lower than the first,
* which indicates the values are wrapped.
* \param use_delimit_bounds: When false,
* ranges that defined by the start/end indices are excluded.
* This option has no effect when \a use_wrap is enabled.
* \param test_fn: Function to test if the item should be included in the range.
* \param user_data: User data for \a test_fn.
* \param span_step: Indices to iterate over,
* initialize both values to the array length to initialize iteration.
* \param r_span_len: The length of the span, useful when \a use_wrap is enabled,
* where calculating the length isn't a simple subtraction.
*/
bool _bli_array_iter_span(const void *arr,
uint arr_len,
size_t arr_stride,
bool use_wrap,
bool use_delimit_bounds,
bool (*test_fn)(const void *arr_item, void *user_data),
void *user_data,
uint span_step[2],
uint *r_span_len);
#define BLI_array_iter_span( \
arr, arr_len, use_wrap, use_delimit_bounds, test_fn, user_data, span_step, r_span_len) \
_bli_array_iter_span(arr, \
arr_len, \
sizeof(*(arr)), \
use_wrap, \
use_delimit_bounds, \
test_fn, \
user_data, \
span_step, \
r_span_len)
/**
* Simple utility to check memory is zeroed.
*/
bool _bli_array_is_zeroed(const void *arr_v, uint arr_len, size_t arr_stride);
#define BLI_array_is_zeroed(arr, arr_len) _bli_array_is_zeroed(arr, arr_len, sizeof(*(arr)))
/**
* Smart function to sample a rectangle spiraling outside.
* Nice for selection ID.
*
* \param arr_shape: dimensions [w, h].
* \param center: coordinates [x, y] indicating where to start traversing.
*/
bool _bli_array_iter_spiral_square(const void *arr_v,
const int arr_shape[2],
size_t elem_size,
const int center[2],
bool (*test_fn)(const void *arr_item, void *user_data),
void *user_data);
#define BLI_array_iter_spiral_square(arr, arr_shape, center, test_fn, user_data) \
_bli_array_iter_spiral_square(arr, arr_shape, sizeof(*(arr)), center, test_fn, user_data)
} // namespace blender

View File

@@ -0,0 +1,509 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bli
*/
#pragma once
#include <numeric>
#include "BLI_execution_mode.hh"
#include "BLI_generic_span.hh"
#include "BLI_generic_virtual_array.hh"
#include "BLI_index_mask.hh"
#include "BLI_math_base.h"
#include "BLI_offset_indices.hh"
#include "BLI_task.hh"
#include "BLI_virtual_array.hh"
#include "PRF_profile.hh"
namespace blender::array_utils {
constexpr int64_t calc_copy_grain_size(const exec_mode::Tag auto mode, const int64_t type_size)
{
/* The grain size should roughly depend on the work being done per index, which roughly
* corresponds with the size of the type being copied. */
return mode.grain_size(std::max<int64_t>(1, 32768 / type_size));
}
constexpr int64_t calc_copy_grain_size(const exec_mode::Mode mode, const int64_t type_size)
{
return mode.grain_size(std::max<int64_t>(1, 32768 / type_size));
}
/** Similar to #Mode::grain_size(int), but also returns a tag type. */
template<exec_mode::Tag Mode>
constexpr auto exec_mode_tag_for_copy(const Mode mode, const int64_t type_size)
{
if constexpr (mode.is_parallel) {
return exec_mode::ParallelGrainSize{mode.grain_size(std::max<int64_t>(1, 32768 / type_size))};
}
else {
return Mode{mode};
}
}
/**
* Fill the destination span by copying all values from the `src` array.
*/
void copy(const GVArray &src, GMutableSpan dst, exec_mode::Mode mode = exec_mode::parallel);
template<typename T, exec_mode::Tag Mode = exec_mode::Parallel>
inline void copy(const VArray<T> &src, MutableSpan<T> dst, const Mode mode = {})
{
BLI_assert(src.size() == dst.size());
if constexpr (!mode.is_parallel) {
src.materialize_compressed(dst.index_range(), dst);
}
else {
const int64_t grain_size = calc_copy_grain_size(mode, sizeof(T));
threading::parallel_for(src.index_range(), grain_size, [&](const IndexRange range) {
src.materialize(range, dst);
});
}
}
/**
* Fill the destination span by copying all values from the `src` array.
*/
template<typename T, exec_mode::Tag Mode = exec_mode::Parallel>
inline void copy(const Span<T> src, MutableSpan<T> dst, const Mode mode = {})
{
BLI_assert(src.size() == dst.size());
if constexpr (!mode.is_parallel) {
dst.copy_from(src);
}
else {
const int64_t grain_size = calc_copy_grain_size(mode, sizeof(T));
threading::parallel_for(src.index_range(), grain_size, [&](const IndexRange range) {
copy(src.slice(range), dst.slice(range), exec_mode::serial);
});
}
}
/**
* Fill the destination span by copying masked values from the `src` array.
*/
void copy(const GVArray &src,
const IndexMask &selection,
GMutableSpan dst,
exec_mode::Mode mode = exec_mode::parallel);
/**
* Fill the destination span by copying values from the `src` array.
*/
template<typename T, exec_mode::Tag Mode = exec_mode::Parallel>
inline void copy(const Span<T> src,
const IndexMask &selection,
MutableSpan<T> dst,
const Mode mode = {})
{
BLI_assert(src.size() == dst.size());
selection.foreach_index_optimized<int64_t>([&](const int64_t i) { dst[i] = src[i]; },
exec_mode_tag_for_copy(mode, sizeof(T)));
}
template<typename T> T compute_sum(const Span<T> data)
{
/* Explicitly splitting work into chunks for a couple of reasons:
* - Improve numerical stability. While there are even more stable algorithms (e.g. Kahan
* summation), they also add more complexity to the hot code path. So far, this simple approach
* seems to solve the common issues people run into.
* - Support computing the sum using multiple threads.
* - Ensure deterministic results even with floating point numbers.
*/
constexpr int64_t chunk_size = 1024;
const int64_t chunks_num = divide_ceil_ul(data.size(), chunk_size);
Array<T> partial_sums(chunks_num);
threading::parallel_for(partial_sums.index_range(), 1, [&](const IndexRange range) {
for (const int64_t i : range) {
const int64_t start = i * chunk_size;
const Span<T> chunk = data.slice_safe(start, chunk_size);
const T partial_sum = std::accumulate(chunk.begin(), chunk.end(), T());
partial_sums[i] = partial_sum;
}
});
return std::accumulate(partial_sums.begin(), partial_sums.end(), T());
}
/**
* Fill the specified indices of the destination with the values in the source span.
*/
template<typename T, typename IndexT, exec_mode::Tag Mode = exec_mode::Parallel>
inline void scatter(const Span<T> src,
const Span<IndexT> indices,
MutableSpan<T> dst,
const Mode mode = {})
{
BLI_assert(indices.size() == src.size());
if constexpr (!mode.is_parallel) {
for (const int64_t i : indices.index_range()) {
dst[indices[i]] = src[i];
}
}
else {
const int64_t grain_size = calc_copy_grain_size(mode, sizeof(T));
threading::parallel_for(indices.index_range(), grain_size, [&](const IndexRange range) {
scatter(src.slice(range), indices.slice(range), dst, exec_mode::serial);
});
}
}
template<typename T, exec_mode::Tag Mode = exec_mode::Parallel>
inline void scatter(const Span<T> src,
const IndexMask &indices,
MutableSpan<T> dst,
const Mode mode = {})
{
BLI_assert(indices.size() == src.size());
BLI_assert(indices.min_array_size() <= dst.size());
indices.foreach_index_optimized<int64_t>(
[&](const int64_t index, const int64_t pos) { dst[index] = src[pos]; },
exec_mode_tag_for_copy(mode, sizeof(T)));
}
/**
* Fill the destination span by gathering indexed values from the `src` array.
*/
void gather(const GVArray &src,
const IndexMask &indices,
GMutableSpan dst,
exec_mode::Mode mode = exec_mode::parallel);
/**
* Fill the destination span by gathering indexed values from the `src` array.
*/
void gather(GSpan src,
const IndexMask &indices,
GMutableSpan dst,
exec_mode::Mode mode = exec_mode::parallel);
/**
* Fill the destination span by gathering indexed values from the `src` array.
*/
template<typename T, exec_mode::Tag Mode = exec_mode::Parallel>
inline void gather(const VArray<T> &src,
const IndexMask &indices,
MutableSpan<T> dst,
const Mode mode = {})
{
PRF_scope_with_name("array_utils::gather", ProfileCategory::Default);
BLI_assert(indices.size() >= dst.size());
if constexpr (!mode.is_parallel) {
src.materialize_compressed(indices, dst);
}
else {
const int64_t grain_size = calc_copy_grain_size(mode, sizeof(T));
threading::parallel_for(indices.index_range(), grain_size, [&](const IndexRange range) {
src.materialize_compressed(indices.slice(range), dst.slice(range));
});
}
}
/**
* Fill the destination span by gathering indexed values from the `src` array.
*/
template<typename T, typename IndexT, exec_mode::Tag Mode = exec_mode::Parallel>
inline void gather(const Span<T> src,
const Span<IndexT> indices,
const IndexMask &dst_mask,
MutableSpan<T> dst,
const Mode mode = {})
{
PRF_scope_with_name("array_utils::gather", ProfileCategory::Default);
BLI_assert(indices.size() >= dst.size());
dst_mask.foreach_index_optimized<int64_t>([&](const int64_t i) { dst[i] = src[indices[i]]; },
exec_mode_tag_for_copy(mode, sizeof(T)));
}
/**
* Fill the destination span by gathering indexed values from the `src` array.
*/
template<typename T, typename IndexT, exec_mode::Tag Mode = exec_mode::Parallel>
inline void gather(const Span<T> src,
const Span<IndexT> indices,
MutableSpan<T> dst,
const Mode mode = {})
{
gather(src, indices, IndexMask(dst.size()), dst, mode);
}
/**
* Fill the destination span by gathering indexed values from the `src` array.
*/
template<typename T, typename IndexT, exec_mode::Tag Mode = exec_mode::Parallel>
inline void gather(const VArray<T> &src,
const Span<IndexT> indices,
const IndexMask &dst_mask,
MutableSpan<T> dst,
const Mode mode = {})
{
PRF_scope_with_name("array_utils::gather", ProfileCategory::Default);
BLI_assert(indices.size() >= dst_mask.min_array_size());
const CommonVArrayInfo info = src.common_info();
switch (info.type) {
case CommonVArrayInfo::Type::Any: {
dst_mask.foreach_index_optimized<int64_t>([&](const int64_t i) { dst[i] = src[indices[i]]; },
exec_mode_tag_for_copy(mode, sizeof(T)));
break;
}
case CommonVArrayInfo::Type::Span: {
const Span span(static_cast<const T *>(info.data), src.size());
gather(span, indices, dst_mask, dst, mode);
break;
}
case CommonVArrayInfo::Type::Single: {
index_mask::masked_fill(dst, *static_cast<const T *>(info.data), dst_mask);
break;
}
}
}
/**
* Fill the destination span by gathering indexed values from the `src` array.
*/
template<typename T, typename IndexT, exec_mode::Tag Mode = exec_mode::Parallel>
inline void gather(const VArray<T> &src,
const Span<IndexT> indices,
MutableSpan<T> dst,
const Mode mode = {})
{
gather(src, indices, dst.index_range(), dst, mode);
}
template<typename T>
inline void gather_group_to_group(const OffsetIndices<int> src_offsets,
const OffsetIndices<int> dst_offsets,
const IndexMask &selection,
const Span<T> src,
MutableSpan<T> dst)
{
selection.foreach_index(
[&](const int64_t src_i, const int64_t dst_i) {
dst.slice(dst_offsets[dst_i]).copy_from(src.slice(src_offsets[src_i]));
},
exec_mode::grain_size(512));
}
template<typename T>
inline void gather_group_to_group(const OffsetIndices<int> src_offsets,
const OffsetIndices<int> dst_offsets,
const IndexMask &selection,
const VArray<T> src,
MutableSpan<T> dst)
{
selection.foreach_index(
[&](const int64_t src_i, const int64_t dst_i) {
src.materialize_compressed(src_offsets[src_i], dst.slice(dst_offsets[dst_i]));
},
exec_mode::grain_size(512));
}
template<typename T>
inline void gather_to_groups(const OffsetIndices<int> dst_offsets,
const IndexMask &src_selection,
const Span<T> src,
MutableSpan<T> dst)
{
src_selection.foreach_index(
[&](const int src_i, const int dst_i) { dst.slice(dst_offsets[dst_i]).fill(src[src_i]); },
exec_mode::grain_size(1024));
}
/**
* Copy the \a src data from the groups defined by \a src_offsets to the groups in \a dst defined
* by \a dst_offsets. Groups to use are masked by \a selection, and it is assumed that the
* corresponding groups have the same size.
*/
void copy_group_to_group(OffsetIndices<int> src_offsets,
OffsetIndices<int> dst_offsets,
const IndexMask &selection,
GSpan src,
GMutableSpan dst);
template<typename T>
void copy_group_to_group(OffsetIndices<int> src_offsets,
OffsetIndices<int> dst_offsets,
const IndexMask &selection,
Span<T> src,
MutableSpan<T> dst)
{
copy_group_to_group(src_offsets, dst_offsets, selection, GSpan(src), GMutableSpan(dst));
}
/**
* Count the number of occurrences of each index.
* \param indices: The indices to count.
* \param counts: The number of occurrences of each index. Typically initialized to zero.
* Must be large enough to contain the maximum index.
*
* \note The memory referenced by the two spans must not overlap.
*/
void count_indices(Span<int> indices, MutableSpan<int> counts);
void invert_booleans(MutableSpan<bool> span);
void invert_booleans(MutableSpan<bool> span, const IndexMask &mask);
int64_t count_booleans(const VArray<bool> &varray);
int64_t count_booleans(const VArray<bool> &varray, const IndexMask &mask);
enum class BooleanMix {
None,
AllFalse,
AllTrue,
Mixed,
};
BooleanMix booleans_mix_calc(const VArray<bool> &varray, IndexRange range_to_check);
inline BooleanMix booleans_mix_calc(const VArray<bool> &varray)
{
return booleans_mix_calc(varray, varray.index_range());
}
/** Check if the value exists in the array. */
bool contains(const VArray<bool> &varray, const IndexMask &indices_to_check, bool value);
/** Return indices in the mask that are non-negative. */
IndexMask indices_non_negative(const IndexMask &universe,
Span<int> values,
LinearAllocator<> &memory);
/** Return indices in the mask that are not negative and less than the given size. */
IndexMask indices_in_range(const IndexMask &universe,
Span<int> values,
IndexRange range,
LinearAllocator<> &memory);
/**
* Finds all the index ranges for which consecutive values in \a span equal \a value.
*/
template<typename T> inline Vector<IndexRange> find_all_ranges(const Span<T> span, const T &value)
{
if (span.is_empty()) {
return Vector<IndexRange>();
}
Vector<IndexRange> ranges;
int64_t length = (span.first() == value) ? 1 : 0;
for (const int64_t i : span.index_range().drop_front(1)) {
if (span[i - 1] == value && span[i] != value) {
ranges.append(IndexRange::from_end_size(i, length));
length = 0;
}
else if (span[i] == value) {
length++;
}
}
if (length > 0) {
ranges.append(IndexRange::from_end_size(span.size(), length));
}
return ranges;
}
/**
* Fill the span with increasing indices: 0, 1, 2, ...
* Optionally, the start value can be provided.
*/
template<typename T> inline void fill_index_range(MutableSpan<T> span, const T start = 0)
{
std::iota(span.begin(), span.end(), start);
}
template<typename T, exec_mode::Tag Mode = exec_mode::Parallel>
inline void fill_index_range(const IndexMask &mask, MutableSpan<T> span, const Mode mode = {})
{
mask.foreach_index_optimized<T>([&](const T index) { span[index] = index; }, mode);
}
template<typename T, exec_mode::Tag Mode = exec_mode::Parallel>
inline void fill_index_range(const IndexMask &mask,
MutableSpan<T> span,
const T start,
const Mode mode = {})
{
if (start == 0) {
fill_index_range(mask, span, mode);
}
else {
mask.foreach_index_optimized<T>([&](const T index) { span[index] = start + index; }, mode);
}
}
template<typename T>
bool indexed_data_equal(const Span<T> all_values, const Span<int> indices, const Span<T> values)
{
BLI_assert(indices.size() == values.size());
for (const int i : indices.index_range()) {
if (all_values[indices[i]] != values[i]) {
return false;
}
}
return true;
}
bool indices_are_range(Span<int> indices, IndexRange range);
/**
* Returns the index of the (first) maximum element in the virtual array or std::nullopt if the
* array is empty.
*/
template<typename T>
inline std::optional<int64_t> max_element_index(const Span<T> &span,
const int64_t grain_size = 8192)
{
const T *max_it = threading::parallel_reduce(
span.index_range(),
grain_size,
span.begin(),
[&](const IndexRange range, const T *init_max) {
const Span<T> sub_span = span.slice(range);
const T *max_elem = std::max_element(sub_span.begin(), sub_span.end());
if (*max_elem < *init_max) {
return init_max;
}
return max_elem;
},
[&](const T *a, const T *b) {
if (*a < *b) {
return a;
}
return b;
});
return std::distance(span.begin(), max_it);
}
template<typename T>
inline std::optional<int64_t> max_element_index(const VArray<T> &array,
const int64_t grain_size = 8192)
{
if (!array || array.is_empty()) {
return std::nullopt;
}
if (array.is_single()) {
return array.first();
}
if (array.is_span()) {
return max_element_index(array.get_internal_span(), grain_size);
}
return threading::parallel_reduce(
array.index_range(),
grain_size,
array.first(),
[&](const IndexRange range, const int64_t init_i) {
int64_t max_index = init_i;
T max_elem = array[max_index];
for (const int i : range) {
if (max_elem < array[i]) {
max_index = i;
max_elem = array[i];
}
}
return max_index;
},
[&](const int64_t index_a, const int64_t index_b) {
if (array[index_a] < array[index_b]) {
return index_b;
}
return index_a;
});
}
} // namespace blender::array_utils

View File

@@ -0,0 +1,36 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bli
*/
#pragma once
/* Clang defines this. */
#ifndef __has_feature
# define __has_feature(x) 0
#endif
#if (defined(__SANITIZE_ADDRESS__) || __has_feature(address_sanitizer)) && \
(!defined(_MSC_VER) || _MSC_VER > 1929) /* MSVC 2019 and below doesn't ship ASAN headers. */
# include "sanitizer/asan_interface.h"
# define WITH_ASAN
#else
/* Ensure return value is used. Just using UNUSED_VARS results in a warning. */
# define ASAN_POISON_MEMORY_REGION(addr, size) (void)(0 && ((size) != 0 && (addr) != NULL))
# define ASAN_UNPOISON_MEMORY_REGION(addr, size) (void)(0 && ((size) != 0 && (addr) != NULL))
#endif
/**
* Mark a region of memory as "freed". When using address sanitizer, accessing the given memory
* region will cause an use-after-poison error. This can be used to find errors when dealing with
* uninitialized memory in custom containers.
*/
#define BLI_asan_poison(addr, size) ASAN_POISON_MEMORY_REGION(addr, size)
/**
* Mark a region of memory as usable again.
*/
#define BLI_asan_unpoison(addr, size) ASAN_UNPOISON_MEMORY_REGION(addr, size)

View File

@@ -0,0 +1,120 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bli
*
* Defines:
* - #BLI_assert
* - #BLI_STATIC_ASSERT
* - #BLI_STATIC_ASSERT_ALIGN
*/
#include <stddef.h>
/* Utility functions. */
void _BLI_assert_print_pos(const char *file, int line, const char *function, const char *id);
void _BLI_assert_print_extra(const char *str);
void _BLI_assert_print_backtrace();
void _BLI_assert_abort();
void _BLI_assert_unreachable_print(const char *file, int line, const char *function);
#ifdef _MSC_VER
# include <crtdbg.h> /* for _STATIC_ASSERT */
#endif
#ifndef NDEBUG
/* _BLI_ASSERT_PRINT_POS */
# if defined(__GNUC__)
# define _BLI_ASSERT_PRINT_POS(a) _BLI_assert_print_pos(__FILE__, __LINE__, __func__, #a)
# elif defined(_MSC_VER)
# define _BLI_ASSERT_PRINT_POS(a) _BLI_assert_print_pos(__FILE__, __LINE__, __func__, #a)
# else
# define _BLI_ASSERT_PRINT_POS(a) _BLI_assert_print_pos(__FILE__, __LINE__, "<?>", #a)
# endif
/* _BLI_ASSERT_ABORT */
# ifdef WITH_ASSERT_ABORT
# define _BLI_ASSERT_ABORT _BLI_assert_abort
# else
# define _BLI_ASSERT_ABORT() (void)0
# endif
/* BLI_assert */
# define BLI_assert(a) \
(void)((!(a)) ? ((_BLI_assert_print_backtrace(), \
_BLI_ASSERT_PRINT_POS(a), \
_BLI_ASSERT_ABORT(), \
NULL)) : \
NULL)
/** A version of #BLI_assert() to pass an additional message to be printed on failure. */
# define BLI_assert_msg(a, msg) \
(void)((!(a)) ? ((_BLI_assert_print_backtrace(), \
_BLI_ASSERT_PRINT_POS(a), \
_BLI_assert_print_extra(msg), \
_BLI_ASSERT_ABORT(), \
NULL)) : \
NULL)
#else
# define BLI_assert(a) ((void)0)
# define BLI_assert_msg(a, msg) ((void)0)
#endif
#if defined(__cplusplus)
/* C++11 */
# define BLI_STATIC_ASSERT(a, msg) static_assert(a, msg);
#elif defined(_MSC_VER)
/* Visual Studio */
# if !defined(__clang__)
# define BLI_STATIC_ASSERT(a, msg) static_assert(a, msg);
# else
# define BLI_STATIC_ASSERT(a, msg) _STATIC_ASSERT(a);
# endif
#elif defined(__COVERITY__)
/* Workaround error with COVERITY. */
# define BLI_STATIC_ASSERT(a, msg)
#elif defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L)
/* C11 */
# define BLI_STATIC_ASSERT(a, msg) _Static_assert(a, msg);
#else
/* Old unsupported compiler */
# define BLI_STATIC_ASSERT(a, msg)
#endif
#define BLI_STATIC_ASSERT_ALIGN(st, align) \
BLI_STATIC_ASSERT((sizeof(st) % (align) == 0), "Structure must be strictly aligned")
/**
* Indicates that this line of code should never be executed. If it is reached, it will abort in
* debug builds and print an error in release builds.
*/
#define BLI_assert_unreachable() \
{ \
_BLI_assert_unreachable_print(__FILE__, __LINE__, __func__); \
BLI_assert_msg(0, "This line of code is marked to be unreachable."); \
} \
((void)0)
#ifdef __cplusplus
/**
* Indicates that this line should never be reached, even at compile-time. Just doing
* `static_assert(false)` doesn't work in C++20 for this use-case. BLI_assert_unreachable is
* similar but does not catch issues at compile-time.
*/
# define BLI_assert_unreachable_static() \
static_assert([]<bool flag = false>() { return flag; }(), \
"Unreachable code path reached at compile-time!")
template<class T> inline constexpr bool _bli_always_false = false;
/**
* Like #BLI_assert_unreachable_static but allows passing in a type. This results in a more useful
* error message containing the type name.
*/
# define BLI_assert_unreachable_static_t(T) \
static_assert(_bli_always_false<T>, "Unreachable code path reached at compile-time!")
#endif

View File

@@ -0,0 +1,150 @@
/* SPDX-FileCopyrightText: 2014 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bli
* \brief An implementation of the A* (AStar) algorithm to solve shortest path problem.
*/
#include "DNA_listBase.h"
#include "BLI_bitmap.h"
namespace blender {
/* -------------------------------------------------------------------- */
struct BLI_AStarGNLink {
int nodes[2];
float cost;
void *custom_data;
};
struct BLI_AStarGNode {
ListBaseT<LinkData> neighbor_links;
void *custom_data;
};
struct BLI_AStarSolution {
/* Final 'most useful' data. */
/** Number of steps (i.e. walked links) in path
* (nodes num, including start and end, is steps + 1). */
int steps;
/** Store the path, in reversed order (from destination to source node), as indices. */
int *prev_nodes;
/** Indices are nodes' ones, as prev_nodes, but they map to relevant link. */
BLI_AStarGNLink **prev_links;
void *custom_data;
/* Mostly runtime data. */
BLI_bitmap *done_nodes;
float *g_costs;
int *g_steps;
struct MemArena *mem; /* Memory arena. */
};
struct BLI_AStarGraph {
int node_num;
BLI_AStarGNode *nodes;
void *custom_data;
struct MemArena *mem; /* Memory arena. */
};
/**
* Initialize a node in A* graph.
*
* \param custom_data: an opaque pointer attached to this link,
* available e.g. to cost callback function.
*/
void BLI_astar_node_init(BLI_AStarGraph *as_graph, int node_index, void *custom_data);
/**
* Add a link between two nodes of our A* graph.
*
* \param cost: The 'length' of the link
* (actual distance between two vertices or face centers e.g.).
* \param custom_data: An opaque pointer attached to this link,
* available e.g. to cost callback function.
*/
void BLI_astar_node_link_add(
BLI_AStarGraph *as_graph, int node1_index, int node2_index, float cost, void *custom_data);
/**
* \return The index of the other node of given link.
*/
int BLI_astar_node_link_other_node(BLI_AStarGNLink *lnk, int idx);
/**
* Initialize a solution data for given A* graph. Does not compute anything!
*
* \param custom_data: an opaque pointer attached to this link, available e.g.
* to cost callback function.
*
* \note BLI_AStarSolution stores nearly all data needed during solution compute.
*/
void BLI_astar_solution_init(BLI_AStarGraph *as_graph,
BLI_AStarSolution *as_solution,
void *custom_data);
/**
* Clear given solution's data, but does not release its memory.
* Avoids having to recreate/allocate a memory-arena in loops, e.g.
*
* \note This *has to be called* between each path solving.
*/
void BLI_astar_solution_clear(BLI_AStarSolution *as_solution);
/**
* Release the memory allocated for this solution.
*/
void BLI_astar_solution_free(BLI_AStarSolution *as_solution);
/**
* Callback computing the current cost (distance) to next node,
* and the estimated overall cost to destination node
* (A* expects this estimation to always be less or equal than actual shortest path
* from next node to destination one).
*
* \param link: the graph link between current node and next one.
* \param node_idx_curr: current node index.
* \param node_idx_next: next node index.
* \param node_idx_dst: destination node index.
*/
using astar_f_cost = float (*)(BLI_AStarGraph *as_graph,
BLI_AStarSolution *as_solution,
BLI_AStarGNLink *link,
int node_idx_curr,
int node_idx_next,
int node_idx_dst);
/**
* Initialize an A* graph. Total number of nodes must be known.
*
* Nodes might be e.g. vertices, faces, ... etc.
*
* \param custom_data: an opaque pointer attached to this link,
* available e.g. to cost callback function.
*/
void BLI_astar_graph_init(BLI_AStarGraph *as_graph, int node_num, void *custom_data);
void BLI_astar_graph_free(BLI_AStarGraph *as_graph);
/**
* Solve a path in given graph, using given 'cost' callback function.
*
* \param max_steps: maximum number of nodes the found path may have.
* Useful in performance-critical usages.
* If no path is found within given steps, returns false too.
* \return true if a path was found, false otherwise.
*/
bool BLI_astar_graph_solve(BLI_AStarGraph *as_graph,
int node_index_src,
int node_index_dst,
astar_f_cost f_cost_cb,
BLI_AStarSolution *r_solution,
int max_steps);
} // namespace blender

View File

@@ -0,0 +1,158 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bli
*/
#pragma once
#include <atomic>
#include "BLI_array.hh"
namespace blender {
/**
* Same as `DisjointSet` but is thread safe (at slightly higher cost for the single threaded case).
*
* The implementation is based on the following paper:
* "Wait-free Parallel Algorithms for the Union-Find Problem"
* by Richard J. Anderson and Heather Woll.
*
* It's also inspired by this implementation: https://github.com/wjakob/dset.
*/
class AtomicDisjointSet {
private:
/* Can generally used relaxed memory order with this algorithm. */
static constexpr auto relaxed = std::memory_order_relaxed;
struct Item {
int parent;
int rank;
};
/**
* An #Item per element. It's important that the entire item is in a single atomic, so that it
* can be updated atomically. */
mutable Array<std::atomic<Item>> items_;
public:
/**
* Create a new disjoing set with the given set. Initially, every element is in a separate set.
*/
AtomicDisjointSet(const int size);
/**
* Join the sets containing elements x and y. Nothing happens when they were in the same set
* before.
*/
void join(int x, int y)
{
while (true) {
x = this->find_root(x);
y = this->find_root(y);
if (x == y) {
/* They are in the same set already. */
return;
}
int x_rank = items_[x].load(relaxed).rank;
int y_rank = items_[y].load(relaxed).rank;
if (
/* Implement union by rank heuristic. */
x_rank > y_rank
/* If the rank is the same, make a consistent decision. */
|| (x_rank == y_rank && x < y))
{
std::swap(x_rank, y_rank);
std::swap(x, y);
}
/* Update parent of item x. */
Item x_item_old = {x, x_rank};
const Item x_item_new{y, x_rank};
if (!items_[x].compare_exchange_strong(x_item_old, x_item_new, relaxed)) {
/* Another thread has updated item x, start again. */
continue;
}
if (x_rank == y_rank) {
/* Increase rank of item y. This may fail when another thread has updated item y in the
* meantime. That may lead to worse behavior with the union by rank heurist, but seems to
* be ok in practice. */
Item y_item_old{y, y_rank};
const Item y_item_new{y, y_rank + 1};
items_[y].compare_exchange_weak(y_item_old, y_item_new, relaxed);
}
return;
}
}
/**
* Return true when x and y are in the same set.
*/
bool in_same_set(int x, int y) const
{
while (true) {
x = this->find_root(x);
y = this->find_root(y);
if (x == y) {
return true;
}
if (items_[x].load(relaxed).parent == x) {
return false;
}
}
}
/**
* Find the element that represents the set containing x currently.
*/
int find_root(int x) const
{
while (true) {
const Item item = items_[x].load(relaxed);
if (x == item.parent) {
return x;
}
const int new_parent = items_[item.parent].load(relaxed).parent;
if (item.parent != new_parent) {
/* This halves the path for faster future lookups. That fail but that does not change
* correctness. */
Item expected = item;
const Item desired{new_parent, item.rank};
items_[x].compare_exchange_weak(expected, desired, relaxed);
}
x = new_parent;
}
}
/**
* True when x represents a set.
*/
bool is_root(const int x) const
{
const Item item = items_[x].load(relaxed);
return item.parent == x;
}
/**
* Get an identifier for each id. This is deterministic and does not depend on the order of
* joins. The ids are ordered by their first occurrence. Consequently, `result[0]` is always zero
* (unless there are no elements).
* \return The total number of unique IDs.
*/
int calc_reduced_ids(MutableSpan<int> result) const;
/**
* Count the number of disjoint sets.
*/
int count_sets() const;
};
} // namespace blender

View File

@@ -0,0 +1,56 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bli
*/
#include <algorithm>
namespace blender::binary_search {
/**
* Find the index of the first element where the predicate is true. The predicate must also be
* true for all following elements. If the predicate is false for all elements, the size of the
* range is returned.
*/
template<typename Iterator, typename Predicate>
static int64_t first_if(Iterator begin, Iterator end, Predicate &&predicate)
{
return std::lower_bound(begin,
end,
nullptr,
[&](const auto &value, void * /*dummy*/) { return !predicate(value); }) -
begin;
}
/**
* Find the index of the last element where the predicate is true. The predicate must also be
* true for all previous elements. If the predicate is false for all elements, the -1 is returned.
*/
template<typename Iterator, typename Predicate>
static int64_t last_if(Iterator begin, Iterator end, Predicate &&predicate)
{
return std::upper_bound(begin,
end,
nullptr,
[&](void * /*dummy*/, const auto &value) { return !predicate(value); }) -
begin - 1;
}
template<typename Range, typename Predicate>
int64_t first_if(const Range &range, Predicate &&predicate)
{
return first_if(range.begin(), range.end(), predicate);
}
template<typename Range, typename Predicate>
int64_t last_if(const Range &range, Predicate &&predicate)
{
return last_if(range.begin(), range.end(), predicate);
}
} // namespace blender::binary_search

View File

@@ -0,0 +1,29 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bli
*/
#pragma once
#include "BLI_bit_span.hh"
#include "BLI_span.hh"
namespace blender::bits {
/**
* Converts the bools to bits and `or`s them into the given bits. For pure conversion, the bits
* should therefore be zero initialized before they are passed into this function.
*
* \param allowed_overshoot: How many bools/bits can be read/written after the end of the given
* spans. This can help with performance because the internal algorithm can process many elements
* at once.
*
* \return True if any of the checked bools were true (this also includes the bools in the
* overshoot).
*/
bool or_bools_into_bits(Span<bool> bools, MutableBitSpan r_bits, int64_t allowed_overshoot = 0);
} // namespace blender::bits

View File

@@ -0,0 +1,166 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bli
*/
#pragma once
#include "BLI_bit_span_ops.hh"
#include "BLI_bit_vector.hh"
namespace blender {
namespace bits {
/**
* A #BitGroupVector is a compact data structure that allows storing an arbitrary but fixed number
* of bits per element. For example, it could be used to compactly store 5 bits per vertex in a
* mesh. The data structure stores the bits in a way so that the #BitSpan for every element is
* bounded according to #is_bounded_span. The makes sure that operations on entire groups can be
* implemented efficiently. For example, one can easy `or` one group into another.
*/
template<int64_t InlineBufferCapacity = 64, typename Allocator = GuardedAllocator>
class BitGroupVector {
private:
/**
* Number of bits per group.
*/
int64_t group_size_ = 0;
/**
* Actually stored number of bits per group so that individual groups are bounded according to
* #is_bounded_span.
*/
int64_t aligned_group_size_ = 0;
BitVector<InlineBufferCapacity, Allocator> data_;
static int64_t align_group_size(const int64_t group_size)
{
if (group_size < 64) {
/* Align to next power of two so that a single group never spans across two ints. */
return power_of_2_max(group_size);
}
/* Align to multiple of BitsPerInt. */
return (group_size + BitsPerInt - 1) & ~(BitsPerInt - 1);
}
public:
BitGroupVector(Allocator allocator = {}) noexcept : data_(allocator) {}
BitGroupVector(NoExceptConstructor, Allocator allocator = {}) noexcept
: BitGroupVector(allocator)
{
}
BitGroupVector(const int64_t size_in_groups,
const int64_t group_size,
const bool value = false,
Allocator allocator = {})
: group_size_(group_size),
aligned_group_size_(align_group_size(group_size)),
data_(size_in_groups * aligned_group_size_, value, allocator)
{
BLI_assert(group_size >= 0);
BLI_assert(size_in_groups >= 0);
}
BitGroupVector(const BitGroupVector &other)
: group_size_(other.group_size_),
aligned_group_size_(other.aligned_group_size_),
data_(other.data_)
{
}
BitGroupVector(BitGroupVector &&other)
: group_size_(other.group_size_),
aligned_group_size_(other.aligned_group_size_),
data_(std::move(other.data_))
{
}
BitGroupVector &operator=(const BitGroupVector &other)
{
return copy_assign_container(*this, other);
}
BitGroupVector &operator=(BitGroupVector &&other)
{
return move_assign_container(*this, std::move(other));
}
/** Get all the bits at an index. */
BoundedBitSpan operator[](const int64_t i) const
{
BLI_assert(this->index_range().contains(i));
const int64_t offset = aligned_group_size_ * i;
return {data_.data() + (offset >> BitToIntIndexShift),
IndexRange(offset & BitIndexMask, group_size_)};
}
/** Get all the bits at an index. */
MutableBoundedBitSpan operator[](const int64_t i)
{
BLI_assert(this->index_range().contains(i));
const int64_t offset = aligned_group_size_ * i;
return {data_.data() + (offset >> BitToIntIndexShift),
IndexRange(offset & BitIndexMask, group_size_)};
}
/** Number of groups. */
int64_t size() const
{
return aligned_group_size_ == 0 ? 0 : data_.size() / aligned_group_size_;
}
bool is_empty() const
{
return this->size() == 0;
}
/** Number of bits per group. */
int64_t group_size() const
{
return group_size_;
}
IndexRange index_range() const
{
return IndexRange{this->size()};
}
/**
* Get all stored bits. Note that this may also contain padding bits. This can be used to e.g.
* mix multiple #BitGroupVector.
*/
BoundedBitSpan all_bits() const
{
return data_;
}
MutableBoundedBitSpan all_bits()
{
return data_;
}
/**
* Updates each group by computing the bitwise-and with the given bits.
*/
void foreach_and(const BoundedBitSpan bits)
{
/* This can still be optimized due to the additional knowledge we have how consecutive groups
* are laid out in memory. It is possible to updated multiple small groups at once. */
BLI_assert(bits.size() == group_size_);
for (const int64_t i : this->index_range()) {
MutableBoundedBitSpan group = (*this)[i];
group &= bits;
}
}
};
} // namespace bits
using bits::BitGroupVector;
} // namespace blender

View File

@@ -0,0 +1,246 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bli
*
* This file provides the basis for processing "indexed bits" (i.e. every bit has an index).
* The main purpose of this file is to define how bits are indexed within a memory buffer.
* For example, one has to define whether the first bit is the least or most significant bit and
* how endianness affect the bit order.
*
* The order is defined as follows:
* - Every indexed bit is part of an #BitInt. These ints are ordered by their address as usual.
* - Within each #BitInt, the bits are ordered from least to most significant.
*/
#include "BLI_utildefines.h"
#include <iosfwd>
namespace blender {
namespace bits {
/** Using a large integer type is better because then it's easier to process many bits at once. */
using BitInt = uint64_t;
/** Number of bits that fit into #BitInt. */
static constexpr int64_t BitsPerInt = int64_t(sizeof(BitInt) * 8);
/** Shift amount to get from a bit index to an int index. Equivalent to `log(BitsPerInt, 2)`. */
static constexpr int64_t BitToIntIndexShift = 3 + (sizeof(BitInt) >= 2) + (sizeof(BitInt) >= 4) +
(sizeof(BitInt) >= 8);
/** Bit mask containing a 1 for the last few bits that index a bit inside of an #BitInt. */
static constexpr BitInt BitIndexMask = (BitInt(1) << BitToIntIndexShift) - 1;
inline BitInt mask_first_n_bits(const int64_t n)
{
BLI_assert(n >= 0);
BLI_assert(n <= BitsPerInt);
if (n == BitsPerInt) {
return BitInt(-1);
}
return (BitInt(1) << n) - 1;
}
inline BitInt mask_last_n_bits(const int64_t n)
{
return ~mask_first_n_bits(BitsPerInt - n);
}
inline BitInt mask_range_bits(const int64_t start, const int64_t size)
{
BLI_assert(start >= 0);
BLI_assert(size >= 0);
const int64_t end = start + size;
BLI_assert(end <= BitsPerInt);
if (end == BitsPerInt) {
return mask_last_n_bits(size);
}
return ((BitInt(1) << end) - 1) & ~((BitInt(1) << start) - 1);
}
inline BitInt mask_single_bit(const int64_t bit_index)
{
BLI_assert(bit_index >= 0);
BLI_assert(bit_index < BitsPerInt);
return BitInt(1) << bit_index;
}
inline BitInt *int_containing_bit(BitInt *data, const int64_t bit_index)
{
return data + (bit_index >> BitToIntIndexShift);
}
inline const BitInt *int_containing_bit(const BitInt *data, const int64_t bit_index)
{
return data + (bit_index >> BitToIntIndexShift);
}
/**
* This is a read-only pointer to a specific bit. The value of the bit can be retrieved, but
* not changed.
*/
class BitRef {
private:
/** Points to the exact integer that the bit is in. */
const BitInt *int_;
/** All zeros except for a single one at the bit that is referenced. */
BitInt mask_;
friend class MutableBitRef;
public:
BitRef() = default;
/**
* Reference a specific bit in an array. Note that #data does *not* have to point to the
* exact integer the bit is in.
*/
BitRef(const BitInt *data, const int64_t bit_index)
{
int_ = int_containing_bit(data, bit_index);
mask_ = mask_single_bit(bit_index & BitIndexMask);
}
/**
* Return true when the bit is currently 1 and false otherwise.
*/
bool test() const
{
const BitInt value = *int_;
const BitInt masked_value = value & mask_;
return masked_value != 0;
}
operator bool() const
{
return this->test();
}
};
/**
* Similar to #BitRef, but also allows changing the referenced bit.
*/
class MutableBitRef {
private:
/** Points to the integer that the bit is in. */
BitInt *int_;
/** All zeros except for a single one at the bit that is referenced. */
BitInt mask_;
public:
MutableBitRef() = default;
/**
* Reference a specific bit in an array. Note that #data does *not* have to point to the
* exact int the bit is in.
*/
MutableBitRef(BitInt *data, const int64_t bit_index)
{
int_ = int_containing_bit(data, bit_index);
mask_ = mask_single_bit(bit_index & BitIndexMask);
}
/**
* Support implicitly casting to a read-only #BitRef.
*/
operator BitRef() const
{
BitRef bit_ref;
bit_ref.int_ = int_;
bit_ref.mask_ = mask_;
return bit_ref;
}
/**
* Return true when the bit is currently 1 and false otherwise.
*/
bool test() const
{
const BitInt value = *int_;
const BitInt masked_value = value & mask_;
return masked_value != 0;
}
operator bool() const
{
return this->test();
}
/**
* Change the bit to a 1.
*/
void set()
{
*int_ |= mask_;
}
/**
* Change the bit to a 0.
*/
void reset()
{
*int_ &= ~mask_;
}
/**
* Change the bit to a 1 if #value is true and 0 otherwise. If the value is highly unpredictable
* by the CPU branch predictor, it can be faster to use #set_branchless instead.
*/
void set(const bool value)
{
if (value) {
this->set();
}
else {
this->reset();
}
}
/**
* Does the same as #set, but does not use a branch. This is faster when the input value is
* unpredictable for the CPU branch predictor (best case for this function is a uniform random
* distribution with 50% probability for true and false). If the value is predictable, this is
* likely slower than #set.
*/
void set_branchless(const bool value)
{
const BitInt value_int = BitInt(value);
BLI_assert(ELEM(value_int, 0, 1));
const BitInt old = *int_;
*int_ =
/* Unset bit. */
(~mask_ & old)
/* Optionally set it again. The -1 turns a 1 into `0x00...` and a 0 into `0xff...`. */
| (mask_ & ~(value_int - 1));
}
MutableBitRef &operator|=(const bool value)
{
if (value) {
this->set();
}
return *this;
}
MutableBitRef &operator&=(const bool value)
{
if (!value) {
this->reset();
}
return *this;
}
};
std::ostream &operator<<(std::ostream &stream, const BitRef &bit);
std::ostream &operator<<(std::ostream &stream, const MutableBitRef &bit);
} // namespace bits
using bits::BitRef;
using bits::MutableBitRef;
} // namespace blender

View File

@@ -0,0 +1,486 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bli
*/
#pragma once
#include <optional>
#include "BLI_bit_ref.hh"
#include "BLI_index_range.hh"
#include "BLI_math_bits.h"
#include "BLI_memory_utils.hh"
namespace blender {
namespace bits {
/** Base class for a const and non-const bit-iterator. */
class BitIteratorBase {
protected:
const BitInt *data_;
int64_t bit_index_;
public:
BitIteratorBase(const BitInt *data, const int64_t bit_index) : data_(data), bit_index_(bit_index)
{
}
BitIteratorBase &operator++()
{
bit_index_++;
return *this;
}
friend bool operator!=(const BitIteratorBase &a, const BitIteratorBase &b)
{
BLI_assert(a.data_ == b.data_);
return a.bit_index_ != b.bit_index_;
}
};
/** Allows iterating over the bits in a memory buffer. */
class BitIterator : public BitIteratorBase {
public:
BitIterator(const BitInt *data, const int64_t bit_index) : BitIteratorBase(data, bit_index) {}
BitRef operator*() const
{
return BitRef(data_, bit_index_);
}
};
/** Allows iterating over the bits in a memory buffer. */
class MutableBitIterator : public BitIteratorBase {
public:
MutableBitIterator(BitInt *data, const int64_t bit_index) : BitIteratorBase(data, bit_index) {}
MutableBitRef operator*() const
{
return MutableBitRef(const_cast<BitInt *>(data_), bit_index_);
}
};
class OneBitIterator {
private:
BitInt data_;
int current_bit_;
public:
explicit OneBitIterator(BitInt data) : data_(data)
{
this->operator++();
}
int operator*() const
{
return current_bit_;
}
OneBitIterator &operator++()
{
if (data_ > 0) {
current_bit_ = bitscan_forward_clear_uint64(&data_);
}
else {
current_bit_ = -1;
}
return *this;
}
friend bool operator!=(const OneBitIterator &a, const OneBitIterator &b)
{
return a.current_bit_ != b.current_bit_;
}
};
class OneBitIteratorRange {
private:
const BitInt data_;
public:
OneBitIteratorRange(BitInt data) : data_(data) {}
OneBitIterator begin() const
{
return OneBitIterator(data_);
}
OneBitIterator end() const
{
return OneBitIterator(0);
}
};
inline OneBitIteratorRange iter_1_indices(BitInt value)
{
return OneBitIteratorRange(value);
}
/**
* Similar to #Span, but references a range of bits instead of normal C++ types (which must be at
* least one byte large). Use #MutableBitSpan if the values are supposed to be modified.
*
* The beginning and end of a #BitSpan does *not* have to be at byte/int boundaries. It can start
* and end at any bit.
*/
class BitSpan {
protected:
/** Base pointer to the integers containing the bits. The actual bit span might start at a much
* higher address when `bit_range_.start()` is large. */
const BitInt *data_ = nullptr;
/** The range of referenced bits. */
IndexRange bit_range_ = {0, 0};
public:
/** Construct an empty span. */
BitSpan() = default;
BitSpan(const BitInt *data, const int64_t size_in_bits) : data_(data), bit_range_(size_in_bits)
{
}
BitSpan(const BitInt *data, const IndexRange bit_range) : data_(data), bit_range_(bit_range) {}
/** Number of bits referenced by the span. */
int64_t size() const
{
return bit_range_.size();
}
bool is_empty() const
{
return bit_range_.is_empty();
}
IndexRange index_range() const
{
return IndexRange(bit_range_.size());
}
[[nodiscard]] BitRef operator[](const int64_t index) const
{
BLI_assert(index >= 0);
BLI_assert(index < bit_range_.size());
return {data_, bit_range_.start() + index};
}
[[nodiscard]] BitSpan slice(const IndexRange range) const
{
return {data_, bit_range_.slice(range)};
}
BitSpan take_front(const int64_t n) const
{
return {data_, bit_range_.take_front(n)};
}
BitSpan take_back(const int64_t n) const
{
return {data_, bit_range_.take_back(n)};
}
BitSpan drop_front(const int64_t n) const
{
return {data_, bit_range_.drop_front(n)};
}
BitSpan drop_back(const int64_t n) const
{
return {data_, bit_range_.drop_back(n)};
}
const BitInt *data() const
{
return data_;
}
const IndexRange &bit_range() const
{
return bit_range_;
}
BitIterator begin() const
{
return {data_, bit_range_.start()};
}
BitIterator end() const
{
return {data_, bit_range_.one_after_last()};
}
};
/**
* Checks if the span fulfills the requirements for a bounded span. Bounded spans can often be
* processed more efficiently, because fewer cases have to be considered when aligning multiple
* such spans.
*
* See comments in the function for the exact requirements.
*/
inline bool is_bounded_span(const BitSpan span)
{
const int64_t offset = span.bit_range().start();
const int64_t size = span.size();
if (offset >= BitsPerInt) {
/* The data pointer must point at the first int already. If the offset is a multiple of
* #BitsPerInt, the bit span could theoretically become bounded as well if the data pointer is
* adjusted. But that is not handled here. */
return false;
}
if (size < BitsPerInt) {
/** Don't allow small sized spans to cross `BitInt` boundaries. */
return offset + size <= 64;
}
if (offset != 0) {
/* Start of larger spans must be aligned to `BitInt` boundaries. */
return false;
}
return true;
}
/**
* Same as #BitSpan but fulfills the requirements mentioned on #is_bounded_span.
*/
class BoundedBitSpan : public BitSpan {
public:
BoundedBitSpan() = default;
BoundedBitSpan(const BitInt *data, const int64_t size_in_bits) : BitSpan(data, size_in_bits)
{
BLI_assert(is_bounded_span(*this));
}
BoundedBitSpan(const BitInt *data, const IndexRange bit_range) : BitSpan(data, bit_range)
{
BLI_assert(is_bounded_span(*this));
}
explicit BoundedBitSpan(const BitSpan other) : BitSpan(other)
{
BLI_assert(is_bounded_span(*this));
}
int64_t offset() const
{
return bit_range_.start();
}
int64_t full_ints_num() const
{
return bit_range_.size() >> BitToIntIndexShift;
}
int64_t final_bits_num() const
{
return bit_range_.size() & BitIndexMask;
}
BoundedBitSpan take_front(const int64_t n) const
{
return {data_, bit_range_.take_front(n)};
}
};
/** Same as #BitSpan, but also allows modifying the referenced bits. */
class MutableBitSpan {
protected:
BitInt *data_ = nullptr;
IndexRange bit_range_ = {0, 0};
public:
MutableBitSpan() = default;
MutableBitSpan(BitInt *data, const int64_t size) : data_(data), bit_range_(size) {}
MutableBitSpan(BitInt *data, const IndexRange bit_range) : data_(data), bit_range_(bit_range) {}
int64_t size() const
{
return bit_range_.size();
}
bool is_empty() const
{
return bit_range_.is_empty();
}
IndexRange index_range() const
{
return IndexRange(bit_range_.size());
}
MutableBitRef operator[](const int64_t index) const
{
BLI_assert(index >= 0);
BLI_assert(index < bit_range_.size());
return {data_, bit_range_.start() + index};
}
MutableBitSpan slice(const IndexRange range) const
{
return {data_, bit_range_.slice(range)};
}
MutableBitSpan take_front(const int64_t n) const
{
return {data_, bit_range_.take_front(n)};
}
MutableBitSpan take_back(const int64_t n) const
{
return {data_, bit_range_.take_back(n)};
}
BitInt *data() const
{
return data_;
}
const IndexRange &bit_range() const
{
return bit_range_;
}
MutableBitIterator begin() const
{
return {data_, bit_range_.start()};
}
MutableBitIterator end() const
{
return {data_, bit_range_.one_after_last()};
}
operator BitSpan() const
{
return {data_, bit_range_};
}
/** Sets all referenced bits to 1. */
void set_all();
/** Sets all referenced bits to 0. */
void reset_all();
void copy_from(const BitSpan other);
void copy_from(const BoundedBitSpan other);
/** Sets all referenced bits to either 0 or 1. */
void set_all(const bool value)
{
if (value) {
this->set_all();
}
else {
this->reset_all();
}
}
/** Same as #set_all to mirror #MutableSpan. */
void fill(const bool value)
{
this->set_all(value);
}
};
/**
* Same as #MutableBitSpan but fulfills the requirements mentioned on #is_bounded_span.
*/
class MutableBoundedBitSpan : public MutableBitSpan {
public:
MutableBoundedBitSpan() = default;
MutableBoundedBitSpan(BitInt *data, const int64_t size) : MutableBitSpan(data, size)
{
BLI_assert(is_bounded_span(*this));
}
MutableBoundedBitSpan(BitInt *data, const IndexRange bit_range) : MutableBitSpan(data, bit_range)
{
BLI_assert(is_bounded_span(*this));
}
explicit MutableBoundedBitSpan(const MutableBitSpan other) : MutableBitSpan(other)
{
BLI_assert(is_bounded_span(*this));
}
operator BoundedBitSpan() const
{
return BoundedBitSpan{BitSpan(*this)};
}
int64_t offset() const
{
return bit_range_.start();
}
int64_t full_ints_num() const
{
return bit_range_.size() >> BitToIntIndexShift;
}
int64_t final_bits_num() const
{
return bit_range_.size() & BitIndexMask;
}
MutableBoundedBitSpan take_front(const int64_t n) const
{
return {data_, bit_range_.take_front(n)};
}
BoundedBitSpan as_span() const
{
return BoundedBitSpan(data_, bit_range_);
}
void copy_from(const BitSpan other);
void copy_from(const BoundedBitSpan other);
};
inline std::optional<BoundedBitSpan> try_get_bounded_span(const BitSpan span)
{
if (is_bounded_span(span)) {
return BoundedBitSpan(span);
}
if (span.bit_range().start() % BitsPerInt == 0) {
return BoundedBitSpan(span.data() + (span.bit_range().start() >> BitToIntIndexShift),
span.size());
}
return std::nullopt;
}
/**
* Overloaded in BLI_bit_vector.hh. The purpose is to make passing #BitVector into bit span
* operations more efficient (interpreting it as `BoundedBitSpan` instead of just `BitSpan`).
*/
template<typename T> inline T to_best_bit_span(const T &data)
{
static_assert(is_same_any_v<std::decay_t<T>,
BitSpan,
MutableBitSpan,
BoundedBitSpan,
MutableBoundedBitSpan>);
return data;
}
template<typename... Args>
constexpr bool all_bounded_spans =
(is_same_any_v<std::decay_t<Args>, BoundedBitSpan, MutableBoundedBitSpan> && ...);
std::ostream &operator<<(std::ostream &stream, const BitSpan &span);
std::ostream &operator<<(std::ostream &stream, const MutableBitSpan &span);
} // namespace bits
using bits::BitSpan;
using bits::BoundedBitSpan;
using bits::MutableBitSpan;
using bits::MutableBoundedBitSpan;
} // namespace blender

View File

@@ -0,0 +1,369 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bli
*/
#pragma once
#include "BLI_bit_span.hh"
#include "BLI_math_bits.h"
namespace blender::bits {
namespace detail {
/**
* Evaluates the expression on one or more bit spans and stores the result in the first.
*
* The expected type for the expression is:
* (BitInt ...one_or_more_args) -> BitInt
*/
template<typename ExprFn, typename FirstBitSpanT, typename... BitSpanT>
inline void mix_into_first_expr(ExprFn &&expr,
const FirstBitSpanT &first_arg,
const BitSpanT &...args)
{
const int64_t size = first_arg.size();
BLI_assert(((size == args.size()) && ...));
if (size == 0) {
return;
}
if constexpr (all_bounded_spans<FirstBitSpanT, BitSpanT...>) {
BitInt *first_data = first_arg.data();
const int64_t first_offset = first_arg.offset();
const int64_t full_ints_num = first_arg.full_ints_num();
/* Compute expression without any masking, all the spans are expected to be aligned to the
* beginning of a #BitInt. */
for (const int64_t i : IndexRange(full_ints_num)) {
first_data[i] = expr(first_data[i], args.data()[i]...);
}
/* Compute expression for the remaining bits. */
if (const int64_t final_bits = first_arg.final_bits_num()) {
const BitInt result = expr(first_data[full_ints_num] >> first_offset,
(args.data()[full_ints_num] >> args.offset())...);
const BitInt mask = mask_range_bits(first_offset, final_bits);
first_data[full_ints_num] = ((result << first_offset) & mask) |
(first_data[full_ints_num] & ~mask);
}
}
else {
/* Fallback for arbitrary bit spans. This could be implemented more efficiently but adds more
* complexity and is not necessary yet. */
for (const int64_t i : IndexRange(size)) {
const bool result = expr(BitInt(first_arg[i].test()), BitInt(args[i].test())...) != 0;
first_arg[i].set(result);
}
}
}
/**
* Evaluates the expression on one or more bit spans and returns true when the result contains a 1
* anywhere.
*
* The expected type for the expression is:
* (BitInt ...one_or_more_args) -> BitInt
*/
template<typename ExprFn, typename FirstBitSpanT, typename... BitSpanT>
inline bool any_set_expr(ExprFn &&expr, const FirstBitSpanT &first_arg, const BitSpanT &...args)
{
const int64_t size = first_arg.size();
BLI_assert(((size == args.size()) && ...));
if (size == 0) {
return false;
}
if constexpr (all_bounded_spans<FirstBitSpanT, BitSpanT...>) {
const BitInt *first_data = first_arg.data();
const int64_t full_ints_num = first_arg.full_ints_num();
/* Compute expression without any masking, all the spans are expected to be aligned to the
* beginning of a #BitInt. */
for (const int64_t i : IndexRange(full_ints_num)) {
if (expr(first_data[i], args.data()[i]...) != 0) {
return true;
}
}
/* Compute expression for the remaining bits. */
if (const int64_t final_bits = first_arg.final_bits_num()) {
const BitInt result = expr(first_data[full_ints_num] >> first_arg.offset(),
(args.data()[full_ints_num] >> args.offset())...);
const BitInt mask = mask_first_n_bits(final_bits);
if ((result & mask) != 0) {
return true;
}
}
return false;
}
else {
/* Fallback for arbitrary bit spans. This could be implemented more efficiently but adds more
* complexity and is not necessary yet. */
for (const int64_t i : IndexRange(size)) {
const BitInt result = expr(BitInt(first_arg[i].test()), BitInt(args[i].test())...);
if (result & 1) {
return true;
}
}
return false;
}
}
/**
* Evaluates the expression on one or more bit spans and calls the `handle` function for each bit
* index where the result is 1.
*
* The expected type for the expression is:
* (BitInt ...one_or_more_args) -> BitInt
*/
template<typename ExprFn, typename HandleFn, typename FirstBitSpanT, typename... BitSpanT>
inline void foreach_1_index_expr(ExprFn &&expr,
HandleFn &&handle,
const FirstBitSpanT &first_arg,
const BitSpanT &...args)
{
static_assert(std::is_invocable_v<HandleFn, int64_t>);
constexpr bool is_cancellable = std::is_invocable_r_v<bool, HandleFn, int64_t>;
const int64_t size = first_arg.size();
BLI_assert(((size == args.size()) && ...));
if (size == 0) {
return;
}
if constexpr (all_bounded_spans<FirstBitSpanT, BitSpanT...>) {
const BitInt *first_data = first_arg.data();
const int64_t full_ints_num = first_arg.full_ints_num();
/* Iterate over full ints without any bit masks. */
for (const int64_t int_i : IndexRange(full_ints_num)) {
BitInt tmp = expr(first_data[int_i], args.data()[int_i]...);
const int64_t offset = int_i << BitToIntIndexShift;
while (tmp != 0) {
static_assert(std::is_same_v<BitInt, uint64_t>);
const int index_in_int = bitscan_forward_uint64(tmp);
const int64_t index_in_span = index_in_int + offset;
if constexpr (is_cancellable) {
if (!handle(index_in_span)) {
return;
}
}
else {
handle(index_in_span);
}
tmp &= ~mask_single_bit(index_in_int);
}
}
/* Iterate over remaining bits. */
if (const int64_t final_bits = first_arg.final_bits_num()) {
BitInt tmp = expr(first_data[full_ints_num] >> first_arg.offset(),
(args.data()[full_ints_num] >> args.offset())...) &
mask_first_n_bits(final_bits);
const int64_t offset = full_ints_num << BitToIntIndexShift;
while (tmp != 0) {
static_assert(std::is_same_v<BitInt, uint64_t>);
const int index_in_int = bitscan_forward_uint64(tmp);
const int64_t index_in_span = index_in_int + offset;
if constexpr (is_cancellable) {
if (!handle(index_in_span)) {
return;
}
}
else {
handle(index_in_span);
}
tmp &= ~mask_single_bit(index_in_int);
}
}
}
else {
/* Fallback for arbitrary bit spans. This could be implemented more efficiently but adds more
* complexity and is not necessary yet. */
for (const int64_t i : IndexRange(size)) {
const BitInt result = expr(BitInt(first_arg[i].test()), BitInt(args[i].test())...);
if (result & 1) {
if constexpr (is_cancellable) {
if (!handle(i)) {
return;
}
}
else {
handle(i);
}
}
}
}
}
template<typename ExprFn, typename FirstBitSpanT, typename... BitSpanT>
inline std::optional<int64_t> find_first_1_index_expr(ExprFn &&expr,
const FirstBitSpanT &first_arg,
const BitSpanT &...args)
{
std::optional<int64_t> result;
detail::foreach_1_index_expr(
expr,
[&](const int64_t i) {
result = i;
return false;
},
first_arg,
args...);
return result;
}
} // namespace detail
template<typename ExprFn, typename FirstBitSpanT, typename... BitSpanT>
inline void mix_into_first_expr(ExprFn &&expr, FirstBitSpanT &&first_arg, const BitSpanT &...args)
{
detail::mix_into_first_expr(expr, to_best_bit_span(first_arg), to_best_bit_span(args)...);
}
template<typename ExprFn, typename FirstBitSpanT, typename... BitSpanT>
inline bool any_set_expr(ExprFn &&expr, const FirstBitSpanT &first_arg, const BitSpanT &...args)
{
return detail::any_set_expr(expr, to_best_bit_span(first_arg), to_best_bit_span(args)...);
}
template<typename ExprFn, typename HandleFn, typename FirstBitSpanT, typename... BitSpanT>
inline void foreach_1_index_expr(ExprFn &&expr,
HandleFn &&handle,
const FirstBitSpanT &first_arg,
const BitSpanT &...args)
{
detail::foreach_1_index_expr(
expr, handle, to_best_bit_span(first_arg), to_best_bit_span(args)...);
}
template<typename BitSpanT> inline void invert(BitSpanT &&data)
{
mix_into_first_expr([](const BitInt x) { return ~x; }, data);
}
template<typename FirstBitSpanT, typename... BitSpanT>
inline void inplace_or(FirstBitSpanT &first_arg, const BitSpanT &...args)
{
mix_into_first_expr([](const auto... x) { return (x | ...); }, first_arg, args...);
}
template<typename FirstBitSpanT, typename MaskBitSpanT, typename... BitSpanT>
inline void inplace_or_masked(FirstBitSpanT &&first_arg,
const MaskBitSpanT &mask,
const BitSpanT &...args)
{
mix_into_first_expr(
[](const BitInt a, const BitInt mask, const auto... x) { return a | ((x | ...) & mask); },
first_arg,
mask,
args...);
}
template<typename FirstBitSpanT, typename... BitSpanT>
inline void copy_from_or(FirstBitSpanT &first_arg, const BitSpanT &...args)
{
mix_into_first_expr(
[](auto /*first*/, auto... rest) { return (rest | ...); }, first_arg, args...);
}
template<typename FirstBitSpanT, typename... BitSpanT>
inline void inplace_and(FirstBitSpanT &first_arg, const BitSpanT &...args)
{
mix_into_first_expr([](const auto... x) { return (x & ...); }, first_arg, args...);
}
template<typename... BitSpanT>
inline void operator|=(MutableBitSpan first_arg, const BitSpanT &...args)
{
inplace_or(first_arg, args...);
}
template<typename... BitSpanT>
inline void operator|=(MutableBoundedBitSpan first_arg, const BitSpanT &...args)
{
inplace_or(first_arg, args...);
}
template<typename... BitSpanT>
inline void operator&=(MutableBitSpan first_arg, const BitSpanT &...args)
{
inplace_and(first_arg, args...);
}
template<typename... BitSpanT>
inline void operator&=(MutableBoundedBitSpan first_arg, const BitSpanT &...args)
{
inplace_and(first_arg, args...);
}
template<typename... BitSpanT> inline bool has_common_set_bits(const BitSpanT &...args)
{
return any_set_expr([](const auto... x) { return (x & ...); }, args...);
}
template<typename BitSpanT> inline bool any_bit_set(const BitSpanT &arg)
{
return has_common_set_bits(arg);
}
template<typename... BitSpanT> inline bool has_common_unset_bits(const BitSpanT &...args)
{
return any_set_expr([](const auto... x) { return ~(x | ...); }, args...);
}
template<typename BitSpanT> inline bool any_bit_unset(const BitSpanT &arg)
{
return has_common_unset_bits(arg);
}
template<typename BitSpanT, typename Fn> inline void foreach_1_index(const BitSpanT &data, Fn &&fn)
{
foreach_1_index_expr([](const BitInt x) { return x; }, fn, data);
}
template<typename BitSpanT, typename Fn> inline void foreach_0_index(const BitSpanT &data, Fn &&fn)
{
foreach_1_index_expr([](const BitInt x) { return ~x; }, fn, data);
}
template<typename ExprFn, typename FirstBitSpanT, typename... BitSpanT>
inline std::optional<int64_t> find_first_1_index_expr(ExprFn &&Expr,
const FirstBitSpanT &first_arg,
const BitSpanT &...args)
{
return detail::find_first_1_index_expr(
Expr, to_best_bit_span(first_arg), to_best_bit_span(args)...);
}
template<typename BitSpanT> inline std::optional<int64_t> find_first_1_index(const BitSpanT &data)
{
return find_first_1_index_expr([](const BitInt x) { return x; }, data);
}
template<typename BitSpanT> inline std::optional<int64_t> find_first_0_index(const BitSpanT &data)
{
return find_first_1_index_expr([](const BitInt x) { return ~x; }, data);
}
template<typename BitSpanT1, typename BitSpanT2>
inline bool spans_equal(const BitSpanT1 &a, const BitSpanT2 &b)
{
if (a.size() != b.size()) {
return false;
}
return !any_set_expr([](const BitInt a, const BitInt b) { return a ^ b; }, a, b);
}
template<typename BitSpanT1, typename BitSpanT2, typename BitSpanT3>
inline bool spans_equal_masked(const BitSpanT1 &a, const BitSpanT2 &b, const BitSpanT3 &mask)
{
BLI_assert(mask.size() == a.size());
BLI_assert(mask.size() == b.size());
return !bits::any_set_expr(
[](const BitInt a, const BitInt b, const BitInt mask) { return (a ^ b) & mask; },
a,
b,
mask);
}
} // namespace blender::bits

View File

@@ -0,0 +1,140 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bli
*/
#pragma once
#include <limits>
#include "BLI_bit_span.hh"
#include "BLI_index_ranges_builder.hh"
#include "BLI_math_bits.h"
#include "BLI_simd.hh"
namespace blender::bits {
/**
* Extracts index ranges from the given bits.
* For example 00111011 would result in two ranges: [2-4], [6-7].
*
* It's especially optimized to handle cases where there are very many or very few set bits.
*/
template<typename IntT>
inline void bits_to_index_ranges(const BitSpan bits, IndexRangesBuilder<IntT> &builder)
{
if (bits.is_empty()) {
return;
}
/* -1 because we also need to store the end of the last range. */
constexpr int64_t max_index = std::numeric_limits<IntT>::max() - 1;
UNUSED_VARS_NDEBUG(max_index);
auto append_range = [&](const IndexRange range) {
BLI_assert(range.last() <= max_index);
builder.add_range(IntT(range.start()), IntT(range.one_after_last()));
};
auto process_bit_int = [&](const BitInt value,
const int64_t start_bit,
const int64_t bits_num,
const int64_t start) {
/* The bits in the mask are the ones we should look at. */
const BitInt mask = mask_range_bits(start_bit, bits_num);
const BitInt masked_value = mask & value;
if (masked_value == 0) {
/* Do nothing. */
return;
}
if (masked_value == mask) {
/* All bits are set. */
append_range(IndexRange::from_begin_size(start, bits_num));
return;
}
const int64_t bit_i_to_output_offset = start - start_bit;
/* Iterate over ranges of 1s. For example, if the bits are 0b000111110001111000, the loop
* below requires two iterations. The worst case for this is when there are very many small
* ranges of 1s (e.g. 0b10101010101). So far it seems like the overhead of detecting such
* cases is higher than the potential benefit of using another algorithm. */
BitInt current_value = masked_value;
while (current_value != 0) {
/* Find start of next range of 1s. */
const int64_t first_set_bit_i = int64_t(bitscan_forward_uint64(current_value));
/* This mask is used to find the end of the 1s range. */
const BitInt find_unset_value = ~(current_value | mask_first_n_bits(first_set_bit_i) |
~mask);
if (find_unset_value == 0) {
/* In this case, the range one 1s extends to the end of the current integer. */
const IndexRange range = IndexRange::from_begin_end(first_set_bit_i, start_bit + bits_num);
append_range(range.shift(bit_i_to_output_offset));
break;
}
/* Find the index of the first 0 after the range of 1s. */
const int64_t next_unset_bit_i = int64_t(bitscan_forward_uint64(find_unset_value));
/* Store the range of 1s. */
const IndexRange range = IndexRange::from_begin_end(first_set_bit_i, next_unset_bit_i);
append_range(range.shift(bit_i_to_output_offset));
/* Remove the processed range of 1s so that it is ignored in the next iteration. */
current_value &= ~mask_first_n_bits(next_unset_bit_i);
}
return;
};
const BitInt *data = bits.data();
const IndexRange bit_range = bits.bit_range();
/* As much as possible we want to process full 64-bit integers at once. However, the bit-span may
* not be aligned, so it's first split up into aligned and unaligned sections. */
const AlignedIndexRanges ranges = split_index_range_by_alignment(bit_range, bits::BitsPerInt);
/* Process the first (partial) integer in the bit-span. */
if (!ranges.prefix.is_empty()) {
const BitInt first_int = *int_containing_bit(data, bit_range.start());
process_bit_int(
first_int, BitInt(ranges.prefix.start()) & BitIndexMask, ranges.prefix.size(), 0);
}
/* Process all the full integers in the bit-span. */
if (!ranges.aligned.is_empty()) {
const BitInt *start = int_containing_bit(data, ranges.aligned.start());
const int64_t ints_to_check = ranges.aligned.size() / BitsPerInt;
int64_t int_i = 0;
/* Checking for chunks of 0 bits can be speedup using intrinsics quite significantly. */
#if BLI_HAVE_SSE4
for (; int_i + 1 < ints_to_check; int_i += 2) {
/* Loads the next 128 bit. */
const __m128i group = _mm_loadu_si128(reinterpret_cast<const __m128i *>(start + int_i));
/* Checks if all the 128 bits are zero. */
const bool group_is_zero = _mm_testz_si128(group, group);
if (group_is_zero) {
continue;
}
/* If at least one of them is not zero, process the two integers separately. */
for (int j = 0; j < 2; j++) {
process_bit_int(
start[int_i + j], 0, BitsPerInt, ranges.prefix.size() + (int_i + j) * BitsPerInt);
}
}
#endif
/* Process the remaining integers. */
for (; int_i < ints_to_check; int_i++) {
process_bit_int(start[int_i], 0, BitsPerInt, ranges.prefix.size() + int_i * BitsPerInt);
}
}
/* Process the final few bits that don't fill up a full integer. */
if (!ranges.suffix.is_empty()) {
const BitInt last_int = *int_containing_bit(data, bit_range.last());
process_bit_int(
last_int, 0, ranges.suffix.size(), ranges.prefix.size() + ranges.aligned.size());
}
}
} // namespace blender::bits

View File

@@ -0,0 +1,406 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bli
*
* A `BitVector` is a dynamically growing contiguous arrays of bits. Its main purpose is
* to provide a compact way to map indices to bools. It requires 8 times less memory compared to a
* `Vector<bool>`.
*
* Advantages of using a bit- instead of byte-vector are:
* - Uses less memory.
* - Allows checking the state of many elements at the same time (8 times more bits than bytes fit
* into a CPU register). This can improve performance.
*
* The compact nature of storing bools in individual bits has some downsides that have to be kept
* in mind:
* - Writing to separate bits in the same int is not thread-safe. Therefore, an existing vector of
* bool can't easily be replaced with a bit vector, if it is written to from multiple threads.
* Read-only access from multiple threads is fine though.
* - Writing individual elements is more expensive when the array is in cache already. That is
* because changing a bit is always a read-modify-write operation on the int the bit resides in.
* - Reading individual elements is more expensive when the array is in cache already. That is
* because additional bit-wise operations have to be applied after the corresponding int is
* read.
*
* Comparison to `std::vector<bool>`:
* - `BitVector` has an interface that is more optimized for dealing with bits.
* - `BitVector` has an inline buffer that is used to avoid allocations when the vector is
* small.
*
* Comparison to `BLI_bitmap`:
* - `BitVector` offers a more C++ friendly interface.
* - `BLI_bitmap` should only be used in C code that can not use `BitVector`.
*/
#include "BLI_allocator.hh"
#include "BLI_bit_bool_conversion.hh"
#include "BLI_bit_span.hh"
#include "BLI_span.hh"
namespace blender {
namespace bits {
template<
/**
* Number of bits that can be stored in the vector without doing an allocation.
*/
int64_t InlineBufferCapacity = 64,
/**
* The allocator used by this vector. Should rarely be changed, except when you don't want that
* MEM_* is used internally.
*/
typename Allocator = GuardedAllocator>
class BitVector {
private:
static constexpr int64_t required_ints_for_bits(const int64_t number_of_bits)
{
return (number_of_bits + BitsPerInt - 1) / BitsPerInt;
}
static constexpr int64_t IntsInInlineBuffer = required_ints_for_bits(InlineBufferCapacity);
static constexpr int64_t BitsInInlineBuffer = IntsInInlineBuffer * BitsPerInt;
static constexpr int64_t AllocationAlignment = alignof(BitInt);
/**
* Points to the first integer used by the vector. It might point to the memory in the inline
* buffer.
*/
BitInt *data_;
/** Current size of the vector in bits. */
int64_t size_in_bits_;
/** Number of bits that fit into the vector until a reallocation has to occur. */
int64_t capacity_in_bits_;
/** Used for allocations when the inline buffer is too small. */
BLI_NO_UNIQUE_ADDRESS Allocator allocator_;
/** Contains the bits as long as the vector is small enough. */
BLI_NO_UNIQUE_ADDRESS TypedBuffer<BitInt, IntsInInlineBuffer> inline_buffer_;
public:
BitVector(Allocator allocator = {}) noexcept : allocator_(allocator)
{
data_ = inline_buffer_;
size_in_bits_ = 0;
capacity_in_bits_ = BitsInInlineBuffer;
uninitialized_fill_n(data_, IntsInInlineBuffer, BitInt(0));
}
BitVector(NoExceptConstructor, Allocator allocator = {}) noexcept : BitVector(allocator) {}
BitVector(const BoundedBitSpan span) : BitVector(NoExceptConstructor())
{
const int64_t ints_to_copy = required_ints_for_bits(span.size());
if (span.size() <= BitsInInlineBuffer) {
/* The data is copied into the owned inline buffer. */
data_ = inline_buffer_;
capacity_in_bits_ = BitsInInlineBuffer;
}
else {
/* Allocate a new array because the inline buffer is too small. */
data_ = static_cast<BitInt *>(
allocator_.allocate(ints_to_copy * sizeof(BitInt), AllocationAlignment, __func__));
capacity_in_bits_ = ints_to_copy * BitsPerInt;
}
size_in_bits_ = span.size();
uninitialized_copy_n(span.data(), ints_to_copy, data_);
}
BitVector(const BitVector &other) : BitVector(BoundedBitSpan(other))
{
allocator_ = other.allocator_;
}
BitVector(BitVector &&other) noexcept : BitVector(NoExceptConstructor(), other.allocator_)
{
if (other.is_inline()) {
/* Copy the data into the inline buffer. */
/* For small inline buffers, always copy all the bits because checking how many bits to copy
* would add additional overhead. */
int64_t ints_to_copy = IntsInInlineBuffer;
if constexpr (IntsInInlineBuffer > 8) {
/* Avoid copying too much unnecessary data in case the inline buffer is large. */
ints_to_copy = other.used_ints_amount();
}
data_ = inline_buffer_;
uninitialized_copy_n(other.data_, ints_to_copy, data_);
}
else {
/* Steal the pointer. */
data_ = other.data_;
}
size_in_bits_ = other.size_in_bits_;
capacity_in_bits_ = other.capacity_in_bits_;
/* Clear the other vector because it has been moved from. */
other.data_ = other.inline_buffer_;
other.size_in_bits_ = 0;
other.capacity_in_bits_ = BitsInInlineBuffer;
}
/**
* Create a new vector with the given size and fill it with #value.
*/
BitVector(const int64_t size_in_bits, const bool value = false, Allocator allocator = {})
: BitVector(NoExceptConstructor(), allocator)
{
this->resize(size_in_bits, value);
}
/**
* Create a bit vector based on an array of bools. Each byte of the input array maps to one bit.
*/
explicit BitVector(const Span<bool> values, Allocator allocator = {})
: BitVector(NoExceptConstructor(), allocator)
{
this->resize(values.size(), false);
or_bools_into_bits(values, *this);
}
~BitVector()
{
if (!this->is_inline()) {
allocator_.deallocate(data_);
}
}
BitVector &operator=(const BitVector &other)
{
return copy_assign_container(*this, other);
}
BitVector &operator=(BitVector &&other)
{
return move_assign_container(*this, std::move(other));
}
operator BoundedBitSpan() const
{
return {data_, IndexRange(size_in_bits_)};
}
operator MutableBoundedBitSpan()
{
return {data_, IndexRange(size_in_bits_)};
}
/**
* Number of bits in the bit vector.
*/
int64_t size() const
{
return size_in_bits_;
}
/**
* Number of bits that can be stored before the BitVector has to grow.
*/
int64_t capacity() const
{
return capacity_in_bits_;
}
bool is_empty() const
{
return size_in_bits_ == 0;
}
BitInt *data()
{
return data_;
}
const BitInt *data() const
{
return data_;
}
/**
* Get a read-only reference to a specific bit.
*/
[[nodiscard]] BitRef operator[](const int64_t index) const
{
BLI_assert(index >= 0);
BLI_assert(index < size_in_bits_);
return {data_, index};
}
/**
* Get a mutable reference to a specific bit.
*/
[[nodiscard]] MutableBitRef operator[](const int64_t index)
{
BLI_assert(index >= 0);
BLI_assert(index < size_in_bits_);
return {data_, index};
}
IndexRange index_range() const
{
return IndexRange(size_in_bits_);
}
/**
* Add a new bit to the end of the vector.
*/
void append(const bool value)
{
this->ensure_space_for_one();
MutableBitRef bit{data_, size_in_bits_};
bit.set(value);
size_in_bits_++;
}
BitIterator begin() const
{
return {data_, 0};
}
BitIterator end() const
{
return {data_, size_in_bits_};
}
MutableBitIterator begin()
{
return {data_, 0};
}
MutableBitIterator end()
{
return {data_, size_in_bits_};
}
/**
* Change the size of the vector. If the new vector is larger than the old one, the new elements
* are filled with #value.
*/
void resize(const int64_t new_size_in_bits, const bool value = false)
{
BLI_assert(new_size_in_bits >= 0);
const int64_t old_size_in_bits = size_in_bits_;
if (new_size_in_bits > old_size_in_bits) {
this->reserve(new_size_in_bits);
}
size_in_bits_ = new_size_in_bits;
if (old_size_in_bits < new_size_in_bits) {
MutableBitSpan(data_, IndexRange::from_begin_end(old_size_in_bits, new_size_in_bits))
.set_all(value);
}
}
/**
* Set #value on every element.
*/
void fill(const bool value)
{
MutableBitSpan(data_, size_in_bits_).set_all(value);
}
/**
* Make sure that the capacity of the vector is large enough to hold the given amount of bits.
* The actual size is not changed.
*/
void reserve(const int new_capacity_in_bits)
{
this->realloc_to_at_least(new_capacity_in_bits);
}
/**
* Reset the size of the vector to zero elements, but keep the same memory capacity to be
* refilled again.
*/
void clear()
{
size_in_bits_ = 0;
}
/**
* Free memory and reset the vector to zero elements.
*/
void clear_and_shrink()
{
size_in_bits_ = 0;
capacity_in_bits_ = 0;
if (!this->is_inline()) {
allocator_.deallocate(data_);
}
data_ = inline_buffer_;
}
private:
void ensure_space_for_one()
{
if (UNLIKELY(size_in_bits_ >= capacity_in_bits_)) {
this->realloc_to_at_least(size_in_bits_ + 1);
}
}
BLI_NOINLINE void realloc_to_at_least(const int64_t min_capacity_in_bits,
const BitInt initial_value_for_new_ints = 0)
{
if (capacity_in_bits_ >= min_capacity_in_bits) {
return;
}
const int64_t min_capacity_in_ints = this->required_ints_for_bits(min_capacity_in_bits);
/* At least double the size of the previous allocation. */
const int64_t min_new_capacity_in_ints = 2 * this->required_ints_for_bits(capacity_in_bits_);
const int64_t new_capacity_in_ints = std::max(min_capacity_in_ints, min_new_capacity_in_ints);
const int64_t ints_to_copy = this->used_ints_amount();
BitInt *new_data = static_cast<BitInt *>(
allocator_.allocate(new_capacity_in_ints * sizeof(BitInt), AllocationAlignment, __func__));
uninitialized_copy_n(data_, ints_to_copy, new_data);
/* Always initialize new capacity even if it isn't used yet. That's necessary to avoid warnings
* caused by using uninitialized memory. This happens when e.g. setting a clearing a bit in an
* uninitialized int. */
uninitialized_fill_n(
new_data + ints_to_copy, new_capacity_in_ints - ints_to_copy, initial_value_for_new_ints);
if (!this->is_inline()) {
allocator_.deallocate(data_);
}
data_ = new_data;
capacity_in_bits_ = new_capacity_in_ints * BitsPerInt;
}
bool is_inline() const
{
return data_ == inline_buffer_;
}
int64_t used_ints_amount() const
{
return this->required_ints_for_bits(size_in_bits_);
}
};
template<int64_t InlineBufferCapacity, typename Allocator>
inline BoundedBitSpan to_best_bit_span(const BitVector<InlineBufferCapacity, Allocator> &data)
{
return data;
}
template<int64_t InlineBufferCapacity, typename Allocator>
inline MutableBoundedBitSpan to_best_bit_span(BitVector<InlineBufferCapacity, Allocator> &data)
{
return data;
}
} // namespace bits
using bits::BitVector;
} // namespace blender

View File

@@ -0,0 +1,150 @@
/* SPDX-FileCopyrightText: 2012 by Nicholas Bishop. All rights reserved.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bli
*/
#include "BLI_utildefines.h"
namespace blender {
typedef unsigned int BLI_bitmap;
/* WARNING: the bitmap does not keep track of its own size or check
* for out-of-bounds access */
/* internal use */
/* 2^5 = 32 (bits) */
#define _BITMAP_POWER 5
/* 0b11111 */
#define _BITMAP_MASK 31
/**
* Number of blocks needed to hold '_num' bits.
*/
#define _BITMAP_NUM_BLOCKS(_num) (((_num) + _BITMAP_MASK) >> _BITMAP_POWER)
/**
* Size (in bytes) used to hold '_num' bits.
*/
#define BLI_BITMAP_SIZE(_num) ((size_t)(_BITMAP_NUM_BLOCKS(_num)) * sizeof(BLI_bitmap))
/**
* Allocate memory for a bitmap with '_num' bits; free with MEM_delete().
*/
#define BLI_BITMAP_NEW(_num, _alloc_string) \
((BLI_bitmap *)MEM_new_zeroed(BLI_BITMAP_SIZE(_num), _alloc_string))
/**
* Allocate a bitmap on the stack.
*/
#define BLI_BITMAP_NEW_ALLOCA(_num) \
((BLI_bitmap *)memset(alloca(BLI_BITMAP_SIZE(_num)), 0, BLI_BITMAP_SIZE(_num)))
/**
* Allocate using given MemArena.
*/
#define BLI_BITMAP_NEW_MEMARENA(_mem, _num) \
(CHECK_TYPE_INLINE(_mem, MemArena *), \
((BLI_bitmap *)BLI_memarena_calloc(_mem, BLI_BITMAP_SIZE(_num))))
/**
* Declares a bitmap as a variable.
*/
#define BLI_BITMAP_DECLARE(_name, _num) BLI_bitmap _name[_BITMAP_NUM_BLOCKS(_num)] = {}
/**
* Get the value of a single bit at '_index'.
*/
#define BLI_BITMAP_TEST(_bitmap, _index) \
(CHECK_TYPE_ANY(_bitmap, BLI_bitmap *, const BLI_bitmap *), \
((_bitmap)[(_index) >> _BITMAP_POWER] & (1u << ((_index) & _BITMAP_MASK))))
#define BLI_BITMAP_TEST_AND_SET_ATOMIC(_bitmap, _index) \
(CHECK_TYPE_ANY(_bitmap, BLI_bitmap *, const BLI_bitmap *), \
(atomic_fetch_and_or_uint32((uint32_t *)&(_bitmap)[(_index) >> _BITMAP_POWER], \
(1u << ((_index) & _BITMAP_MASK))) & \
(1u << ((_index) & _BITMAP_MASK))))
#define BLI_BITMAP_TEST_BOOL(_bitmap, _index) \
(CHECK_TYPE_ANY(_bitmap, BLI_bitmap *, const BLI_bitmap *), \
(BLI_BITMAP_TEST(_bitmap, _index) != 0))
/**
* Set the value of a single bit at '_index'.
*/
#define BLI_BITMAP_ENABLE(_bitmap, _index) \
(CHECK_TYPE_ANY(_bitmap, BLI_bitmap *, const BLI_bitmap *), \
((_bitmap)[(_index) >> _BITMAP_POWER] |= (1u << ((_index) & _BITMAP_MASK))))
/**
* Clear the value of a single bit at '_index'.
*/
#define BLI_BITMAP_DISABLE(_bitmap, _index) \
(CHECK_TYPE_ANY(_bitmap, BLI_bitmap *, const BLI_bitmap *), \
((_bitmap)[(_index) >> _BITMAP_POWER] &= ~(1u << ((_index) & _BITMAP_MASK))))
/**
* Flip the value of a single bit at '_index'.
*/
#define BLI_BITMAP_FLIP(_bitmap, _index) \
(CHECK_TYPE_ANY(_bitmap, BLI_bitmap *, const BLI_bitmap *), \
((_bitmap)[(_index) >> _BITMAP_POWER] ^= (1u << ((_index) & _BITMAP_MASK))))
/**
* Set or clear the value of a single bit at '_index'.
*/
#define BLI_BITMAP_SET(_bitmap, _index, _set) \
{ \
CHECK_TYPE(_bitmap, BLI_bitmap *); \
if (_set) { \
BLI_BITMAP_ENABLE(_bitmap, _index); \
} \
else { \
BLI_BITMAP_DISABLE(_bitmap, _index); \
} \
} \
(void)0
/**
* Resize bitmap to have space for '_num' bits.
*/
#define BLI_BITMAP_RESIZE(_bitmap, _num) \
{ \
CHECK_TYPE(_bitmap, BLI_bitmap *); \
(_bitmap) = (unsigned int *)MEM_realloc_zeroed(_bitmap, BLI_BITMAP_SIZE(_num)); \
} \
(void)0
/**
* Set or clear all bits in the bitmap.
*/
void BLI_bitmap_set_all(BLI_bitmap *bitmap, bool set, size_t bits);
/**
* Invert all bits in the bitmap.
*/
void BLI_bitmap_flip_all(BLI_bitmap *bitmap, size_t bits);
/**
* Copy all bits from one bitmap to another.
*/
void BLI_bitmap_copy_all(BLI_bitmap *dst, const BLI_bitmap *src, size_t bits);
/**
* Combine two bitmaps with boolean AND.
*/
void BLI_bitmap_and_all(BLI_bitmap *dst, const BLI_bitmap *src, size_t bits);
/**
* Combine two bitmaps with boolean OR.
*/
void BLI_bitmap_or_all(BLI_bitmap *dst, const BLI_bitmap *src, size_t bits);
/**
* Find index of the lowest unset bit.
* Returns -1 if all the bits are set.
*/
int BLI_bitmap_find_first_unset(const BLI_bitmap *bitmap, size_t bits);
} // namespace blender

View File

@@ -0,0 +1,55 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bli
*/
#include "BLI_math_vector_types.hh"
#include "BLI_span.hh"
namespace blender {
/**
* Plot a line from \a p1 to \a p2 (inclusive).
*
* \note For clipped line drawing, see: http://stackoverflow.com/a/40902741/432509
*/
void BLI_bitmap_draw_2d_line_v2v2i(const int p1[2],
const int p2[2],
bool (*callback)(int, int, void *),
void *user_data);
/**
* \note Unclipped (clipped version can be added if needed).
*/
void BLI_bitmap_draw_2d_tri_v2i(const int p1[2],
const int p2[2],
const int p3[2],
void (*callback)(int x, int x_end, int y, void *),
void *user_data);
/**
* Draws a filled polygon with support for self intersections.
*
* \param callback: Takes the x, y coords and x-span (\a x_end is not inclusive),
* note that \a x_end will always be greater than \a x, so we can use:
*
* \code{.c}
* do {
* func(x, y);
* } while (++x != x_end);
* \endcode
*/
void BLI_bitmap_draw_2d_poly_v2i_n(int xmin,
int ymin,
int xmax,
int ymax,
Span<int2> verts,
void (*callback)(int x, int x_end, int y, void *),
void *user_data);
} // namespace blender

View File

@@ -0,0 +1,517 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bli
*/
#include <optional>
#include "BLI_bounds_types.hh"
#include "BLI_index_mask.hh"
#include "BLI_math_matrix.hh"
#include "BLI_math_vector.hh"
#include "BLI_span.hh"
#include "BLI_task.hh"
#include "BLI_virtual_array.hh"
#include "PRF_profile.hh"
namespace blender {
namespace bounds {
template<typename T> [[nodiscard]] inline Bounds<T> merge(const Bounds<T> &a, const Bounds<T> &b)
{
return {math::min(a.min, b.min), math::max(a.max, b.max)};
}
template<typename T>
[[nodiscard]] inline std::optional<Bounds<T>> merge(const std::optional<Bounds<T>> &a,
const std::optional<Bounds<T>> &b)
{
if (a.has_value() && b.has_value()) {
return merge(*a, *b);
}
if (a.has_value()) {
return a;
}
if (b.has_value()) {
return b;
}
return std::nullopt;
}
template<typename T>
[[nodiscard]] inline std::optional<Bounds<T>> merge(const std::optional<Bounds<T>> &a,
const Bounds<T> &b)
{
return merge(a, std::optional<Bounds<T>>(b));
}
template<typename T>
[[nodiscard]] inline std::optional<Bounds<T>> min_max(const std::optional<Bounds<T>> &a,
const T &b)
{
if (a.has_value()) {
return merge(*a, {b, b});
}
return Bounds<T>{b, b};
}
/**
* Find the smallest and largest values element-wise in the span.
*/
template<typename T> [[nodiscard]] inline std::optional<Bounds<T>> min_max(const Span<T> values)
{
if (values.is_empty()) {
return std::nullopt;
}
PRF_scope_with_name("bounds::min_max_with_radii", ProfileCategory::Default);
const Bounds<T> init{values.first(), values.first()};
return threading::parallel_reduce(
values.index_range(),
1024,
init,
[&](const IndexRange range, const Bounds<T> &init) {
Bounds<T> result = init;
for (const int i : range) {
math::min_max(values[i], result.min, result.max);
}
return result;
},
[](const Bounds<T> &a, const Bounds<T> &b) { return merge(a, b); });
}
template<typename T>
[[nodiscard]] inline std::optional<Bounds<T>> min_max(const IndexMask &mask, const Span<T> values)
{
if (values.is_empty() || mask.is_empty()) {
return std::nullopt;
}
if (mask.size() == values.size()) {
/* To avoid mask slice/lookup. */
return min_max(values);
}
PRF_scope_with_name("bounds::min_max_with_radii", ProfileCategory::Default);
const Bounds<T> init{values[mask.first()], values[mask.first()]};
return threading::parallel_reduce(
mask.index_range().drop_front(1),
1024,
init,
[&](const IndexRange range, const Bounds<T> &init) {
Bounds<T> result = init;
mask.slice(range).foreach_index_optimized<int64_t>(
[&](const int i) { math::min_max(values[i], result.min, result.max); });
return result;
},
[](const Bounds<T> &a, const Bounds<T> &b) { return merge(a, b); });
}
/**
* Find the smallest and largest values element-wise in the span, adding the radius to each element
* first. The template type T is expected to have an addition operator implemented with RadiusT.
*/
template<typename T, typename RadiusT>
[[nodiscard]] inline std::optional<Bounds<T>> min_max_with_radii(const Span<T> values,
const Span<RadiusT> radii)
{
BLI_assert(values.size() == radii.size());
if (values.is_empty()) {
return std::nullopt;
}
PRF_scope_with_name("bounds::min_max_with_radii", ProfileCategory::Default);
const Bounds<T> init{values.first(), values.first()};
return threading::parallel_reduce(
values.index_range(),
1024,
init,
[&](const IndexRange range, const Bounds<T> &init) {
Bounds<T> result = init;
for (const int i : range) {
result.min = math::min(values[i] - radii[i], result.min);
result.max = math::max(values[i] + radii[i], result.max);
}
return result;
},
[](const Bounds<T> &a, const Bounds<T> &b) { return merge(a, b); });
}
/**
* Returns a new bound that contains the intersection of the two given bound.
* Returns no box if there are no overlap.
*/
template<typename T>
[[nodiscard]] inline std::optional<Bounds<T>> intersect(const Bounds<T> &a, const Bounds<T> &b)
{
const Bounds<T> result{math::max(a.min, b.min), math::min(a.max, b.max)};
if (result.is_empty()) {
return std::nullopt;
}
return result;
}
template<typename T>
[[nodiscard]] inline std::optional<Bounds<T>> intersect(const std::optional<Bounds<T>> &a,
const std::optional<Bounds<T>> &b)
{
if (!a.has_value() || !b.has_value()) {
return std::nullopt;
}
return intersect(*a, *b);
}
/**
* Finds the maximum value for elements in the array.
*/
template<typename T> inline std::optional<T> max(const VArray<T> &values)
{
if (values.is_empty()) {
return std::nullopt;
}
PRF_scope_with_name("bounds::max", ProfileCategory::Default);
if (const std::optional<T> value = values.get_if_single()) {
return value;
}
const VArraySpan<int> values_span = values;
return threading::parallel_reduce(
values_span.index_range(),
2048,
std::numeric_limits<T>::min(),
[&](const IndexRange range, int current_max) {
for (const int value : values_span.slice(range)) {
current_max = std::max(current_max, value);
}
return current_max;
},
[](const int a, const int b) { return std::max(a, b); });
}
/**
* Return the eight corners of a 3D bounding box.
* <pre>
*
* Z Y
* | /
* |/
* .-----X
* 2----------6
* /| /|
* / | / |
* 1----------5 |
* | | | |
* | 3-------|--7
* | / | /
* |/ |/
* 0----------4
* </pre>
*/
template<typename T>
inline std::array<VecBase<T, 3>, 8> corners(const Bounds<VecBase<T, 3>> &bounds)
{
return {
VecBase<T, 3>{bounds.min[0], bounds.min[1], bounds.min[2]},
VecBase<T, 3>{bounds.min[0], bounds.min[1], bounds.max[2]},
VecBase<T, 3>{bounds.min[0], bounds.max[1], bounds.max[2]},
VecBase<T, 3>{bounds.min[0], bounds.max[1], bounds.min[2]},
VecBase<T, 3>{bounds.max[0], bounds.min[1], bounds.min[2]},
VecBase<T, 3>{bounds.max[0], bounds.min[1], bounds.max[2]},
VecBase<T, 3>{bounds.max[0], bounds.max[1], bounds.max[2]},
VecBase<T, 3>{bounds.max[0], bounds.max[1], bounds.min[2]},
};
}
/**
* Return the four corners of a 2D bounding box.
* <pre>
*
* Y
* |
* |
* .-----X
*
* 3----------2
* | |
* | |
* | |
* | |
* 0----------1
* </pre>
*/
template<typename T>
inline std::array<VecBase<T, 2>, 4> corners(const Bounds<VecBase<T, 2>> &bounds)
{
return {
bounds.min,
VecBase<T, 2>{bounds.max.x, bounds.min.y},
bounds.max,
VecBase<T, 2>{bounds.min.x, bounds.max.y},
};
}
/**
* Transform a 3D bounding box.
*
* Note: this necessarily grows the bounding box, to ensure that the transformed
* bounding box fully contains the original. Therefore, calling this iteratively
* to transform from space A to space B, and then from space B to space C, etc.,
* will also iteratively grow the bounding box on each call. Try to avoid doing
* that, and instead first compose the transform matrices and then use that to
* transform the bounding box.
*/
template<typename T, int D>
inline Bounds<VecBase<T, 3>> transform_bounds(const MatBase<T, D, D> &matrix,
const Bounds<VecBase<T, 3>> &bounds)
{
std::array<VecBase<T, 3>, 8> points = corners(bounds);
for (VecBase<T, 3> &p : points) {
p = math::transform_point(matrix, p);
}
return {math::min(Span(points)), math::max(Span(points))};
}
/**
* Transform a 2D bounding box.
*
* See the note on the 3D variant.
*/
template<typename T, int D>
inline Bounds<VecBase<T, 2>> transform_bounds(const MatBase<T, D, D> &matrix,
const Bounds<VecBase<T, 2>> &bounds)
{
std::array<VecBase<T, 2>, 4> points = corners(bounds);
for (VecBase<T, 2> &p : points) {
p = math::transform_point(matrix, p);
}
return {math::min(Span(points)), math::max(Span(points))};
}
namespace detail {
template<typename T, int Size>
[[nodiscard]] inline bool any_less_than_v(const VecBase<T, Size> &a, const VecBase<T, Size> &b)
{
for (int i = 0; i < Size; i++) {
if (a[i] < b[i]) {
return true;
}
}
return false;
}
template<typename T, int Size>
[[nodiscard]] inline bool any_greater_than_v(const VecBase<T, Size> &a, const VecBase<T, Size> &b)
{
for (int i = 0; i < Size; i++) {
if (a[i] > b[i]) {
return true;
}
}
return false;
}
template<typename T, int Size>
[[nodiscard]] inline bool any_less_or_equal_than_v(const VecBase<T, Size> &a,
const VecBase<T, Size> &b)
{
for (int i = 0; i < Size; i++) {
if (a[i] <= b[i]) {
return true;
}
}
return false;
}
template<typename T> [[nodiscard]] inline bool any_less_than(const T &a, const T &b)
{
if constexpr (std::is_integral_v<T> || std::is_floating_point_v<T>) {
return a < b;
}
else {
return any_less_than_v(a, b);
}
}
template<typename T> [[nodiscard]] inline bool any_greater_than(const T &a, const T &b)
{
if constexpr (std::is_integral_v<T> || std::is_floating_point_v<T>) {
return a > b;
}
else {
return any_greater_than_v(a, b);
}
}
template<typename T> [[nodiscard]] inline bool any_less_or_equal_than(const T &a, const T &b)
{
if constexpr (std::is_integral_v<T> || std::is_floating_point_v<T>) {
return a <= b;
}
else {
return any_less_or_equal_than_v(a, b);
}
}
template<typename T> [[nodiscard]] inline Bounds<T> segment_bounds(const T &start, const T &end)
{
Bounds<T> bounds{start, start};
math::min_max(end, bounds.min, bounds.max);
return bounds;
}
/** Returns true if (p1 / q1) > (p2 / q2). */
template<typename T>
[[nodiscard]] inline bool rational_greater_than(const T &p1, const T &q1, const T &p2, const T &q2)
{
BLI_assert(q1 > T(0) && q2 > T(0));
return p1 * q2 > p2 * q1;
}
/** Returns true if (p1 / q1) < (p2 / q2). */
template<typename T>
[[nodiscard]] inline bool rational_less_than(const T &p1, const T &q1, const T &p2, const T &q2)
{
BLI_assert(q1 > T(0) && q2 > T(0));
return p1 * q2 < p2 * q1;
}
/** Adaptation of Liang-Barsky for N dimensions. */
template<typename T, int Size>
[[nodiscard]] inline bool segment_enter_exit_bounds(const Bounds<VecBase<T, Size>> &bounds,
const VecBase<T, Size> &start,
const VecBase<T, Size> &end)
{
T p_enter = T(0);
T q_enter = T(1);
T p_exit = T(1);
T q_exit = T(1);
/* t_enter = p_enter / q_enter */
/* t_exit = p_exit / q_exit */
for (int i = 0; i < Size; i++) {
const T di = end[i] - start[i];
if (di == T(0)) {
/* Segment is parallel to i-th axis. */
if (start[i] < bounds.min[i] || start[i] > bounds.max[i]) {
return false;
}
continue;
}
/* Note: We flip the sign here to ensure the denominator is positive. This doesn't change the
* value of the rational number. */
const T p_low = (di > T(0)) ? bounds.min[i] - start[i] : -(bounds.max[i] - start[i]);
const T p_high = (di > T(0)) ? bounds.max[i] - start[i] : -(bounds.min[i] - start[i]);
const T di_abs = (di > T(0)) ? di : -di;
/* t_low = p_low / di_abs */
/* t_high = p_high / di_abs */
if (rational_greater_than(p_low, di_abs, p_enter, q_enter)) {
/* t_low > t_enter */
p_enter = p_low;
q_enter = di_abs;
}
if (rational_less_than(p_high, di_abs, p_exit, q_exit)) {
/* t_high < t_exit */
p_exit = p_high;
q_exit = di_abs;
}
if (rational_greater_than(p_enter, q_enter, p_exit, q_exit)) {
/* t_enter > t_exit */
return false;
}
}
/* t_exit >= 0 and t_enter <= 1 */
return p_exit >= T(0) && p_enter <= q_enter;
}
} // namespace detail
} // namespace bounds
template<typename T> inline bool Bounds<T>::is_empty() const
{
return bounds::detail::any_less_or_equal_than(this->max, this->min);
}
template<typename T> inline T Bounds<T>::center() const
{
return math::midpoint(this->min, this->max);
}
template<typename T> inline T Bounds<T>::size() const
{
return math::abs(max - min);
}
template<typename T> inline void Bounds<T>::translate(const T &offset)
{
this->min += offset;
this->max += offset;
}
template<typename T> inline void Bounds<T>::scale_from_center(const T &scale)
{
const T center = this->center();
const T new_half_size = this->size() / T(2) * scale;
this->min = center - new_half_size;
this->max = center + new_half_size;
}
template<typename T> inline void Bounds<T>::resize(const T &new_size)
{
this->min = this->center() - (new_size / T(2));
this->max = this->min + new_size;
}
template<typename T> inline void Bounds<T>::recenter(const T &new_center)
{
const T offset = new_center - this->center();
this->translate(offset);
}
template<typename T>
template<typename PaddingT>
inline void Bounds<T>::pad(const PaddingT &padding)
{
this->min = this->min - padding;
this->max = this->max + padding;
}
template<typename T> inline bool Bounds<T>::contains(const T &point) const
{
if (bounds::detail::any_less_than(point, this->min)) {
return false;
}
if (bounds::detail::any_greater_than(point, this->max)) {
return false;
}
return true;
}
template<typename T> inline bool Bounds<T>::intersects(const Bounds<T> &other) const
{
if (bounds::intersect(*this, other)) {
return true;
}
return false;
}
template<typename T> inline bool Bounds<T>::intersects_segment(const T &start, const T &end) const
{
/* Check end points first to properly handle degenerate case where the segment is a point. */
if (this->contains(start) || this->contains(end)) {
return true;
}
if constexpr (std::is_integral_v<T> || std::is_floating_point_v<T>) {
return this->intersects(bounds::detail::segment_bounds(start, end));
}
else {
/* Check if the segment is entering and exiting the bounds. */
return bounds::detail::segment_enter_exit_bounds(*this, start, end);
}
}
} // namespace blender

View File

@@ -0,0 +1,82 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bli
*/
namespace blender {
template<typename T> struct Bounds {
T min;
T max;
Bounds() = default;
explicit Bounds(const T &value) : min(value), max(value) {}
Bounds(const T &min, const T &max) : min(min), max(max) {}
/**
* Returns true when the size of the bounds is zero (or negative).
* This matches the behavior of #BLI_rcti_is_empty/#BLI_rctf_is_empty.
*/
bool is_empty() const;
/**
* Return the center (i.e. the midpoint) of the bounds.
* This matches the behavior of #BLI_rctf_cent/#BLI_rcti_cent.
*/
T center() const;
/**
* Return the size of the bounds.
* E.g. for a Bounds<float3> this would return the dimensions of bounding box as a float3.
* This matches the behavior of #BLI_rctf_size/#BLI_rcti_size.
*/
T size() const;
/**
* Translate the bounds by #offset.
* This matches the behavior of #BLI_rctf_translate/#BLI_rcti_translate.
*/
void translate(const T &offset);
/**
* Scale the bounds from the center.
* This matches the behavior of #BLI_rctf_scale/#BLI_rcti_scale.
*/
void scale_from_center(const T &scale);
/**
* Resize the bounds in-place to ensure their size is #new_size.
* The center of the bounds doesn't change.
* This matches the behavior of #BLI_rctf_resize/#BLI_rcti_resize.
*/
void resize(const T &new_size);
/**
* Translate the bounds such that their center is #new_center.
* This matches the behavior of #BLI_rctf_recenter/#BLI_rcti_recenter.
*/
void recenter(const T &new_center);
/**
* Adds some padding to the bounds.
* This matches the behavior of #BLI_rcti_pad/#BLI_rctf_pad.
*/
template<typename PaddingT> void pad(const PaddingT &padding);
/**
* Returns true if \a point is inside the bounds.
* This matches the behavior of #BLI_rctf_isect_pt/#BLI_rcti_isect_pt.
*/
bool contains(const T &point) const;
/**
* Returns true if the \a other bounds is inside or intersect this one.
*/
bool intersects(const Bounds<T> &other) const;
/**
* Returns true if a line segment from \a start to \a end is inside or intersects the bounds.
*/
bool intersects_segment(const T &start, const T &end) const;
};
} // namespace blender

View File

@@ -0,0 +1,74 @@
/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bli
*/
#include "DNA_listBase.h"
namespace blender {
/* Box Packer */
struct BoxPack {
float x;
float y;
float w;
float h;
/* Verts this box uses
* (BL,TR,TL,BR) / 0,1,2,3 */
struct BoxVert *v[4];
int index;
};
/**
* Main box-packing function accessed from other functions
* This sets boxes x,y to positive values, sorting from 0,0 outwards.
* There is no limit to the space boxes may take, only that they will be packed
* tightly into the lower left hand corner (0,0)
*
* \param boxarray: a pre-allocated array of boxes.
* only the 'box->x' and 'box->y' are set, 'box->w' and 'box->h' are used,
* 'box->index' is not used at all, the only reason its there
* is that the box array is sorted by area and programs need to be able
* to have some way of writing the boxes back to the original data.
* \param len: the number of boxes in the array.
* \param sort_boxes: Sort `box_array` before packing.
* \param r_tot_x, r_tot_y: set so you can normalize the data.
*/
void BLI_box_pack_2d(
BoxPack *boxarray, unsigned int len, bool sort_boxes, float *r_tot_x, float *r_tot_y);
struct FixedSizeBoxPack {
struct FixedSizeBoxPack *next, *prev;
int x, y;
int w, h;
};
/**
* Packs boxes into a fixed area.
*
* Boxes and packed are linked lists containing structs that can be cast to
* #FixedSizeBoxPack (i.e. contains a #FixedSizeBoxPack as its first element).
* Boxes that were packed successfully are placed into *packed and removed from *boxes.
*
* The algorithm is a simplified version of https://github.com/TeamHypersomnia/rectpack2D.
* Better ones could be used, but for the current use case (packing Image tiles into GPU
* textures) this is fine.
*
* Note that packing efficiency depends on the order of the input boxes. Generally speaking,
* larger boxes should come first, though how exactly size is best defined (e.g. area, perimeter)
* depends on the particular application.
*/
void BLI_box_pack_2d_fixedarea(ListBaseT<FixedSizeBoxPack> *boxes,
int width,
int height,
ListBaseT<FixedSizeBoxPack> *packed);
} // namespace blender

View File

@@ -0,0 +1,457 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bli
*
* Based on Chromium's `build_config.h`, governed by a BSD-style license,
* with tweaks and extensions needed for the Blender project.
*/
/**
* Compile-time detection of compiler and hardware platform configuration.
* There are few categories of the defined symbols this header provides.
*
*
* Operating system detection
* ==========================
*
* An operating system is detected is defined as an `OS_<NAME>` symbols. For example, on Windows
* the `OS_WINDOWS` is defined to 1, and all the other symbols prefixed with `OS_` are defined to 0
* (except of the aggregates described above).
*
* There are aggregates which allows to access "family" of the operating system:
*
* - OS_BSD is defined for 1 for all BSD family of OS (FreeBSD, NextBSD, DragonFly...).
* - OS_POSIX is defined to 1 if the OS implements POSIX API.
*
*
* Compiler detection
* ==================
*
* The following compilers are detected: CLANG, GCC, MSVC, MINGW32, MINGW64.
*
* The COMPILER_<family> for the detected compiler is defined to 1, and all the rest of the
* compiler defines are set to 0.
*
* The aggregate COMPILER_MINGW is defined when the compiler is wither MINGW32 or MINGW64.
*
*
* CPU detection
* =============
*
* The commonly detected CPU capabilities are:
* - Family: `ARCH_CPU_<FAMILY>_FAMILY`
* - CPU bitness: `ARCH_CPU_<32|64>_BITS`
* - Endianness: `ARCH_CPU_<LITTLE|BIG>_ENDIAN`
*
* Supported CPU families: X86, S390, PPC, ARM, MIPS.
*/
#pragma once
/**
* All commonly used symbols (which are checked on a "top" level, from outside of any
* platform-specific `ifdef` block) are to be explicitly defined to 0 when they are not "active".
* Such an approach helps catching cases when one is attempted to access build configuration
* variable without including the header by simply using the `-Wundef` compiler attribute.
*/
/* -------------------------------------------------------------------- */
/** \name A set of macros to use for platform detection.
* \{ */
#if defined(__native_client__)
/* __native_client__ must be first, so that other OS_ defines are not set. */
# define OS_NACL 1
/* OS_NACL comes in two sand-boxing technology flavors, SFI or Non-SFI. PNaCl toolchain defines
* __native_client_nonsfi__ macro in Non-SFI build mode, while it does not in SFI build mode. */
# if defined(__native_client_nonsfi__)
# define OS_NACL_NONSFI
# else
# define OS_NACL_SFI
# endif
#elif defined(_AIX)
# define OS_AIX 1
#elif defined(ANDROID)
# define OS_ANDROID 1
#elif defined(__APPLE__)
/* Only include TargetConditions after testing ANDROID as some android builds on mac don't have
* this header available and it's not needed unless the target is really mac/IOS. */
# include <TargetConditionals.h>
# define OS_MAC 1
# if defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE
# define OS_IOS 1
# endif
#elif defined(__HAIKU__)
# define OS_HAIKU 1
#elif defined(__hpux)
# define OS_HPUX 1
#elif defined(__linux__)
# define OS_LINUX 1
/* Include a system header to pull in features.h for GLIBC/UCLIBC macros. */
# include <unistd.h>
# if defined(__GLIBC__) && !defined(__UCLIBC__)
/* We really are using GLIBC, not UCLIBC pretending to be GLIBC. */
# define LIBC_GLIBC 1
# endif
#elif defined(__sgi)
# define OS_IRIX 1
#elif defined(_WIN32)
# define OS_WINDOWS 1
#elif defined(__Fuchsia__)
# define OS_FUCHSIA 1
#elif defined(__DragonFly__)
# define OS_DRAGONFLYBSD 1
#elif defined(__FreeBSD__)
# define OS_FREEBSD 1
#elif defined(__NetBSD__)
# define OS_NETBSD 1
#elif defined(__OpenBSD__)
# define OS_OPENBSD 1
#elif defined(__sun)
# define OS_SOLARIS 1
#elif defined(__QNXNTO__)
# define OS_QNX 1
#elif defined(__asmjs__) || defined(__wasm__)
# define OS_ASMJS 1
#elif defined(__MVS__)
# define OS_ZOS 1
#else
# error Please add support for your platform in BLI_build_config.h
#endif
#if !defined(OS_AIX)
# define OS_AIX 0
#endif
#if !defined(OS_ASMJS)
# define OS_ASMJS 0
#endif
#if !defined(OS_NACL)
# define OS_NACL 0
#endif
#if !defined(OS_NACL_NONSFI)
# define OS_NACL_NONSFI 0
#endif
#if !defined(OS_NACL_SFI)
# define OS_NACL_SFI 0
#endif
#if !defined(OS_ANDROID)
# define OS_ANDROID 0
#endif
#if !defined(OS_MAC)
# define OS_MAC 0
#endif
#if !defined(OS_IOS)
# define OS_IOS 0
#endif
#if !defined(OS_HAIKU)
# define OS_HAIKU 0
#endif
#if !defined(OS_HPUX)
# define OS_HPUX 0
#endif
#if !defined(OS_IRIX)
# define OS_IRIX 0
#endif
#if !defined(OS_LINUX)
# define OS_LINUX 0
#endif
#if !defined(LIBC_GLIBC)
# define LIBC_GLIBC 0
#endif
#if !defined(OS_WINDOWS)
# define OS_WINDOWS 0
#endif
#if !defined(OS_FUCHSIA)
# define OS_FUCHSIA 0
#endif
#if !defined(OS_DRAGONFLYBSD)
# define OS_DRAGONFLYBSD 0
#endif
#if !defined(OS_FREEBSD)
# define OS_FREEBSD 0
#endif
#if !defined(OS_NETBSD)
# define OS_NETBSD 0
#endif
#if !defined(OS_OPENBSD)
# define OS_OPENBSD 0
#endif
#if !defined(OS_SOLARIS)
# define OS_SOLARIS 0
#endif
#if !defined(OS_QNX)
# define OS_QNX 0
#endif
#if !defined(OS_ZOS)
# define OS_ZOS 0
#endif
/** \} */
/* -------------------------------------------------------------------- */
/** \name *BSD OS family detection.
* For access to standard BSD features, use OS_BSD instead of a more specific macro.
* \{ */
#if OS_DRAGONFLYBSD || OS_FREEBSD || OS_OPENBSD || OS_NETBSD
# define OS_BSD 1
#else
# define OS_BSD 0
#endif
/** \} */
/* -------------------------------------------------------------------- */
/** \name POSIX system detection.
* For access to standard POSIXish features use OS_POSIX instead of a more specific macro.
* \{ */
#if OS_AIX || OS_ANDROID || OS_ASMJS || OS_FREEBSD || OS_LINUX || OS_MAC || OS_NACL || \
OS_NETBSD || OS_OPENBSD || OS_QNX || OS_SOLARIS
# define OS_POSIX 1
#else
# define OS_POSIX 0
#endif
/** \} */
/* -------------------------------------------------------------------- */
/** \name Compiler detection, including its capabilities.
* \{ */
#if defined(__clang__)
# define COMPILER_CLANG 1
#elif defined(__GNUC__)
# define COMPILER_GCC 1
# define COMPILER_GCC_VERSION (__GNUC__ * 100 + __GNUC_MINOR__)
#elif defined(_MSC_VER)
# define COMPILER_MSVC 1
# define COMPILER_MSVC_VERSION (_MSC_VER)
#elif defined(__MINGW32__)
# define COMPILER_MINGW32 1
#elif defined(__MINGW64__)
# define COMPILER_MINGW64 1
#else
# error Please add support for your compiler in BLI_build_config.h
#endif
#if !defined(COMPILER_CLANG)
# define COMPILER_CLANG 0
#endif
#if !defined(COMPILER_GCC)
# define COMPILER_GCC 0
#endif
#if !defined(COMPILER_MSVC)
# define COMPILER_MSVC 0
#endif
#if !defined(COMPILER_MINGW32)
# define COMPILER_MINGW32 0
#endif
#if !defined(COMPILER_MINGW64)
# define COMPILER_MINGW64 0
#endif
/* Compiler is any of MinGW family. */
#if COMPILER_MINGW32 || COMPILER_MINGW64
# define COMPILER_MINGW 1
#else
# define COMPILER_MINGW 0
#endif
/** \} */
/* -------------------------------------------------------------------- */
/** \name Processor architecture detection.
* For more info on what's defined, see:
*
* http://msdn.microsoft.com/en-us/library/b0084kay.aspx
* http://www.agner.org/optimize/calling_conventions.pdf
*
* or with GCC, run: `echo | gcc -E -dM -`
* \{ */
#if defined(_M_X64) || defined(__x86_64__)
# define ARCH_CPU_X86_FAMILY 1
# define ARCH_CPU_X86_64 1
# define ARCH_CPU_64_BITS 1
# define ARCH_CPU_LITTLE_ENDIAN 1
#elif defined(_M_IX86) || defined(__i386__)
# define ARCH_CPU_X86_FAMILY 1
# define ARCH_CPU_X86 1
# define ARCH_CPU_32_BITS 1
# define ARCH_CPU_LITTLE_ENDIAN 1
#elif defined(__s390x__)
# define ARCH_CPU_S390_FAMILY 1
# define ARCH_CPU_S390X 1
# define ARCH_CPU_64_BITS 1
# define ARCH_CPU_BIG_ENDIAN 1
#elif defined(__s390__)
# define ARCH_CPU_S390_FAMILY 1
# define ARCH_CPU_S390 1
# define ARCH_CPU_31_BITS 1
# define ARCH_CPU_BIG_ENDIAN 1
#elif (defined(__PPC64__) || defined(__PPC__)) && defined(__BIG_ENDIAN__)
# define ARCH_CPU_PPC64_FAMILY 1
# define ARCH_CPU_PPC64 1
# define ARCH_CPU_64_BITS 1
# define ARCH_CPU_BIG_ENDIAN 1
#elif defined(__PPC64__)
# define ARCH_CPU_PPC64_FAMILY 1
# define ARCH_CPU_PPC64 1
# define ARCH_CPU_64_BITS 1
# define ARCH_CPU_LITTLE_ENDIAN 1
#elif defined(__ARMEL__)
# define ARCH_CPU_ARM_FAMILY 1
# define ARCH_CPU_ARMEL 1
# define ARCH_CPU_32_BITS 1
# define ARCH_CPU_LITTLE_ENDIAN 1
#elif defined(__aarch64__) || defined(_M_ARM64)
# define ARCH_CPU_ARM_FAMILY 1
# define ARCH_CPU_ARM64 1
# define ARCH_CPU_64_BITS 1
# define ARCH_CPU_LITTLE_ENDIAN 1
#elif defined(__pnacl__) || defined(__asmjs__) || defined(__wasm__)
# define ARCH_CPU_32_BITS 1
# define ARCH_CPU_LITTLE_ENDIAN 1
#elif defined(__MIPSEL__)
# if defined(__LP64__)
# define ARCH_CPU_MIPS_FAMILY 1
# define ARCH_CPU_MIPS64EL 1
# define ARCH_CPU_64_BITS 1
# define ARCH_CPU_LITTLE_ENDIAN 1
# else
# define ARCH_CPU_MIPS_FAMILY 1
# define ARCH_CPU_MIPSEL 1
# define ARCH_CPU_32_BITS 1
# define ARCH_CPU_LITTLE_ENDIAN 1
# endif
#elif defined(__MIPSEB__)
# if defined(__LP64__)
# define ARCH_CPU_MIPS_FAMILY 1
# define ARCH_CPU_MIPS64 1
# define ARCH_CPU_64_BITS 1
# define ARCH_CPU_BIG_ENDIAN 1
# else
# define ARCH_CPU_MIPS_FAMILY 1
# define ARCH_CPU_MIPS 1
# define ARCH_CPU_32_BITS 1
# define ARCH_CPU_BIG_ENDIAN 1
# endif
#elif defined(__riscv)
# define ARCH_CPU_RISCV_FAMILY 1
# if defined(__LP128__)
# define ARCH_CPU_RISCV128 1
# define ARCH_CPU_128_BITS 1
# elif defined(__LP64__)
# define ARCH_CPU_RISCV64 1
# define ARCH_CPU_64_BITS 1
# else
# define ARCH_CPU_RISCV32 1
# define ARCH_CPU_32_BITS 1
# endif
# if (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)
# define ARCH_CPU_LITTLE_ENDIAN 1
# else
# define ARCH_CPU_BIG_ENDIAN 1
# endif
#elif defined(__loongarch_lp64)
# define ARCH_CPU_LOONG_FAMILY 1
# define ARCH_CPU_LOONG64 1
# define ARCH_CPU_64_BITS 1
# define ARCH_CPU_LITTLE_ENDIAN 1
#else
# error Please add support for your architecture in BLI_build_config.h
#endif
#if !defined(ARCH_CPU_LITTLE_ENDIAN)
# define ARCH_CPU_LITTLE_ENDIAN 0
#endif
#if !defined(ARCH_CPU_BIG_ENDIAN)
# define ARCH_CPU_BIG_ENDIAN 0
#endif
#if !defined(ARCH_CPU_31_BITS)
# define ARCH_CPU_31_BITS 0
#endif
#if !defined(ARCH_CPU_32_BITS)
# define ARCH_CPU_32_BITS 0
#endif
#if !defined(ARCH_CPU_64_BITS)
# define ARCH_CPU_64_BITS 0
#endif
#if !defined(ARCH_CPU_128_BITS)
# define ARCH_CPU_128_BITS 0
#endif
#if !defined(ARCH_CPU_X86_FAMILY)
# define ARCH_CPU_X86_FAMILY 0
#endif
#if !defined(ARCH_CPU_ARM_FAMILY)
# define ARCH_CPU_ARM_FAMILY 0
#endif
#if !defined(ARCH_CPU_MIPS_FAMILY)
# define ARCH_CPU_MIPS_FAMILY 0
#endif
#if !defined(ARCH_CPU_PPC64_FAMILY)
# define ARCH_CPU_PPC64_FAMILY 0
#endif
#if !defined(ARCH_CPU_S390_FAMILY)
# define ARCH_CPU_S390_FAMILY 0
#endif
#if !defined(ARCH_CPU_RISCV_FAMILY)
# define ARCH_CPU_RISCV_FAMILY 0
#endif
#if !defined(ARCH_CPU_LOONG_FAMILY)
# define ARCH_CPU_LOONG_FAMILY 0
#endif
#if !defined(ARCH_CPU_ARM64)
# define ARCH_CPU_ARM64 0
#endif
#if !defined(ARCH_CPU_ARMEL)
# define ARCH_CPU_ARMEL 0
#endif
#if !defined(ARCH_CPU_MIPS)
# define ARCH_CPU_MIPS 0
#endif
#if !defined(ARCH_CPU_MIPS64)
# define ARCH_CPU_MIPS64 0
#endif
#if !defined(ARCH_CPU_MIPS64EL)
# define ARCH_CPU_MIPS64EL 0
#endif
#if !defined(ARCH_CPU_MIPSEL)
# define ARCH_CPU_MIPSEL 0
#endif
#if !defined(ARCH_CPU_PPC64)
# define ARCH_CPU_PPC64 0
#endif
#if !defined(ARCH_CPU_S390)
# define ARCH_CPU_S390 0
#endif
#if !defined(ARCH_CPU_S390X)
# define ARCH_CPU_S390X 0
#endif
#if !defined(ARCH_CPU_X86)
# define ARCH_CPU_X86 0
#endif
#if !defined(ARCH_CPU_X86_64)
# define ARCH_CPU_X86_64 0
#endif
#if !defined(ARCH_CPU_RISCV32)
# define ARCH_CPU_RISCV32 0
#endif
#if !defined(ARCH_CPU_RISCV64)
# define ARCH_CPU_RISCV64 0
#endif
#if !defined(ARCH_CPU_RISCV128)
# define ARCH_CPU_RISCV128 0
#endif
#if !defined(ARCH_CPU_LOONG64)
# define ARCH_CPU_LOONG64 0
#endif
/** \} */

View File

@@ -0,0 +1,122 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bli
*/
#pragma once
/**
* A #CacheMutex is used to protect a lazily computed cache from being computed more than once.
* Using #CacheMutex instead of a "raw mutex" to protect a cache has some benefits:
* - Avoid common pitfalls like forgetting to use task isolation or a double checked lock.
* - Cleaner and less redundant code because the same locking patterns don't have to be repeated
* everywhere.
* - One can benefit from potential future improvements to #CacheMutex of which there are a few
* mentioned below.
*
* The data protected by #CacheMutex is not part of #CacheMutex. Instead, the #CacheMutex and its
* protected data should generally be placed next to each other.
*
* Each #CacheMutex protects exactly one cache, so multiple cache mutexes have to be used when a
* class has multiple caches. That is contrary to a "custom" solution using `Mutex` where one
* mutex could protect multiple caches at the cost of higher lock contention.
*
* To make sure the cache is up to date, call `CacheMutex::ensure` and pass in the function that
* computes the cache.
*
* To tell the #CacheMutex that the cache is invalidated and to be re-evaluated upon next access
* use `CacheMutex::tag_dirty`.
*
* This example shows how one could implement a lazily computed average vertex position in an
* imaginary `Mesh` data structure:
*
* \code{.cpp}
* class Mesh {
* private:
* mutable CacheMutex average_position_cache_mutex_;
* mutable float3 average_position_cache_;
*
* public:
* const float3 &average_position() const
* {
* average_position_cache_mutex_.ensure([&]() {
* average_position_cache_ = actually_compute_average_position();
* });
* return average_position_cache_;
* }
*
* void tag_positions_changed()
* {
* average_position_cache_mutex_.tag_dirty();
* }
* };
* \endcode
*
* Possible future improvements:
* - Avoid task isolation when we know that the cache computation does not use threading.
* - Try to use a smaller mutex. The mutex does not have to be fair for this use case.
* - Try to join the cache computation instead of blocking if another thread is computing the cache
* already.
*/
#include <atomic>
#include "BLI_function_ref.hh"
#include "BLI_mutex.hh"
namespace blender {
class CacheMutex {
private:
Mutex mutex_;
std::atomic<bool> cache_valid_ = false;
public:
/**
* Make sure the cache exists and is up to date. This calls `compute_cache` once to update the
* cache (which is stored outside of this class) if it is dirty, otherwise it does nothing.
*
* This function is thread-safe under the assumption that the same parameters are passed from
* every thread.
*/
void ensure(const FunctionRef<void()> compute_cache)
{
/* Handle fast case when the cache is up-to-date. */
if (cache_valid_.load(std::memory_order_acquire)) {
return;
}
this->ensure_impl(compute_cache);
}
/**
* Reset the cache. The next time #ensure is called, it will recompute that code.
*/
void tag_dirty()
{
cache_valid_.store(false);
}
/**
* Return true if the cache currently does not exist or has been invalidated.
*/
bool is_dirty() const
{
return !this->is_cached();
}
/**
* Return true if the cache exists and is valid.
*/
bool is_cached() const
{
return cache_valid_.load(std::memory_order_relaxed);
}
private:
void ensure_impl(FunctionRef<void()> compute_cache);
};
} // namespace blender

View File

@@ -0,0 +1,155 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bli
*/
#pragma once
#include <ostream>
#include "BLI_color_types.hh"
#include "BLI_colorspace.hh"
#include "BLI_compiler_compat.h"
#include "BLI_math_color.h"
#include "BLI_math_vector.h"
namespace blender {
/*
* Stream output.
*/
std::ostream &operator<<(std::ostream &stream, const eAlpha &space);
std::ostream &operator<<(std::ostream &stream, const eSpace &space);
template<typename ChannelStorageType, eSpace Space, eAlpha Alpha>
std::ostream &operator<<(std::ostream &stream,
const ColorRGBA<ChannelStorageType, Space, Alpha> &c);
namespace color {
/**
* Change precision of color to uint8_t.
*/
BLI_INLINE ColorTheme4b to_byte(const ColorTheme4f &theme4f)
{
ColorTheme4b theme4b;
rgba_float_to_uchar(theme4b, theme4f);
return theme4b;
}
BLI_INLINE ColorTheme4b to_byte(const ColorTheme4b &theme4b)
{
return theme4b;
}
template<eAlpha Alpha>
BLI_INLINE ColorSceneLinearByteEncoded4b<Alpha> encode(const ColorSceneLinear4f<Alpha> &color)
{
float4 value = static_cast<const float *>(color);
if (!colorspace::scene_linear_is_rec709) {
copy_v3_v3(value, colorspace::scene_linear_to_rec709 * value.xyz());
}
ColorSceneLinearByteEncoded4b<Alpha> encoded;
linearrgb_to_srgb_uchar4(encoded, value);
return encoded;
}
/**
* Change precision of color to float.
*/
BLI_INLINE ColorTheme4f to_float(const ColorTheme4b &theme4b)
{
ColorTheme4f theme4f;
rgba_uchar_to_float(theme4f, theme4b);
return theme4f;
}
BLI_INLINE ColorTheme4f to_float(const ColorTheme4f &theme4f)
{
return theme4f;
}
template<eAlpha Alpha>
BLI_INLINE ColorSceneLinear4f<Alpha> decode(const ColorSceneLinearByteEncoded4b<Alpha> &color)
{
ColorSceneLinear4f<Alpha> decoded;
srgb_to_linearrgb_uchar4(decoded, color);
if (!colorspace::scene_linear_is_rec709) {
copy_v3_v3(decoded, colorspace::rec709_to_scene_linear * float3(decoded));
}
return decoded;
}
/**
* Convert color and alpha association to premultiplied alpha.
*
* Does nothing when color already has a premultiplied alpha.
*/
template<eAlpha Alpha>
ColorSceneLinear4f<eAlpha::Premultiplied> premultiply_alpha(const ColorSceneLinear4f<Alpha> &color)
{
if constexpr (Alpha == eAlpha::Straight) {
ColorSceneLinear4f<eAlpha::Premultiplied> premultiplied;
straight_to_premul_v4_v4(premultiplied, color);
return premultiplied;
}
else {
return color;
}
}
/**
* Convert color and alpha association to straight alpha.
*
* Does nothing when color has straight alpha.
*/
template<eAlpha Alpha>
ColorSceneLinear4f<eAlpha::Straight> unpremultiply_alpha(const ColorSceneLinear4f<Alpha> &color)
{
if constexpr (Alpha == eAlpha::Premultiplied) {
ColorSceneLinear4f<eAlpha::Straight> straighten;
premul_to_straight_v4_v4(straighten, color);
return straighten;
}
else {
return color;
}
}
/**
* Convert between theme and scene linear colors.
*/
BLI_INLINE ColorSceneLinear4f<eAlpha::Straight> to_scene_linear(const ColorTheme4f &theme4f)
{
ColorSceneLinear4f<eAlpha::Straight> scene_linear;
srgb_to_linearrgb_v4(scene_linear, theme4f);
return scene_linear;
}
BLI_INLINE ColorSceneLinear4f<eAlpha::Straight> to_scene_linear(const ColorTheme4b &theme4b)
{
ColorSceneLinear4f<eAlpha::Straight> scene_linear;
srgb_to_linearrgb_uchar4(scene_linear, theme4b);
return scene_linear;
}
BLI_INLINE ColorTheme4f to_theme4f(const ColorSceneLinear4f<eAlpha::Straight> &scene_linear)
{
ColorTheme4f theme4f;
linearrgb_to_srgb_v4(theme4f, scene_linear);
return theme4f;
}
BLI_INLINE ColorTheme4b to_theme4b(const ColorSceneLinear4f<eAlpha::Straight> &scene_linear)
{
ColorTheme4b theme4b;
linearrgb_to_srgb_uchar4(theme4b, scene_linear);
return theme4b;
}
} // namespace color
} // namespace blender

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,222 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bli
*/
#pragma once
#include <cstdint>
#include <type_traits>
#include "BLI_unique_hash.hh"
#include "BLI_utildefines.h"
namespace blender {
/**
* CPP based color structures.
*
* Strongly typed color storage structures with space and alpha association.
* Will increase readability and visibility of typical mistakes when
* working with colors.
*
* The storage structs can hold 4 channels (r, g, b and a).
*
* Usage:
*
* Convert a theme byte color to a linearrgb premultiplied.
* \code{.cc}
* ColorTheme4b theme_color;
* ColorSceneLinear4f<eAlpha::Premultiplied> linearrgb_color =
* BLI_color_convert_to_scene_linear(theme_color).premultiply_alpha();
* \endcode
*
* The API is structured to make most use of inlining. Most notable are space
* conversions done via `BLI_color_convert_to*` functions.
*
* - Conversions between spaces (theme <=> scene linear) should always be done by
* invoking the `BLI_color_convert_to*` methods.
* - Encoding colors (compressing to store colors inside a less precision storage)
* should be done by invoking the `encode` and `decode` methods.
* - Changing alpha association should be done by invoking `premultiply_alpha` or
* `unpremultiply_alpha` methods.
*
* # Encoding.
*
* Color encoding is used to store colors with less precision as in using `uint8_t` in
* stead of `float`. This encoding is supported for `eSpace::SceneLinear`.
* To make this clear to the developer the `eSpace::SceneLinearByteEncoded`
* space is added.
*
* # Precision
*
* Colors can be stored using `uint8_t` or `float` colors. The conversion
* between the two precisions are available as methods. (`to_4b` and
* `to_4f`).
*
* # Alpha conversion
*
* Alpha conversion is only supported in SceneLinear space.
*
* Extending this file:
* - This file can be extended with `ColorHex/Hsl/Hsv` for different representations
* of rgb based colors. `ColorHsl4f<eSpace::SceneLinear, eAlpha::Premultiplied>`
* - Add non RGB spaces/storage for ColorXyz.
*/
/** Enumeration containing the different alpha modes. */
enum class eAlpha {
/** Color and alpha are unassociated. */
Straight,
/** Color and alpha are associated. */
Premultiplied,
};
/** Enumeration containing internal spaces. */
enum class eSpace {
/** Blender theme color space (sRGB). */
Theme,
/** Blender internal scene linear color space (maps to scene_linear role in OCIO). */
SceneLinear,
/** Blender internal scene linear color space compressed to be stored in 4 uint8_t. */
SceneLinearByteEncoded,
};
/** Template class to store RGBA values with different precision, space, and alpha association. */
template<typename ChannelStorageType, eSpace Space, eAlpha Alpha> class ColorRGBA {
public:
ChannelStorageType r, g, b, a;
constexpr ColorRGBA() = default;
constexpr ColorRGBA(const ChannelStorageType rgba[4])
: r(rgba[0]), g(rgba[1]), b(rgba[2]), a(rgba[3])
{
}
constexpr ColorRGBA(const ChannelStorageType r,
const ChannelStorageType g,
const ChannelStorageType b,
const ChannelStorageType a)
: r(r), g(g), b(b), a(a)
{
}
operator ChannelStorageType *()
{
return &r;
}
operator const ChannelStorageType *() const
{
return &r;
}
friend bool operator==(const ColorRGBA &a, const ColorRGBA &b) = default;
constexpr uint64_t hash() const
{
return get_default_hash(r, g, b, a);
}
void hash_unique(UniqueHashBytes &hash) const
{
hash_unique_default(r, hash);
hash_unique_default(g, hash);
hash_unique_default(b, hash);
hash_unique_default(a, hash);
}
};
/* Forward declarations of concrete color classes. */
template<eAlpha Alpha> class ColorSceneLinear4f;
template<eAlpha Alpha> class ColorSceneLinearByteEncoded4b;
template<typename ChannelStorageType> class ColorTheme4;
template<eAlpha Alpha>
class ColorSceneLinear4f final : public ColorRGBA<float, eSpace::SceneLinear, Alpha> {
public:
constexpr ColorSceneLinear4f() = default;
constexpr explicit ColorSceneLinear4f(float value)
: ColorRGBA<float, eSpace::SceneLinear, Alpha>(value, value, value, value)
{
}
template<typename U>
constexpr explicit ColorSceneLinear4f(U value)
requires(std::is_convertible_v<U, float>)
: ColorSceneLinear4f(float(value))
{
}
constexpr ColorSceneLinear4f(const float *rgba)
: ColorRGBA<float, eSpace::SceneLinear, Alpha>(rgba)
{
}
constexpr ColorSceneLinear4f(float r, float g, float b, float a)
: ColorRGBA<float, eSpace::SceneLinear, Alpha>(r, g, b, a)
{
}
};
template<eAlpha Alpha>
class ColorSceneLinearByteEncoded4b final
: public ColorRGBA<uint8_t, eSpace::SceneLinearByteEncoded, Alpha> {
public:
constexpr ColorSceneLinearByteEncoded4b() = default;
constexpr ColorSceneLinearByteEncoded4b(const uint8_t *rgba)
: ColorRGBA<uint8_t, eSpace::SceneLinearByteEncoded, Alpha>(rgba)
{
}
constexpr ColorSceneLinearByteEncoded4b(uint8_t r, uint8_t g, uint8_t b, uint8_t a)
: ColorRGBA<uint8_t, eSpace::SceneLinearByteEncoded, Alpha>(r, g, b, a)
{
}
};
/**
* Theme color template class.
*
* Don't use directly, but use `ColorTheme4b/ColorTheme4b`.
*
* This has been implemented as a template to improve inlining. When implemented as concrete
* classes (ColorTheme4b/f) the functions would be hidden in a compile unit what wouldn't be
* inlined.
*/
template<typename ChannelStorageType>
class ColorTheme4 final : public ColorRGBA<ChannelStorageType, eSpace::Theme, eAlpha::Straight> {
public:
constexpr ColorTheme4() = default;
constexpr ColorTheme4(const ChannelStorageType *rgba)
: ColorRGBA<ChannelStorageType, eSpace::Theme, eAlpha::Straight>(rgba)
{
}
constexpr ColorTheme4(ChannelStorageType r,
ChannelStorageType g,
ChannelStorageType b,
ChannelStorageType a)
: ColorRGBA<ChannelStorageType, eSpace::Theme, eAlpha::Straight>(r, g, b, a)
{
}
};
using ColorTheme4b = ColorTheme4<uint8_t>;
using ColorTheme4f = ColorTheme4<float>;
/* Internal roles. For convenience to shorten the type names and hide complexity. */
using ColorGeometry4f = ColorSceneLinear4f<eAlpha::Premultiplied>;
using ColorGeometry4b = ColorSceneLinearByteEncoded4b<eAlpha::Premultiplied>;
using ColorPaint4f = ColorSceneLinear4f<eAlpha::Straight>;
using ColorPaint4b = ColorSceneLinearByteEncoded4b<eAlpha::Straight>;
} // namespace blender

View File

@@ -0,0 +1,38 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bli
*/
#pragma once
#include "BLI_math_matrix_types.hh"
#include "BLI_math_vector_types.hh"
namespace blender {
namespace colorspace {
/* Coefficients to compute luma from RGB. */
extern float3 luma_coefficients;
/* Conversion between scene linear and common linear spaces. */
extern float3x3 scene_linear_to_xyz;
extern float3x3 xyz_to_scene_linear;
extern float3x3 scene_linear_to_aces;
extern float3x3 aces_to_scene_linear;
extern float3x3 scene_linear_to_acescg;
extern float3x3 acescg_to_scene_linear;
extern float3x3 scene_linear_to_rec709;
extern float3x3 rec709_to_scene_linear;
extern float3x3 scene_linear_to_rec2020;
extern float3x3 rec2020_to_scene_linear;
/* For fast path when converting to/from sRGB. */
extern bool scene_linear_is_rec709;
}; // namespace colorspace
} // namespace blender

View File

@@ -0,0 +1,92 @@
/* SPDX-FileCopyrightText: 2013 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bli
*/
/* hint to make sure function result is actually used */
#ifdef __GNUC__
# define ATTR_WARN_UNUSED_RESULT __attribute__((warn_unused_result))
#else
# define ATTR_WARN_UNUSED_RESULT
#endif
/* hint to mark function arguments expected to be non-null
* if no arguments are given to the macro, all of pointer
* arguments would be expected to be non-null
*/
#ifdef __GNUC__
# define ATTR_NONNULL(args...) __attribute__((nonnull(args)))
#else
# define ATTR_NONNULL(...)
#endif
/* never returns NULL */
#ifdef __GNUC__
# define ATTR_RETURNS_NONNULL __attribute__((returns_nonnull))
#else
# define ATTR_RETURNS_NONNULL
#endif
/* hint to mark function as it wouldn't return */
#if defined(__GNUC__) || defined(__clang__)
# define ATTR_NORETURN __attribute__((noreturn))
#else
# define ATTR_NORETURN
#endif
/* hint to treat any non-null function return value cannot alias any other pointer */
#ifdef __GNUC__
# define ATTR_MALLOC __attribute__((malloc))
#else
# define ATTR_MALLOC
#endif
/* the function return value points to memory (2 args for 'size * tot') */
#if defined(__GNUC__) && !defined(__clang__)
# define ATTR_ALLOC_SIZE(args...) __attribute__((alloc_size(args)))
#else
# define ATTR_ALLOC_SIZE(...)
#endif
/* ensures a NULL terminating argument as the n'th last argument of a variadic function */
#ifdef __GNUC__
# define ATTR_SENTINEL(arg_pos) __attribute__((sentinel(arg_pos)))
#else
# define ATTR_SENTINEL(arg_pos)
#endif
/* hint to compiler that function uses printf-style format string */
#ifdef __GNUC__
# define ATTR_PRINTF_FORMAT(format_param, dots_param) \
__attribute__((format(printf, format_param, dots_param)))
#else
# define ATTR_PRINTF_FORMAT(format_param, dots_param)
#endif
/* Use to suppress `-Wimplicit-fallthrough` (in place of `break`). */
#ifndef ATTR_FALLTHROUGH
# ifdef __GNUC__
# define ATTR_FALLTHROUGH __attribute__((fallthrough))
# else
# define ATTR_FALLTHROUGH ((void)0)
# endif
#endif
/* Declare the memory alignment in Bytes. */
#if defined(_WIN32) && !defined(FREE_WINDOWS)
# define ATTR_ALIGN(x) __declspec(align(x))
#else
# define ATTR_ALIGN(x) __attribute__((aligned(x)))
#endif
/* Alignment directive */
#ifdef _WIN64
# define BLI_ALIGN_STRUCT __declspec(align(64))
#else
# define BLI_ALIGN_STRUCT
#endif

View File

@@ -0,0 +1,52 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/* `#define typeof()` triggers a bug in some clang-format versions,
* disable format for entire file to keep results consistent. */
/* clang-format off */
#pragma once
/** \file
* \ingroup bli
*
* Use to help with cross platform portability.
*/
#if defined(_MSC_VER)
# define alloca _alloca
#endif
#if (defined(__GNUC__) || defined(__clang__)) && defined(__cplusplus)
extern "C++" {
/** Some magic to be sure we don't have reference in the type. */
template<typename T> static inline T decltype_helper(T x)
{
return x;
}
#define typeof(x) decltype(decltype_helper(x))
}
#endif
/* little macro so inline keyword works */
#if defined(_MSC_VER) && !defined(__clang__)
# define BLI_INLINE static __forceinline
#else
# define BLI_INLINE static inline __attribute__((always_inline)) __attribute__((__unused__))
#endif
#if defined(_MSC_VER) && !defined(__clang__)
# define BLI_INLINE_METHOD __forceinline
#else
# define BLI_INLINE_METHOD inline __attribute__((always_inline)) __attribute__((__unused__))
#endif
#if defined(__GNUC__)
# define BLI_NOINLINE __attribute__((noinline))
#elif defined(_MSC_VER)
# define BLI_NOINLINE __declspec(noinline)
#else
# define BLI_NOINLINE
#endif

View File

@@ -0,0 +1,682 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bli
*
* Type checking macros (often used to ensure valid use of macro args).
* These depend on compiler extensions and c11 in some cases.
*/
#include "BLI_utildefines_variadic.h"
/* Causes warning:
* incompatible types when assigning to type 'Foo' from type 'Bar'
* ... the compiler optimizes away the temp var */
#ifdef __GNUC__
# define CHECK_TYPE(var, type) \
{ \
typeof(var) *__tmp = (type *)NULL; \
(void)__tmp; \
} \
(void)0
# define CHECK_TYPE_PAIR(var_a, var_b) \
{ \
const typeof(var_a) *__tmp = (typeof(var_b) *)NULL; \
(void)__tmp; \
} \
(void)0
# define CHECK_TYPE_PAIR_INLINE(var_a, var_b) \
((void)({ \
const typeof(var_a) *__tmp = (typeof(var_b) *)NULL; \
(void)__tmp; \
}))
#else
# define CHECK_TYPE(var, type) \
{ \
EXPR_NOP(var); \
} \
(void)0
# define CHECK_TYPE_PAIR(var_a, var_b) \
{ \
(EXPR_NOP(var_a), EXPR_NOP(var_b)); \
} \
(void)0
# define CHECK_TYPE_PAIR_INLINE(var_a, var_b) (EXPR_NOP(var_a), EXPR_NOP(var_b))
#endif
/* can be used in simple macros */
#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L)
# define CHECK_TYPE_INLINE(val, type) \
(void)((void)(((type)0) != (0 ? (val) : ((type)0))), _Generic((val), type: 0, const type: 0))
/* NOTE: The NONCONST version is needed for scalar types on CLANG, to avoid warnings. */
# define CHECK_TYPE_INLINE_NONCONST(val, type) \
(void)((void)(((type)0) != (0 ? (val) : ((type)0))), _Generic((val), type: 0))
#else
# define CHECK_TYPE_INLINE_NONCONST(val, type) ((void)(((type)0) != (0 ? (val) : ((type)0))))
# define CHECK_TYPE_INLINE(val, type) ((void)(((type)0) != (0 ? (val) : ((type)0))))
#endif
#if defined(__GNUC__) || defined(__clang__)
# define CHECK_TYPE_NONCONST(var) \
__extension__({ \
void *non_const = 0 ? (var) : NULL; \
(void)non_const; \
})
#else
# define CHECK_TYPE_NONCONST(var) EXPR_NOP(var)
#endif
/**
* CHECK_TYPE_ANY: handy macro, eg:
* `CHECK_TYPE_ANY(var, Foo *, Bar *, Baz *)`
*
* excuse ridiculously long generated args.
* \code{.py}
* for i in range(63):
* args = [(chr(ord('a') + (c % 26)) + (chr(ord('0') + (c // 26)))) for c in range(i + 1)]
* print("#define _VA_CHECK_TYPE_ANY%d(v, %s) \\" % (i + 2, ", ".join(args)))
* print(" ((void)_Generic((v), %s))" % (": 0, ".join(args) + ": 0"))
* \endcode
*/
#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L)
/* Over wrapped args. */
/* clang-format off */
#define _VA_CHECK_TYPE_ANY2(v, a0) \
((void)_Generic((v), a0: 0))
#define _VA_CHECK_TYPE_ANY3(v, a0, b0) \
((void)_Generic((v), a0: 0, b0: 0))
#define _VA_CHECK_TYPE_ANY4(v, a0, b0, c0) \
((void)_Generic((v), a0: 0, b0: 0, c0: 0))
#define _VA_CHECK_TYPE_ANY5(v, a0, b0, c0, d0) \
((void)_Generic((v), a0: 0, b0: 0, c0: 0, d0: 0))
#define _VA_CHECK_TYPE_ANY6(v, a0, b0, c0, d0, e0) \
((void)_Generic((v), a0: 0, b0: 0, c0: 0, d0: 0, e0: 0))
#define _VA_CHECK_TYPE_ANY7(v, a0, b0, c0, d0, e0, f0) \
((void)_Generic((v), a0: 0, b0: 0, c0: 0, d0: 0, e0: 0, f0: 0))
#define _VA_CHECK_TYPE_ANY8(v, a0, b0, c0, d0, e0, f0, g0) \
((void)_Generic((v), a0: 0, b0: 0, c0: 0, d0: 0, e0: 0, f0: 0, g0: 0))
#define _VA_CHECK_TYPE_ANY9(v, a0, b0, c0, d0, e0, f0, g0, h0) \
((void)_Generic((v), a0: 0, b0: 0, c0: 0, d0: 0, e0: 0, f0: 0, g0: 0, h0: 0))
#define _VA_CHECK_TYPE_ANY10(v, a0, b0, c0, d0, e0, f0, g0, h0, i0) \
((void)_Generic((v), a0: 0, b0: 0, c0: 0, d0: 0, e0: 0, f0: 0, g0: 0, h0: 0, i0: 0))
#define _VA_CHECK_TYPE_ANY11(v, a0, b0, c0, d0, e0, f0, g0, h0, i0, j0) \
((void)_Generic((v), a0: 0, b0: 0, c0: 0, d0: 0, e0: 0, f0: 0, g0: 0, h0: 0, i0: 0, j0: 0))
#define _VA_CHECK_TYPE_ANY12(v, a0, b0, c0, d0, e0, f0, g0, h0, i0, j0, k0) \
((void)_Generic((v), a0: 0, b0: 0, c0: 0, d0: 0, e0: 0, f0: 0, g0: 0, h0: 0, i0: 0, j0: 0, k0: 0))
#define _VA_CHECK_TYPE_ANY13(v, a0, b0, c0, d0, e0, f0, g0, h0, i0, j0, k0, l0) \
((void)_Generic((v), a0: 0, b0: 0, c0: 0, d0: 0, e0: 0, f0: 0, g0: 0, h0: 0, i0: 0, j0: 0, k0: 0, l0: 0))
#define _VA_CHECK_TYPE_ANY14(v, a0, b0, c0, d0, e0, f0, g0, h0, i0, j0, k0, l0, m0) \
((void)_Generic((v), a0: 0, b0: 0, c0: 0, d0: 0, e0: 0, f0: 0, g0: 0, h0: 0, i0: 0, j0: 0, k0: 0, l0: 0, m0: 0))
#define _VA_CHECK_TYPE_ANY15(v, a0, b0, c0, d0, e0, f0, g0, h0, i0, j0, k0, l0, m0, n0) \
((void)_Generic((v), a0: 0, b0: 0, c0: 0, d0: 0, e0: 0, f0: 0, g0: 0, h0: 0, i0: 0, j0: 0, k0: 0, l0: 0, m0: 0, \
n0: 0))
#define _VA_CHECK_TYPE_ANY16(v, a0, b0, c0, d0, e0, f0, g0, h0, i0, j0, k0, l0, m0, n0, o0) \
((void)_Generic((v), a0: 0, b0: 0, c0: 0, d0: 0, e0: 0, f0: 0, g0: 0, h0: 0, i0: 0, j0: 0, k0: 0, l0: 0, m0: 0, \
n0: 0, o0: 0))
#define _VA_CHECK_TYPE_ANY17(v, a0, b0, c0, d0, e0, f0, g0, h0, i0, j0, k0, l0, m0, n0, o0, p0) \
((void)_Generic((v), a0: 0, b0: 0, c0: 0, d0: 0, e0: 0, f0: 0, g0: 0, h0: 0, i0: 0, j0: 0, k0: 0, l0: 0, m0: 0, \
n0: 0, o0: 0, p0: 0))
#define _VA_CHECK_TYPE_ANY18(v, a0, b0, c0, d0, e0, f0, g0, h0, i0, j0, k0, l0, m0, n0, o0, p0, q0) \
((void)_Generic((v), a0: 0, b0: 0, c0: 0, d0: 0, e0: 0, f0: 0, g0: 0, h0: 0, i0: 0, j0: 0, k0: 0, l0: 0, m0: 0, \
n0: 0, o0: 0, p0: 0, q0: 0))
#define _VA_CHECK_TYPE_ANY19(v, a0, b0, c0, d0, e0, f0, g0, h0, i0, j0, k0, l0, m0, n0, o0, p0, q0, r0) \
((void)_Generic((v), a0: 0, b0: 0, c0: 0, d0: 0, e0: 0, f0: 0, g0: 0, h0: 0, i0: 0, j0: 0, k0: 0, l0: 0, m0: 0, \
n0: 0, o0: 0, p0: 0, q0: 0, r0: 0))
#define _VA_CHECK_TYPE_ANY20(v, a0, b0, c0, d0, e0, f0, g0, h0, i0, j0, k0, l0, m0, n0, o0, p0, q0, r0, s0) \
((void)_Generic((v), a0: 0, b0: 0, c0: 0, d0: 0, e0: 0, f0: 0, g0: 0, h0: 0, i0: 0, j0: 0, k0: 0, l0: 0, m0: 0, \
n0: 0, o0: 0, p0: 0, q0: 0, r0: 0, s0: 0))
#define _VA_CHECK_TYPE_ANY21(v, a0, b0, c0, d0, e0, f0, g0, h0, i0, j0, k0, l0, m0, n0, o0, p0, q0, r0, s0, t0) \
((void)_Generic((v), a0: 0, b0: 0, c0: 0, d0: 0, e0: 0, f0: 0, g0: 0, h0: 0, i0: 0, j0: 0, k0: 0, l0: 0, m0: 0, \
n0: 0, o0: 0, p0: 0, q0: 0, r0: 0, s0: 0, t0: 0))
#define _VA_CHECK_TYPE_ANY22(v, a0, b0, c0, d0, e0, f0, g0, h0, i0, j0, k0, l0, m0, n0, o0, p0, q0, r0, s0, t0, u0) \
((void)_Generic((v), a0: 0, b0: 0, c0: 0, d0: 0, e0: 0, f0: 0, g0: 0, h0: 0, i0: 0, j0: 0, k0: 0, l0: 0, m0: 0, \
n0: 0, o0: 0, p0: 0, q0: 0, r0: 0, s0: 0, t0: 0, u0: 0))
#define _VA_CHECK_TYPE_ANY23(v, a0, b0, c0, d0, e0, f0, g0, h0, i0, j0, k0, l0, m0, n0, o0, p0, q0, r0, s0, t0, u0, \
v0) \
((void)_Generic((v), a0: 0, b0: 0, c0: 0, d0: 0, e0: 0, f0: 0, g0: 0, h0: 0, i0: 0, j0: 0, k0: 0, l0: 0, m0: 0, \
n0: 0, o0: 0, p0: 0, q0: 0, r0: 0, s0: 0, t0: 0, u0: 0, v0: 0))
#define _VA_CHECK_TYPE_ANY24(v, a0, b0, c0, d0, e0, f0, g0, h0, i0, j0, k0, l0, m0, n0, o0, p0, q0, r0, s0, t0, u0, \
v0, w0) \
((void)_Generic((v), a0: 0, b0: 0, c0: 0, d0: 0, e0: 0, f0: 0, g0: 0, h0: 0, i0: 0, j0: 0, k0: 0, l0: 0, m0: 0, \
n0: 0, o0: 0, p0: 0, q0: 0, r0: 0, s0: 0, t0: 0, u0: 0, v0: 0, w0: 0))
#define _VA_CHECK_TYPE_ANY25(v, a0, b0, c0, d0, e0, f0, g0, h0, i0, j0, k0, l0, m0, n0, o0, p0, q0, r0, s0, t0, u0, \
v0, w0, x0) \
((void)_Generic((v), a0: 0, b0: 0, c0: 0, d0: 0, e0: 0, f0: 0, g0: 0, h0: 0, i0: 0, j0: 0, k0: 0, l0: 0, m0: 0, \
n0: 0, o0: 0, p0: 0, q0: 0, r0: 0, s0: 0, t0: 0, u0: 0, v0: 0, w0: 0, x0: 0))
#define _VA_CHECK_TYPE_ANY26(v, a0, b0, c0, d0, e0, f0, g0, h0, i0, j0, k0, l0, m0, n0, o0, p0, q0, r0, s0, t0, u0, \
v0, w0, x0, y0) \
((void)_Generic((v), a0: 0, b0: 0, c0: 0, d0: 0, e0: 0, f0: 0, g0: 0, h0: 0, i0: 0, j0: 0, k0: 0, l0: 0, m0: 0, \
n0: 0, o0: 0, p0: 0, q0: 0, r0: 0, s0: 0, t0: 0, u0: 0, v0: 0, w0: 0, x0: 0, y0: 0))
#define _VA_CHECK_TYPE_ANY27(v, a0, b0, c0, d0, e0, f0, g0, h0, i0, j0, k0, l0, m0, n0, o0, p0, q0, r0, s0, t0, u0, \
v0, w0, x0, y0, z0) \
((void)_Generic((v), a0: 0, b0: 0, c0: 0, d0: 0, e0: 0, f0: 0, g0: 0, h0: 0, i0: 0, j0: 0, k0: 0, l0: 0, m0: 0, \
n0: 0, o0: 0, p0: 0, q0: 0, r0: 0, s0: 0, t0: 0, u0: 0, v0: 0, w0: 0, x0: 0, y0: 0, z0: 0))
#define _VA_CHECK_TYPE_ANY28(v, a0, b0, c0, d0, e0, f0, g0, h0, i0, j0, k0, l0, m0, n0, o0, p0, q0, r0, s0, t0, u0, \
v0, w0, x0, y0, z0, a1) \
((void)_Generic((v), a0: 0, b0: 0, c0: 0, d0: 0, e0: 0, f0: 0, g0: 0, h0: 0, i0: 0, j0: 0, k0: 0, l0: 0, m0: 0, \
n0: 0, o0: 0, p0: 0, q0: 0, r0: 0, s0: 0, t0: 0, u0: 0, v0: 0, w0: 0, x0: 0, y0: 0, z0: 0, a1: 0))
#define _VA_CHECK_TYPE_ANY29(v, a0, b0, c0, d0, e0, f0, g0, h0, i0, j0, k0, l0, m0, n0, o0, p0, q0, r0, s0, t0, u0, \
v0, w0, x0, y0, z0, a1, b1) \
((void)_Generic((v), a0: 0, b0: 0, c0: 0, d0: 0, e0: 0, f0: 0, g0: 0, h0: 0, i0: 0, j0: 0, k0: 0, l0: 0, m0: 0, \
n0: 0, o0: 0, p0: 0, q0: 0, r0: 0, s0: 0, t0: 0, u0: 0, v0: 0, w0: 0, x0: 0, y0: 0, z0: 0, a1: 0, b1: 0))
#define _VA_CHECK_TYPE_ANY30(v, a0, b0, c0, d0, e0, f0, g0, h0, i0, j0, k0, l0, m0, n0, o0, p0, q0, r0, s0, t0, u0, \
v0, w0, x0, y0, z0, a1, b1, c1) \
((void)_Generic((v), a0: 0, b0: 0, c0: 0, d0: 0, e0: 0, f0: 0, g0: 0, h0: 0, i0: 0, j0: 0, k0: 0, l0: 0, m0: 0, \
n0: 0, o0: 0, p0: 0, q0: 0, r0: 0, s0: 0, t0: 0, u0: 0, v0: 0, w0: 0, x0: 0, y0: 0, z0: 0, a1: 0, b1: 0, c1: 0))
#define _VA_CHECK_TYPE_ANY31(v, a0, b0, c0, d0, e0, f0, g0, h0, i0, j0, k0, l0, m0, n0, o0, p0, q0, r0, s0, t0, u0, \
v0, w0, x0, y0, z0, a1, b1, c1, d1) \
((void)_Generic((v), a0: 0, b0: 0, c0: 0, d0: 0, e0: 0, f0: 0, g0: 0, h0: 0, i0: 0, j0: 0, k0: 0, l0: 0, m0: 0, \
n0: 0, o0: 0, p0: 0, q0: 0, r0: 0, s0: 0, t0: 0, u0: 0, v0: 0, w0: 0, x0: 0, y0: 0, z0: 0, a1: 0, b1: 0, c1: 0, \
d1: 0))
#define _VA_CHECK_TYPE_ANY32(v, a0, b0, c0, d0, e0, f0, g0, h0, i0, j0, k0, l0, m0, n0, o0, p0, q0, r0, s0, t0, u0, \
v0, w0, x0, y0, z0, a1, b1, c1, d1, e1) \
((void)_Generic((v), a0: 0, b0: 0, c0: 0, d0: 0, e0: 0, f0: 0, g0: 0, h0: 0, i0: 0, j0: 0, k0: 0, l0: 0, m0: 0, \
n0: 0, o0: 0, p0: 0, q0: 0, r0: 0, s0: 0, t0: 0, u0: 0, v0: 0, w0: 0, x0: 0, y0: 0, z0: 0, a1: 0, b1: 0, c1: 0, \
d1: 0, e1: 0))
#define _VA_CHECK_TYPE_ANY33(v, a0, b0, c0, d0, e0, f0, g0, h0, i0, j0, k0, l0, m0, n0, o0, p0, q0, r0, s0, t0, u0, \
v0, w0, x0, y0, z0, a1, b1, c1, d1, e1, f1) \
((void)_Generic((v), a0: 0, b0: 0, c0: 0, d0: 0, e0: 0, f0: 0, g0: 0, h0: 0, i0: 0, j0: 0, k0: 0, l0: 0, m0: 0, \
n0: 0, o0: 0, p0: 0, q0: 0, r0: 0, s0: 0, t0: 0, u0: 0, v0: 0, w0: 0, x0: 0, y0: 0, z0: 0, a1: 0, b1: 0, c1: 0, \
d1: 0, e1: 0, f1: 0))
#define _VA_CHECK_TYPE_ANY34(v, a0, b0, c0, d0, e0, f0, g0, h0, i0, j0, k0, l0, m0, n0, o0, p0, q0, r0, s0, t0, u0, \
v0, w0, x0, y0, z0, a1, b1, c1, d1, e1, f1, g1) \
((void)_Generic((v), a0: 0, b0: 0, c0: 0, d0: 0, e0: 0, f0: 0, g0: 0, h0: 0, i0: 0, j0: 0, k0: 0, l0: 0, m0: 0, \
n0: 0, o0: 0, p0: 0, q0: 0, r0: 0, s0: 0, t0: 0, u0: 0, v0: 0, w0: 0, x0: 0, y0: 0, z0: 0, a1: 0, b1: 0, c1: 0, \
d1: 0, e1: 0, f1: 0, g1: 0))
#define _VA_CHECK_TYPE_ANY35(v, a0, b0, c0, d0, e0, f0, g0, h0, i0, j0, k0, l0, m0, n0, o0, p0, q0, r0, s0, t0, u0, \
v0, w0, x0, y0, z0, a1, b1, c1, d1, e1, f1, g1, h1) \
((void)_Generic((v), a0: 0, b0: 0, c0: 0, d0: 0, e0: 0, f0: 0, g0: 0, h0: 0, i0: 0, j0: 0, k0: 0, l0: 0, m0: 0, \
n0: 0, o0: 0, p0: 0, q0: 0, r0: 0, s0: 0, t0: 0, u0: 0, v0: 0, w0: 0, x0: 0, y0: 0, z0: 0, a1: 0, b1: 0, c1: 0, \
d1: 0, e1: 0, f1: 0, g1: 0, h1: 0))
#define _VA_CHECK_TYPE_ANY36(v, a0, b0, c0, d0, e0, f0, g0, h0, i0, j0, k0, l0, m0, n0, o0, p0, q0, r0, s0, t0, u0, \
v0, w0, x0, y0, z0, a1, b1, c1, d1, e1, f1, g1, h1, i1) \
((void)_Generic((v), a0: 0, b0: 0, c0: 0, d0: 0, e0: 0, f0: 0, g0: 0, h0: 0, i0: 0, j0: 0, k0: 0, l0: 0, m0: 0, \
n0: 0, o0: 0, p0: 0, q0: 0, r0: 0, s0: 0, t0: 0, u0: 0, v0: 0, w0: 0, x0: 0, y0: 0, z0: 0, a1: 0, b1: 0, c1: 0, \
d1: 0, e1: 0, f1: 0, g1: 0, h1: 0, i1: 0))
#define _VA_CHECK_TYPE_ANY37(v, a0, b0, c0, d0, e0, f0, g0, h0, i0, j0, k0, l0, m0, n0, o0, p0, q0, r0, s0, t0, u0, \
v0, w0, x0, y0, z0, a1, b1, c1, d1, e1, f1, g1, h1, i1, j1) \
((void)_Generic((v), a0: 0, b0: 0, c0: 0, d0: 0, e0: 0, f0: 0, g0: 0, h0: 0, i0: 0, j0: 0, k0: 0, l0: 0, m0: 0, \
n0: 0, o0: 0, p0: 0, q0: 0, r0: 0, s0: 0, t0: 0, u0: 0, v0: 0, w0: 0, x0: 0, y0: 0, z0: 0, a1: 0, b1: 0, c1: 0, \
d1: 0, e1: 0, f1: 0, g1: 0, h1: 0, i1: 0, j1: 0))
#define _VA_CHECK_TYPE_ANY38(v, a0, b0, c0, d0, e0, f0, g0, h0, i0, j0, k0, l0, m0, n0, o0, p0, q0, r0, s0, t0, u0, \
v0, w0, x0, y0, z0, a1, b1, c1, d1, e1, f1, g1, h1, i1, j1, k1) \
((void)_Generic((v), a0: 0, b0: 0, c0: 0, d0: 0, e0: 0, f0: 0, g0: 0, h0: 0, i0: 0, j0: 0, k0: 0, l0: 0, m0: 0, \
n0: 0, o0: 0, p0: 0, q0: 0, r0: 0, s0: 0, t0: 0, u0: 0, v0: 0, w0: 0, x0: 0, y0: 0, z0: 0, a1: 0, b1: 0, c1: 0, \
d1: 0, e1: 0, f1: 0, g1: 0, h1: 0, i1: 0, j1: 0, k1: 0))
#define _VA_CHECK_TYPE_ANY39(v, a0, b0, c0, d0, e0, f0, g0, h0, i0, j0, k0, l0, m0, n0, o0, p0, q0, r0, s0, t0, u0, \
v0, w0, x0, y0, z0, a1, b1, c1, d1, e1, f1, g1, h1, i1, j1, k1, l1) \
((void)_Generic((v), a0: 0, b0: 0, c0: 0, d0: 0, e0: 0, f0: 0, g0: 0, h0: 0, i0: 0, j0: 0, k0: 0, l0: 0, m0: 0, \
n0: 0, o0: 0, p0: 0, q0: 0, r0: 0, s0: 0, t0: 0, u0: 0, v0: 0, w0: 0, x0: 0, y0: 0, z0: 0, a1: 0, b1: 0, c1: 0, \
d1: 0, e1: 0, f1: 0, g1: 0, h1: 0, i1: 0, j1: 0, k1: 0, l1: 0))
#define _VA_CHECK_TYPE_ANY40(v, a0, b0, c0, d0, e0, f0, g0, h0, i0, j0, k0, l0, m0, n0, o0, p0, q0, r0, s0, t0, u0, \
v0, w0, x0, y0, z0, a1, b1, c1, d1, e1, f1, g1, h1, i1, j1, k1, l1, m1) \
((void)_Generic((v), a0: 0, b0: 0, c0: 0, d0: 0, e0: 0, f0: 0, g0: 0, h0: 0, i0: 0, j0: 0, k0: 0, l0: 0, m0: 0, \
n0: 0, o0: 0, p0: 0, q0: 0, r0: 0, s0: 0, t0: 0, u0: 0, v0: 0, w0: 0, x0: 0, y0: 0, z0: 0, a1: 0, b1: 0, c1: 0, \
d1: 0, e1: 0, f1: 0, g1: 0, h1: 0, i1: 0, j1: 0, k1: 0, l1: 0, m1: 0))
#define _VA_CHECK_TYPE_ANY41(v, a0, b0, c0, d0, e0, f0, g0, h0, i0, j0, k0, l0, m0, n0, o0, p0, q0, r0, s0, t0, u0, \
v0, w0, x0, y0, z0, a1, b1, c1, d1, e1, f1, g1, h1, i1, j1, k1, l1, m1, n1) \
((void)_Generic((v), a0: 0, b0: 0, c0: 0, d0: 0, e0: 0, f0: 0, g0: 0, h0: 0, i0: 0, j0: 0, k0: 0, l0: 0, m0: 0, \
n0: 0, o0: 0, p0: 0, q0: 0, r0: 0, s0: 0, t0: 0, u0: 0, v0: 0, w0: 0, x0: 0, y0: 0, z0: 0, a1: 0, b1: 0, c1: 0, \
d1: 0, e1: 0, f1: 0, g1: 0, h1: 0, i1: 0, j1: 0, k1: 0, l1: 0, m1: 0, n1: 0))
#define _VA_CHECK_TYPE_ANY42(v, a0, b0, c0, d0, e0, f0, g0, h0, i0, j0, k0, l0, m0, n0, o0, p0, q0, r0, s0, t0, u0, \
v0, w0, x0, y0, z0, a1, b1, c1, d1, e1, f1, g1, h1, i1, j1, k1, l1, m1, n1, o1) \
((void)_Generic((v), a0: 0, b0: 0, c0: 0, d0: 0, e0: 0, f0: 0, g0: 0, h0: 0, i0: 0, j0: 0, k0: 0, l0: 0, m0: 0, \
n0: 0, o0: 0, p0: 0, q0: 0, r0: 0, s0: 0, t0: 0, u0: 0, v0: 0, w0: 0, x0: 0, y0: 0, z0: 0, a1: 0, b1: 0, c1: 0, \
d1: 0, e1: 0, f1: 0, g1: 0, h1: 0, i1: 0, j1: 0, k1: 0, l1: 0, m1: 0, n1: 0, o1: 0))
#define _VA_CHECK_TYPE_ANY43(v, a0, b0, c0, d0, e0, f0, g0, h0, i0, j0, k0, l0, m0, n0, o0, p0, q0, r0, s0, t0, u0, \
v0, w0, x0, y0, z0, a1, b1, c1, d1, e1, f1, g1, h1, i1, j1, k1, l1, m1, n1, o1, p1) \
((void)_Generic((v), a0: 0, b0: 0, c0: 0, d0: 0, e0: 0, f0: 0, g0: 0, h0: 0, i0: 0, j0: 0, k0: 0, l0: 0, m0: 0, \
n0: 0, o0: 0, p0: 0, q0: 0, r0: 0, s0: 0, t0: 0, u0: 0, v0: 0, w0: 0, x0: 0, y0: 0, z0: 0, a1: 0, b1: 0, c1: 0, \
d1: 0, e1: 0, f1: 0, g1: 0, h1: 0, i1: 0, j1: 0, k1: 0, l1: 0, m1: 0, n1: 0, o1: 0, p1: 0))
#define _VA_CHECK_TYPE_ANY44(v, a0, b0, c0, d0, e0, f0, g0, h0, i0, j0, k0, l0, m0, n0, o0, p0, q0, r0, s0, t0, u0, \
v0, w0, x0, y0, z0, a1, b1, c1, d1, e1, f1, g1, h1, i1, j1, k1, l1, m1, n1, o1, p1, q1) \
((void)_Generic((v), a0: 0, b0: 0, c0: 0, d0: 0, e0: 0, f0: 0, g0: 0, h0: 0, i0: 0, j0: 0, k0: 0, l0: 0, m0: 0, \
n0: 0, o0: 0, p0: 0, q0: 0, r0: 0, s0: 0, t0: 0, u0: 0, v0: 0, w0: 0, x0: 0, y0: 0, z0: 0, a1: 0, b1: 0, c1: 0, \
d1: 0, e1: 0, f1: 0, g1: 0, h1: 0, i1: 0, j1: 0, k1: 0, l1: 0, m1: 0, n1: 0, o1: 0, p1: 0, q1: 0))
#define _VA_CHECK_TYPE_ANY45(v, a0, b0, c0, d0, e0, f0, g0, h0, i0, j0, k0, l0, m0, n0, o0, p0, q0, r0, s0, t0, u0, \
v0, w0, x0, y0, z0, a1, b1, c1, d1, e1, f1, g1, h1, i1, j1, k1, l1, m1, n1, o1, p1, q1, r1) \
((void)_Generic((v), a0: 0, b0: 0, c0: 0, d0: 0, e0: 0, f0: 0, g0: 0, h0: 0, i0: 0, j0: 0, k0: 0, l0: 0, m0: 0, \
n0: 0, o0: 0, p0: 0, q0: 0, r0: 0, s0: 0, t0: 0, u0: 0, v0: 0, w0: 0, x0: 0, y0: 0, z0: 0, a1: 0, b1: 0, c1: 0, \
d1: 0, e1: 0, f1: 0, g1: 0, h1: 0, i1: 0, j1: 0, k1: 0, l1: 0, m1: 0, n1: 0, o1: 0, p1: 0, q1: 0, r1: 0))
#define _VA_CHECK_TYPE_ANY46(v, a0, b0, c0, d0, e0, f0, g0, h0, i0, j0, k0, l0, m0, n0, o0, p0, q0, r0, s0, t0, u0, \
v0, w0, x0, y0, z0, a1, b1, c1, d1, e1, f1, g1, h1, i1, j1, k1, l1, m1, n1, o1, p1, q1, r1, s1) \
((void)_Generic((v), a0: 0, b0: 0, c0: 0, d0: 0, e0: 0, f0: 0, g0: 0, h0: 0, i0: 0, j0: 0, k0: 0, l0: 0, m0: 0, \
n0: 0, o0: 0, p0: 0, q0: 0, r0: 0, s0: 0, t0: 0, u0: 0, v0: 0, w0: 0, x0: 0, y0: 0, z0: 0, a1: 0, b1: 0, c1: 0, \
d1: 0, e1: 0, f1: 0, g1: 0, h1: 0, i1: 0, j1: 0, k1: 0, l1: 0, m1: 0, n1: 0, o1: 0, p1: 0, q1: 0, r1: 0, s1: 0))
#define _VA_CHECK_TYPE_ANY47(v, a0, b0, c0, d0, e0, f0, g0, h0, i0, j0, k0, l0, m0, n0, o0, p0, q0, r0, s0, t0, u0, \
v0, w0, x0, y0, z0, a1, b1, c1, d1, e1, f1, g1, h1, i1, j1, k1, l1, m1, n1, o1, p1, q1, r1, s1, t1) \
((void)_Generic((v), a0: 0, b0: 0, c0: 0, d0: 0, e0: 0, f0: 0, g0: 0, h0: 0, i0: 0, j0: 0, k0: 0, l0: 0, m0: 0, \
n0: 0, o0: 0, p0: 0, q0: 0, r0: 0, s0: 0, t0: 0, u0: 0, v0: 0, w0: 0, x0: 0, y0: 0, z0: 0, a1: 0, b1: 0, c1: 0, \
d1: 0, e1: 0, f1: 0, g1: 0, h1: 0, i1: 0, j1: 0, k1: 0, l1: 0, m1: 0, n1: 0, o1: 0, p1: 0, q1: 0, r1: 0, s1: 0, \
t1: 0))
#define _VA_CHECK_TYPE_ANY48(v, a0, b0, c0, d0, e0, f0, g0, h0, i0, j0, k0, l0, m0, n0, o0, p0, q0, r0, s0, t0, u0, \
v0, w0, x0, y0, z0, a1, b1, c1, d1, e1, f1, g1, h1, i1, j1, k1, l1, m1, n1, o1, p1, q1, r1, s1, t1, u1) \
((void)_Generic((v), a0: 0, b0: 0, c0: 0, d0: 0, e0: 0, f0: 0, g0: 0, h0: 0, i0: 0, j0: 0, k0: 0, l0: 0, m0: 0, \
n0: 0, o0: 0, p0: 0, q0: 0, r0: 0, s0: 0, t0: 0, u0: 0, v0: 0, w0: 0, x0: 0, y0: 0, z0: 0, a1: 0, b1: 0, c1: 0, \
d1: 0, e1: 0, f1: 0, g1: 0, h1: 0, i1: 0, j1: 0, k1: 0, l1: 0, m1: 0, n1: 0, o1: 0, p1: 0, q1: 0, r1: 0, s1: 0, \
t1: 0, u1: 0))
#define _VA_CHECK_TYPE_ANY49(v, a0, b0, c0, d0, e0, f0, g0, h0, i0, j0, k0, l0, m0, n0, o0, p0, q0, r0, s0, t0, u0, \
v0, w0, x0, y0, z0, a1, b1, c1, d1, e1, f1, g1, h1, i1, j1, k1, l1, m1, n1, o1, p1, q1, r1, s1, t1, u1, v1) \
((void)_Generic((v), a0: 0, b0: 0, c0: 0, d0: 0, e0: 0, f0: 0, g0: 0, h0: 0, i0: 0, j0: 0, k0: 0, l0: 0, m0: 0, \
n0: 0, o0: 0, p0: 0, q0: 0, r0: 0, s0: 0, t0: 0, u0: 0, v0: 0, w0: 0, x0: 0, y0: 0, z0: 0, a1: 0, b1: 0, c1: 0, \
d1: 0, e1: 0, f1: 0, g1: 0, h1: 0, i1: 0, j1: 0, k1: 0, l1: 0, m1: 0, n1: 0, o1: 0, p1: 0, q1: 0, r1: 0, s1: 0, \
t1: 0, u1: 0, v1: 0))
#define _VA_CHECK_TYPE_ANY50(v, a0, b0, c0, d0, e0, f0, g0, h0, i0, j0, k0, l0, m0, n0, o0, p0, q0, r0, s0, t0, u0, \
v0, w0, x0, y0, z0, a1, b1, c1, d1, e1, f1, g1, h1, i1, j1, k1, l1, m1, n1, o1, p1, q1, r1, s1, t1, u1, v1, w1) \
((void)_Generic((v), a0: 0, b0: 0, c0: 0, d0: 0, e0: 0, f0: 0, g0: 0, h0: 0, i0: 0, j0: 0, k0: 0, l0: 0, m0: 0, \
n0: 0, o0: 0, p0: 0, q0: 0, r0: 0, s0: 0, t0: 0, u0: 0, v0: 0, w0: 0, x0: 0, y0: 0, z0: 0, a1: 0, b1: 0, c1: 0, \
d1: 0, e1: 0, f1: 0, g1: 0, h1: 0, i1: 0, j1: 0, k1: 0, l1: 0, m1: 0, n1: 0, o1: 0, p1: 0, q1: 0, r1: 0, s1: 0, \
t1: 0, u1: 0, v1: 0, w1: 0))
#define _VA_CHECK_TYPE_ANY51(v, a0, b0, c0, d0, e0, f0, g0, h0, i0, j0, k0, l0, m0, n0, o0, p0, q0, r0, s0, t0, u0, \
v0, w0, x0, y0, z0, a1, b1, c1, d1, e1, f1, g1, h1, i1, j1, k1, l1, m1, n1, o1, p1, q1, r1, s1, t1, u1, v1, w1, \
x1) \
((void)_Generic((v), a0: 0, b0: 0, c0: 0, d0: 0, e0: 0, f0: 0, g0: 0, h0: 0, i0: 0, j0: 0, k0: 0, l0: 0, m0: 0, \
n0: 0, o0: 0, p0: 0, q0: 0, r0: 0, s0: 0, t0: 0, u0: 0, v0: 0, w0: 0, x0: 0, y0: 0, z0: 0, a1: 0, b1: 0, c1: 0, \
d1: 0, e1: 0, f1: 0, g1: 0, h1: 0, i1: 0, j1: 0, k1: 0, l1: 0, m1: 0, n1: 0, o1: 0, p1: 0, q1: 0, r1: 0, s1: 0, \
t1: 0, u1: 0, v1: 0, w1: 0, x1: 0))
#define _VA_CHECK_TYPE_ANY52(v, a0, b0, c0, d0, e0, f0, g0, h0, i0, j0, k0, l0, m0, n0, o0, p0, q0, r0, s0, t0, u0, \
v0, w0, x0, y0, z0, a1, b1, c1, d1, e1, f1, g1, h1, i1, j1, k1, l1, m1, n1, o1, p1, q1, r1, s1, t1, u1, v1, w1, \
x1, y1) \
((void)_Generic((v), a0: 0, b0: 0, c0: 0, d0: 0, e0: 0, f0: 0, g0: 0, h0: 0, i0: 0, j0: 0, k0: 0, l0: 0, m0: 0, \
n0: 0, o0: 0, p0: 0, q0: 0, r0: 0, s0: 0, t0: 0, u0: 0, v0: 0, w0: 0, x0: 0, y0: 0, z0: 0, a1: 0, b1: 0, c1: 0, \
d1: 0, e1: 0, f1: 0, g1: 0, h1: 0, i1: 0, j1: 0, k1: 0, l1: 0, m1: 0, n1: 0, o1: 0, p1: 0, q1: 0, r1: 0, s1: 0, \
t1: 0, u1: 0, v1: 0, w1: 0, x1: 0, y1: 0))
#define _VA_CHECK_TYPE_ANY53(v, a0, b0, c0, d0, e0, f0, g0, h0, i0, j0, k0, l0, m0, n0, o0, p0, q0, r0, s0, t0, u0, \
v0, w0, x0, y0, z0, a1, b1, c1, d1, e1, f1, g1, h1, i1, j1, k1, l1, m1, n1, o1, p1, q1, r1, s1, t1, u1, v1, w1, \
x1, y1, z1) \
((void)_Generic((v), a0: 0, b0: 0, c0: 0, d0: 0, e0: 0, f0: 0, g0: 0, h0: 0, i0: 0, j0: 0, k0: 0, l0: 0, m0: 0, \
n0: 0, o0: 0, p0: 0, q0: 0, r0: 0, s0: 0, t0: 0, u0: 0, v0: 0, w0: 0, x0: 0, y0: 0, z0: 0, a1: 0, b1: 0, c1: 0, \
d1: 0, e1: 0, f1: 0, g1: 0, h1: 0, i1: 0, j1: 0, k1: 0, l1: 0, m1: 0, n1: 0, o1: 0, p1: 0, q1: 0, r1: 0, s1: 0, \
t1: 0, u1: 0, v1: 0, w1: 0, x1: 0, y1: 0, z1: 0))
#define _VA_CHECK_TYPE_ANY54(v, a0, b0, c0, d0, e0, f0, g0, h0, i0, j0, k0, l0, m0, n0, o0, p0, q0, r0, s0, t0, u0, \
v0, w0, x0, y0, z0, a1, b1, c1, d1, e1, f1, g1, h1, i1, j1, k1, l1, m1, n1, o1, p1, q1, r1, s1, t1, u1, v1, w1, \
x1, y1, z1, a2) \
((void)_Generic((v), a0: 0, b0: 0, c0: 0, d0: 0, e0: 0, f0: 0, g0: 0, h0: 0, i0: 0, j0: 0, k0: 0, l0: 0, m0: 0, \
n0: 0, o0: 0, p0: 0, q0: 0, r0: 0, s0: 0, t0: 0, u0: 0, v0: 0, w0: 0, x0: 0, y0: 0, z0: 0, a1: 0, b1: 0, c1: 0, \
d1: 0, e1: 0, f1: 0, g1: 0, h1: 0, i1: 0, j1: 0, k1: 0, l1: 0, m1: 0, n1: 0, o1: 0, p1: 0, q1: 0, r1: 0, s1: 0, \
t1: 0, u1: 0, v1: 0, w1: 0, x1: 0, y1: 0, z1: 0, a2: 0))
#define _VA_CHECK_TYPE_ANY55(v, a0, b0, c0, d0, e0, f0, g0, h0, i0, j0, k0, l0, m0, n0, o0, p0, q0, r0, s0, t0, u0, \
v0, w0, x0, y0, z0, a1, b1, c1, d1, e1, f1, g1, h1, i1, j1, k1, l1, m1, n1, o1, p1, q1, r1, s1, t1, u1, v1, w1, \
x1, y1, z1, a2, b2) \
((void)_Generic((v), a0: 0, b0: 0, c0: 0, d0: 0, e0: 0, f0: 0, g0: 0, h0: 0, i0: 0, j0: 0, k0: 0, l0: 0, m0: 0, \
n0: 0, o0: 0, p0: 0, q0: 0, r0: 0, s0: 0, t0: 0, u0: 0, v0: 0, w0: 0, x0: 0, y0: 0, z0: 0, a1: 0, b1: 0, c1: 0, \
d1: 0, e1: 0, f1: 0, g1: 0, h1: 0, i1: 0, j1: 0, k1: 0, l1: 0, m1: 0, n1: 0, o1: 0, p1: 0, q1: 0, r1: 0, s1: 0, \
t1: 0, u1: 0, v1: 0, w1: 0, x1: 0, y1: 0, z1: 0, a2: 0, b2: 0))
#define _VA_CHECK_TYPE_ANY56(v, a0, b0, c0, d0, e0, f0, g0, h0, i0, j0, k0, l0, m0, n0, o0, p0, q0, r0, s0, t0, u0, \
v0, w0, x0, y0, z0, a1, b1, c1, d1, e1, f1, g1, h1, i1, j1, k1, l1, m1, n1, o1, p1, q1, r1, s1, t1, u1, v1, w1, \
x1, y1, z1, a2, b2, c2) \
((void)_Generic((v), a0: 0, b0: 0, c0: 0, d0: 0, e0: 0, f0: 0, g0: 0, h0: 0, i0: 0, j0: 0, k0: 0, l0: 0, m0: 0, \
n0: 0, o0: 0, p0: 0, q0: 0, r0: 0, s0: 0, t0: 0, u0: 0, v0: 0, w0: 0, x0: 0, y0: 0, z0: 0, a1: 0, b1: 0, c1: 0, \
d1: 0, e1: 0, f1: 0, g1: 0, h1: 0, i1: 0, j1: 0, k1: 0, l1: 0, m1: 0, n1: 0, o1: 0, p1: 0, q1: 0, r1: 0, s1: 0, \
t1: 0, u1: 0, v1: 0, w1: 0, x1: 0, y1: 0, z1: 0, a2: 0, b2: 0, c2: 0))
#define _VA_CHECK_TYPE_ANY57(v, a0, b0, c0, d0, e0, f0, g0, h0, i0, j0, k0, l0, m0, n0, o0, p0, q0, r0, s0, t0, u0, \
v0, w0, x0, y0, z0, a1, b1, c1, d1, e1, f1, g1, h1, i1, j1, k1, l1, m1, n1, o1, p1, q1, r1, s1, t1, u1, v1, w1, \
x1, y1, z1, a2, b2, c2, d2) \
((void)_Generic((v), a0: 0, b0: 0, c0: 0, d0: 0, e0: 0, f0: 0, g0: 0, h0: 0, i0: 0, j0: 0, k0: 0, l0: 0, m0: 0, \
n0: 0, o0: 0, p0: 0, q0: 0, r0: 0, s0: 0, t0: 0, u0: 0, v0: 0, w0: 0, x0: 0, y0: 0, z0: 0, a1: 0, b1: 0, c1: 0, \
d1: 0, e1: 0, f1: 0, g1: 0, h1: 0, i1: 0, j1: 0, k1: 0, l1: 0, m1: 0, n1: 0, o1: 0, p1: 0, q1: 0, r1: 0, s1: 0, \
t1: 0, u1: 0, v1: 0, w1: 0, x1: 0, y1: 0, z1: 0, a2: 0, b2: 0, c2: 0, d2: 0))
#define _VA_CHECK_TYPE_ANY58(v, a0, b0, c0, d0, e0, f0, g0, h0, i0, j0, k0, l0, m0, n0, o0, p0, q0, r0, s0, t0, u0, \
v0, w0, x0, y0, z0, a1, b1, c1, d1, e1, f1, g1, h1, i1, j1, k1, l1, m1, n1, o1, p1, q1, r1, s1, t1, u1, v1, w1, \
x1, y1, z1, a2, b2, c2, d2, e2) \
((void)_Generic((v), a0: 0, b0: 0, c0: 0, d0: 0, e0: 0, f0: 0, g0: 0, h0: 0, i0: 0, j0: 0, k0: 0, l0: 0, m0: 0, \
n0: 0, o0: 0, p0: 0, q0: 0, r0: 0, s0: 0, t0: 0, u0: 0, v0: 0, w0: 0, x0: 0, y0: 0, z0: 0, a1: 0, b1: 0, c1: 0, \
d1: 0, e1: 0, f1: 0, g1: 0, h1: 0, i1: 0, j1: 0, k1: 0, l1: 0, m1: 0, n1: 0, o1: 0, p1: 0, q1: 0, r1: 0, s1: 0, \
t1: 0, u1: 0, v1: 0, w1: 0, x1: 0, y1: 0, z1: 0, a2: 0, b2: 0, c2: 0, d2: 0, e2: 0))
#define _VA_CHECK_TYPE_ANY59(v, a0, b0, c0, d0, e0, f0, g0, h0, i0, j0, k0, l0, m0, n0, o0, p0, q0, r0, s0, t0, u0, \
v0, w0, x0, y0, z0, a1, b1, c1, d1, e1, f1, g1, h1, i1, j1, k1, l1, m1, n1, o1, p1, q1, r1, s1, t1, u1, v1, w1, \
x1, y1, z1, a2, b2, c2, d2, e2, f2) \
((void)_Generic((v), a0: 0, b0: 0, c0: 0, d0: 0, e0: 0, f0: 0, g0: 0, h0: 0, i0: 0, j0: 0, k0: 0, l0: 0, m0: 0, \
n0: 0, o0: 0, p0: 0, q0: 0, r0: 0, s0: 0, t0: 0, u0: 0, v0: 0, w0: 0, x0: 0, y0: 0, z0: 0, a1: 0, b1: 0, c1: 0, \
d1: 0, e1: 0, f1: 0, g1: 0, h1: 0, i1: 0, j1: 0, k1: 0, l1: 0, m1: 0, n1: 0, o1: 0, p1: 0, q1: 0, r1: 0, s1: 0, \
t1: 0, u1: 0, v1: 0, w1: 0, x1: 0, y1: 0, z1: 0, a2: 0, b2: 0, c2: 0, d2: 0, e2: 0, f2: 0))
#define _VA_CHECK_TYPE_ANY60(v, a0, b0, c0, d0, e0, f0, g0, h0, i0, j0, k0, l0, m0, n0, o0, p0, q0, r0, s0, t0, u0, \
v0, w0, x0, y0, z0, a1, b1, c1, d1, e1, f1, g1, h1, i1, j1, k1, l1, m1, n1, o1, p1, q1, r1, s1, t1, u1, v1, w1, \
x1, y1, z1, a2, b2, c2, d2, e2, f2, g2) \
((void)_Generic((v), a0: 0, b0: 0, c0: 0, d0: 0, e0: 0, f0: 0, g0: 0, h0: 0, i0: 0, j0: 0, k0: 0, l0: 0, m0: 0, \
n0: 0, o0: 0, p0: 0, q0: 0, r0: 0, s0: 0, t0: 0, u0: 0, v0: 0, w0: 0, x0: 0, y0: 0, z0: 0, a1: 0, b1: 0, c1: 0, \
d1: 0, e1: 0, f1: 0, g1: 0, h1: 0, i1: 0, j1: 0, k1: 0, l1: 0, m1: 0, n1: 0, o1: 0, p1: 0, q1: 0, r1: 0, s1: 0, \
t1: 0, u1: 0, v1: 0, w1: 0, x1: 0, y1: 0, z1: 0, a2: 0, b2: 0, c2: 0, d2: 0, e2: 0, f2: 0, g2: 0))
#define _VA_CHECK_TYPE_ANY61(v, a0, b0, c0, d0, e0, f0, g0, h0, i0, j0, k0, l0, m0, n0, o0, p0, q0, r0, s0, t0, u0, \
v0, w0, x0, y0, z0, a1, b1, c1, d1, e1, f1, g1, h1, i1, j1, k1, l1, m1, n1, o1, p1, q1, r1, s1, t1, u1, v1, w1, \
x1, y1, z1, a2, b2, c2, d2, e2, f2, g2, h2) \
((void)_Generic((v), a0: 0, b0: 0, c0: 0, d0: 0, e0: 0, f0: 0, g0: 0, h0: 0, i0: 0, j0: 0, k0: 0, l0: 0, m0: 0, \
n0: 0, o0: 0, p0: 0, q0: 0, r0: 0, s0: 0, t0: 0, u0: 0, v0: 0, w0: 0, x0: 0, y0: 0, z0: 0, a1: 0, b1: 0, c1: 0, \
d1: 0, e1: 0, f1: 0, g1: 0, h1: 0, i1: 0, j1: 0, k1: 0, l1: 0, m1: 0, n1: 0, o1: 0, p1: 0, q1: 0, r1: 0, s1: 0, \
t1: 0, u1: 0, v1: 0, w1: 0, x1: 0, y1: 0, z1: 0, a2: 0, b2: 0, c2: 0, d2: 0, e2: 0, f2: 0, g2: 0, h2: 0))
#define _VA_CHECK_TYPE_ANY62(v, a0, b0, c0, d0, e0, f0, g0, h0, i0, j0, k0, l0, m0, n0, o0, p0, q0, r0, s0, t0, u0, \
v0, w0, x0, y0, z0, a1, b1, c1, d1, e1, f1, g1, h1, i1, j1, k1, l1, m1, n1, o1, p1, q1, r1, s1, t1, u1, v1, w1, \
x1, y1, z1, a2, b2, c2, d2, e2, f2, g2, h2, i2) \
((void)_Generic((v), a0: 0, b0: 0, c0: 0, d0: 0, e0: 0, f0: 0, g0: 0, h0: 0, i0: 0, j0: 0, k0: 0, l0: 0, m0: 0, \
n0: 0, o0: 0, p0: 0, q0: 0, r0: 0, s0: 0, t0: 0, u0: 0, v0: 0, w0: 0, x0: 0, y0: 0, z0: 0, a1: 0, b1: 0, c1: 0, \
d1: 0, e1: 0, f1: 0, g1: 0, h1: 0, i1: 0, j1: 0, k1: 0, l1: 0, m1: 0, n1: 0, o1: 0, p1: 0, q1: 0, r1: 0, s1: 0, \
t1: 0, u1: 0, v1: 0, w1: 0, x1: 0, y1: 0, z1: 0, a2: 0, b2: 0, c2: 0, d2: 0, e2: 0, f2: 0, g2: 0, h2: 0, i2: 0))
#define _VA_CHECK_TYPE_ANY63(v, a0, b0, c0, d0, e0, f0, g0, h0, i0, j0, k0, l0, m0, n0, o0, p0, q0, r0, s0, t0, u0, \
v0, w0, x0, y0, z0, a1, b1, c1, d1, e1, f1, g1, h1, i1, j1, k1, l1, m1, n1, o1, p1, q1, r1, s1, t1, u1, v1, w1, \
x1, y1, z1, a2, b2, c2, d2, e2, f2, g2, h2, i2, j2) \
((void)_Generic((v), a0: 0, b0: 0, c0: 0, d0: 0, e0: 0, f0: 0, g0: 0, h0: 0, i0: 0, j0: 0, k0: 0, l0: 0, m0: 0, \
n0: 0, o0: 0, p0: 0, q0: 0, r0: 0, s0: 0, t0: 0, u0: 0, v0: 0, w0: 0, x0: 0, y0: 0, z0: 0, a1: 0, b1: 0, c1: 0, \
d1: 0, e1: 0, f1: 0, g1: 0, h1: 0, i1: 0, j1: 0, k1: 0, l1: 0, m1: 0, n1: 0, o1: 0, p1: 0, q1: 0, r1: 0, s1: 0, \
t1: 0, u1: 0, v1: 0, w1: 0, x1: 0, y1: 0, z1: 0, a2: 0, b2: 0, c2: 0, d2: 0, e2: 0, f2: 0, g2: 0, h2: 0, i2: 0, \
j2: 0))
#define _VA_CHECK_TYPE_ANY64(v, a0, b0, c0, d0, e0, f0, g0, h0, i0, j0, k0, l0, m0, n0, o0, p0, q0, r0, s0, t0, u0, \
v0, w0, x0, y0, z0, a1, b1, c1, d1, e1, f1, g1, h1, i1, j1, k1, l1, m1, n1, o1, p1, q1, r1, s1, t1, u1, v1, w1, \
x1, y1, z1, a2, b2, c2, d2, e2, f2, g2, h2, i2, j2, k2) \
((void)_Generic((v), a0: 0, b0: 0, c0: 0, d0: 0, e0: 0, f0: 0, g0: 0, h0: 0, i0: 0, j0: 0, k0: 0, l0: 0, m0: 0, \
n0: 0, o0: 0, p0: 0, q0: 0, r0: 0, s0: 0, t0: 0, u0: 0, v0: 0, w0: 0, x0: 0, y0: 0, z0: 0, a1: 0, b1: 0, c1: 0, \
d1: 0, e1: 0, f1: 0, g1: 0, h1: 0, i1: 0, j1: 0, k1: 0, l1: 0, m1: 0, n1: 0, o1: 0, p1: 0, q1: 0, r1: 0, s1: 0, \
t1: 0, u1: 0, v1: 0, w1: 0, x1: 0, y1: 0, z1: 0, a2: 0, b2: 0, c2: 0, d2: 0, e2: 0, f2: 0, g2: 0, h2: 0, i2: 0, \
j2: 0, k2: 0))
# define CHECK_TYPE_ANY(...) VA_NARGS_CALL_OVERLOAD(_VA_CHECK_TYPE_ANY, __VA_ARGS__)
/* clang-format on */
#else
# define CHECK_TYPE_ANY(...) (void)0
#endif
/**
* GENERIC_TYPE_ANY: handy macro to reuse a single expression for multiple types, eg:
*
* \code{.c}
* _Generic(value,
* GENERIC_TYPE_ANY(result_a, Foo *, Bar *, Baz *),
* GENERIC_TYPE_ANY(result_b, Spam *, Spaz *, Spot *),
* )
* \endcode
*
* excuse ridiculously long generated args.
* \code{.py}
* for i in range(63):
* args = [(chr(ord('a') + (c % 26)) + (chr(ord('0') + (c // 26)))) for c in range(i + 1)]
* print("#define _VA_GENERIC_TYPE_ANY%d(r, %s) \\" % (i + 2, ", ".join(args)))
* print(" %s: r " % (": r, ".join(args)))
* \endcode
*/
/* Over wrapped args. */
/* clang-format off */
#define _VA_GENERIC_TYPE_ANY2(r, a0) \
a0: r
#define _VA_GENERIC_TYPE_ANY3(r, a0, b0) \
a0: r, b0: r
#define _VA_GENERIC_TYPE_ANY4(r, a0, b0, c0) \
a0: r, b0: r, c0: r
#define _VA_GENERIC_TYPE_ANY5(r, a0, b0, c0, d0) \
a0: r, b0: r, c0: r, d0: r
#define _VA_GENERIC_TYPE_ANY6(r, a0, b0, c0, d0, e0) \
a0: r, b0: r, c0: r, d0: r, e0: r
#define _VA_GENERIC_TYPE_ANY7(r, a0, b0, c0, d0, e0, f0) \
a0: r, b0: r, c0: r, d0: r, e0: r, f0: r
#define _VA_GENERIC_TYPE_ANY8(r, a0, b0, c0, d0, e0, f0, g0) \
a0: r, b0: r, c0: r, d0: r, e0: r, f0: r, g0: r
#define _VA_GENERIC_TYPE_ANY9(r, a0, b0, c0, d0, e0, f0, g0, h0) \
a0: r, b0: r, c0: r, d0: r, e0: r, f0: r, g0: r, h0: r
#define _VA_GENERIC_TYPE_ANY10(r, a0, b0, c0, d0, e0, f0, g0, h0, i0) \
a0: r, b0: r, c0: r, d0: r, e0: r, f0: r, g0: r, h0: r, i0: r
#define _VA_GENERIC_TYPE_ANY11(r, a0, b0, c0, d0, e0, f0, g0, h0, i0, j0) \
a0: r, b0: r, c0: r, d0: r, e0: r, f0: r, g0: r, h0: r, i0: r, j0: r
#define _VA_GENERIC_TYPE_ANY12(r, a0, b0, c0, d0, e0, f0, g0, h0, i0, j0, k0) \
a0: r, b0: r, c0: r, d0: r, e0: r, f0: r, g0: r, h0: r, i0: r, j0: r, k0: r
#define _VA_GENERIC_TYPE_ANY13(r, a0, b0, c0, d0, e0, f0, g0, h0, i0, j0, k0, l0) \
a0: r, b0: r, c0: r, d0: r, e0: r, f0: r, g0: r, h0: r, i0: r, j0: r, k0: r, l0: r
#define _VA_GENERIC_TYPE_ANY14(r, a0, b0, c0, d0, e0, f0, g0, h0, i0, j0, k0, l0, m0) \
a0: r, b0: r, c0: r, d0: r, e0: r, f0: r, g0: r, h0: r, i0: r, j0: r, k0: r, l0: r, m0: r
#define _VA_GENERIC_TYPE_ANY15(r, a0, b0, c0, d0, e0, f0, g0, h0, i0, j0, k0, l0, m0, n0) \
a0: r, b0: r, c0: r, d0: r, e0: r, f0: r, g0: r, h0: r, i0: r, j0: r, k0: r, l0: r, m0: r, n0: r
#define _VA_GENERIC_TYPE_ANY16(r, a0, b0, c0, d0, e0, f0, g0, h0, i0, j0, k0, l0, m0, n0, o0) \
a0: r, b0: r, c0: r, d0: r, e0: r, f0: r, g0: r, h0: r, i0: r, j0: r, k0: r, l0: r, m0: r, n0: r, o0: r
#define _VA_GENERIC_TYPE_ANY17(r, a0, b0, c0, d0, e0, f0, g0, h0, i0, j0, k0, l0, m0, n0, o0, p0) \
a0: r, b0: r, c0: r, d0: r, e0: r, f0: r, g0: r, h0: r, i0: r, j0: r, k0: r, l0: r, m0: r, n0: r, o0: r, p0: r
#define _VA_GENERIC_TYPE_ANY18(r, a0, b0, c0, d0, e0, f0, g0, h0, i0, j0, k0, l0, m0, n0, o0, p0, q0) \
a0: r, b0: r, c0: r, d0: r, e0: r, f0: r, g0: r, h0: r, i0: r, j0: r, k0: r, l0: r, m0: r, n0: r, o0: r, p0: r, \
q0: r
#define _VA_GENERIC_TYPE_ANY19(r, a0, b0, c0, d0, e0, f0, g0, h0, i0, j0, k0, l0, m0, n0, o0, p0, q0, r0) \
a0: r, b0: r, c0: r, d0: r, e0: r, f0: r, g0: r, h0: r, i0: r, j0: r, k0: r, l0: r, m0: r, n0: r, o0: r, p0: r, \
q0: r, r0: r
#define _VA_GENERIC_TYPE_ANY20(r, a0, b0, c0, d0, e0, f0, g0, h0, i0, j0, k0, l0, m0, n0, o0, p0, q0, r0, s0) \
a0: r, b0: r, c0: r, d0: r, e0: r, f0: r, g0: r, h0: r, i0: r, j0: r, k0: r, l0: r, m0: r, n0: r, o0: r, p0: r, \
q0: r, r0: r, s0: r
#define _VA_GENERIC_TYPE_ANY21(r, a0, b0, c0, d0, e0, f0, g0, h0, i0, j0, k0, l0, m0, n0, o0, p0, q0, r0, s0, t0) \
a0: r, b0: r, c0: r, d0: r, e0: r, f0: r, g0: r, h0: r, i0: r, j0: r, k0: r, l0: r, m0: r, n0: r, o0: r, p0: r, \
q0: r, r0: r, s0: r, t0: r
#define _VA_GENERIC_TYPE_ANY22(r, a0, b0, c0, d0, e0, f0, g0, h0, i0, j0, k0, l0, m0, n0, o0, p0, q0, r0, s0, t0, \
u0) \
a0: r, b0: r, c0: r, d0: r, e0: r, f0: r, g0: r, h0: r, i0: r, j0: r, k0: r, l0: r, m0: r, n0: r, o0: r, p0: r, \
q0: r, r0: r, s0: r, t0: r, u0: r
#define _VA_GENERIC_TYPE_ANY23(r, a0, b0, c0, d0, e0, f0, g0, h0, i0, j0, k0, l0, m0, n0, o0, p0, q0, r0, s0, t0, \
u0, v0) \
a0: r, b0: r, c0: r, d0: r, e0: r, f0: r, g0: r, h0: r, i0: r, j0: r, k0: r, l0: r, m0: r, n0: r, o0: r, p0: r, \
q0: r, r0: r, s0: r, t0: r, u0: r, v0: r
#define _VA_GENERIC_TYPE_ANY24(r, a0, b0, c0, d0, e0, f0, g0, h0, i0, j0, k0, l0, m0, n0, o0, p0, q0, r0, s0, t0, \
u0, v0, w0) \
a0: r, b0: r, c0: r, d0: r, e0: r, f0: r, g0: r, h0: r, i0: r, j0: r, k0: r, l0: r, m0: r, n0: r, o0: r, p0: r, \
q0: r, r0: r, s0: r, t0: r, u0: r, v0: r, w0: r
#define _VA_GENERIC_TYPE_ANY25(r, a0, b0, c0, d0, e0, f0, g0, h0, i0, j0, k0, l0, m0, n0, o0, p0, q0, r0, s0, t0, \
u0, v0, w0, x0) \
a0: r, b0: r, c0: r, d0: r, e0: r, f0: r, g0: r, h0: r, i0: r, j0: r, k0: r, l0: r, m0: r, n0: r, o0: r, p0: r, \
q0: r, r0: r, s0: r, t0: r, u0: r, v0: r, w0: r, x0: r
#define _VA_GENERIC_TYPE_ANY26(r, a0, b0, c0, d0, e0, f0, g0, h0, i0, j0, k0, l0, m0, n0, o0, p0, q0, r0, s0, t0, \
u0, v0, w0, x0, y0) \
a0: r, b0: r, c0: r, d0: r, e0: r, f0: r, g0: r, h0: r, i0: r, j0: r, k0: r, l0: r, m0: r, n0: r, o0: r, p0: r, \
q0: r, r0: r, s0: r, t0: r, u0: r, v0: r, w0: r, x0: r, y0: r
#define _VA_GENERIC_TYPE_ANY27(r, a0, b0, c0, d0, e0, f0, g0, h0, i0, j0, k0, l0, m0, n0, o0, p0, q0, r0, s0, t0, \
u0, v0, w0, x0, y0, z0) \
a0: r, b0: r, c0: r, d0: r, e0: r, f0: r, g0: r, h0: r, i0: r, j0: r, k0: r, l0: r, m0: r, n0: r, o0: r, p0: r, \
q0: r, r0: r, s0: r, t0: r, u0: r, v0: r, w0: r, x0: r, y0: r, z0: r
#define _VA_GENERIC_TYPE_ANY28(r, a0, b0, c0, d0, e0, f0, g0, h0, i0, j0, k0, l0, m0, n0, o0, p0, q0, r0, s0, t0, \
u0, v0, w0, x0, y0, z0, a1) \
a0: r, b0: r, c0: r, d0: r, e0: r, f0: r, g0: r, h0: r, i0: r, j0: r, k0: r, l0: r, m0: r, n0: r, o0: r, p0: r, \
q0: r, r0: r, s0: r, t0: r, u0: r, v0: r, w0: r, x0: r, y0: r, z0: r, a1: r
#define _VA_GENERIC_TYPE_ANY29(r, a0, b0, c0, d0, e0, f0, g0, h0, i0, j0, k0, l0, m0, n0, o0, p0, q0, r0, s0, t0, \
u0, v0, w0, x0, y0, z0, a1, b1) \
a0: r, b0: r, c0: r, d0: r, e0: r, f0: r, g0: r, h0: r, i0: r, j0: r, k0: r, l0: r, m0: r, n0: r, o0: r, p0: r, \
q0: r, r0: r, s0: r, t0: r, u0: r, v0: r, w0: r, x0: r, y0: r, z0: r, a1: r, b1: r
#define _VA_GENERIC_TYPE_ANY30(r, a0, b0, c0, d0, e0, f0, g0, h0, i0, j0, k0, l0, m0, n0, o0, p0, q0, r0, s0, t0, \
u0, v0, w0, x0, y0, z0, a1, b1, c1) \
a0: r, b0: r, c0: r, d0: r, e0: r, f0: r, g0: r, h0: r, i0: r, j0: r, k0: r, l0: r, m0: r, n0: r, o0: r, p0: r, \
q0: r, r0: r, s0: r, t0: r, u0: r, v0: r, w0: r, x0: r, y0: r, z0: r, a1: r, b1: r, c1: r
#define _VA_GENERIC_TYPE_ANY31(r, a0, b0, c0, d0, e0, f0, g0, h0, i0, j0, k0, l0, m0, n0, o0, p0, q0, r0, s0, t0, \
u0, v0, w0, x0, y0, z0, a1, b1, c1, d1) \
a0: r, b0: r, c0: r, d0: r, e0: r, f0: r, g0: r, h0: r, i0: r, j0: r, k0: r, l0: r, m0: r, n0: r, o0: r, p0: r, \
q0: r, r0: r, s0: r, t0: r, u0: r, v0: r, w0: r, x0: r, y0: r, z0: r, a1: r, b1: r, c1: r, d1: r
#define _VA_GENERIC_TYPE_ANY32(r, a0, b0, c0, d0, e0, f0, g0, h0, i0, j0, k0, l0, m0, n0, o0, p0, q0, r0, s0, t0, \
u0, v0, w0, x0, y0, z0, a1, b1, c1, d1, e1) \
a0: r, b0: r, c0: r, d0: r, e0: r, f0: r, g0: r, h0: r, i0: r, j0: r, k0: r, l0: r, m0: r, n0: r, o0: r, p0: r, \
q0: r, r0: r, s0: r, t0: r, u0: r, v0: r, w0: r, x0: r, y0: r, z0: r, a1: r, b1: r, c1: r, d1: r, e1: r
#define _VA_GENERIC_TYPE_ANY33(r, a0, b0, c0, d0, e0, f0, g0, h0, i0, j0, k0, l0, m0, n0, o0, p0, q0, r0, s0, t0, \
u0, v0, w0, x0, y0, z0, a1, b1, c1, d1, e1, f1) \
a0: r, b0: r, c0: r, d0: r, e0: r, f0: r, g0: r, h0: r, i0: r, j0: r, k0: r, l0: r, m0: r, n0: r, o0: r, p0: r, \
q0: r, r0: r, s0: r, t0: r, u0: r, v0: r, w0: r, x0: r, y0: r, z0: r, a1: r, b1: r, c1: r, d1: r, e1: r, f1: r
#define _VA_GENERIC_TYPE_ANY34(r, a0, b0, c0, d0, e0, f0, g0, h0, i0, j0, k0, l0, m0, n0, o0, p0, q0, r0, s0, t0, \
u0, v0, w0, x0, y0, z0, a1, b1, c1, d1, e1, f1, g1) \
a0: r, b0: r, c0: r, d0: r, e0: r, f0: r, g0: r, h0: r, i0: r, j0: r, k0: r, l0: r, m0: r, n0: r, o0: r, p0: r, \
q0: r, r0: r, s0: r, t0: r, u0: r, v0: r, w0: r, x0: r, y0: r, z0: r, a1: r, b1: r, c1: r, d1: r, e1: r, f1: r, \
g1: r
#define _VA_GENERIC_TYPE_ANY35(r, a0, b0, c0, d0, e0, f0, g0, h0, i0, j0, k0, l0, m0, n0, o0, p0, q0, r0, s0, t0, \
u0, v0, w0, x0, y0, z0, a1, b1, c1, d1, e1, f1, g1, h1) \
a0: r, b0: r, c0: r, d0: r, e0: r, f0: r, g0: r, h0: r, i0: r, j0: r, k0: r, l0: r, m0: r, n0: r, o0: r, p0: r, \
q0: r, r0: r, s0: r, t0: r, u0: r, v0: r, w0: r, x0: r, y0: r, z0: r, a1: r, b1: r, c1: r, d1: r, e1: r, f1: r, \
g1: r, h1: r
#define _VA_GENERIC_TYPE_ANY36(r, a0, b0, c0, d0, e0, f0, g0, h0, i0, j0, k0, l0, m0, n0, o0, p0, q0, r0, s0, t0, \
u0, v0, w0, x0, y0, z0, a1, b1, c1, d1, e1, f1, g1, h1, i1) \
a0: r, b0: r, c0: r, d0: r, e0: r, f0: r, g0: r, h0: r, i0: r, j0: r, k0: r, l0: r, m0: r, n0: r, o0: r, p0: r, \
q0: r, r0: r, s0: r, t0: r, u0: r, v0: r, w0: r, x0: r, y0: r, z0: r, a1: r, b1: r, c1: r, d1: r, e1: r, f1: r, \
g1: r, h1: r, i1: r
#define _VA_GENERIC_TYPE_ANY37(r, a0, b0, c0, d0, e0, f0, g0, h0, i0, j0, k0, l0, m0, n0, o0, p0, q0, r0, s0, t0, \
u0, v0, w0, x0, y0, z0, a1, b1, c1, d1, e1, f1, g1, h1, i1, j1) \
a0: r, b0: r, c0: r, d0: r, e0: r, f0: r, g0: r, h0: r, i0: r, j0: r, k0: r, l0: r, m0: r, n0: r, o0: r, p0: r, \
q0: r, r0: r, s0: r, t0: r, u0: r, v0: r, w0: r, x0: r, y0: r, z0: r, a1: r, b1: r, c1: r, d1: r, e1: r, f1: r, \
g1: r, h1: r, i1: r, j1: r
#define _VA_GENERIC_TYPE_ANY38(r, a0, b0, c0, d0, e0, f0, g0, h0, i0, j0, k0, l0, m0, n0, o0, p0, q0, r0, s0, t0, \
u0, v0, w0, x0, y0, z0, a1, b1, c1, d1, e1, f1, g1, h1, i1, j1, k1) \
a0: r, b0: r, c0: r, d0: r, e0: r, f0: r, g0: r, h0: r, i0: r, j0: r, k0: r, l0: r, m0: r, n0: r, o0: r, p0: r, \
q0: r, r0: r, s0: r, t0: r, u0: r, v0: r, w0: r, x0: r, y0: r, z0: r, a1: r, b1: r, c1: r, d1: r, e1: r, f1: r, \
g1: r, h1: r, i1: r, j1: r, k1: r
#define _VA_GENERIC_TYPE_ANY39(r, a0, b0, c0, d0, e0, f0, g0, h0, i0, j0, k0, l0, m0, n0, o0, p0, q0, r0, s0, t0, \
u0, v0, w0, x0, y0, z0, a1, b1, c1, d1, e1, f1, g1, h1, i1, j1, k1, l1) \
a0: r, b0: r, c0: r, d0: r, e0: r, f0: r, g0: r, h0: r, i0: r, j0: r, k0: r, l0: r, m0: r, n0: r, o0: r, p0: r, \
q0: r, r0: r, s0: r, t0: r, u0: r, v0: r, w0: r, x0: r, y0: r, z0: r, a1: r, b1: r, c1: r, d1: r, e1: r, f1: r, \
g1: r, h1: r, i1: r, j1: r, k1: r, l1: r
#define _VA_GENERIC_TYPE_ANY40(r, a0, b0, c0, d0, e0, f0, g0, h0, i0, j0, k0, l0, m0, n0, o0, p0, q0, r0, s0, t0, \
u0, v0, w0, x0, y0, z0, a1, b1, c1, d1, e1, f1, g1, h1, i1, j1, k1, l1, m1) \
a0: r, b0: r, c0: r, d0: r, e0: r, f0: r, g0: r, h0: r, i0: r, j0: r, k0: r, l0: r, m0: r, n0: r, o0: r, p0: r, \
q0: r, r0: r, s0: r, t0: r, u0: r, v0: r, w0: r, x0: r, y0: r, z0: r, a1: r, b1: r, c1: r, d1: r, e1: r, f1: r, \
g1: r, h1: r, i1: r, j1: r, k1: r, l1: r, m1: r
#define _VA_GENERIC_TYPE_ANY41(r, a0, b0, c0, d0, e0, f0, g0, h0, i0, j0, k0, l0, m0, n0, o0, p0, q0, r0, s0, t0, \
u0, v0, w0, x0, y0, z0, a1, b1, c1, d1, e1, f1, g1, h1, i1, j1, k1, l1, m1, n1) \
a0: r, b0: r, c0: r, d0: r, e0: r, f0: r, g0: r, h0: r, i0: r, j0: r, k0: r, l0: r, m0: r, n0: r, o0: r, p0: r, \
q0: r, r0: r, s0: r, t0: r, u0: r, v0: r, w0: r, x0: r, y0: r, z0: r, a1: r, b1: r, c1: r, d1: r, e1: r, f1: r, \
g1: r, h1: r, i1: r, j1: r, k1: r, l1: r, m1: r, n1: r
#define _VA_GENERIC_TYPE_ANY42(r, a0, b0, c0, d0, e0, f0, g0, h0, i0, j0, k0, l0, m0, n0, o0, p0, q0, r0, s0, t0, \
u0, v0, w0, x0, y0, z0, a1, b1, c1, d1, e1, f1, g1, h1, i1, j1, k1, l1, m1, n1, o1) \
a0: r, b0: r, c0: r, d0: r, e0: r, f0: r, g0: r, h0: r, i0: r, j0: r, k0: r, l0: r, m0: r, n0: r, o0: r, p0: r, \
q0: r, r0: r, s0: r, t0: r, u0: r, v0: r, w0: r, x0: r, y0: r, z0: r, a1: r, b1: r, c1: r, d1: r, e1: r, f1: r, \
g1: r, h1: r, i1: r, j1: r, k1: r, l1: r, m1: r, n1: r, o1: r
#define _VA_GENERIC_TYPE_ANY43(r, a0, b0, c0, d0, e0, f0, g0, h0, i0, j0, k0, l0, m0, n0, o0, p0, q0, r0, s0, t0, \
u0, v0, w0, x0, y0, z0, a1, b1, c1, d1, e1, f1, g1, h1, i1, j1, k1, l1, m1, n1, o1, p1) \
a0: r, b0: r, c0: r, d0: r, e0: r, f0: r, g0: r, h0: r, i0: r, j0: r, k0: r, l0: r, m0: r, n0: r, o0: r, p0: r, \
q0: r, r0: r, s0: r, t0: r, u0: r, v0: r, w0: r, x0: r, y0: r, z0: r, a1: r, b1: r, c1: r, d1: r, e1: r, f1: r, \
g1: r, h1: r, i1: r, j1: r, k1: r, l1: r, m1: r, n1: r, o1: r, p1: r
#define _VA_GENERIC_TYPE_ANY44(r, a0, b0, c0, d0, e0, f0, g0, h0, i0, j0, k0, l0, m0, n0, o0, p0, q0, r0, s0, t0, \
u0, v0, w0, x0, y0, z0, a1, b1, c1, d1, e1, f1, g1, h1, i1, j1, k1, l1, m1, n1, o1, p1, q1) \
a0: r, b0: r, c0: r, d0: r, e0: r, f0: r, g0: r, h0: r, i0: r, j0: r, k0: r, l0: r, m0: r, n0: r, o0: r, p0: r, \
q0: r, r0: r, s0: r, t0: r, u0: r, v0: r, w0: r, x0: r, y0: r, z0: r, a1: r, b1: r, c1: r, d1: r, e1: r, f1: r, \
g1: r, h1: r, i1: r, j1: r, k1: r, l1: r, m1: r, n1: r, o1: r, p1: r, q1: r
#define _VA_GENERIC_TYPE_ANY45(r, a0, b0, c0, d0, e0, f0, g0, h0, i0, j0, k0, l0, m0, n0, o0, p0, q0, r0, s0, t0, \
u0, v0, w0, x0, y0, z0, a1, b1, c1, d1, e1, f1, g1, h1, i1, j1, k1, l1, m1, n1, o1, p1, q1, r1) \
a0: r, b0: r, c0: r, d0: r, e0: r, f0: r, g0: r, h0: r, i0: r, j0: r, k0: r, l0: r, m0: r, n0: r, o0: r, p0: r, \
q0: r, r0: r, s0: r, t0: r, u0: r, v0: r, w0: r, x0: r, y0: r, z0: r, a1: r, b1: r, c1: r, d1: r, e1: r, f1: r, \
g1: r, h1: r, i1: r, j1: r, k1: r, l1: r, m1: r, n1: r, o1: r, p1: r, q1: r, r1: r
#define _VA_GENERIC_TYPE_ANY46(r, a0, b0, c0, d0, e0, f0, g0, h0, i0, j0, k0, l0, m0, n0, o0, p0, q0, r0, s0, t0, \
u0, v0, w0, x0, y0, z0, a1, b1, c1, d1, e1, f1, g1, h1, i1, j1, k1, l1, m1, n1, o1, p1, q1, r1, s1) \
a0: r, b0: r, c0: r, d0: r, e0: r, f0: r, g0: r, h0: r, i0: r, j0: r, k0: r, l0: r, m0: r, n0: r, o0: r, p0: r, \
q0: r, r0: r, s0: r, t0: r, u0: r, v0: r, w0: r, x0: r, y0: r, z0: r, a1: r, b1: r, c1: r, d1: r, e1: r, f1: r, \
g1: r, h1: r, i1: r, j1: r, k1: r, l1: r, m1: r, n1: r, o1: r, p1: r, q1: r, r1: r, s1: r
#define _VA_GENERIC_TYPE_ANY47(r, a0, b0, c0, d0, e0, f0, g0, h0, i0, j0, k0, l0, m0, n0, o0, p0, q0, r0, s0, t0, \
u0, v0, w0, x0, y0, z0, a1, b1, c1, d1, e1, f1, g1, h1, i1, j1, k1, l1, m1, n1, o1, p1, q1, r1, s1, t1) \
a0: r, b0: r, c0: r, d0: r, e0: r, f0: r, g0: r, h0: r, i0: r, j0: r, k0: r, l0: r, m0: r, n0: r, o0: r, p0: r, \
q0: r, r0: r, s0: r, t0: r, u0: r, v0: r, w0: r, x0: r, y0: r, z0: r, a1: r, b1: r, c1: r, d1: r, e1: r, f1: r, \
g1: r, h1: r, i1: r, j1: r, k1: r, l1: r, m1: r, n1: r, o1: r, p1: r, q1: r, r1: r, s1: r, t1: r
#define _VA_GENERIC_TYPE_ANY48(r, a0, b0, c0, d0, e0, f0, g0, h0, i0, j0, k0, l0, m0, n0, o0, p0, q0, r0, s0, t0, \
u0, v0, w0, x0, y0, z0, a1, b1, c1, d1, e1, f1, g1, h1, i1, j1, k1, l1, m1, n1, o1, p1, q1, r1, s1, t1, u1) \
a0: r, b0: r, c0: r, d0: r, e0: r, f0: r, g0: r, h0: r, i0: r, j0: r, k0: r, l0: r, m0: r, n0: r, o0: r, p0: r, \
q0: r, r0: r, s0: r, t0: r, u0: r, v0: r, w0: r, x0: r, y0: r, z0: r, a1: r, b1: r, c1: r, d1: r, e1: r, f1: r, \
g1: r, h1: r, i1: r, j1: r, k1: r, l1: r, m1: r, n1: r, o1: r, p1: r, q1: r, r1: r, s1: r, t1: r, u1: r
#define _VA_GENERIC_TYPE_ANY49(r, a0, b0, c0, d0, e0, f0, g0, h0, i0, j0, k0, l0, m0, n0, o0, p0, q0, r0, s0, t0, \
u0, v0, w0, x0, y0, z0, a1, b1, c1, d1, e1, f1, g1, h1, i1, j1, k1, l1, m1, n1, o1, p1, q1, r1, s1, t1, u1, v1) \
a0: r, b0: r, c0: r, d0: r, e0: r, f0: r, g0: r, h0: r, i0: r, j0: r, k0: r, l0: r, m0: r, n0: r, o0: r, p0: r, \
q0: r, r0: r, s0: r, t0: r, u0: r, v0: r, w0: r, x0: r, y0: r, z0: r, a1: r, b1: r, c1: r, d1: r, e1: r, f1: r, \
g1: r, h1: r, i1: r, j1: r, k1: r, l1: r, m1: r, n1: r, o1: r, p1: r, q1: r, r1: r, s1: r, t1: r, u1: r, v1: r
#define _VA_GENERIC_TYPE_ANY50(r, a0, b0, c0, d0, e0, f0, g0, h0, i0, j0, k0, l0, m0, n0, o0, p0, q0, r0, s0, t0, \
u0, v0, w0, x0, y0, z0, a1, b1, c1, d1, e1, f1, g1, h1, i1, j1, k1, l1, m1, n1, o1, p1, q1, r1, s1, t1, u1, v1, \
w1) \
a0: r, b0: r, c0: r, d0: r, e0: r, f0: r, g0: r, h0: r, i0: r, j0: r, k0: r, l0: r, m0: r, n0: r, o0: r, p0: r, \
q0: r, r0: r, s0: r, t0: r, u0: r, v0: r, w0: r, x0: r, y0: r, z0: r, a1: r, b1: r, c1: r, d1: r, e1: r, f1: r, \
g1: r, h1: r, i1: r, j1: r, k1: r, l1: r, m1: r, n1: r, o1: r, p1: r, q1: r, r1: r, s1: r, t1: r, u1: r, v1: r, \
w1: r
#define _VA_GENERIC_TYPE_ANY51(r, a0, b0, c0, d0, e0, f0, g0, h0, i0, j0, k0, l0, m0, n0, o0, p0, q0, r0, s0, t0, \
u0, v0, w0, x0, y0, z0, a1, b1, c1, d1, e1, f1, g1, h1, i1, j1, k1, l1, m1, n1, o1, p1, q1, r1, s1, t1, u1, v1, \
w1, x1) \
a0: r, b0: r, c0: r, d0: r, e0: r, f0: r, g0: r, h0: r, i0: r, j0: r, k0: r, l0: r, m0: r, n0: r, o0: r, p0: r, \
q0: r, r0: r, s0: r, t0: r, u0: r, v0: r, w0: r, x0: r, y0: r, z0: r, a1: r, b1: r, c1: r, d1: r, e1: r, f1: r, \
g1: r, h1: r, i1: r, j1: r, k1: r, l1: r, m1: r, n1: r, o1: r, p1: r, q1: r, r1: r, s1: r, t1: r, u1: r, v1: r, \
w1: r, x1: r
#define _VA_GENERIC_TYPE_ANY52(r, a0, b0, c0, d0, e0, f0, g0, h0, i0, j0, k0, l0, m0, n0, o0, p0, q0, r0, s0, t0, \
u0, v0, w0, x0, y0, z0, a1, b1, c1, d1, e1, f1, g1, h1, i1, j1, k1, l1, m1, n1, o1, p1, q1, r1, s1, t1, u1, v1, \
w1, x1, y1) \
a0: r, b0: r, c0: r, d0: r, e0: r, f0: r, g0: r, h0: r, i0: r, j0: r, k0: r, l0: r, m0: r, n0: r, o0: r, p0: r, \
q0: r, r0: r, s0: r, t0: r, u0: r, v0: r, w0: r, x0: r, y0: r, z0: r, a1: r, b1: r, c1: r, d1: r, e1: r, f1: r, \
g1: r, h1: r, i1: r, j1: r, k1: r, l1: r, m1: r, n1: r, o1: r, p1: r, q1: r, r1: r, s1: r, t1: r, u1: r, v1: r, \
w1: r, x1: r, y1: r
#define _VA_GENERIC_TYPE_ANY53(r, a0, b0, c0, d0, e0, f0, g0, h0, i0, j0, k0, l0, m0, n0, o0, p0, q0, r0, s0, t0, \
u0, v0, w0, x0, y0, z0, a1, b1, c1, d1, e1, f1, g1, h1, i1, j1, k1, l1, m1, n1, o1, p1, q1, r1, s1, t1, u1, v1, \
w1, x1, y1, z1) \
a0: r, b0: r, c0: r, d0: r, e0: r, f0: r, g0: r, h0: r, i0: r, j0: r, k0: r, l0: r, m0: r, n0: r, o0: r, p0: r, \
q0: r, r0: r, s0: r, t0: r, u0: r, v0: r, w0: r, x0: r, y0: r, z0: r, a1: r, b1: r, c1: r, d1: r, e1: r, f1: r, \
g1: r, h1: r, i1: r, j1: r, k1: r, l1: r, m1: r, n1: r, o1: r, p1: r, q1: r, r1: r, s1: r, t1: r, u1: r, v1: r, \
w1: r, x1: r, y1: r, z1: r
#define _VA_GENERIC_TYPE_ANY54(r, a0, b0, c0, d0, e0, f0, g0, h0, i0, j0, k0, l0, m0, n0, o0, p0, q0, r0, s0, t0, \
u0, v0, w0, x0, y0, z0, a1, b1, c1, d1, e1, f1, g1, h1, i1, j1, k1, l1, m1, n1, o1, p1, q1, r1, s1, t1, u1, v1, \
w1, x1, y1, z1, a2) \
a0: r, b0: r, c0: r, d0: r, e0: r, f0: r, g0: r, h0: r, i0: r, j0: r, k0: r, l0: r, m0: r, n0: r, o0: r, p0: r, \
q0: r, r0: r, s0: r, t0: r, u0: r, v0: r, w0: r, x0: r, y0: r, z0: r, a1: r, b1: r, c1: r, d1: r, e1: r, f1: r, \
g1: r, h1: r, i1: r, j1: r, k1: r, l1: r, m1: r, n1: r, o1: r, p1: r, q1: r, r1: r, s1: r, t1: r, u1: r, v1: r, \
w1: r, x1: r, y1: r, z1: r, a2: r
#define _VA_GENERIC_TYPE_ANY55(r, a0, b0, c0, d0, e0, f0, g0, h0, i0, j0, k0, l0, m0, n0, o0, p0, q0, r0, s0, t0, \
u0, v0, w0, x0, y0, z0, a1, b1, c1, d1, e1, f1, g1, h1, i1, j1, k1, l1, m1, n1, o1, p1, q1, r1, s1, t1, u1, v1, \
w1, x1, y1, z1, a2, b2) \
a0: r, b0: r, c0: r, d0: r, e0: r, f0: r, g0: r, h0: r, i0: r, j0: r, k0: r, l0: r, m0: r, n0: r, o0: r, p0: r, \
q0: r, r0: r, s0: r, t0: r, u0: r, v0: r, w0: r, x0: r, y0: r, z0: r, a1: r, b1: r, c1: r, d1: r, e1: r, f1: r, \
g1: r, h1: r, i1: r, j1: r, k1: r, l1: r, m1: r, n1: r, o1: r, p1: r, q1: r, r1: r, s1: r, t1: r, u1: r, v1: r, \
w1: r, x1: r, y1: r, z1: r, a2: r, b2: r
#define _VA_GENERIC_TYPE_ANY56(r, a0, b0, c0, d0, e0, f0, g0, h0, i0, j0, k0, l0, m0, n0, o0, p0, q0, r0, s0, t0, \
u0, v0, w0, x0, y0, z0, a1, b1, c1, d1, e1, f1, g1, h1, i1, j1, k1, l1, m1, n1, o1, p1, q1, r1, s1, t1, u1, v1, \
w1, x1, y1, z1, a2, b2, c2) \
a0: r, b0: r, c0: r, d0: r, e0: r, f0: r, g0: r, h0: r, i0: r, j0: r, k0: r, l0: r, m0: r, n0: r, o0: r, p0: r, \
q0: r, r0: r, s0: r, t0: r, u0: r, v0: r, w0: r, x0: r, y0: r, z0: r, a1: r, b1: r, c1: r, d1: r, e1: r, f1: r, \
g1: r, h1: r, i1: r, j1: r, k1: r, l1: r, m1: r, n1: r, o1: r, p1: r, q1: r, r1: r, s1: r, t1: r, u1: r, v1: r, \
w1: r, x1: r, y1: r, z1: r, a2: r, b2: r, c2: r
#define _VA_GENERIC_TYPE_ANY57(r, a0, b0, c0, d0, e0, f0, g0, h0, i0, j0, k0, l0, m0, n0, o0, p0, q0, r0, s0, t0, \
u0, v0, w0, x0, y0, z0, a1, b1, c1, d1, e1, f1, g1, h1, i1, j1, k1, l1, m1, n1, o1, p1, q1, r1, s1, t1, u1, v1, \
w1, x1, y1, z1, a2, b2, c2, d2) \
a0: r, b0: r, c0: r, d0: r, e0: r, f0: r, g0: r, h0: r, i0: r, j0: r, k0: r, l0: r, m0: r, n0: r, o0: r, p0: r, \
q0: r, r0: r, s0: r, t0: r, u0: r, v0: r, w0: r, x0: r, y0: r, z0: r, a1: r, b1: r, c1: r, d1: r, e1: r, f1: r, \
g1: r, h1: r, i1: r, j1: r, k1: r, l1: r, m1: r, n1: r, o1: r, p1: r, q1: r, r1: r, s1: r, t1: r, u1: r, v1: r, \
w1: r, x1: r, y1: r, z1: r, a2: r, b2: r, c2: r, d2: r
#define _VA_GENERIC_TYPE_ANY58(r, a0, b0, c0, d0, e0, f0, g0, h0, i0, j0, k0, l0, m0, n0, o0, p0, q0, r0, s0, t0, \
u0, v0, w0, x0, y0, z0, a1, b1, c1, d1, e1, f1, g1, h1, i1, j1, k1, l1, m1, n1, o1, p1, q1, r1, s1, t1, u1, v1, \
w1, x1, y1, z1, a2, b2, c2, d2, e2) \
a0: r, b0: r, c0: r, d0: r, e0: r, f0: r, g0: r, h0: r, i0: r, j0: r, k0: r, l0: r, m0: r, n0: r, o0: r, p0: r, \
q0: r, r0: r, s0: r, t0: r, u0: r, v0: r, w0: r, x0: r, y0: r, z0: r, a1: r, b1: r, c1: r, d1: r, e1: r, f1: r, \
g1: r, h1: r, i1: r, j1: r, k1: r, l1: r, m1: r, n1: r, o1: r, p1: r, q1: r, r1: r, s1: r, t1: r, u1: r, v1: r, \
w1: r, x1: r, y1: r, z1: r, a2: r, b2: r, c2: r, d2: r, e2: r
#define _VA_GENERIC_TYPE_ANY59(r, a0, b0, c0, d0, e0, f0, g0, h0, i0, j0, k0, l0, m0, n0, o0, p0, q0, r0, s0, t0, \
u0, v0, w0, x0, y0, z0, a1, b1, c1, d1, e1, f1, g1, h1, i1, j1, k1, l1, m1, n1, o1, p1, q1, r1, s1, t1, u1, v1, \
w1, x1, y1, z1, a2, b2, c2, d2, e2, f2) \
a0: r, b0: r, c0: r, d0: r, e0: r, f0: r, g0: r, h0: r, i0: r, j0: r, k0: r, l0: r, m0: r, n0: r, o0: r, p0: r, \
q0: r, r0: r, s0: r, t0: r, u0: r, v0: r, w0: r, x0: r, y0: r, z0: r, a1: r, b1: r, c1: r, d1: r, e1: r, f1: r, \
g1: r, h1: r, i1: r, j1: r, k1: r, l1: r, m1: r, n1: r, o1: r, p1: r, q1: r, r1: r, s1: r, t1: r, u1: r, v1: r, \
w1: r, x1: r, y1: r, z1: r, a2: r, b2: r, c2: r, d2: r, e2: r, f2: r
#define _VA_GENERIC_TYPE_ANY60(r, a0, b0, c0, d0, e0, f0, g0, h0, i0, j0, k0, l0, m0, n0, o0, p0, q0, r0, s0, t0, \
u0, v0, w0, x0, y0, z0, a1, b1, c1, d1, e1, f1, g1, h1, i1, j1, k1, l1, m1, n1, o1, p1, q1, r1, s1, t1, u1, v1, \
w1, x1, y1, z1, a2, b2, c2, d2, e2, f2, g2) \
a0: r, b0: r, c0: r, d0: r, e0: r, f0: r, g0: r, h0: r, i0: r, j0: r, k0: r, l0: r, m0: r, n0: r, o0: r, p0: r, \
q0: r, r0: r, s0: r, t0: r, u0: r, v0: r, w0: r, x0: r, y0: r, z0: r, a1: r, b1: r, c1: r, d1: r, e1: r, f1: r, \
g1: r, h1: r, i1: r, j1: r, k1: r, l1: r, m1: r, n1: r, o1: r, p1: r, q1: r, r1: r, s1: r, t1: r, u1: r, v1: r, \
w1: r, x1: r, y1: r, z1: r, a2: r, b2: r, c2: r, d2: r, e2: r, f2: r, g2: r
#define _VA_GENERIC_TYPE_ANY61(r, a0, b0, c0, d0, e0, f0, g0, h0, i0, j0, k0, l0, m0, n0, o0, p0, q0, r0, s0, t0, \
u0, v0, w0, x0, y0, z0, a1, b1, c1, d1, e1, f1, g1, h1, i1, j1, k1, l1, m1, n1, o1, p1, q1, r1, s1, t1, u1, v1, \
w1, x1, y1, z1, a2, b2, c2, d2, e2, f2, g2, h2) \
a0: r, b0: r, c0: r, d0: r, e0: r, f0: r, g0: r, h0: r, i0: r, j0: r, k0: r, l0: r, m0: r, n0: r, o0: r, p0: r, \
q0: r, r0: r, s0: r, t0: r, u0: r, v0: r, w0: r, x0: r, y0: r, z0: r, a1: r, b1: r, c1: r, d1: r, e1: r, f1: r, \
g1: r, h1: r, i1: r, j1: r, k1: r, l1: r, m1: r, n1: r, o1: r, p1: r, q1: r, r1: r, s1: r, t1: r, u1: r, v1: r, \
w1: r, x1: r, y1: r, z1: r, a2: r, b2: r, c2: r, d2: r, e2: r, f2: r, g2: r, h2: r
#define _VA_GENERIC_TYPE_ANY62(r, a0, b0, c0, d0, e0, f0, g0, h0, i0, j0, k0, l0, m0, n0, o0, p0, q0, r0, s0, t0, \
u0, v0, w0, x0, y0, z0, a1, b1, c1, d1, e1, f1, g1, h1, i1, j1, k1, l1, m1, n1, o1, p1, q1, r1, s1, t1, u1, v1, \
w1, x1, y1, z1, a2, b2, c2, d2, e2, f2, g2, h2, i2) \
a0: r, b0: r, c0: r, d0: r, e0: r, f0: r, g0: r, h0: r, i0: r, j0: r, k0: r, l0: r, m0: r, n0: r, o0: r, p0: r, \
q0: r, r0: r, s0: r, t0: r, u0: r, v0: r, w0: r, x0: r, y0: r, z0: r, a1: r, b1: r, c1: r, d1: r, e1: r, f1: r, \
g1: r, h1: r, i1: r, j1: r, k1: r, l1: r, m1: r, n1: r, o1: r, p1: r, q1: r, r1: r, s1: r, t1: r, u1: r, v1: r, \
w1: r, x1: r, y1: r, z1: r, a2: r, b2: r, c2: r, d2: r, e2: r, f2: r, g2: r, h2: r, i2: r
#define _VA_GENERIC_TYPE_ANY63(r, a0, b0, c0, d0, e0, f0, g0, h0, i0, j0, k0, l0, m0, n0, o0, p0, q0, r0, s0, t0, \
u0, v0, w0, x0, y0, z0, a1, b1, c1, d1, e1, f1, g1, h1, i1, j1, k1, l1, m1, n1, o1, p1, q1, r1, s1, t1, u1, v1, \
w1, x1, y1, z1, a2, b2, c2, d2, e2, f2, g2, h2, i2, j2) \
a0: r, b0: r, c0: r, d0: r, e0: r, f0: r, g0: r, h0: r, i0: r, j0: r, k0: r, l0: r, m0: r, n0: r, o0: r, p0: r, \
q0: r, r0: r, s0: r, t0: r, u0: r, v0: r, w0: r, x0: r, y0: r, z0: r, a1: r, b1: r, c1: r, d1: r, e1: r, f1: r, \
g1: r, h1: r, i1: r, j1: r, k1: r, l1: r, m1: r, n1: r, o1: r, p1: r, q1: r, r1: r, s1: r, t1: r, u1: r, v1: r, \
w1: r, x1: r, y1: r, z1: r, a2: r, b2: r, c2: r, d2: r, e2: r, f2: r, g2: r, h2: r, i2: r, j2: r
#define _VA_GENERIC_TYPE_ANY64(r, a0, b0, c0, d0, e0, f0, g0, h0, i0, j0, k0, l0, m0, n0, o0, p0, q0, r0, s0, t0, \
u0, v0, w0, x0, y0, z0, a1, b1, c1, d1, e1, f1, g1, h1, i1, j1, k1, l1, m1, n1, o1, p1, q1, r1, s1, t1, u1, v1, \
w1, x1, y1, z1, a2, b2, c2, d2, e2, f2, g2, h2, i2, j2, k2) \
a0: r, b0: r, c0: r, d0: r, e0: r, f0: r, g0: r, h0: r, i0: r, j0: r, k0: r, l0: r, m0: r, n0: r, o0: r, p0: r, \
q0: r, r0: r, s0: r, t0: r, u0: r, v0: r, w0: r, x0: r, y0: r, z0: r, a1: r, b1: r, c1: r, d1: r, e1: r, f1: r, \
g1: r, h1: r, i1: r, j1: r, k1: r, l1: r, m1: r, n1: r, o1: r, p1: r, q1: r, r1: r, s1: r, t1: r, u1: r, v1: r, \
w1: r, x1: r, y1: r, z1: r, a2: r, b2: r, c2: r, d2: r, e2: r, f2: r, g2: r, h2: r, i2: r, j2: r, k2: r
/* clang-format on */
#define GENERIC_TYPE_ANY(...) VA_NARGS_CALL_OVERLOAD(_VA_GENERIC_TYPE_ANY, __VA_ARGS__)

View File

@@ -0,0 +1,43 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bli
*
* Utilities for lossless data compression.
*/
#include <cstddef>
#include <cstdint>
namespace blender {
/**
* Transforms array of data, making it more compressible,
* especially if data is smoothly varying. Typically you do
* this before compression with a general purpose compressor.
*
* Transposes input array so that output data is first byte of
* all items, then 2nd byte of all items, etc. And successive
* items within each "byte stream" are stored as difference
* from previous byte.
*
* See https://aras-p.info/blog/2023/03/01/Float-Compression-7-More-Filtering-Optimization/
* for details.
*/
void filter_transpose_delta(const uint8_t *src, uint8_t *dst, size_t items_num, size_t item_size);
/**
* Reverses the data transform done by #unfilter_transpose_delta.
* Typically you do this after decompression with a general purpose
* compressor.
*/
void unfilter_transpose_delta(const uint8_t *src,
uint8_t *dst,
size_t items_num,
size_t item_size);
} // namespace blender

View File

@@ -0,0 +1,174 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bli
*
* When logging computed values, we generally want to know where the value was computed. For
* example, geometry nodes logs socket values so that they can be displayed in the ui. For that we
* can combine the logged value with a `ComputeContext`, which identifies the place where the value
* was computed.
*
* This is not a trivial problem because e.g. just storing a pointer to the socket a value
* belongs to is not enough. That's because the same socket may correspond to many different values
* when the socket is used in a node group that is used multiple times. In this case, not only does
* the socket have to be stored but also the entire nested node group path that led to the
* evaluation of the socket.
*
* Storing the entire "context path" for every logged value is not feasible, because that path can
* become quite long. So that would need much more memory, more compute overhead and makes it
* complicated to compare if two contexts are the same. If the identifier for a compute context
* would have a variable size, it would also be much harder to create a map from context to values.
*
* The solution implemented below uses the following key ideas:
* - Every compute context can be hashed to a unique fixed size value (`ComputeContextHash`). While
* technically there could be hash collisions, the hashing algorithm has to be chosen to make
* that practically impossible. This way an entire context path, possibly consisting of many
* nested contexts, is represented by a single value that can be stored easily.
* - A nested compute context is build as singly linked list, where every compute context has a
* pointer to the parent compute context. Note that a link in the other direction is not possible
* because the same parent compute context may be used by many different children which possibly
* run on different threads.
*/
#include "BLI_cache_mutex.hh"
#include "BLI_string_ref.hh"
#include "BLI_unique_hash.hh"
namespace blender {
class ComputeContext;
/**
* A hash that uniquely identifies a specific (non-fixed-size) compute context.
*/
struct ComputeContextHash : public UniqueHash {
/**
* Standard way to create a compute context hash.
* \param parent: The optional parent context.
* \param type_str: A string literal that identifies the context type. This is used to avoid hash
* collisions between different context types.
* \param args: Additional arguments that affect the hash. Note that only the shallow bytes of
* these types are used. So they generally should not contain any padding.
*/
template<size_t N, typename... Args>
static ComputeContextHash from(const ComputeContext *parent,
const char (&type_str)[N],
Args &&...args);
friend std::ostream &operator<<(std::ostream &stream, const ComputeContextHash &hash);
/**
* Compute a context hash by packing all the arguments into a contiguous buffer and hashing
* that.
*/
template<typename... Args> static ComputeContextHash from_shallow_bytes(Args &&...args);
/** Compute a context hash from a contiguous buffer. */
static ComputeContextHash from_bytes(const void *data, int64_t len);
};
/**
* Identifies the context in which a computation happens. This context can be used to identify
* values logged during the computation. For more details, see the comment at the top of the file.
*
* This class should be subclassed to implement specific contexts.
*/
class ComputeContext {
protected:
/**
* Pointer to the context that this context is child of. That allows nesting compute
* contexts.
*/
const ComputeContext *parent_ = nullptr;
/**
* The hash that uniquely identifies this context. It's a combined hash of this context as well
* as all the parent contexts. It's computed lazily to keep initial construction of compute
* contexts very cheap.
*/
mutable ComputeContextHash hash_;
private:
mutable CacheMutex hash_mutex_;
/**
* Number of parent contexts. This can be used to limit the maximum depth to prevent
* stack-overflows.
*/
int parents_num_ = 0;
public:
ComputeContext(const ComputeContext *parent)
: parent_(parent), parents_num_(parent ? parent->parents_num_ + 1 : 0)
{
}
virtual ~ComputeContext() = default;
const ComputeContextHash &hash() const
{
hash_mutex_.ensure([&]() { hash_ = this->compute_hash(); });
return hash_;
}
const ComputeContext *parent() const
{
return parent_;
}
int parents_num() const
{
return parents_num_;
}
/**
* Print the entire nested context stack.
*/
void print_stack(std::ostream &stream, StringRef name) const;
/**
* Print information about this specific context. This has to be implemented by each subclass.
*/
virtual void print_current_in_line(std::ostream &stream) const = 0;
friend std::ostream &operator<<(std::ostream &stream, const ComputeContext &compute_context);
private:
/** Compute the hash of this context, usually using #ComputeContextHash::from. */
virtual ComputeContextHash compute_hash() const = 0;
};
template<size_t N, typename... Args>
inline ComputeContextHash ComputeContextHash::from(const ComputeContext *parent,
const char (&type_str)[N],
Args &&...args)
{
return ComputeContextHash::from_shallow_bytes(
parent ? parent->hash() : ComputeContextHash{0, 0}, type_str, args...);
}
template<typename... Args>
inline ComputeContextHash ComputeContextHash::from_shallow_bytes(Args &&...args)
{
/* Copy all values into a contiguous buffer. Intentionally don't use std::tuple to avoid any
* potential padding. */
constexpr int64_t size_sum = (sizeof(args) + ...);
char buffer[size_sum];
int64_t offset = 0;
(
[&] {
using Arg = std::remove_reference_t<std::remove_cv_t<Args>>;
static_assert(std::has_unique_object_representations_v<Arg>);
const Arg &arg = args;
memcpy(buffer + offset, &arg, sizeof(Arg));
offset += sizeof(Arg);
}(),
...);
/* Compute the hash of that buffer. */
return ComputeContextHash::from_bytes(buffer, offset);
}
} // namespace blender

View File

@@ -0,0 +1,198 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bli
*/
#ifdef WITH_TBB
/* Quiet top level deprecation message, unrelated to API usage here. */
# include <tbb/concurrent_hash_map.h>
#else
# include "BLI_mutex.hh"
# include "BLI_set.hh"
#endif
#include "BLI_hash.hh"
#include "BLI_hash_tables.hh"
namespace blender {
/**
* A #ConcurrentMap allows adding, removing and looking up values from multiple threads
* concurrently. It has higher memory and performance overhead than a simple #Map when not used
* concurrently though.
*
* For thread-safety, one always has the use an accessor to retrieve or update values. This makes
* sure that only one thread can modify a value at a time. Multiple threads may read from the same
* key at the same time though.
*
* \note #ConcurrentMap does not support iteration over all values.
*
* This is a thin wrapper around #tbb::concurrent_hash_map that also has a fallback implementation
* if TBB is not available. The fallback implementation is not optimized for performance. It mainly
* intends to be a simple implementation that can compile whenever the TBB variant can compile.
*/
template<typename Key,
typename Value,
typename Hash = DefaultHash<Key>,
typename IsEqual = DefaultEquality<Key>>
class ConcurrentMap {
public:
using size_type = int64_t;
/* Sometimes TBB requires the value to be constructible. */
static_assert(std::is_copy_constructible_v<Value>);
#ifdef WITH_TBB
private:
struct Hasher {
template<typename T> size_t hash(const T &value) const
{
return Hash{}(value);
}
template<typename T1, typename T2> bool equal(const T1 &a, const T2 &b) const
{
return IsEqual{}(a, b);
}
};
using TBBMap = tbb::concurrent_hash_map<Key, Value, Hasher>;
TBBMap map_;
public:
using MutableAccessor = typename TBBMap::accessor;
using ConstAccessor = typename TBBMap::const_accessor;
/**
* Try to find the key-value-pair for the given key and get write-access to it. Only one thread
* may have write access to it at a time. The looked up value can be accessed through the
* accessor.
*
* \return True if the lookup was successful.
*/
bool lookup(MutableAccessor &accessor, const Key &key)
{
return map_.find(accessor, key);
}
/**
* Same as above, but only retrieves read-access which multiple threads can have at the same
* time.
*/
bool lookup(ConstAccessor &accessor, const Key &key)
{
return map_.find(accessor, key);
}
/**
* Add the key to the map if it does not exist yet. The value is default initialized and can be
* updated through the accessor.
*/
bool add(MutableAccessor &accessor, const Key &key)
{
return map_.insert(accessor, key);
}
bool add(ConstAccessor &accessor, const Key &key)
{
return map_.insert(accessor, key);
}
/**
* Remove the key-value-pair that corresponds to this key. This waits until no one else is using
* it anymore.
*/
bool remove(const Key &key)
{
return map_.erase(key);
}
#else
private:
/**
* In the fallback implementation, we actually use a #Set, because the API expects the key and
* value to be stored in a `std::pair`. #Set can support this use case too.
*/
struct SetKey {
std::pair<Key, Value> item;
SetKey(Key key) : item(std::move(key), Value()) {}
uint64_t hash() const
{
return Hash{}(this->item.first);
}
static uint64_t hash_as(const Key &key)
{
return Hash{}(key);
}
friend bool operator==(const SetKey &a, const SetKey &b)
{
return IsEqual{}(a.item.first, b.item.first);
}
friend bool operator==(const Key &a, const SetKey &b)
{
return IsEqual{}(a, b.item.first);
}
friend bool operator==(const SetKey &a, const Key &b)
{
return IsEqual{}(a.item.first, b);
}
};
using UsedSet = Set<SetKey>;
struct Accessor {
std::unique_lock<Mutex> mutex;
std::pair<Key, Value> *data = nullptr;
std::pair<Key, Value> *operator->()
{
return this->data;
}
};
Mutex mutex_;
UsedSet set_;
public:
using MutableAccessor = Accessor;
using ConstAccessor = Accessor;
bool lookup(Accessor &accessor, const Key &key)
{
accessor.mutex = std::unique_lock(mutex_);
SetKey *stored_key = const_cast<SetKey *>(set_.lookup_key_ptr_as(key));
if (!stored_key) {
return false;
}
accessor.data = &stored_key->item;
return true;
}
bool add(Accessor &accessor, const Key &key)
{
accessor.mutex = std::unique_lock(mutex_);
const bool newly_added = !set_.contains_as(key);
SetKey &stored_key = const_cast<SetKey &>(set_.lookup_key_or_add_as(key));
accessor.data = &stored_key.item;
return newly_added;
}
bool remove(const Key &key)
{
std::unique_lock lock(mutex_);
return set_.remove_as(key);
}
#endif
};
} // namespace blender

View File

@@ -0,0 +1,18 @@
/* SPDX-FileCopyrightText: 2018 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bli
* \brief Set of utility functions and constants to work with consoles.
*/
/* Format string where one could BLI_snprintf() R, G and B values
* and get proper marker to start colored output in the console.
*/
#define TRUECOLOR_ANSI_COLOR_FORMAT "\x1b[38;2;%d;%d;%dm"
/* Marker which indicates that colored output is finished. */
#define TRUECOLOR_ANSI_COLOR_FINISH "\x1b[0m"

View File

@@ -0,0 +1,42 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "BLI_math_vector_types.hh"
#include "BLI_span.hh"
namespace blender {
/** \file
* \ingroup bli
*/
/**
* Extract 2D convex hull.
*
* \param points: An array of 2D points.
* \param points_num: The number of points in points.
* \param r_points: An array of the convex hull vertex indices (max is `points_num`).
* - Points are ordered counter clockwise.
* - The first point in `r_points` will be the lowest Y value
* (lowest (X, Y) when there are multiple Y aligned vertices).
* - The polygons cross product is always positive (or zero).
*
* \return The number of indices in `r_points`.
*
* \note Performance is `O(points_num.log(points_num))`, same as `qsort`.
*/
int BLI_convexhull_2d(Span<float2> points, int r_points[/*points_num*/]);
/**
* \return The best angle for fitting the points to an axis aligned bounding box.
*
* \note We could return the index of the best edge too if its needed.
*
* \param points: Arbitrary 2D points.
*/
float BLI_convexhull_aabb_fit_points_2d(Span<float2> points);
} // namespace blender

View File

@@ -0,0 +1,791 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bli
*
* The `CPPType` class allows working with arbitrary C++ types in a generic way. An instance of
* #CPPType wraps exactly one type like `int` or `std::string`.
*
* With #CPPType one can write generic data structures and algorithms. That is similar to what C++
* templates allow. The difference is that when using templates, the types have to be known at
* compile time and the code has to be instantiated multiple times. On the other hand, when using
* #CPPType, the data type only has to be known at run-time, and the code only has to be compiled
* once. Whether #CPPType or classic c++ templates should be used depends on the context:
* - If the data type is not known at run-time, #CPPType should be used.
* - If the data type is known to be one of a few, it depends on how performance sensitive the code
* is.
* - If it it's a small hot loop, a template can be used to optimize for every type (at the
* cost of longer compile time, a larger binary and the complexity that comes from using
* templates).
* - If the code is not performance sensitive, it usually makes sense to use #CPPType instead.
* - Sometimes a combination can make sense. Optimized code can be generated at compile-time for
* some types, while there is a fallback code path using #CPPType for all other types.
* #CPPType::to_static_type allows dispatching between both versions based on the type.
*
* Under some circumstances, #CPPType serves a similar role as #std::type_info. However, #CPPType
* has much more utility because it contains methods for actually working with instances of the
* type.
*
* Every type has a size and an alignment. Every function dealing with C++ types in a generic way,
* has to make sure that alignment rules are followed. The methods provided by a #CPPType instance
* will check for correct alignment as well.
*
* Every type has a name that is for debugging purposes only. It should not be used as identifier.
*
* To check if two instances of #CPPType represent the same type, only their pointers have to be
* compared. Any C++ type has at most one corresponding #CPPType instance.
*
* A #CPPType instance comes with many methods that allow dealing with types in a generic way. Most
* methods come in three variants. Using the default-construct methods as an example:
* - `default_construct(void *ptr)`:
* Constructs a single instance of that type at the given pointer.
* - `default_construct_n(void *ptr, int64_t n)`:
* Constructs n instances of that type in an array that starts at the given pointer.
* - `default_construct_indices(void *ptr, const IndexMask &mask)`:
* Constructs multiple instances of that type in an array that starts at the given pointer.
* Only the indices referenced by `mask` will by constructed.
*
* In some cases default-construction does nothing (e.g. for trivial types like int). The
* `default_value` method provides some default value anyway that can be copied instead. What the
* default value is, depends on the type. Usually it is something like 0 or an empty string.
*
*
* Implementation Considerations
* -----------------------------
*
* Concepts like inheritance are currently not captured by this system. This is not because it is
* not possible, but because it was not necessary to add this complexity yet.
*
* One could also implement CPPType itself using virtual methods and a child class for every
* wrapped type. However, the approach used now with explicit function pointers to works better.
* Here are some reasons:
* - If CPPType would be inherited once for every used C++ type, we would get a lot of classes
* that would only be instanced once each.
* - Methods like `default_construct` that operate on a single instance have to be fast. Even this
* one necessary indirection using function pointers adds a lot of overhead. If all methods were
* virtual, there would be a second level of indirection that increases the overhead even more.
* - If it becomes necessary, we could pass the function pointers to C functions more easily than
* pointers to virtual member functions.
*/
#include <atomic>
#include "BLI_dynamic_stack_buffer.hh" // IWYU pragma: keep
#include "BLI_enum_flags.hh"
#include "BLI_hash.hh"
#include "BLI_index_mask_fwd.hh"
#include "BLI_map.hh"
#include "BLI_parameter_pack_utils.hh"
#include "BLI_string_ref.hh"
#include "BLI_utility_mixins.hh"
namespace blender {
struct UniqueHashBytes;
/**
* Different types support different features. Features like copy constructability can be detected
* automatically easily. For some features this is harder as of C++17. Those have flags in this
* enum and need to be determined by the programmer.
*/
enum class CPPTypeFlags {
None = 0,
Hashable = 1 << 0,
Printable = 1 << 1,
EqualityComparable = 1 << 2,
IdentityDefaultValue = 1 << 3,
BasicType = Hashable | Printable | EqualityComparable,
};
ENUM_OPERATORS(CPPTypeFlags)
class CPPType : NonCopyable, NonMovable {
public:
/**
* Required memory in bytes for an instance of this type.
*
* C++ equivalent:
* `sizeof(T);`
*/
int64_t size = 0;
/**
* Required memory alignment for an instance of this type.
*
* C++ equivalent:
* `alignof(T);`
*/
int64_t alignment = 0;
/**
* When true, the value is like a normal C type, it can be copied around with #memcpy and does
* not have to be destructed.
*
* C++ equivalent:
* `std::is_trivial_v<T>;`
*/
bool is_trivial = false;
/**
* When true, the destructor does not have to be called on this type. This can sometimes be used
* for optimization purposes.
*
* C++ equivalent:
* `std::is_trivially_destructible_v<T>;`
*/
bool is_trivially_destructible = false;
/**
* Returns true, when the type has the following functions:
* - Default constructor.
* - Copy constructor.
* - Move constructor.
* - Copy assignment operator.
* - Move assignment operator.
* - Destructor.
*/
bool has_special_member_functions = false;
bool is_default_constructible = false;
bool is_copy_constructible = false;
bool is_move_constructible = false;
bool is_destructible = false;
bool is_copy_assignable = false;
bool is_move_assignable = false;
/**
* An index that is assigned when the type is registered. Each #CPPtype has a unique index.
* While the pointer of a #CPPType is also unique, sometimes it's easier to work with an index
* that is a relatively small number (generally <100).
*/
int type_index = -1;
private:
uintptr_t alignment_mask_ = 0;
void (*default_construct_)(void *ptr) = nullptr;
void (*default_construct_n_)(void *ptr, int64_t n) = nullptr;
void (*default_construct_indices_)(void *ptr, const IndexMask &mask) = nullptr;
void (*value_initialize_)(void *ptr) = nullptr;
void (*value_initialize_n_)(void *ptr, int64_t n) = nullptr;
void (*value_initialize_indices_)(void *ptr, const IndexMask &mask) = nullptr;
void (*destruct_)(void *ptr) = nullptr;
void (*destruct_n_)(void *ptr, int64_t n) = nullptr;
void (*destruct_indices_)(void *ptr, const IndexMask &mask) = nullptr;
void (*copy_assign_)(const void *src, void *dst) = nullptr;
void (*copy_assign_n_)(const void *src, void *dst, int64_t n) = nullptr;
void (*copy_assign_indices_)(const void *src, void *dst, const IndexMask &mask) = nullptr;
void (*copy_assign_compressed_)(const void *src, void *dst, const IndexMask &mask) = nullptr;
void (*copy_construct_)(const void *src, void *dst) = nullptr;
void (*copy_construct_n_)(const void *src, void *dst, int64_t n) = nullptr;
void (*copy_construct_indices_)(const void *src, void *dst, const IndexMask &mask) = nullptr;
void (*copy_construct_compressed_)(const void *src, void *dst, const IndexMask &mask) = nullptr;
void (*move_assign_)(void *src, void *dst) = nullptr;
void (*move_assign_n_)(void *src, void *dst, int64_t n) = nullptr;
void (*move_assign_indices_)(void *src, void *dst, const IndexMask &mask) = nullptr;
void (*move_construct_)(void *src, void *dst) = nullptr;
void (*move_construct_n_)(void *src, void *dst, int64_t n) = nullptr;
void (*move_construct_indices_)(void *src, void *dst, const IndexMask &mask) = nullptr;
void (*relocate_assign_)(void *src, void *dst) = nullptr;
void (*relocate_assign_n_)(void *src, void *dst, int64_t n) = nullptr;
void (*relocate_assign_indices_)(void *src, void *dst, const IndexMask &mask) = nullptr;
void (*relocate_construct_)(void *src, void *dst) = nullptr;
void (*relocate_construct_n_)(void *src, void *dst, int64_t n) = nullptr;
void (*relocate_construct_indices_)(void *src, void *dst, const IndexMask &mask) = nullptr;
void (*fill_assign_n_)(const void *value, void *dst, int64_t n) = nullptr;
void (*fill_assign_indices_)(const void *value, void *dst, const IndexMask &mask) = nullptr;
void (*fill_construct_n_)(const void *value, void *dst, int64_t n) = nullptr;
void (*fill_construct_indices_)(const void *value, void *dst, const IndexMask &mask) = nullptr;
void (*print_)(const void *value, std::stringstream &ss) = nullptr;
bool (*is_equal_)(const void *a, const void *b) = nullptr;
uint64_t (*hash_)(const void *value) = nullptr;
void (*hash_unique_)(const void *value, UniqueHashBytes &hash) = nullptr;
const void *default_value_ = nullptr;
std::string debug_name_;
public:
template<typename T, CPPTypeFlags Flags>
CPPType(TypeTag<T> /*type*/, TypeForValue<CPPTypeFlags, Flags> /*flags*/, StringRef debug_name);
virtual ~CPPType() = default;
/**
* Get the `CPPType` that corresponds to a specific static type.
* This only works for types that actually implement the template specialization using
* `BLI_CPP_TYPE_REGISTER`.
*/
template<typename T> static const CPPType &get();
/**
* Returns the name of the type for debugging purposes. This name should not be used as
* identifier.
*/
StringRefNull name() const;
bool is_printable() const;
bool is_equality_comparable() const;
bool is_hashable() const;
/**
* Returns true, when the given pointer fulfills the alignment requirement of this type.
*/
bool pointer_has_valid_alignment(const void *ptr) const;
bool pointer_can_point_to_instance(const void *ptr) const;
/**
* Call the default constructor at the given memory location.
* The memory should be uninitialized before this method is called.
* For some trivial types (like int), this method does nothing.
*
* C++ equivalent:
* `new (ptr) T;`
*/
void default_construct(void *ptr) const;
void default_construct_n(void *ptr, int64_t n) const;
void default_construct_indices(void *ptr, const IndexMask &mask) const;
/**
* Same as #default_construct, but does zero initialization for trivial types.
*
* C++ equivalent:
* `new (ptr) T();`
*/
void value_initialize(void *ptr) const;
void value_initialize_n(void *ptr, int64_t n) const;
void value_initialize_indices(void *ptr, const IndexMask &mask) const;
/**
* Call the destructor on the given instance of this type. The pointer must not be nullptr.
*
* For some trivial types, this does nothing.
*
* C++ equivalent:
* `ptr->~T();`
*/
void destruct(void *ptr) const;
void destruct_n(void *ptr, int64_t n) const;
void destruct_indices(void *ptr, const IndexMask &mask) const;
/**
* Copy an instance of this type from src to dst.
*
* C++ equivalent:
* `dst = src;`
*/
void copy_assign(const void *src, void *dst) const;
void copy_assign_n(const void *src, void *dst, int64_t n) const;
void copy_assign_indices(const void *src, void *dst, const IndexMask &mask) const;
/**
* Similar to #copy_assign_indices, but does not leave gaps in the #dst array.
*/
void copy_assign_compressed(const void *src, void *dst, const IndexMask &mask) const;
/**
* Copy an instance of this type from src to dst.
*
* The memory pointed to by dst should be uninitialized.
*
* C++ equivalent:
* `new (dst) T(src);`
*/
void copy_construct(const void *src, void *dst) const;
void copy_construct_n(const void *src, void *dst, int64_t n) const;
void copy_construct_indices(const void *src, void *dst, const IndexMask &mask) const;
/**
* Similar to #copy_construct_indices, but does not leave gaps in the #dst array.
*/
void copy_construct_compressed(const void *src, void *dst, const IndexMask &mask) const;
/**
* Move an instance of this type from src to dst.
*
* The memory pointed to by dst should be initialized.
*
* C++ equivalent:
* `dst = std::move(src);`
*/
void move_assign(void *src, void *dst) const;
void move_assign_n(void *src, void *dst, int64_t n) const;
void move_assign_indices(void *src, void *dst, const IndexMask &mask) const;
/**
* Move an instance of this type from src to dst.
*
* The memory pointed to by dst should be uninitialized.
*
* C++ equivalent:
* `new (dst) T(std::move(src));`
*/
void move_construct(void *src, void *dst) const;
void move_construct_n(void *src, void *dst, int64_t n) const;
void move_construct_indices(void *src, void *dst, const IndexMask &mask) const;
/**
* Relocates an instance of this type from src to dst. src will point to uninitialized memory
* afterwards.
*
* C++ equivalent:
* `dst = std::move(src);`
* `src->~T();`
*/
void relocate_assign(void *src, void *dst) const;
void relocate_assign_n(void *src, void *dst, int64_t n) const;
void relocate_assign_indices(void *src, void *dst, const IndexMask &mask) const;
/**
* Relocates an instance of this type from src to dst. src will point to uninitialized memory
* afterwards.
*
* C++ equivalent:
* `new (dst) T(std::move(src))`
* `src->~T();`
*/
void relocate_construct(void *src, void *dst) const;
void relocate_construct_n(void *src, void *dst, int64_t n) const;
void relocate_construct_indices(void *src, void *dst, const IndexMask &mask) const;
/**
* Copy the given value to the first n elements in an array starting at dst.
*
* Other instances of the same type should live in the array before this method is called.
*/
void fill_assign_n(const void *value, void *dst, int64_t n) const;
void fill_assign_indices(const void *value, void *dst, const IndexMask &mask) const;
/**
* Copy the given value to the first n elements in an array starting at dst.
*
* The array should be uninitialized before this method is called.
*/
void fill_construct_n(const void *value, void *dst, int64_t n) const;
void fill_construct_indices(const void *value, void *dst, const IndexMask &mask) const;
bool can_exist_in_buffer(const int64_t buffer_size, const int64_t buffer_alignment) const;
void print(const void *value, std::stringstream &ss) const;
std::string to_string(const void *value) const;
void print_or_default(const void *value, std::stringstream &ss, StringRef default_value) const;
bool is_equal(const void *a, const void *b) const;
bool is_equal_or_false(const void *a, const void *b) const;
uint64_t hash(const void *value) const;
uint64_t hash_or_fallback(const void *value, uint64_t fallback_hash) const;
void hash_unique(const void *value, UniqueHashBytes &hash) const;
/**
* Get a pointer to a constant value of this type. The specific value depends on the type.
* It is usually a zero-initialized or default constructed value.
*/
const void *default_value() const;
uint64_t hash() const;
void (*destruct_fn() const)(void *);
template<typename T> bool is() const;
template<typename... T> bool is_any() const;
/**
* Convert a #CPPType that is only known at run-time, to a static type that is known at
* compile-time. This allows the compiler to optimize a function for specific types, while all
* other types can still use a generic fallback function.
*
* \tparam Types: The types that code should be generated for.
* \param fn: The function object to call. This is expected to have a templated `operator()` and
* a non-templated `operator()`. The templated version will be called if the current #CPPType
* matches any of the given types.
* \return True if the function was called.
*/
template<typename... Types, typename Fn> bool to_static_type_try(Fn &&fn) const;
/** Same as #to_static_type_try, but asserts if the type is valid. */
template<typename... Types, typename Fn> void to_static_type(Fn &&fn) const;
private:
/**
* Helper used in #to_static_type_try as a typed function pointer for each type in the list.
* A named static function is used instead of a lambda to avoid a known MSVC bug where a
* non-capturing lambda inside a comma fold expression that references the pack parameter
* causes MSVC to generate zero iterations, leaving the map empty.
*/
template<typename T, typename Fn> static void call_with_type_impl_(const Fn &fn)
{
fn.template operator()<T>();
}
};
namespace detail {
/**
* Global static variable that contains the #CPPType for a given type after it has been registered
* with #BLI_CPP_TYPE_REGISTER. This should generally be accessed through #CPPType::get<T>. */
template<typename T> inline TypedBuffer<CPPType> cpp_type_impl{};
} // namespace detail
/**
* Initialize and register basic cpp types.
*/
void register_cpp_types();
/* Utility for allocating an uninitialized buffer for a single value of the given #CPPType. */
#define BUFFER_FOR_CPP_TYPE_VALUE(type, variable_name) \
DynamicStackBuffer<64, 64> stack_buffer_for_##variable_name((type).size, (type).alignment); \
void *variable_name = stack_buffer_for_##variable_name.buffer();
/* Give a compile error instead of a link error when type information is missing. */
template<> const CPPType &CPPType::get<void>() = delete;
/**
* Two types only compare equal when their pointer is equal. No two instances of CPPType for the
* same C++ type should be created.
*/
inline bool operator==(const CPPType &a, const CPPType &b)
{
return &a == &b;
}
inline bool operator!=(const CPPType &a, const CPPType &b)
{
return !(&a == &b);
}
template<typename T> inline const CPPType &CPPType::get()
{
const CPPType &type = detail::cpp_type_impl<std::decay_t<T>>.ref();
/* Should have been initialized by #BLI_CPP_TYPE_REGISTER.
* If this is hit in test code, make sure the test calls `register_cpp_types` (for blenlib
* tests) or `BKE_cpp_types_init` (for general tests). */
BLI_assert(type.size > 0);
return type;
}
inline StringRefNull CPPType::name() const
{
return debug_name_;
}
inline bool CPPType::is_printable() const
{
return print_ != nullptr;
}
inline bool CPPType::is_equality_comparable() const
{
return is_equal_ != nullptr;
}
inline bool CPPType::is_hashable() const
{
return hash_ != nullptr;
}
inline bool CPPType::pointer_has_valid_alignment(const void *ptr) const
{
return (uintptr_t(ptr) & alignment_mask_) == 0;
}
inline bool CPPType::pointer_can_point_to_instance(const void *ptr) const
{
return ptr != nullptr && pointer_has_valid_alignment(ptr);
}
inline void CPPType::default_construct(void *ptr) const
{
default_construct_(ptr);
}
inline void CPPType::default_construct_n(void *ptr, int64_t n) const
{
default_construct_n_(ptr, n);
}
inline void CPPType::default_construct_indices(void *ptr, const IndexMask &mask) const
{
default_construct_indices_(ptr, mask);
}
inline void CPPType::value_initialize(void *ptr) const
{
value_initialize_(ptr);
}
inline void CPPType::value_initialize_n(void *ptr, int64_t n) const
{
value_initialize_n_(ptr, n);
}
inline void CPPType::value_initialize_indices(void *ptr, const IndexMask &mask) const
{
value_initialize_indices_(ptr, mask);
}
inline void CPPType::destruct(void *ptr) const
{
destruct_(ptr);
}
inline void CPPType::destruct_n(void *ptr, int64_t n) const
{
destruct_n_(ptr, n);
}
inline void CPPType::destruct_indices(void *ptr, const IndexMask &mask) const
{
destruct_indices_(ptr, mask);
}
inline void CPPType::copy_assign(const void *src, void *dst) const
{
copy_assign_(src, dst);
}
inline void CPPType::copy_assign_n(const void *src, void *dst, int64_t n) const
{
copy_assign_n_(src, dst, n);
}
inline void CPPType::copy_assign_indices(const void *src, void *dst, const IndexMask &mask) const
{
copy_assign_indices_(src, dst, mask);
}
inline void CPPType::copy_assign_compressed(const void *src,
void *dst,
const IndexMask &mask) const
{
copy_assign_compressed_(src, dst, mask);
}
inline void CPPType::copy_construct(const void *src, void *dst) const
{
copy_construct_(src, dst);
}
inline void CPPType::copy_construct_n(const void *src, void *dst, int64_t n) const
{
copy_construct_n_(src, dst, n);
}
inline void CPPType::copy_construct_indices(const void *src,
void *dst,
const IndexMask &mask) const
{
copy_construct_indices_(src, dst, mask);
}
inline void CPPType::copy_construct_compressed(const void *src,
void *dst,
const IndexMask &mask) const
{
copy_construct_compressed_(src, dst, mask);
}
inline void CPPType::move_assign(void *src, void *dst) const
{
move_assign_(src, dst);
}
inline void CPPType::move_assign_n(void *src, void *dst, int64_t n) const
{
move_assign_n_(src, dst, n);
}
inline void CPPType::move_assign_indices(void *src, void *dst, const IndexMask &mask) const
{
move_assign_indices_(src, dst, mask);
}
inline void CPPType::move_construct(void *src, void *dst) const
{
move_construct_(src, dst);
}
inline void CPPType::move_construct_n(void *src, void *dst, int64_t n) const
{
move_construct_n_(src, dst, n);
}
inline void CPPType::move_construct_indices(void *src, void *dst, const IndexMask &mask) const
{
move_construct_indices_(src, dst, mask);
}
inline void CPPType::relocate_assign(void *src, void *dst) const
{
relocate_assign_(src, dst);
}
inline void CPPType::relocate_assign_n(void *src, void *dst, int64_t n) const
{
relocate_assign_n_(src, dst, n);
}
inline void CPPType::relocate_assign_indices(void *src, void *dst, const IndexMask &mask) const
{
relocate_assign_indices_(src, dst, mask);
}
inline void CPPType::relocate_construct(void *src, void *dst) const
{
relocate_construct_(src, dst);
}
inline void CPPType::relocate_construct_n(void *src, void *dst, int64_t n) const
{
relocate_construct_n_(src, dst, n);
}
inline void CPPType::relocate_construct_indices(void *src, void *dst, const IndexMask &mask) const
{
relocate_construct_indices_(src, dst, mask);
}
inline void CPPType::fill_assign_n(const void *value, void *dst, int64_t n) const
{
fill_assign_n_(value, dst, n);
}
inline void CPPType::fill_assign_indices(const void *value, void *dst, const IndexMask &mask) const
{
fill_assign_indices_(value, dst, mask);
}
inline void CPPType::fill_construct_n(const void *value, void *dst, int64_t n) const
{
fill_construct_n_(value, dst, n);
}
inline void CPPType::fill_construct_indices(const void *value,
void *dst,
const IndexMask &mask) const
{
fill_construct_indices_(value, dst, mask);
}
inline bool CPPType::can_exist_in_buffer(const int64_t buffer_size,
const int64_t buffer_alignment) const
{
return this->size <= buffer_size && this->alignment <= buffer_alignment;
}
inline void CPPType::print(const void *value, std::stringstream &ss) const
{
BLI_assert(this->pointer_can_point_to_instance(value));
print_(value, ss);
}
inline bool CPPType::is_equal(const void *a, const void *b) const
{
return is_equal_(a, b);
}
inline bool CPPType::is_equal_or_false(const void *a, const void *b) const
{
if (this->is_equality_comparable()) {
return this->is_equal(a, b);
}
return false;
}
inline uint64_t CPPType::hash(const void *value) const
{
return hash_(value);
}
inline uint64_t CPPType::hash_or_fallback(const void *value, uint64_t fallback_hash) const
{
if (this->is_hashable()) {
return this->hash(value);
}
return fallback_hash;
}
inline void CPPType::hash_unique(const void *value, UniqueHashBytes &hash) const
{
this->hash_unique_(value, hash);
}
inline const void *CPPType::default_value() const
{
return default_value_;
}
inline uint64_t CPPType::hash() const
{
return get_default_hash(this);
}
inline void (*CPPType::destruct_fn() const)(void *)
{
return destruct_;
}
template<typename T> inline bool CPPType::is() const
{
return this == &CPPType::get<std::decay_t<T>>();
}
template<typename... T> inline bool CPPType::is_any() const
{
return (this->is<T>() || ...);
}
template<typename... Types, typename Fn> inline void CPPType::to_static_type(Fn &&fn) const
{
if (this->to_static_type_try<Types...>(fn)) {
return;
}
BLI_assert_unreachable();
}
template<typename... Types, typename Fn> inline bool CPPType::to_static_type_try(Fn &&fn) const
{
/* Strip any reference from Fn to normalize the type used for the static map, ensuring the
* same static is used regardless of whether fn is an lvalue or rvalue. */
using Fn_ = std::remove_reference_t<Fn>;
using Callback = void (*)(const Fn_ &);
/* Use an array indexed by #CPPType::type_index instead of a #Map for faster lookup and less
* generated code. The array can be quite a bit larger at run-time than the number of types but
* the total number of types is fairly limited, so that should be fine. */
static Array<Callback, 0> callback_array = [&]() {
/* This way to compute the max generates less code than using std::max with an initializer
* list. */
int max_type_index = 0;
((max_type_index = std::max(max_type_index, CPPType::get<Types>().type_index)), ...);
Array<Callback, 0> callback_array(max_type_index + 1, nullptr);
/* Using call_with_type_impl_ instead of a lambda due to an MSVC bug. */
((callback_array[CPPType::get<Types>().type_index] = call_with_type_impl_<Types, Fn_>), ...);
return callback_array;
}();
if (this->type_index >= callback_array.size()) {
return false;
}
const Callback callback = callback_array[this->type_index];
if (!callback) {
return false;
}
callback(fn);
return true;
}
} // namespace blender

View File

@@ -0,0 +1,498 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bli
*/
#include <sstream>
#include "BLI_cpp_type.hh"
#include "BLI_index_mask.hh"
#include "BLI_unique_hash.hh"
#include "BLI_utildefines.h"
namespace blender {
namespace cpp_type_util {
template<typename T> inline bool pointer_has_valid_alignment(const void *ptr)
{
return (uintptr_t(ptr) % alignof(T)) == 0;
}
template<typename T> inline bool pointer_can_point_to_instance(const void *ptr)
{
return ptr != nullptr && pointer_has_valid_alignment<T>(ptr);
}
template<typename T> void default_construct_cb(void *ptr)
{
BLI_assert(pointer_can_point_to_instance<T>(ptr));
new (ptr) T;
}
template<typename T> void default_construct_indices_cb(void *ptr, const IndexMask &mask)
{
BLI_assert(mask.size() == 0 || pointer_can_point_to_instance<T>(ptr));
if constexpr (std::is_trivially_constructible_v<T>) {
return;
}
mask.foreach_index_optimized<int64_t>([&](int64_t i) { new (static_cast<T *>(ptr) + i) T; });
}
template<typename T> void default_construct_n_cb(void *ptr, const int64_t n)
{
default_construct_indices_cb<T>(ptr, IndexMask(n));
}
template<typename T> void value_initialize_cb(void *ptr)
{
BLI_assert(pointer_can_point_to_instance<T>(ptr));
new (ptr) T();
}
template<typename T> void value_initialize_indices_cb(void *ptr, const IndexMask &mask)
{
BLI_assert(mask.size() == 0 || pointer_can_point_to_instance<T>(ptr));
mask.foreach_index_optimized<int64_t>([&](int64_t i) { new (static_cast<T *>(ptr) + i) T(); });
}
template<typename T> void value_initialize_n_cb(void *ptr, const int64_t n)
{
value_initialize_indices_cb<T>(ptr, IndexMask(n));
}
template<typename T> void destruct_cb(void *ptr)
{
BLI_assert(pointer_can_point_to_instance<T>(ptr));
(static_cast<T *>(ptr))->~T();
}
template<typename T> void destruct_indices_cb(void *ptr, const IndexMask &mask)
{
BLI_assert(mask.size() == 0 || pointer_can_point_to_instance<T>(ptr));
if (std::is_trivially_destructible_v<T>) {
return;
}
T *ptr_ = static_cast<T *>(ptr);
mask.foreach_index_optimized<int64_t>([&](int64_t i) { ptr_[i].~T(); });
}
template<typename T> void destruct_n_cb(void *ptr, const int64_t n)
{
destruct_indices_cb<T>(ptr, IndexMask(n));
}
template<typename T> void copy_assign_cb(const void *src, void *dst)
{
BLI_assert(pointer_can_point_to_instance<T>(src));
BLI_assert(pointer_can_point_to_instance<T>(dst));
*static_cast<T *>(dst) = *static_cast<const T *>(src);
}
template<typename T> void copy_assign_indices_cb(const void *src, void *dst, const IndexMask &mask)
{
BLI_assert(mask.size() == 0 || src != dst);
BLI_assert(mask.size() == 0 || pointer_can_point_to_instance<T>(src));
BLI_assert(mask.size() == 0 || pointer_can_point_to_instance<T>(dst));
if constexpr (std::is_trivially_copy_assignable_v<T>) {
index_mask::detail::copy_assign(static_cast<const T *>(src), mask, static_cast<T *>(dst));
}
else {
const T *src_ = static_cast<const T *>(src);
T *dst_ = static_cast<T *>(dst);
mask.foreach_index([&](int64_t i) { dst_[i] = src_[i]; });
}
}
template<typename T> void copy_assign_n_cb(const void *src, void *dst, const int64_t n)
{
copy_assign_indices_cb<T>(src, dst, IndexMask(n));
}
template<typename T>
void copy_assign_compressed_cb(const void *src, void *dst, const IndexMask &mask)
{
BLI_assert(mask.size() == 0 || src != dst);
BLI_assert(mask.size() == 0 || pointer_can_point_to_instance<T>(src));
BLI_assert(mask.size() == 0 || pointer_can_point_to_instance<T>(dst));
if constexpr (std::is_trivially_copy_assignable_v<T>) {
index_mask::detail::gather_assign(static_cast<const T *>(src), mask, static_cast<T *>(dst));
}
else {
const T *src_ = static_cast<const T *>(src);
T *dst_ = static_cast<T *>(dst);
mask.foreach_index([&](const int64_t i, const int64_t pos) { dst_[pos] = src_[i]; });
}
}
template<typename T> void copy_construct_cb(const void *src, void *dst)
{
BLI_assert(src != dst || std::is_trivially_copy_constructible_v<T>);
BLI_assert(pointer_can_point_to_instance<T>(src));
BLI_assert(pointer_can_point_to_instance<T>(dst));
uninitialized_copy_n(static_cast<const T *>(src), 1, static_cast<T *>(dst));
}
template<typename T>
void copy_construct_indices_cb(const void *src, void *dst, const IndexMask &mask)
{
BLI_assert(mask.size() == 0 || src != dst);
BLI_assert(mask.size() == 0 || pointer_can_point_to_instance<T>(src));
BLI_assert(mask.size() == 0 || pointer_can_point_to_instance<T>(dst));
const T *src_ = static_cast<const T *>(src);
T *dst_ = static_cast<T *>(dst);
mask.foreach_index_optimized<int64_t>([&](int64_t i) { new (dst_ + i) T(src_[i]); });
}
template<typename T> void copy_construct_n_cb(const void *src, void *dst, const int64_t n)
{
copy_construct_indices_cb<T>(src, dst, IndexMask(n));
}
template<typename T>
void copy_construct_compressed_cb(const void *src, void *dst, const IndexMask &mask)
{
BLI_assert(mask.size() == 0 || src != dst);
BLI_assert(mask.size() == 0 || pointer_can_point_to_instance<T>(src));
BLI_assert(mask.size() == 0 || pointer_can_point_to_instance<T>(dst));
const T *src_ = static_cast<const T *>(src);
T *dst_ = static_cast<T *>(dst);
mask.foreach_index_optimized<int64_t>(
[&](const int64_t i, const int64_t pos) { new (dst_ + pos) T(src_[i]); });
}
template<typename T> void move_assign_cb(void *src, void *dst)
{
BLI_assert(pointer_can_point_to_instance<T>(src));
BLI_assert(pointer_can_point_to_instance<T>(dst));
initialized_move_n(static_cast<T *>(src), 1, static_cast<T *>(dst));
}
template<typename T> void move_assign_indices_cb(void *src, void *dst, const IndexMask &mask)
{
BLI_assert(mask.size() == 0 || src != dst);
BLI_assert(mask.size() == 0 || pointer_can_point_to_instance<T>(src));
BLI_assert(mask.size() == 0 || pointer_can_point_to_instance<T>(dst));
T *src_ = static_cast<T *>(src);
T *dst_ = static_cast<T *>(dst);
mask.foreach_index_optimized<int64_t>([&](int64_t i) { dst_[i] = std::move(src_[i]); });
}
template<typename T> void move_assign_n_cb(void *src, void *dst, const int64_t n)
{
move_assign_indices_cb<T>(src, dst, IndexMask(n));
}
template<typename T> void move_construct_cb(void *src, void *dst)
{
BLI_assert(src != dst || std::is_trivially_move_constructible_v<T>);
BLI_assert(pointer_can_point_to_instance<T>(src));
BLI_assert(pointer_can_point_to_instance<T>(dst));
uninitialized_move_n(static_cast<T *>(src), 1, static_cast<T *>(dst));
}
template<typename T> void move_construct_indices_cb(void *src, void *dst, const IndexMask &mask)
{
BLI_assert(mask.size() == 0 || src != dst);
BLI_assert(mask.size() == 0 || pointer_can_point_to_instance<T>(src));
BLI_assert(mask.size() == 0 || pointer_can_point_to_instance<T>(dst));
T *src_ = static_cast<T *>(src);
T *dst_ = static_cast<T *>(dst);
mask.foreach_index_optimized<int64_t>([&](int64_t i) { new (dst_ + i) T(std::move(src_[i])); });
}
template<typename T> void move_construct_n_cb(void *src, void *dst, const int64_t n)
{
move_construct_indices_cb<T>(src, dst, IndexMask(n));
}
template<typename T> void relocate_assign_cb(void *src, void *dst)
{
BLI_assert(src != dst || std::is_trivially_move_constructible_v<T>);
BLI_assert(pointer_can_point_to_instance<T>(src));
BLI_assert(pointer_can_point_to_instance<T>(dst));
T *src_ = static_cast<T *>(src);
T *dst_ = static_cast<T *>(dst);
*dst_ = std::move(*src_);
src_->~T();
}
template<typename T> void relocate_assign_indices_cb(void *src, void *dst, const IndexMask &mask)
{
BLI_assert(mask.size() == 0 || src != dst);
BLI_assert(mask.size() == 0 || pointer_can_point_to_instance<T>(src));
BLI_assert(mask.size() == 0 || pointer_can_point_to_instance<T>(dst));
T *src_ = static_cast<T *>(src);
T *dst_ = static_cast<T *>(dst);
mask.foreach_index_optimized<int64_t>([&](int64_t i) {
dst_[i] = std::move(src_[i]);
src_[i].~T();
});
}
template<typename T> void relocate_assign_n_cb(void *src, void *dst, const int64_t n)
{
relocate_assign_indices_cb<T>(src, dst, IndexMask(n));
}
template<typename T> void relocate_construct_cb(void *src, void *dst)
{
BLI_assert(src != dst || std::is_trivially_move_constructible_v<T>);
BLI_assert(pointer_can_point_to_instance<T>(src));
BLI_assert(pointer_can_point_to_instance<T>(dst));
T *src_ = static_cast<T *>(src);
T *dst_ = static_cast<T *>(dst);
new (dst_) T(std::move(*src_));
src_->~T();
}
template<typename T>
void relocate_construct_indices_cb(void *src, void *dst, const IndexMask &mask)
{
BLI_assert(mask.size() == 0 || src != dst);
BLI_assert(mask.size() == 0 || pointer_can_point_to_instance<T>(src));
BLI_assert(mask.size() == 0 || pointer_can_point_to_instance<T>(dst));
T *src_ = static_cast<T *>(src);
T *dst_ = static_cast<T *>(dst);
mask.foreach_index_optimized<int64_t>([&](int64_t i) {
new (dst_ + i) T(std::move(src_[i]));
src_[i].~T();
});
}
template<typename T> void relocate_construct_n_cb(void *src, void *dst, const int64_t n)
{
relocate_construct_indices_cb<T>(src, dst, IndexMask(n));
}
template<typename T>
void fill_assign_indices_cb(const void *value, void *dst, const IndexMask &mask)
{
BLI_assert(mask.size() == 0 || pointer_can_point_to_instance<T>(value));
BLI_assert(mask.size() == 0 || pointer_can_point_to_instance<T>(dst));
if constexpr (std::is_trivially_copy_assignable_v<T>) {
index_mask::detail::fill(static_cast<T *>(dst), *static_cast<const T *>(value), mask);
}
else {
const T &value_ = *static_cast<const T *>(value);
T *dst_ = static_cast<T *>(dst);
mask.foreach_index([&](int64_t i) { dst_[i] = value_; });
}
}
template<typename T> void fill_assign_n_cb(const void *value, void *dst, const int64_t n)
{
fill_assign_indices_cb<T>(value, dst, IndexMask(n));
}
template<typename T> void fill_construct_cb(const void *value, void *dst, int64_t n)
{
const T &value_ = *static_cast<const T *>(value);
T *dst_ = static_cast<T *>(dst);
for (int64_t i = 0; i < n; i++) {
new (dst_ + i) T(value_);
}
}
template<typename T>
void fill_construct_indices_cb(const void *value, void *dst, const IndexMask &mask)
{
const T &value_ = *static_cast<const T *>(value);
T *dst_ = static_cast<T *>(dst);
mask.foreach_index_optimized<int64_t>([&](int64_t i) { new (dst_ + i) T(value_); });
}
template<typename T> void fill_construct_n_cb(const void *value, void *dst, const int64_t n)
{
fill_construct_indices_cb<T>(value, dst, IndexMask(n));
}
template<typename T> void print_cb(const void *value, std::stringstream &ss)
{
const T &value_ = *static_cast<const T *>(value);
ss << value_;
}
template<typename T> bool is_equal_cb(const void *a, const void *b)
{
BLI_assert(pointer_can_point_to_instance<T>(a));
BLI_assert(pointer_can_point_to_instance<T>(b));
const T &a_ = *static_cast<const T *>(a);
const T &b_ = *static_cast<const T *>(b);
return a_ == b_;
}
template<typename T> uint64_t hash_cb(const void *value)
{
BLI_assert(pointer_can_point_to_instance<T>(value));
const T &value_ = *static_cast<const T *>(value);
return get_default_hash(value_);
}
template<typename T> void hash_unique_cb(const void *value, UniqueHashBytes &hash)
{
BLI_assert(pointer_can_point_to_instance<T>(value));
const T &value_ = *static_cast<const T *>(value);
return hash_unique_default(value_, hash);
}
inline std::atomic<int> type_index_counter{0};
} // namespace cpp_type_util
template<typename T, CPPTypeFlags Flags>
CPPType::CPPType(TypeTag<T> /*type*/,
TypeForValue<CPPTypeFlags, Flags> /*flags*/,
const StringRef debug_name)
{
using namespace cpp_type_util;
debug_name_ = debug_name;
this->size = int64_t(sizeof(T));
this->alignment = int64_t(alignof(T));
this->is_trivial = std::is_trivial_v<T>;
this->is_trivially_destructible = std::is_trivially_destructible_v<T>;
if constexpr (std::is_default_constructible_v<T>) {
default_construct_ = default_construct_cb<T>;
default_construct_n_ = default_construct_n_cb<T>;
default_construct_indices_ = default_construct_indices_cb<T>;
value_initialize_ = value_initialize_cb<T>;
value_initialize_n_ = value_initialize_n_cb<T>;
value_initialize_indices_ = value_initialize_indices_cb<T>;
if constexpr (bool(Flags & CPPTypeFlags::IdentityDefaultValue)) {
static const T default_value = T::identity();
default_value_ = &default_value;
}
else {
static const T default_value = T();
default_value_ = &default_value;
}
}
if constexpr (std::is_destructible_v<T>) {
destruct_ = destruct_cb<T>;
destruct_n_ = destruct_n_cb<T>;
destruct_indices_ = destruct_indices_cb<T>;
}
if constexpr (std::is_copy_assignable_v<T>) {
copy_assign_ = copy_assign_cb<T>;
copy_assign_n_ = copy_assign_n_cb<T>;
copy_assign_indices_ = copy_assign_indices_cb<T>;
copy_assign_compressed_ = copy_assign_compressed_cb<T>;
}
if constexpr (std::is_copy_constructible_v<T>) {
if constexpr (std::is_trivially_copy_constructible_v<T>) {
copy_construct_ = copy_assign_;
copy_construct_n_ = copy_assign_n_;
copy_construct_indices_ = copy_assign_indices_;
copy_construct_compressed_ = copy_assign_compressed_;
}
else {
copy_construct_ = copy_construct_cb<T>;
copy_construct_n_ = copy_construct_n_cb<T>;
copy_construct_indices_ = copy_construct_indices_cb<T>;
copy_construct_compressed_ = copy_construct_compressed_cb<T>;
}
}
if constexpr (std::is_move_assignable_v<T>) {
if constexpr (std::is_trivially_move_assignable_v<T>) {
/* This casts away the const from the src pointer. This is fine for trivial types as moving
* them does not change the original value. */
move_assign_ = reinterpret_cast<decltype(move_assign_)>(copy_assign_);
move_assign_n_ = reinterpret_cast<decltype(move_assign_n_)>(copy_assign_n_);
move_assign_indices_ = reinterpret_cast<decltype(move_assign_indices_)>(
copy_assign_indices_);
}
else {
move_assign_ = move_assign_cb<T>;
move_assign_n_ = move_assign_n_cb<T>;
move_assign_indices_ = move_assign_indices_cb<T>;
}
}
if constexpr (std::is_move_constructible_v<T>) {
if constexpr (std::is_trivially_move_constructible_v<T>) {
move_construct_ = move_assign_;
move_construct_n_ = move_assign_n_;
move_construct_indices_ = move_assign_indices_;
}
else {
move_construct_ = move_construct_cb<T>;
move_construct_n_ = move_construct_n_cb<T>;
move_construct_indices_ = move_construct_indices_cb<T>;
}
}
if constexpr (std::is_destructible_v<T>) {
if constexpr (std::is_trivially_move_assignable_v<T> && std::is_trivially_destructible_v<T>) {
relocate_assign_ = move_assign_;
relocate_assign_n_ = move_assign_n_;
relocate_assign_indices_ = move_assign_indices_;
relocate_construct_ = move_assign_;
relocate_construct_n_ = move_assign_n_;
relocate_construct_indices_ = move_assign_indices_;
}
else {
if constexpr (std::is_move_assignable_v<T>) {
relocate_assign_ = relocate_assign_cb<T>;
relocate_assign_n_ = relocate_assign_n_cb<T>;
relocate_assign_indices_ = relocate_assign_indices_cb<T>;
}
if constexpr (std::is_move_constructible_v<T>) {
relocate_construct_ = relocate_construct_cb<T>;
relocate_construct_n_ = relocate_construct_n_cb<T>;
relocate_construct_indices_ = relocate_construct_indices_cb<T>;
}
}
}
if constexpr (std::is_copy_assignable_v<T>) {
fill_assign_n_ = fill_assign_n_cb<T>;
fill_assign_indices_ = fill_assign_indices_cb<T>;
}
if constexpr (std::is_copy_constructible_v<T>) {
if constexpr (std::is_trivially_constructible_v<T>) {
fill_construct_n_ = fill_assign_n_;
fill_construct_indices_ = fill_assign_indices_;
}
else {
fill_construct_n_ = fill_construct_n_cb<T>;
fill_construct_indices_ = fill_construct_indices_cb<T>;
}
}
if constexpr (bool(Flags & CPPTypeFlags::Hashable)) {
hash_ = hash_cb<T>;
hash_unique_ = hash_unique_cb<T>;
}
if constexpr (bool(Flags & CPPTypeFlags::Printable)) {
print_ = print_cb<T>;
}
if constexpr (bool(Flags & CPPTypeFlags::EqualityComparable)) {
is_equal_ = is_equal_cb<T>;
}
alignment_mask_ = uintptr_t(this->alignment) - uintptr_t(1);
this->has_special_member_functions = (default_construct_ && copy_construct_ && copy_assign_ &&
move_construct_ && move_assign_ && destruct_);
this->is_default_constructible = default_construct_ != nullptr;
this->is_copy_constructible = copy_construct_ != nullptr;
this->is_move_constructible = move_construct_ != nullptr;
this->is_destructible = destruct_ != nullptr;
this->is_copy_assignable = copy_assign_ != nullptr;
this->is_move_assignable = move_assign_ != nullptr;
this->type_index = type_index_counter++;
}
namespace detail {
template<typename T, CPPTypeFlags FLAGS> inline void register_cpp_type(const StringRef type_name)
{
static CPPType *cpp_type = new (detail::cpp_type_impl<T>.ptr())
CPPType(TypeTag<T>(), TypeForValue<CPPTypeFlags, FLAGS>(), type_name);
/* Call destructor on exit. */
struct CPPTypeDestructor {
~CPPTypeDestructor()
{
std::destroy_at(cpp_type);
}
};
static CPPTypeDestructor cpp_type_destructor;
}
} // namespace detail
/** Register a #CPPType created with #CPPType::get<T>(). */
#define BLI_CPP_TYPE_REGISTER(TYPE_NAME, FLAGS) \
blender::detail::register_cpp_type<TYPE_NAME, FLAGS>(STRINGIFY(TYPE_NAME))
} // namespace blender

View File

@@ -0,0 +1,247 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bli
*/
#include "BLI_any.hh"
#include "BLI_function_ref.hh"
#include "BLI_linear_allocator.hh"
#include "BLI_offset_indices.hh"
#include "BLI_string_ref.hh"
#include "BLI_vector.hh"
namespace blender::csv_parse {
/**
* Contains the fields of a single record of a .csv file. Usually that corresponds to a single
* line.
*/
class CsvRecord {
private:
Span<Span<char>> fields_;
public:
CsvRecord(Span<Span<char>> fields);
/** Number of fields in the record. */
int64_t size() const;
IndexRange index_range() const;
/** Get the field at the given index. Empty data is returned if the index is too large. */
Span<char> field(const int64_t index) const;
StringRef field_str(const int64_t index) const;
};
/**
* Contains the fields of multiple records.
*/
class CsvRecords {
private:
OffsetIndices<int64_t> offsets_;
Span<Span<char>> fields_;
public:
CsvRecords(OffsetIndices<int64_t> offsets, Span<Span<char>> fields);
/** Number of records (rows). */
int64_t size() const;
IndexRange index_range() const;
/** Get the record at the given index. */
CsvRecord record(const int64_t index) const;
};
struct CsvParseOptions {
/** The character that separates fields within a row. */
char delimiter = ',';
/**
* The character that can be used to enclose fields which contain the delimiter or span multiple
* lines.
*/
char quote = '"';
/**
* Characters that can be used to escape the quote character.
* By default, `""` or `\"` both represent an escaped quote.
*/
Span<char> quote_escape_chars = Span<char>(StringRef("\"\\"));
/** Approximate number of bytes per chunk that the input is split into. */
int64_t chunk_size_bytes = 64 * 1024;
};
/**
* Parses a `.csv` file. There are two important aspects to the way this interface is designed:
* 1. It allows the file to be split into chunks that can be parsed in parallel.
* 2. Splitting the file into individual records and fields is separated from parsing the actual
* content into e.g. floats. This simplifies the implementation of both parts because the
* logical parsing does not have to worry about e.g. the delimiter or quote characters. It also
* simplifies unit testing.
*
* \param buffer: The buffer containing the `.csv` file.
* \param options: Options that control how the file is parsed.
* \param process_header: A function that is called at most once and contains the fields of the
* first row/record.
* \param process_records: A function that is called potentially many times in parallel and that
* processes a chunk of parsed records. Typically this function parses raw byte fields into e.g.
* ints or floats. The result of the parsing process has to be returned. Note that under specific
* circumstances, this function may be called twice for the same records. That can happen when
* the `.csv` file contains multi-line fields which were split incorrectly at first.
* \return A vector containing the return values of the `process_records` function in the correct
* order. #std::nullopt is returned if the file was malformed, e.g.
* if it has a quoted field that is not closed.
*/
std::optional<Vector<Any<>>> parse_csv_in_chunks(
const Span<char> buffer,
const CsvParseOptions &options,
FunctionRef<void(const CsvRecord &record)> process_header,
FunctionRef<Any<>(const CsvRecords &records)> process_records);
/**
* Same as above, but uses a templated chunk type instead of using #Any which can be more
* convenient to use.
*/
template<typename ChunkT>
inline std::optional<Vector<ChunkT>> parse_csv_in_chunks(
const Span<char> buffer,
const CsvParseOptions &options,
FunctionRef<void(const CsvRecord &record)> process_header,
FunctionRef<ChunkT(const CsvRecords &records)> process_records)
{
std::optional<Vector<Any<>>> result = parse_csv_in_chunks(
buffer, options, process_header, [&](const CsvRecords &records) {
return Any<>(process_records(records));
});
if (!result.has_value()) {
return std::nullopt;
}
Vector<ChunkT> result_chunks;
result_chunks.reserve(result->size());
for (Any<> &value : *result) {
result_chunks.append(std::move(value.get<ChunkT>()));
}
return result_chunks;
}
/**
* Fields in a CSV file may contain escaped quote characters (e.g. `""` or `\"`).
* This function replaces these with just the quote character.
* The returned string may be reference the input string if it's the same.
* Otherwise the returned string is allocated in the given allocator.
*/
StringRef unescape_field(const StringRef str,
const CsvParseOptions &options,
LinearAllocator<> &allocator);
/* -------------------------------------------------------------------- */
/** \name #CsvRecord inline functions.
* \{ */
inline CsvRecord::CsvRecord(Span<Span<char>> fields) : fields_(fields) {}
inline int64_t CsvRecord::size() const
{
return fields_.size();
}
inline IndexRange CsvRecord::index_range() const
{
return fields_.index_range();
}
inline Span<char> CsvRecord::field(const int64_t index) const
{
BLI_assert(index >= 0);
if (index >= fields_.size()) {
return {};
}
return fields_[index];
}
inline StringRef CsvRecord::field_str(const int64_t index) const
{
const Span<char> value = this->field(index);
return StringRef(value.data(), value.size());
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name #CsvRecords inline functions.
* \{ */
inline CsvRecords::CsvRecords(const OffsetIndices<int64_t> offsets, const Span<Span<char>> fields)
: offsets_(offsets), fields_(fields)
{
}
inline int64_t CsvRecords::size() const
{
return offsets_.size();
}
inline IndexRange CsvRecords::index_range() const
{
return offsets_.index_range();
}
inline CsvRecord CsvRecords::record(const int64_t index) const
{
return CsvRecord(fields_.slice(offsets_[index]));
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Internal functions exposed for testing.
* \{ */
namespace detail {
/**
* Find the index that ends the current field, i.e. the index of the next delimiter of newline.
* The start index has to be the index of the first character in the field. It may also be the
* end of the field already if it is empty.
*
* \param start: The index of the first character in the field. This may also be the end of the
* field already if it is empty.
* \param delimiter: The character that ends the field.
* \return Index of the next delimiter, a newline character or the end of the buffer.
*/
int64_t find_end_of_simple_field(Span<char> buffer, int64_t start, char delimiter);
/**
* Find the index of the quote that ends the current field.
*
* \param start: The index after the opening quote.
* \param quote: The quote character that ends the field.
* \param escape_chars: The characters that may be used to escape the quote character.
* \return Index of the quote character that ends the field, or std::nullopt if the field is
* malformed and does not have an end.
*/
std::optional<int64_t> find_end_of_quoted_field(Span<char> buffer,
int64_t start,
char quote,
Span<char> escape_chars);
/**
* Finds all fields for the record starting at the given index. Typically, the record ends with a
* newline, but quoted multi-line records are supported as well.
*
* \return Index of the start of the next record or the end of the buffer. #std::nullopt is
* returned if the buffer has a malformed record at the end,
* i.e. a quoted field that is not closed.
*/
std::optional<int64_t> parse_record_fields(const Span<char> buffer,
const int64_t start,
const char delimiter,
const char quote,
const Span<char> quote_escape_chars,
Vector<Span<char>> &r_fields);
} // namespace detail
/** \} */
} // namespace blender::csv_parse

View File

@@ -0,0 +1,217 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "BLI_array.hh"
#include "BLI_math_mpq.hh"
#include "BLI_math_vector_mpq_types.hh"
#include "BLI_math_vector_types.hh"
#include "BLI_vector.hh"
namespace blender {
/** \file
* \ingroup bli
*
* This header file contains both a C interface and a C++ interface
* to the 2D Constrained Delaunay Triangulation library routine.
*/
/**
* Interface for Constrained Delaunay Triangulation (CDT) in 2D.
*
* The input is a set of vertices, edges between those vertices,
* and faces using those vertices.
* Those inputs are called "constraints". The output must contain
* those constraints, or at least edges, points, and vertices that
* may be pieced together to form the constraints. Part of the
* work of doing the CDT is to detect intersections and mergers
* among the input elements, so these routines are also useful
* for doing 2D intersection.
*
* The output is a triangulation of the plane that includes the
* constraints in the above sense, and also satisfies the
* "Delaunay condition" as modified to take into account that
* the constraints must be there: for every non-constrained edge
* in the output, there is a circle through the endpoints that
* does not contain any of the vertices directly connected to
* those endpoints. What this means in practice is that as
* much as possible the triangles look "nice" -- not too long
* and skinny.
*
* Optionally, the output can be a subset of the triangulation
* (but still containing all of the constraints), to get the
* effect of 2D intersection.
*
* The underlying method is incremental, but we need to know
* beforehand a bounding box for all of the constraints.
* This code can be extended in the future to allow for
* deletion of constraints, if there is a use in Blender
* for dynamically maintaining a triangulation.
*/
/** What triangles and edges of CDT are desired when getting output? */
enum CDT_output_type {
/** All triangles, outer boundary is convex hull. */
CDT_FULL,
/** All triangles fully enclosed by constraint edges or faces. */
CDT_INSIDE,
/**
* Like #CDT_INSIDE, but detect holes and omit those from output.
*
* Uses the "even-odd rule": a point is inside if a ray from that point crosses
* an odd number of boundary edges. This creates alternating filled/unfilled regions
* for nested or overlapping curves. Overlapping regions with the same winding
* direction will cancel out and become holes.
*/
CDT_INSIDE_WITH_HOLES,
/**
* Like #CDT_INSIDE_WITH_HOLES, but uses the **non-zero winding rule**.
*
* A point is inside if the winding number (sum of signed edge crossings) is non-zero.
* This creates "union" behavior: overlapping curves with the same winding direction
* merge together instead of creating holes.
*/
CDT_INSIDE_WITH_HOLES_NONZERO,
/** Only point, edge, and face constraints, and their intersections. */
CDT_CONSTRAINTS,
/**
* Like #CDT_CONSTRAINTS, but keep enough edges so that any output faces that came
* from input faces can be made as valid #BMesh faces in Blender: that is,
* no vertex appears more than once and no isolated holes in faces.
*/
CDT_CONSTRAINTS_VALID_BMESH,
/**
* Like #CDT_CONSTRAINTS_VALID_BMESH, but detect holes using the even-odd rule.
* See #CDT_INSIDE_WITH_HOLES for explanation of the even-odd rule.
*/
CDT_CONSTRAINTS_VALID_BMESH_WITH_HOLES,
/**
* Like #CDT_CONSTRAINTS_VALID_BMESH_WITH_HOLES, but uses non-zero winding rule.
* See #CDT_INSIDE_WITH_HOLES_NONZERO for explanation.
*/
CDT_CONSTRAINTS_VALID_BMESH_WITH_HOLES_NONZERO,
};
namespace meshintersect {
/**
* Input to Constrained Delaunay Triangulation.
* Input vertex coordinates are stored in `vert`. For the rest of the input,
* vertices are referred to by indices into that array.
* Edges and Faces are optional. If provided, they will
* appear in the output triangulation ("constraints").
* One can provide faces and not edges -- the edges
* implied by the faces will be inferred.
*
* The edges are given by pairs of vertex indices.
* The faces are given as groups of vertex indices, in counterclockwise order.
*
* The edges implied by the faces are automatically added
* and need not be put in the edges array, which is intended
* as a way to specify edges that are not part of any face.
*
* Some notes about some special cases and how they are handled:
* - Input faces can have any number of vertices greater than 2. Depending
* on the output option, ngons may be triangulated or they may remain
* as ngons.
* - Input faces may have repeated vertices. Output faces will not,
* except when the CDT_CONSTRAINTS output option is used.
* - Input faces may have edges that self-intersect, but currently the labeling
* of which output faces have which input faces may not be done correctly,
* since the labeling relies on the inside being on the left of edges
* as one traverses the face. Output faces will not self-intersect.
* - Input edges, including those implied by the input faces, may have
* zero-length or near-zero-length edges (nearness as determined by epsilon),
* but those edges will not be in the output.
* - Input edges (including face edges) can overlap or nearly overlap each other.
* The output edges will not overlap, but instead be divided into as many
* edges as necessary to represent each overlap regime.
* - Input vertices may be coincide with, or nearly coincide with (as determined
* by epsilon) other input vertices. Only one representative will survive
* in the output. If an input vertex is within epsilon of an edge (including
* an added triangulation edge), it will be snapped to that edge, so the
* output coordinates may not exactly match the input coordinates in all cases.
* - Wire edges (those not part of faces) and isolated vertices are allowed in
* the input. If they are inside faces, they will be incorporated into the
* triangulation of those faces.
*
* Epsilon is used for "is it near enough" distance calculations.
* If zero is supplied for epsilon, an internal value of 1e-8 used
* instead, since this code will not work correctly if it is not allowed
* to merge "too near" vertices.
*
* Normally the output will contain mappings from outputs to inputs.
* If this is not needed, set need_ids to false and the execution may be much
* faster in some circumstances.
*/
template<typename T> class CDT_input {
public:
Array<VecBase<T, 2>> vert;
Array<std::pair<int, int>> edge;
Array<Vector<int>> face;
T epsilon{0};
bool need_ids{true};
};
/**
* A representation of the triangulation for output.
* See #CDT_input for the representation of the output
* vertices, edges, and faces, all represented in
* a similar way to the input.
*
* The output may have merged some input vertices together,
* if they were closer than some epsilon distance.
* The output edges may be overlapping sub-segments of some
* input edges; or they may be new edges for the triangulation.
* The output faces may be pieces of some input faces, or they
* may be new.
*
* Extra outputs are used to represent the output to input
* mapping of vertices, edges, and faces.
* These are only set if need_ids is true in the input.
*
*
* For edges, the edge_orig triple can also say which original face
* edge is part of a given output edge. See the comment below for how
* to decode the entries in the edge_orig table.
*
* \note Regarding `uint32_t`: Each input face reserves a block of IDs
* to encode its edges. These blocks stack up with the number of faces,
* so `uint32_t` is used to provide sufficient range (see #153708).
*/
template<typename T> class CDT_result {
public:
Array<VecBase<T, 2>> vert;
Array<std::pair<int, int>> edge;
Array<Vector<int>> face;
/* The orig vectors are only populated if the need_ids input field is true. */
/** For each output vert, which input verts correspond to it? */
Array<Vector<uint32_t>> vert_orig;
/**
* For each output edge, which input edges does it overlap?
* The input edge ids are encoded as follows:
* if the value is less than face_edge_offset, then it is
* an index into the input edge[] array.
* else let (a, b) = the quotient and remainder of dividing
* the edge index by face_edge_offset; "a" will be the input face + 1,
* and "b" will be a position within that face.
*/
Array<Vector<uint32_t>> edge_orig;
/** For each output face, which original faces does it overlap? */
Array<Vector<uint32_t>> face_orig;
/** Used to encode edge_orig (see above). */
uint32_t face_edge_offset;
};
CDT_result<double> delaunay_2d_calc(const CDT_input<double> &input, CDT_output_type output_type);
#ifdef WITH_GMP
CDT_result<mpq_class> delaunay_2d_calc(const CDT_input<mpq_class> &input,
CDT_output_type output_type);
#endif
} // namespace meshintersect
} // namespace blender

View File

@@ -0,0 +1,169 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bli
*
* In geometry nodes, many functions accept fields as inputs. For the implementation that means
* that the inputs are virtual arrays. Usually those are backed by actual arrays or single values
* but sometimes virtual arrays are used to compute values on demand or convert between data
* formats.
*
* Using virtual arrays has the downside that individual elements are accessed through a virtual
* method call, which has some overhead compared to normal array access. Whether this overhead is
* negligible depends on the context. For very small functions (e.g. a single addition), the
* overhead can make the function many times slower. Furthermore, it prevents the compiler from
* doing some optimizations (e.g. loop unrolling and inserting SIMD instructions).
*
* The solution is to "devirtualize" the virtual arrays in cases when the overhead cannot be
* ignored. That means that the function is instantiated multiple times at compile time for the
* different cases. For example, there can be an optimized function that adds a span and a single
* value, and another function that adds a span and another span. At run-time there is a dynamic
* dispatch that executes the best function given the specific virtual arrays.
*
* The problem with this devirtualization is that it can result in exponentially increasing compile
* times and binary sizes, depending on the number of parameters that are devirtualized separately.
* So there is always a trade-off between run-time performance and compile-time/binary-size.
*
* This file provides a utility to devirtualize function parameters using a high level API. This
* makes it easy to experiment with different extremes of the mentioned trade-off and allows
* finding a good compromise for each function.
*/
#include <cstdlib>
#include <tuple>
namespace blender {
/**
* Calls the given function with devirtualized parameters if possible. Note that using many
* non-trivial devirtualizers results in exponential code growth.
*
* \return True if the function has been called.
*
* Every devirtualizer is expected to have a `devirtualize(auto fn) -> bool` method.
* This method is expected to do one of two things:
* - Call `fn` with the devirtualized argument and return what `fn` returns.
* - Don't call `fn` (because the devirtualization failed) and return false.
*
* Examples for devirtualizers: #BasicDevirtualizer, #VArrayDevirtualizer.
*/
template<typename Fn, typename... Devirtualizers>
inline bool call_with_devirtualized_parameters(const std::tuple<Devirtualizers...> &devis,
const Fn &fn)
{
/* In theory the code below could be generalized to avoid code duplication. However, the maximum
* number of parameters is expected to be relatively low. Explicitly implementing the different
* cases makes it more obvious to see what is going on and also makes inlining everything easier
* for the compiler. */
constexpr size_t DeviNum = sizeof...(Devirtualizers);
if constexpr (DeviNum == 0) {
fn();
return true;
}
if constexpr (DeviNum == 1) {
return std::get<0>(devis).devirtualize([&](auto param0) {
fn(param0);
return true;
});
}
if constexpr (DeviNum == 2) {
return std::get<0>(devis).devirtualize([&](auto &&param0) {
return std::get<1>(devis).devirtualize([&](auto &&param1) {
fn(param0, param1);
return true;
});
});
}
if constexpr (DeviNum == 3) {
return std::get<0>(devis).devirtualize([&](auto &&param0) {
return std::get<1>(devis).devirtualize([&](auto &&param1) {
return std::get<2>(devis).devirtualize([&](auto &&param2) {
fn(param0, param1, param2);
return true;
});
});
});
}
if constexpr (DeviNum == 4) {
return std::get<0>(devis).devirtualize([&](auto &&param0) {
return std::get<1>(devis).devirtualize([&](auto &&param1) {
return std::get<2>(devis).devirtualize([&](auto &&param2) {
return std::get<3>(devis).devirtualize([&](auto &&param3) {
fn(param0, param1, param2, param3);
return true;
});
});
});
});
}
if constexpr (DeviNum == 5) {
return std::get<0>(devis).devirtualize([&](auto &&param0) {
return std::get<1>(devis).devirtualize([&](auto &&param1) {
return std::get<2>(devis).devirtualize([&](auto &&param2) {
return std::get<3>(devis).devirtualize([&](auto &&param3) {
return std::get<4>(devis).devirtualize([&](auto &&param4) {
fn(param0, param1, param2, param3, param4);
return true;
});
});
});
});
});
}
if constexpr (DeviNum == 6) {
return std::get<0>(devis).devirtualize([&](auto &&param0) {
return std::get<1>(devis).devirtualize([&](auto &&param1) {
return std::get<2>(devis).devirtualize([&](auto &&param2) {
return std::get<3>(devis).devirtualize([&](auto &&param3) {
return std::get<4>(devis).devirtualize([&](auto &&param4) {
return std::get<5>(devis).devirtualize([&](auto &&param5) {
fn(param0, param1, param2, param3, param4, param5);
return true;
});
});
});
});
});
});
}
if constexpr (DeviNum == 7) {
return std::get<0>(devis).devirtualize([&](auto &&param0) {
return std::get<1>(devis).devirtualize([&](auto &&param1) {
return std::get<2>(devis).devirtualize([&](auto &&param2) {
return std::get<3>(devis).devirtualize([&](auto &&param3) {
return std::get<4>(devis).devirtualize([&](auto &&param4) {
return std::get<5>(devis).devirtualize([&](auto &&param5) {
return std::get<6>(devis).devirtualize([&](auto &&param6) {
fn(param0, param1, param2, param3, param4, param5, param6);
return true;
});
});
});
});
});
});
});
}
return false;
}
/**
* A devirtualizer to be used with #call_with_devirtualized_parameters.
*
* This one is very simple, it does not perform any actual devirtualization. It can be used to pass
* parameters to the function that shouldn't be devirtualized.
*/
template<typename T> struct BasicDevirtualizer {
const T value;
template<typename Fn> bool devirtualize(const Fn &fn) const
{
return fn(this->value);
}
};
} // namespace blender

View File

@@ -0,0 +1,45 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bli
*
* \note dials act similar to old rotation based phones and output an angle.
*
* They just are initialized with the center of the dial and a threshold value as input.
*
* When the distance of the current position of the dial from the center
* exceeds the threshold, this position is used to calculate the initial direction.
* After that, the angle from the initial direction is calculated based on
* current and previous directions of the digit, and returned to the user.
*
* Usage examples:
*
* \code{.c}
* float start_position[2] = {0.0f, 0.0f};
* float current_position[2];
* float threshold = 0.5f;
* float angle;
* Dial *dial;
*
* dial = BLI_dial_init(start_position, threshold);
*
* angle = BLI_dial_angle(dial, current_position);
*
* BLI_dial_free(dial);
* \endcode
*/
namespace blender {
struct Dial;
Dial *BLI_dial_init(const float start_position[2], float threshold);
void BLI_dial_free(Dial *dial);
float BLI_dial_angle(Dial *dial, const float current_position[2]);
} // namespace blender

View File

@@ -0,0 +1,110 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bli
*
* This implements the disjoint set data structure with path compression and union by rank.
*/
#include "BLI_array.hh"
#include "BLI_index_range.hh"
namespace blender {
template<typename T = int64_t> class DisjointSet {
private:
Array<T> parents_;
Array<T> ranks_;
public:
/**
* Create a new disjoint set with the given size. Initially, every element is in a separate set.
*/
DisjointSet(const int64_t size) : parents_(size), ranks_(size, 0)
{
BLI_assert(size >= 0);
for (const int64_t i : IndexRange(size)) {
parents_[i] = T(i);
}
}
/**
* Join the sets containing elements x and y. Nothing happens when they have been in the same set
* before. Shared root is returned.
*/
T join(const T x, const T y)
{
T root1 = this->find_root(x);
T root2 = this->find_root(y);
/* x and y are in the same set already. */
if (root1 == root2) {
return root1;
}
/* Implement union by rank heuristic. */
if (ranks_[root1] < ranks_[root2]) {
std::swap(root1, root2);
}
parents_[root2] = root1;
if (ranks_[root1] == ranks_[root2]) {
ranks_[root1]++;
}
return root1;
}
/**
* Return true when x and y are in the same set.
*/
bool in_same_set(const T x, const T y)
{
T root1 = this->find_root(x);
T root2 = this->find_root(y);
return root1 == root2;
}
/**
* Find the element that represents the set containing x currently.
*/
T find_root(const T x)
{
/* Find root by following parents. */
T root = x;
while (parents_[root] != root) {
root = parents_[root];
}
/* Compress path. */
T to_root = x;
while (parents_[to_root] != root) {
const T parent = parents_[to_root];
parents_[to_root] = root;
to_root = parent;
}
return root;
}
/**
* Same as above but intended to be threadsafe. Better to use even if this is expected to be not
* an issue.
*/
T find_root(const T x) const
{
/* Find root by following parents. */
T root = x;
while (parents_[root] != root) {
root = parents_[root];
}
return root;
}
};
} // namespace blender

View File

@@ -0,0 +1,298 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bli
*
* Language grammar: https://www.graphviz.org/doc/info/lang.html
* Attributes: https://www.graphviz.org/doc/info/attrs.html
* Node Shapes: https://www.graphviz.org/doc/info/shapes.html
* Preview: https://dreampuf.github.io/GraphvizOnline
*/
#pragma once
#include "BLI_map.hh"
#include "BLI_set.hh"
#include "BLI_utility_mixins.hh"
#include "BLI_vector.hh"
#include "BLI_dot_export_attribute_enums.hh"
#include <iosfwd>
#include <optional>
namespace blender::dot_export {
class Graph;
class DirectedGraph;
class UndirectedGraph;
class Node;
class NodePort;
class DirectedEdge;
class UndirectedEdge;
class Cluster;
class Attributes {
private:
Map<std::string, std::string> attributes_;
public:
void export__as_bracket_list(std::stringstream &ss) const;
void set(StringRef key, StringRef value)
{
attributes_.add_overwrite(key, value);
}
void set(StringRef key, float value)
{
attributes_.add_overwrite(key, std::to_string(value));
}
};
class Graph {
private:
Vector<std::unique_ptr<Node>> nodes_;
Vector<std::unique_ptr<Cluster>> clusters_;
Set<Node *> top_level_nodes_;
Set<Cluster *> top_level_clusters_;
friend Cluster;
friend Node;
public:
Attributes attributes;
Node &new_node(StringRef label);
Cluster &new_cluster(StringRef label = "");
void export__declare_nodes_and_clusters(std::stringstream &ss) const;
void set_rankdir(Attr_rankdir rankdir)
{
attributes.set("rankdir", rankdir_to_string(rankdir));
}
void set_random_cluster_bgcolors();
};
class Cluster {
private:
Graph &graph_;
Cluster *parent_ = nullptr;
Set<Cluster *> children_;
Set<Node *> nodes_;
friend Graph;
friend Node;
public:
Attributes attributes;
Cluster(Graph &graph) : graph_(graph) {}
void export__declare_nodes_and_clusters(std::stringstream &ss) const;
std::string name() const
{
return "cluster_" + std::to_string(uintptr_t(this));
}
void set_parent_cluster(Cluster *new_parent);
void set_parent_cluster(Cluster &cluster)
{
this->set_parent_cluster(&cluster);
}
Cluster *parent_cluster()
{
return parent_;
}
void set_random_cluster_bgcolors();
bool contains(Node &node) const;
};
class Node {
private:
Graph &graph_;
Cluster *cluster_ = nullptr;
friend Graph;
public:
Attributes attributes;
Node(Graph &graph) : graph_(graph) {}
void set_parent_cluster(Cluster *cluster);
void set_parent_cluster(Cluster &cluster)
{
this->set_parent_cluster(&cluster);
}
Cluster *parent_cluster()
{
return cluster_;
}
void set_shape(Attr_shape shape)
{
attributes.set("shape", shape_to_string(shape));
}
/* See https://www.graphviz.org/doc/info/attrs.html#k:color. */
void set_background_color(StringRef name)
{
attributes.set("fillcolor", name);
attributes.set("style", "filled");
}
void export__as_id(std::stringstream &ss) const;
void export__as_declaration(std::stringstream &ss) const;
};
class UndirectedGraph final : public Graph {
private:
Vector<std::unique_ptr<UndirectedEdge>> edges_;
public:
std::string to_dot_string() const;
UndirectedEdge &new_edge(NodePort a, NodePort b);
};
class DirectedGraph final : public Graph {
private:
Vector<std::unique_ptr<DirectedEdge>> edges_;
public:
std::string to_dot_string() const;
DirectedEdge &new_edge(NodePort from, NodePort to);
};
class NodePort {
private:
Node *node_;
std::optional<std::string> port_name_;
std::optional<std::string> port_position_;
public:
NodePort(Node &node,
std::optional<std::string> port_name = {},
std::optional<std::string> port_position = {})
: node_(&node), port_name_(std::move(port_name)), port_position_(std::move(port_position))
{
}
void to_dot_string(std::stringstream &ss) const;
};
class Edge : NonCopyable, NonMovable {
protected:
NodePort a_;
NodePort b_;
public:
Attributes attributes;
Edge(NodePort a, NodePort b) : a_(std::move(a)), b_(std::move(b)) {}
void set_arrowhead(Attr_arrowType type)
{
attributes.set("arrowhead", arrowType_to_string(type));
}
void set_arrowtail(Attr_arrowType type)
{
attributes.set("arrowtail", arrowType_to_string(type));
}
void set_dir(Attr_dirType type)
{
attributes.set("dir", dirType_to_string(type));
}
void set_label(StringRef label)
{
attributes.set("label", label);
}
};
class DirectedEdge : public Edge {
public:
DirectedEdge(NodePort from, NodePort to) : Edge(std::move(from), std::move(to)) {}
void export__as_edge_statement(std::stringstream &ss) const;
};
class UndirectedEdge : public Edge {
public:
UndirectedEdge(NodePort a, NodePort b) : Edge(std::move(a), std::move(b)) {}
void export__as_edge_statement(std::stringstream &ss) const;
};
std::string color_attr_from_hsv(float h, float s, float v);
struct NodeWithSockets {
struct Socket {
std::string name;
std::optional<std::string> fontcolor;
};
struct Input : public Socket {};
struct Output : public Socket {};
std::string node_name;
Vector<Input> inputs;
Vector<Output> outputs;
Input &add_input(std::string name)
{
this->inputs.append({});
Input &input = this->inputs.last();
input.name = std::move(name);
return input;
}
Output &add_output(std::string name)
{
this->outputs.append({});
Output &output = this->outputs.last();
output.name = std::move(name);
return output;
}
};
class NodeWithSocketsRef {
private:
Node *node_;
public:
NodeWithSocketsRef(Node &node, const NodeWithSockets &data);
Node &node()
{
return *node_;
}
NodePort input(int index) const
{
std::string port = "\"in" + std::to_string(index) + "\"";
return NodePort(*node_, port, "w");
}
NodePort output(int index) const
{
std::string port = "\"out" + std::to_string(index) + "\"";
return NodePort(*node_, port, "e");
}
};
} // namespace blender::dot_export

View File

@@ -0,0 +1,112 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bli
*/
#pragma once
#include "BLI_string_ref.hh"
namespace blender::dot_export {
enum class Attr_rankdir {
LeftToRight,
TopToBottom,
};
inline StringRef rankdir_to_string(Attr_rankdir value)
{
switch (value) {
case Attr_rankdir::LeftToRight:
return "LR";
case Attr_rankdir::TopToBottom:
return "TB";
}
return "";
}
enum class Attr_shape {
Rectangle,
Ellipse,
Circle,
Point,
Diamond,
Square,
};
inline StringRef shape_to_string(Attr_shape value)
{
switch (value) {
case Attr_shape::Rectangle:
return "rectangle";
case Attr_shape::Ellipse:
return "ellipse";
case Attr_shape::Circle:
return "circle";
case Attr_shape::Point:
return "point";
case Attr_shape::Diamond:
return "diamond";
case Attr_shape::Square:
return "square";
}
return "";
}
enum class Attr_arrowType {
Normal,
Inv,
Dot,
None,
Empty,
Box,
Vee,
};
inline StringRef arrowType_to_string(Attr_arrowType value)
{
switch (value) {
case Attr_arrowType::Normal:
return "normal";
case Attr_arrowType::Inv:
return "inv";
case Attr_arrowType::Dot:
return "dot";
case Attr_arrowType::None:
return "none";
case Attr_arrowType::Empty:
return "empty";
case Attr_arrowType::Box:
return "box";
case Attr_arrowType::Vee:
return "vee";
}
return "";
}
enum class Attr_dirType {
Forward,
Back,
Both,
None,
};
inline StringRef dirType_to_string(Attr_dirType value)
{
switch (value) {
case Attr_dirType::Forward:
return "forward";
case Attr_dirType::Back:
return "back";
case Attr_dirType::Both:
return "both";
case Attr_dirType::None:
return "none";
}
return "";
}
} // namespace blender::dot_export

View File

@@ -0,0 +1,62 @@
/* SPDX-FileCopyrightText: 2026 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bli
*/
#include "BLI_assert.h"
#include "MEM_guardedalloc.h"
namespace blender {
/**
* A dynamic stack buffer can be used instead of #alloca when one wants to allocate a dynamic
* amount of memory on the stack. Using this class has some advantages:
* - It falls back to heap allocation, when the size is too large.
* - It can be used in loops safely.
* - If the buffer is heap allocated, it is free automatically in the destructor.
*/
template<size_t ReservedSize = 64, size_t ReservedAlignment = 64>
class alignas(ReservedAlignment) DynamicStackBuffer {
private:
/* Don't create an empty array. This causes problems with some compilers. */
char reserved_buffer_[(ReservedSize > 0) ? ReservedSize : 1];
void *buffer_;
public:
DynamicStackBuffer(const int64_t size, const int64_t alignment)
{
BLI_assert(size >= 0);
BLI_assert(alignment >= 0);
if (size <= ReservedSize && alignment <= ReservedAlignment) {
buffer_ = reserved_buffer_;
}
else {
buffer_ = MEM_new_uninitialized_aligned(size, alignment, __func__);
}
}
~DynamicStackBuffer()
{
if (buffer_ != reserved_buffer_) {
MEM_delete_void(buffer_);
}
}
/* Don't allow any copying or moving of this type. */
DynamicStackBuffer(const DynamicStackBuffer &other) = delete;
DynamicStackBuffer(DynamicStackBuffer &&other) = delete;
DynamicStackBuffer &operator=(const DynamicStackBuffer &other) = delete;
DynamicStackBuffer &operator=(DynamicStackBuffer &&other) = delete;
void *buffer() const
{
return buffer_;
}
};
} // namespace blender

View File

@@ -0,0 +1,106 @@
/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bli
* \brief A dynamically sized string ADT.
* \section aboutdynstr Dynamic String
* This ADT is designed purely for dynamic string creation
* through appending, not for general usage, the intent is
* to build up dynamic strings using a DynStr object, then
* convert it to a c-string and work with that.
*/
#include <stdarg.h>
#include "BLI_compiler_attrs.h"
namespace blender {
struct DynStr;
/** The abstract DynStr type. */
struct DynStr;
/**
* Create a new #DynStr.
*
* \return Pointer to a new #DynStr.
*/
DynStr *BLI_dynstr_new() ATTR_MALLOC ATTR_WARN_UNUSED_RESULT;
/**
* Create a new #DynStr.
*
* \return Pointer to a new #DynStr.
*/
DynStr *BLI_dynstr_new_memarena() ATTR_MALLOC ATTR_WARN_UNUSED_RESULT;
/**
* Append a c-string to a #DynStr.
*
* \param ds: The #DynStr to append to.
* \param cstr: The c-string to append.
*/
void BLI_dynstr_append(DynStr *__restrict ds, const char *cstr) ATTR_NONNULL();
/**
* Append a length clamped c-string to a #DynStr.
*
* \param ds: The #DynStr to append to.
* \param cstr: The c-string to append.
* \param len: The maximum length of the c-string to copy.
*/
void BLI_dynstr_nappend(DynStr *__restrict ds, const char *cstr, int len) ATTR_NONNULL();
/**
* Append a c-string to a #DynStr, but with formatting like `printf`.
*
* \param ds: The #DynStr to append to.
* \param format: The `printf` format string to use.
*/
void BLI_dynstr_appendf(DynStr *__restrict ds, const char *__restrict format, ...)
ATTR_PRINTF_FORMAT(2, 3) ATTR_NONNULL(1, 2);
void BLI_dynstr_vappendf(DynStr *__restrict ds, const char *__restrict format, va_list args)
ATTR_PRINTF_FORMAT(2, 0) ATTR_NONNULL(1, 2);
/**
* Find the length of a #DynStr.
*
* \param ds: The #DynStr of interest.
* \return The length of \a ds.
*/
int BLI_dynstr_get_len(const DynStr *ds) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/**
* Get a #DynStr's contents as a c-string.
* \return The c-string which must be freed using #MEM_delete.
*
* \param ds: The #DynStr of interest.
* \return The contents of \a ds as a c-string.
*/
char *BLI_dynstr_get_cstring(const DynStr *ds) ATTR_MALLOC ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/**
* Get a #DynStr's contents as a c-string.
* The \a rets argument must be allocated to be at
* least the size of `BLI_dynstr_get_len(ds) + 1`.
*
* \param ds: The DynStr of interest.
* \param rets: The string to fill.
*/
void BLI_dynstr_get_cstring_ex(const DynStr *__restrict ds, char *__restrict rets) ATTR_NONNULL();
/**
* Clear the #DynStr
*
* \param ds: The DynStr to clear.
*/
void BLI_dynstr_clear(DynStr *ds) ATTR_NONNULL();
/**
* Free the #DynStr
*
* \param ds: The DynStr to free.
*/
void BLI_dynstr_free(DynStr *ds) ATTR_NONNULL();
} // namespace blender

View File

@@ -0,0 +1,51 @@
/* SPDX-FileCopyrightText: 2001 Robert Penner. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause */
#pragma once
/** \file
* \ingroup bli
*/
namespace blender {
float BLI_easing_back_ease_in(
float time, float begin, float change, float duration, float overshoot);
float BLI_easing_back_ease_out(
float time, float begin, float change, float duration, float overshoot);
float BLI_easing_back_ease_in_out(
float time, float begin, float change, float duration, float overshoot);
float BLI_easing_bounce_ease_out(float time, float begin, float change, float duration);
float BLI_easing_bounce_ease_in(float time, float begin, float change, float duration);
float BLI_easing_bounce_ease_in_out(float time, float begin, float change, float duration);
float BLI_easing_circ_ease_in(float time, float begin, float change, float duration);
float BLI_easing_circ_ease_out(float time, float begin, float change, float duration);
float BLI_easing_circ_ease_in_out(float time, float begin, float change, float duration);
float BLI_easing_cubic_ease_in(float time, float begin, float change, float duration);
float BLI_easing_cubic_ease_out(float time, float begin, float change, float duration);
float BLI_easing_cubic_ease_in_out(float time, float begin, float change, float duration);
float BLI_easing_elastic_ease_in(
float time, float begin, float change, float duration, float amplitude, float period);
float BLI_easing_elastic_ease_out(
float time, float begin, float change, float duration, float amplitude, float period);
float BLI_easing_elastic_ease_in_out(
float time, float begin, float change, float duration, float amplitude, float period);
float BLI_easing_expo_ease_in(float time, float begin, float change, float duration);
float BLI_easing_expo_ease_out(float time, float begin, float change, float duration);
float BLI_easing_expo_ease_in_out(float time, float begin, float change, float duration);
float BLI_easing_linear_ease(float time, float begin, float change, float duration);
float BLI_easing_quad_ease_in(float time, float begin, float change, float duration);
float BLI_easing_quad_ease_out(float time, float begin, float change, float duration);
float BLI_easing_quad_ease_in_out(float time, float begin, float change, float duration);
float BLI_easing_quart_ease_in(float time, float begin, float change, float duration);
float BLI_easing_quart_ease_out(float time, float begin, float change, float duration);
float BLI_easing_quart_ease_in_out(float time, float begin, float change, float duration);
float BLI_easing_quint_ease_in(float time, float begin, float change, float duration);
float BLI_easing_quint_ease_out(float time, float begin, float change, float duration);
float BLI_easing_quint_ease_in_out(float time, float begin, float change, float duration);
float BLI_easing_sine_ease_in(float time, float begin, float change, float duration);
float BLI_easing_sine_ease_out(float time, float begin, float change, float duration);
float BLI_easing_sine_ease_in_out(float time, float begin, float change, float duration);
} // namespace blender

View File

@@ -0,0 +1,26 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bli
*/
/* NOTE: these names are historic and could use a more generic prefix.
* This could be done as part of a bigger refactor. */
/** ENDIAN_ORDER: indicates what endianness the platform where the file was written had. */
#if !defined(__BIG_ENDIAN__) && !defined(__LITTLE_ENDIAN__)
# error Either __BIG_ENDIAN__ or __LITTLE_ENDIAN__ must be defined.
#endif
#define L_ENDIAN 1
#define B_ENDIAN 0
#ifdef __BIG_ENDIAN__
# define ENDIAN_ORDER B_ENDIAN
#else
# define ENDIAN_ORDER L_ENDIAN
#endif

View File

@@ -0,0 +1,44 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/* Use a define instead of `#pragma once` because of `BLI_endian_switch_inline.h` */
#ifndef __BLI_ENDIAN_SWITCH_H__
#define __BLI_ENDIAN_SWITCH_H__
/** \file
* \ingroup bli
*/
#include "BLI_compiler_attrs.h"
#include "BLI_utildefines.h"
/* BLI_endian_switch_inline.h */
namespace blender {
BLI_INLINE void BLI_endian_switch_int16(short *val) ATTR_NONNULL(1);
BLI_INLINE void BLI_endian_switch_uint16(unsigned short *val) ATTR_NONNULL(1);
BLI_INLINE void BLI_endian_switch_int32(int *val) ATTR_NONNULL(1);
BLI_INLINE void BLI_endian_switch_uint32(unsigned int *val) ATTR_NONNULL(1);
BLI_INLINE void BLI_endian_switch_float(float *val) ATTR_NONNULL(1);
BLI_INLINE void BLI_endian_switch_int64(int64_t *val) ATTR_NONNULL(1);
BLI_INLINE void BLI_endian_switch_uint64(uint64_t *val) ATTR_NONNULL(1);
BLI_INLINE void BLI_endian_switch_double(double *val) ATTR_NONNULL(1);
/* endian_switch.c */
void BLI_endian_switch_int16_array(short *val, int size) ATTR_NONNULL(1);
void BLI_endian_switch_uint16_array(unsigned short *val, int size) ATTR_NONNULL(1);
void BLI_endian_switch_int32_array(int *val, int size) ATTR_NONNULL(1);
void BLI_endian_switch_uint32_array(unsigned int *val, int size) ATTR_NONNULL(1);
void BLI_endian_switch_float_array(float *val, int size) ATTR_NONNULL(1);
void BLI_endian_switch_int64_array(int64_t *val, int size) ATTR_NONNULL(1);
void BLI_endian_switch_uint64_array(uint64_t *val, int size) ATTR_NONNULL(1);
void BLI_endian_switch_double_array(double *val, int size) ATTR_NONNULL(1);
} // namespace blender
#include "BLI_endian_switch_inline.h"
#endif /* __BLI_ENDIAN_SWITCH_H__ */

View File

@@ -0,0 +1,83 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "BLI_compiler_compat.h"
#include "BLI_sys_types.h"
/* only include from header */
#ifndef __BLI_ENDIAN_SWITCH_H__
# error "this file isn't to be directly included"
#endif
/** \file
* \ingroup bli
*/
namespace blender {
/* NOTE: using a temp char to switch endian is a lot slower,
* use bit shifting instead. */
/* *** 16 *** */
BLI_INLINE void BLI_endian_switch_int16(short *val)
{
BLI_endian_switch_uint16(reinterpret_cast<unsigned short *>(val));
}
BLI_INLINE void BLI_endian_switch_uint16(unsigned short *val)
{
#ifdef __GNUC__
*val = __builtin_bswap16(*val);
#else
unsigned short tval = *val;
*val = (tval >> 8) | (tval << 8);
#endif
}
/* *** 32 *** */
BLI_INLINE void BLI_endian_switch_int32(int *val)
{
BLI_endian_switch_uint32(reinterpret_cast<unsigned int *>(val));
}
BLI_INLINE void BLI_endian_switch_uint32(unsigned int *val)
{
#ifdef __GNUC__
*val = __builtin_bswap32(*val);
#else
unsigned int tval = *val;
*val = ((tval >> 24)) | ((tval << 8) & 0x00ff0000) | ((tval >> 8) & 0x0000ff00) | ((tval << 24));
#endif
}
BLI_INLINE void BLI_endian_switch_float(float *val)
{
BLI_endian_switch_uint32(reinterpret_cast<unsigned int *>(val));
}
/* *** 64 *** */
BLI_INLINE void BLI_endian_switch_int64(int64_t *val)
{
BLI_endian_switch_uint64(reinterpret_cast<uint64_t *>(val));
}
BLI_INLINE void BLI_endian_switch_uint64(uint64_t *val)
{
#ifdef __GNUC__
*val = __builtin_bswap64(*val);
#else
uint64_t tval = *val;
*val = ((tval >> 56)) | ((tval << 40) & 0x00ff000000000000ll) |
((tval << 24) & 0x0000ff0000000000ll) | ((tval << 8) & 0x000000ff00000000ll) |
((tval >> 8) & 0x00000000ff000000ll) | ((tval >> 24) & 0x0000000000ff0000ll) |
((tval >> 40) & 0x000000000000ff00ll) | ((tval << 56));
#endif
}
BLI_INLINE void BLI_endian_switch_double(double *val)
{
BLI_endian_switch_uint64(reinterpret_cast<uint64_t *>(val));
}
} // namespace blender

View File

@@ -0,0 +1,85 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bli
*/
#ifdef __cplusplus
# include <cstdint>
namespace blender {
/**
* Used below, to represent a ~enum_value in a way that is enum type safe and
* avoids UBSAN warnings about invalid enum values in (a & ~b) constructs.
*/
template<typename T> struct BitwiseNotEnumValue {
uint64_t value;
operator uint64_t() const
{
return value;
}
};
} // namespace blender
/* Use for enum classes that represent bit flags.
* Defines logical operators to combine and mask the flag values.
*
* Note that negation/inversion operator (~) flips all the bits, so the result can contain
* set bits that are not part of the enum values. However that is fine in typical
* inversion operator usage, which is often for masking out bits (`a & ~b`). */
# define ENUM_OPERATORS(_enum_type) \
[[maybe_unused]] [[nodiscard]] inline constexpr _enum_type operator|(_enum_type a, \
_enum_type b) \
{ \
return (_enum_type)(uint64_t(a) | uint64_t(b)); \
} \
[[maybe_unused]] [[nodiscard]] inline constexpr _enum_type operator&(_enum_type a, \
_enum_type b) \
{ \
return (_enum_type)(uint64_t(a) & uint64_t(b)); \
} \
[[maybe_unused]] [[nodiscard]] inline constexpr _enum_type operator&( \
_enum_type a, ::blender::BitwiseNotEnumValue<_enum_type> b) \
{ \
return (_enum_type)(uint64_t(a) & uint64_t(b.value)); \
} \
[[maybe_unused]] [[nodiscard]] inline constexpr ::blender::BitwiseNotEnumValue<_enum_type> \
operator~(_enum_type a) \
{ \
::blender::BitwiseNotEnumValue<_enum_type> result = {~uint64_t(a)}; \
return result; \
} \
[[maybe_unused]] inline _enum_type &operator|=(_enum_type &a, _enum_type b) \
{ \
return a = (_enum_type)(uint64_t(a) | uint64_t(b)); \
} \
[[maybe_unused]] inline _enum_type &operator&=(_enum_type &a, _enum_type b) \
{ \
return a = (_enum_type)(uint64_t(a) & uint64_t(b)); \
} \
[[maybe_unused]] inline _enum_type &operator&=(_enum_type &a, \
::blender::BitwiseNotEnumValue<_enum_type> b) \
{ \
return a = (_enum_type)(uint64_t(a) & uint64_t(b.value)); \
} \
[[maybe_unused]] inline _enum_type &operator^=(_enum_type &a, _enum_type b) \
{ \
return a = (_enum_type)(uint64_t(a) ^ uint64_t(b)); \
} \
[[maybe_unused]] [[nodiscard]] inline constexpr bool flag_is_set(_enum_type flags, \
_enum_type flag_to_test) \
{ \
return (uint64_t(flags) & uint64_t(flag_to_test)) != 0; \
}
#else
# define ENUM_OPERATORS(_enum_type)
#endif

View File

@@ -0,0 +1,112 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bli
*/
#pragma once
#ifdef WITH_TBB
# include <tbb/enumerable_thread_specific.h>
#else
# include <atomic>
# include <functional>
# include "BLI_map.hh"
# include "BLI_mutex.hh"
#endif
#include "BLI_utility_mixins.hh"
namespace blender::threading {
#ifndef WITH_TBB
namespace enumerable_thread_specific_utils {
inline std::atomic<int> next_id = 0;
inline thread_local int thread_id = next_id.fetch_add(1, std::memory_order_relaxed);
} // namespace enumerable_thread_specific_utils
#endif /* !WITH_TBB */
/**
* This is mainly a wrapper for `tbb::enumerable_thread_specific`. The wrapper is needed because we
* want to be able to build without tbb.
*
* More features of the tbb version can be wrapped when they are used.
*/
template<typename T> class EnumerableThreadSpecific : NonCopyable, NonMovable {
#ifdef WITH_TBB
private:
tbb::enumerable_thread_specific<T> values_;
public:
using iterator = typename tbb::enumerable_thread_specific<T>::iterator;
EnumerableThreadSpecific() = default;
template<typename F> EnumerableThreadSpecific(F initializer) : values_(std::move(initializer)) {}
T &local()
{
return values_.local();
}
iterator begin()
{
return values_.begin();
}
iterator end()
{
return values_.end();
}
#else /* WITH_TBB */
private:
Mutex mutex_;
/* Maps thread ids to their corresponding values. The values are not embedded in the map, so that
* their addresses do not change when the map grows. */
Map<int, std::reference_wrapper<T>> values_;
Vector<std::unique_ptr<T>> owned_values_;
std::function<void(void *)> initializer_;
public:
using iterator = typename Map<int, std::reference_wrapper<T>>::MutableValueIterator;
EnumerableThreadSpecific() : initializer_([](void *buffer) { new (buffer) T(); }) {}
template<typename F>
EnumerableThreadSpecific(F initializer)
: initializer_([=](void *buffer) { new (buffer) T(initializer()); })
{
}
T &local()
{
const int thread_id = enumerable_thread_specific_utils::thread_id;
std::lock_guard lock{mutex_};
return values_.lookup_or_add_cb(thread_id, [&]() {
T *value = static_cast<T *>(::operator new(sizeof(T)));
initializer_(value);
owned_values_.append(std::unique_ptr<T>{value});
return std::reference_wrapper<T>{*value};
});
}
iterator begin()
{
return values_.values().begin();
}
iterator end()
{
return values_.values().end();
}
#endif /* WITH_TBB */
};
} // namespace blender::threading

View File

@@ -0,0 +1,88 @@
/* SPDX-FileCopyrightText: 2026 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bli
*/
#pragma once
#include <concepts>
#include <optional>
namespace blender::exec_mode {
/** Potentially use multiple threads to execute the function. */
struct Parallel {
static constexpr bool is_parallel = true;
int grain_size(const int fallback) const
{
return fallback;
}
};
/** Execute the function in the current thread. */
struct Serial {
static constexpr bool is_parallel = false;
};
/**
* Potentially use multiple threads to execute the function, with a configurable grain size to
* influence the parallel task size.
*/
struct ParallelGrainSize {
static constexpr bool is_parallel = true;
int grain_size_override = 1;
int grain_size(const int /*fallback*/) const
{
return this->grain_size_override;
}
};
/**
* Argument used to control whether a function should use parallel execution or not.
*/
template<typename T>
concept Tag = requires {
{
T::is_parallel
} -> std::convertible_to<bool>;
requires(!T::is_parallel || requires(const T t, int fallback) {
{
t.grain_size(fallback)
} -> std::same_as<int>;
});
};
/**
* A version of #Tag that can be used in non-template functions.
*/
struct Mode {
bool is_parallel;
std::optional<int> grain_size_override;
constexpr Mode(Parallel /*tag*/) : is_parallel(true), grain_size_override(std::nullopt) {}
constexpr Mode(Serial /*tag*/) : is_parallel(false), grain_size_override(std::nullopt) {}
constexpr Mode(ParallelGrainSize tag)
: is_parallel(true), grain_size_override(tag.grain_size_override)
{
}
constexpr int grain_size(const int fallback) const
{
return this->grain_size_override.value_or(fallback);
}
};
/** Main access points to control execution mode. */
constexpr Parallel parallel = Parallel();
constexpr Serial serial = Serial();
constexpr ParallelGrainSize grain_size(int grain_size)
{
return ParallelGrainSize{grain_size};
}
} // namespace blender::exec_mode

View File

@@ -0,0 +1,61 @@
/* SPDX-FileCopyrightText: 2018 Blender Authors, Alexander Gavrilov. All rights reserved.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bli
*/
namespace blender {
/** Opaque structure containing pre-parsed data for evaluation. */
struct ExprPyLike_Parsed;
/** Expression evaluation return code. */
enum eExprPyLike_EvalStatus {
EXPR_PYLIKE_SUCCESS = 0,
/* Computation errors; result is still set, but may be NaN */
EXPR_PYLIKE_DIV_BY_ZERO,
EXPR_PYLIKE_MATH_ERROR,
/* Expression dependent errors or bugs; result is 0 */
EXPR_PYLIKE_INVALID,
EXPR_PYLIKE_FATAL_ERROR,
};
/**
* Free the parsed data; NULL argument is ok.
*/
void BLI_expr_pylike_free(struct ExprPyLike_Parsed *expr);
/**
* Check if the parsing result is valid for evaluation.
*/
bool BLI_expr_pylike_is_valid(const struct ExprPyLike_Parsed *expr);
/**
* Check if the parsed expression always evaluates to the same value.
*/
bool BLI_expr_pylike_is_constant(const struct ExprPyLike_Parsed *expr);
/**
* Check if the parsed expression uses the parameter with the given index.
*/
bool BLI_expr_pylike_is_using_param(const struct ExprPyLike_Parsed *expr, int index);
/**
* Compile the expression and return the result.
*
* Parse the expression for evaluation later.
* Returns non-NULL even on failure; use is_valid to check.
*/
ExprPyLike_Parsed *BLI_expr_pylike_parse(const char *expression,
const char **param_names,
int param_names_len);
/**
* Evaluate the expression with the given parameters.
* The order and number of parameters must match the names given to parse.
*/
eExprPyLike_EvalStatus BLI_expr_pylike_eval(struct ExprPyLike_Parsed *expr,
const double *param_values,
int param_values_len,
double *r_result);
} // namespace blender

View File

@@ -0,0 +1,30 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bli
*/
#include "BLI_math_vector_types.hh"
#pragma once
namespace blender::fftw {
/**
* FFTW's real to complex and complex to real transforms are more efficient when their input has a
* specific size. This function finds the most optimal size that is more than or equal the given
* size. The input data can then be zero padded to the optimal size for better performance. See
* Section 4.3.3 Real-data DFTs in the FFTW manual for more information.
*/
int optimal_size_for_real_transform(int size);
int2 optimal_size_for_real_transform(int2 size);
/**
* Initialize the float variant of FFTW. This essentially setup the multi-threading hooks to enable
* multi-threading using TBB's parallel_for and makes the FFTW planner thread safe.
*/
void initialize_float();
} // namespace blender::fftw

View File

@@ -0,0 +1,460 @@
/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bli
* \brief File and directory operations.
*/
#pragma once
#include <stdint.h>
#include <stdio.h>
#include <sys/stat.h>
/* for size_t (needed on windows) */
#include <stddef.h>
#include <limits.h> /* for PATH_MAX */
#include "BLI_compiler_attrs.h"
#include "BLI_enum_flags.hh"
#include "BLI_fileops_types.h"
namespace blender {
#ifndef PATH_MAX
# define PATH_MAX 4096
#endif
/* -------------------------------------------------------------------- */
/** \name Common
* \{ */
/**
* Returns true if the path (file or directory) exists.
*/
bool BLI_exists(const char *path) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/**
* Returns the st_mode from stat-ing the specified path name, or 0 if stat fails
* (most likely doesn't exist or no access).
*/
int BLI_file_stat_mode(const char *path) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/**
* \return 0 on success.
*/
int BLI_copy(const char *path_src, const char *path_dst) ATTR_NONNULL();
/**
* When `path_src` points to a directory, moves all its contents into `path_dst`,
* else rename `path_src` itself to `path_dst`.
* \return 0 on success.
*/
int BLI_path_move(const char *path_src, const char *path_dst) ATTR_NONNULL();
/**
* Rename a file or directory, unless `to` already exists.
*
* \note This matches Windows `rename` logic, _not_ Unix one. It does not allow to replace an
* existing target. Use #BLI_rename_overwrite instead if existing file should be replaced.
*
* \param from: The path to rename from (return failure if it does not exist).
* \param to: The destination path.
* \return zero on success (matching 'rename' behavior).
*/
int BLI_rename(const char *from, const char *to) ATTR_NONNULL();
/**
* Rename a file or directory, replacing target `to` path if it exists.
*
* \note This matches Unix `rename` logic. It does allow to replace an existing target. Use
* #BLI_rename instead if existing file should never be replaced. However, if `to` is an existing,
* non-empty directory, the operation will fail.
*
* \note There is still no feature-parity between behaviors on Windows and Unix, in case the target
* `to` exists and is opened by some process in the system:
* - On Unix, it will typically succeed
* (see https://man7.org/linux/man-pages/man2/rename.2.html for details).
* - On Windows, it will always fail
* (see https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-movefileexw for
* details).
*
* \warning Due to internal limitation/implementation, on Windows, in case paths point to
* directories, it's up to the caller to ensure that `from` and `to` are not the same directory.
* Since `to` is being deleted to make room for `from`, this will result in `from` being deleted as
* well.
*
* See #BLI_path_move to move directories.
*
* \param from: The path to rename from (return failure if it does not exist).
* \param to: The destination path.
* This will be deleted if it already exists, unless it's a directory which will fail.
* \return zero on success (matching 'rename' behavior).
*/
int BLI_rename_overwrite(const char *from, const char *to) ATTR_NONNULL();
/**
* Deletes the specified file or directory.
*
* \param dir: Delete an empty directory instead of a file.
* The value is ignored when `recursive` is true but should true to make the intention clear.
* If the directory is not empty, delete fails.
* \param recursive: Recursively delete files including `path` which may be a directory of a file.
*
* \note Symbolic-Links for (UNIX) behave as follows:
* - Never followed, treated as regular files.
* - Links are removed, not the files/directories they references.
* - When `path` itself links to another directory,
* deleting `path` behaves as if a regular file is being deleted.
* - If `dir` is true and `path` is a link, delete fails.
*
* \return zero on success (matching 'remove' behavior).
*/
int BLI_delete(const char *path, bool dir, bool recursive) ATTR_NONNULL();
/**
* Soft deletes the specified file or directory (depending on dir) by moving the files to the
* recycling bin, optionally doing recursive delete of directory contents.
*
* \return zero on success (matching 'remove' behavior).
*/
int BLI_delete_soft(const char *filepath, const char **r_error_message) ATTR_NONNULL();
#if 0 /* Unused */
int BLI_create_symlink(const char *path, const char *path_dst) ATTR_NONNULL();
#endif
/* Keep in sync with the definition of struct `direntry` in `BLI_fileops_types.h`. */
#ifdef WIN32
# if defined(_MSC_VER)
typedef struct _stat64 BLI_stat_t;
# else
typedef struct _stat BLI_stat_t;
# endif
#else
typedef struct stat BLI_stat_t;
#endif
int BLI_fstat(int fd, BLI_stat_t *buffer) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
int BLI_stat(const char *path, BLI_stat_t *buffer) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
int64_t BLI_ftell(FILE *stream) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
int BLI_fseek(FILE *stream, int64_t offset, int whence);
int64_t BLI_lseek(int fd, int64_t offset, int whence);
#ifdef WIN32
int BLI_wstat(const wchar_t *path, BLI_stat_t *buffer);
#endif
enum eFileAttributes {
FILE_ATTR_READONLY = 1 << 0, /* Read-only or Immutable. */
FILE_ATTR_HIDDEN = 1 << 1, /* Hidden or invisible. */
FILE_ATTR_SYSTEM = 1 << 2, /* Used by the Operating System. */
FILE_ATTR_ARCHIVE = 1 << 3, /* Marked as archived. */
FILE_ATTR_COMPRESSED = 1 << 4, /* Compressed. */
FILE_ATTR_ENCRYPTED = 1 << 5, /* Encrypted. */
FILE_ATTR_RESTRICTED = 1 << 6, /* Protected by OS. */
FILE_ATTR_TEMPORARY = 1 << 7, /* Used for temporary storage. */
FILE_ATTR_SPARSE_FILE = 1 << 8, /* Sparse File. */
FILE_ATTR_OFFLINE = 1 << 9, /* Contents available after a short delay. */
FILE_ATTR_ALIAS = 1 << 10, /* Mac Alias or Windows LNK. File-based redirection. */
FILE_ATTR_REPARSE_POINT = 1 << 11, /* File has associated re-parse point. */
FILE_ATTR_SYMLINK = 1 << 12, /* Reference to another file. */
FILE_ATTR_JUNCTION_POINT = 1 << 13, /* Folder Symbolic-link. */
FILE_ATTR_MOUNT_POINT = 1 << 14, /* Volume mounted as a folder. */
FILE_ATTR_HARDLINK = 1 << 15, /* Duplicated directory entry. */
};
ENUM_OPERATORS(eFileAttributes);
#define FILE_ATTR_ANY_LINK \
(FILE_ATTR_ALIAS | FILE_ATTR_REPARSE_POINT | FILE_ATTR_SYMLINK | FILE_ATTR_JUNCTION_POINT | \
FILE_ATTR_MOUNT_POINT | FILE_ATTR_HARDLINK)
/** \} */
/* -------------------------------------------------------------------- */
/** \name External File Operations
* \{ */
enum FileExternalOperation {
FILE_EXTERNAL_OPERATION_OPEN = 1,
FILE_EXTERNAL_OPERATION_FOLDER_OPEN,
/* Following are Windows-only: */
FILE_EXTERNAL_OPERATION_EDIT,
FILE_EXTERNAL_OPERATION_NEW,
FILE_EXTERNAL_OPERATION_FIND,
FILE_EXTERNAL_OPERATION_SHOW,
FILE_EXTERNAL_OPERATION_PLAY,
FILE_EXTERNAL_OPERATION_BROWSE,
FILE_EXTERNAL_OPERATION_PREVIEW,
FILE_EXTERNAL_OPERATION_PRINT,
FILE_EXTERNAL_OPERATION_INSTALL,
FILE_EXTERNAL_OPERATION_RUNAS,
FILE_EXTERNAL_OPERATION_PROPERTIES,
FILE_EXTERNAL_OPERATION_FOLDER_FIND,
FILE_EXTERNAL_OPERATION_FOLDER_CMD,
};
bool BLI_file_external_operation_supported(const char *filepath, FileExternalOperation operation);
bool BLI_file_external_operation_execute(const char *filepath, FileExternalOperation operation);
/** \} */
/* -------------------------------------------------------------------- */
/** \name Directories
* \{ */
struct direntry;
/**
* Does the specified path point to a directory?
* \note Would be better in `fileops.cc` except that it needs `stat.h` so add here.
*/
bool BLI_is_dir(const char *path) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/**
* Does the specified path point to a non-directory?
*/
bool BLI_is_file(const char *path) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/**
* \return true on success (i.e. given path now exists on FS), false otherwise.
*/
bool BLI_dir_create_recursive(const char *dirname) ATTR_NONNULL();
/**
* Returns the number of free bytes on the volume containing the specified path.
*
* \note Not actually used anywhere.
*/
double BLI_dir_free_space(const char *dir) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/**
* Copies the current working directory into *dir (max size maxncpy), and
* returns a pointer to same.
*
* \note can return NULL when the size is not big enough
*/
char *BLI_current_working_dir(char *dir, size_t maxncpy) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/**
* Get the user's home directory, i.e.
* - Unix: `$HOME` or #passwd::pw_dir.
* - Windows: `%userprofile%`
*
* \return The home directory or null when it cannot be accessed.
*
* \note By convention, failure to access home means any derived directories fail as well
* instead of attempting to create a fallback such as `/`, `/tmp`, `C:\` ... etc.
* Although there may be rare cases where a fallback is appropriate.
*/
const char *BLI_dir_home();
eFileAttributes BLI_file_attributes(const char *path);
/**
* Changes the current working directory to the provided path.
*
* Usage of this function is strongly discouraged as it is not thread safe. It will likely cause
* issues if there is an operation on another thread that does not expect the current working
* directory to change. This has been added to support USDZ export, which has a problematic
* "feature" described in this issue #99807. It will be removed if it is possible to resolve
* that issue upstream in the USD library.
*
* \return true on success, false otherwise.
*/
bool BLI_change_working_dir(const char *dir);
/** \} */
/* -------------------------------------------------------------------- */
/** \name File-List
* \{ */
/**
* Scans the contents of the directory named `dirname`, and allocates and fills in an
* array of entries describing them in `r_filelist`.
*
* \return The length of `r_filelist` array.
*/
unsigned int BLI_filelist_dir_contents(const char *dirname, struct direntry **r_filelist);
/**
* Deep-duplicate of a single direntry.
*/
void BLI_filelist_entry_duplicate(struct direntry *dst, const struct direntry *src);
/**
* Deep-duplicate of a #direntry array including the array itself.
*/
void BLI_filelist_duplicate(struct direntry **dest_filelist,
struct direntry *const src_filelist,
unsigned int nrentries);
/**
* Frees storage for a single direntry, not the direntry itself.
*/
void BLI_filelist_entry_free(struct direntry *entry);
/**
* Frees storage for an array of #direntry, including the array itself.
*/
void BLI_filelist_free(struct direntry *filelist, unsigned int nrentries);
/**
* Convert given entry's size into human-readable strings.
*/
void BLI_filelist_entry_size_to_string(const struct stat *st,
uint64_t st_size_fallback,
bool compact,
char r_size[FILELIST_DIRENTRY_SIZE_LEN]);
/**
* Convert given entry's modes into human-readable strings.
*/
void BLI_filelist_entry_mode_to_string(const struct stat *st,
bool compact,
char r_mode1[FILELIST_DIRENTRY_MODE_LEN],
char r_mode2[FILELIST_DIRENTRY_MODE_LEN],
char r_mode3[FILELIST_DIRENTRY_MODE_LEN]);
/**
* Convert given entry's owner into human-readable strings.
*/
void BLI_filelist_entry_owner_to_string(const struct stat *st,
bool compact,
char r_owner[FILELIST_DIRENTRY_OWNER_LEN]);
/** \} */
/* -------------------------------------------------------------------- */
/** \name Files
* \{ */
FILE *BLI_fopen(const char *filepath, const char *mode) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
void *BLI_gzopen(const char *filepath, const char *mode) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
int BLI_open(const char *filepath, int oflag, int pmode) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
int BLI_access(const char *filepath, int mode) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/**
* A version of `read` with the following differences:
* - continues reading until failure or the requested size is met.
* - Reads `size_t` bytes instead of `int` on WIN32.
* \return the number of bytes read.
*/
int64_t BLI_read(int fd, void *buf, size_t nbytes);
/**
* Returns true if the file with the specified name can be written.
* This implementation uses access(2), which makes the check according
* to the real UID and GID of the process, not its effective UID and GID.
* This shouldn't matter for Blender, which is not going to run privileged anyway.
*/
bool BLI_file_is_writable(const char *filepath) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/**
* Creates the file with nothing in it, or updates its last-modified date if it already exists.
* Returns true if successful (like the unix touch command).
*/
bool BLI_file_touch(const char *filepath) ATTR_NONNULL(1);
/**
* Ensures that the parent directory of `filepath` exists.
*
* \return true on success (i.e. given path now exists on file-system), false otherwise.
*/
bool BLI_file_ensure_parent_dir_exists(const char *filepath) ATTR_NONNULL(1);
/**
* Return alias/shortcut file target.
* \param filepath: The source of the alias.
* \param r_targetpath: Buffer for the target path an alias points to.
*
* \return true when an alias was found and set.
*
* \note This is only used on APPLE/WIN32.
*/
bool BLI_file_alias_target(const char *filepath,
char r_targetpath[/*FILE_MAXDIR*/ 768]) ATTR_WARN_UNUSED_RESULT;
bool BLI_file_magic_is_gzip(const char header[4]);
size_t BLI_file_zstd_from_mem_at_pos(void *buf,
size_t len,
FILE *file,
size_t file_offset,
int compression_level) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
size_t BLI_file_unzstd_to_mem_at_pos(void *buf, size_t len, FILE *file, size_t file_offset)
ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
bool BLI_file_magic_is_zstd(const char header[4]);
/**
* Returns the file size of an opened file descriptor or `size_t(-1)` on failure.
*/
size_t BLI_file_descriptor_size(int file) ATTR_WARN_UNUSED_RESULT;
/**
* Returns the size of a file or `size_t(-1)` on failure..
*/
size_t BLI_file_size(const char *path) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/**
* Compare if one was last modified before the other.
*
* \return true when is `file1` older than `file2`.
*/
bool BLI_file_older(const char *file1, const char *file2) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/**
* Reads the contents of a text file.
*
* \return the lines in a linked list (an empty list when file reading fails).
*/
struct LinkNode *BLI_file_read_as_lines(const char *filepath) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL();
/**
* Read the contents of `fp`, returning the result as a buffer or null when it can't be read.
*
* \param r_size: The size of the file contents read into the buffer (excluding `pad_bytes`).
*/
void *BLI_file_read_data_as_mem_from_handle(FILE *fp,
bool read_size_exact,
size_t pad_bytes,
size_t *r_size);
char *BLI_file_read_text_as_mem(const char *filepath, size_t pad_bytes, size_t *r_size);
/**
* Return the text file data with:
*
* - Newlines replaced with '\0'.
* - Optionally trim white-space, replacing trailing <space> & <tab> with '\0'.
*
* This is an alternative to using #BLI_file_read_as_lines,
* allowing us to loop over lines without converting it into a linked list
* with individual allocations.
*
* \param trim_trailing_space: Replace trailing spaces & tabs with nil.
* This arguments prevents the caller from counting blank lines (if that's important).
* \param pad_bytes: When this is non-zero, the first byte is set to nil,
* to simplify parsing the file.
* It's recommended to pass in 1, so all text is nil terminated.
* \param r_size: The size of the file contents read into the buffer (excluding `pad_bytes`).
*
* Example looping over lines:
*
* \code{.c}
* size_t data_len;
* char *data = BLI_file_read_text_as_mem_with_newline_as_nil(filepath, true, 1, &data_len);
* char *data_end = data + data_len;
* for (char *line = data; line != data_end; line = strlen(line) + 1) {
* printf("line='%s'\n", line);
* }
* \endcode
*/
char *BLI_file_read_text_as_mem_with_newline_as_nil(const char *filepath,
bool trim_trailing_space,
size_t pad_bytes,
size_t *r_size);
void *BLI_file_read_binary_as_mem(const char *filepath, size_t pad_bytes, size_t *r_size);
/**
* Frees memory from a previous call to #BLI_file_read_as_lines.
*/
void BLI_file_free_lines(struct LinkNode *lines);
/* This weirdo pops up in two places. */
#if !defined(WIN32)
# ifndef O_BINARY
# define O_BINARY 0
# endif
#else
void BLI_get_short_name(char short_name[256], const char *filepath);
#endif
/** \} */
} // namespace blender

View File

@@ -0,0 +1,36 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bli
* \brief File and directory operations.
*/
#pragma once
#include "BLI_fileops.h" // IWYU pragma: export
#include "BLI_string_ref.hh"
#include <fstream>
#include <string>
namespace blender {
/**
* std::fstream subclass that handles UTF16 encoding on Windows.
*
* For documentation, see https://en.cppreference.com/w/cpp/io/basic_fstream
*/
class fstream : public std::fstream {
public:
fstream() = default;
explicit fstream(const char *filepath,
std::ios_base::openmode mode = ios_base::in | ios_base::out);
explicit fstream(const std::string &filepath,
std::ios_base::openmode mode = ios_base::in | ios_base::out);
void open(StringRefNull filepath, ios_base::openmode mode = ios_base::in | ios_base::out);
};
} // namespace blender

View File

@@ -0,0 +1,46 @@
/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bli
* \brief Some types for dealing with directories.
*/
#include <sys/stat.h>
namespace blender {
#if defined(WIN32)
typedef unsigned int mode_t;
#endif
#define FILELIST_DIRENTRY_SIZE_LEN 16
#define FILELIST_DIRENTRY_MODE_LEN 4
#define FILELIST_DIRENTRY_OWNER_LEN 16
#define FILELIST_DIRENTRY_TIME_LEN 8
#define FILELIST_DIRENTRY_DATE_LEN 16
struct direntry {
mode_t type;
const char *relname;
const char *path;
#ifdef WIN32 /* keep in sync with the definition of BLI_stat_t in BLI_fileops.h */
# if defined(_MSC_VER)
struct _stat64 s;
# else
struct _stat s;
# endif
#else
struct stat s;
#endif
};
struct dirlink {
struct dirlink *next, *prev;
char *name;
};
} // namespace blender

View File

@@ -0,0 +1,63 @@
/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bli
* \brief Wrapper for reading from various sources (e.g. raw files, compressed files, memory...).
*/
#pragma once
#ifdef WIN32
# include "BLI_winstuff.h"
#else
# include <sys/types.h>
#endif
#include "BLI_compiler_attrs.h"
#include "BLI_utildefines.h"
namespace blender {
#if defined(_MSC_VER) || defined(__APPLE__) || defined(__HAIKU__) || defined(__NetBSD__) || \
defined(__OpenBSD__)
typedef int64_t off64_t;
#endif
struct FileReader;
typedef int64_t (*FileReaderReadFn)(struct FileReader *reader, void *buffer, size_t size);
typedef off64_t (*FileReaderSeekFn)(struct FileReader *reader, off64_t offset, int whence);
typedef void (*FileReaderCloseFn)(struct FileReader *reader);
/** General structure for all #FileReaders, implementations add custom fields at the end. */
struct FileReader {
FileReaderReadFn read;
FileReaderSeekFn seek;
FileReaderCloseFn close;
off64_t offset;
};
/* Functions for opening the various types of FileReader.
* They either succeed and return a valid FileReader, or fail and return NULL.
*
* If a FileReader is created, it has to be cleaned up and freed by calling its close()
* function unless another FileReader has taken ownership - for example, `Zstd` & `Gzip`
* take over the base FileReader and will clean it up when their clean() is called.
*/
/** Create #FileReader from raw file descriptor. */
FileReader *BLI_filereader_new_file(int filedes) ATTR_WARN_UNUSED_RESULT;
/** Create #FileReader from raw file descriptor using memory-mapped IO. */
FileReader *BLI_filereader_new_mmap(int filedes) ATTR_WARN_UNUSED_RESULT;
/** Create #FileReader from a region of memory. */
FileReader *BLI_filereader_new_memory(const void *data, size_t len) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL();
/** Create #FileReader from applying `Zstd` decompression on an underlying file. */
FileReader *BLI_filereader_new_zstd(FileReader *base) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/** Create #FileReader from applying `Gzip` decompression on an underlying file. */
FileReader *BLI_filereader_new_gzip(FileReader *base) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
} // namespace blender

View File

@@ -0,0 +1,28 @@
/* SPDX-FileCopyrightText: 2026 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include <cstddef>
namespace blender {
/**
* This can represent a string at a compile time in a way that can be used as template parameter.
*
* While std::string can be used at compile time, it is not a "structural type" and therefore
* cannot be used as template parameter.
*/
template<size_t N> struct FixedString {
char data[N];
constexpr FixedString(const char (&str)[N])
{
for (size_t i = 0; i < N; i++) {
data[i] = str[i];
}
}
};
} // namespace blender

View File

@@ -0,0 +1,573 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bli
*/
#pragma once
#include <cmath>
#include "BLI_string_ref.hh"
#include "BLI_unroll.hh"
namespace blender::fixed_width_int {
/**
* An unsigned fixed width integer.
*
* For some algorithms, the largest cross platform integer type (`uint64_t`) is not large enough.
* Then one has the choice to use some big-integer implementation like the one from GMP or one can
* use fixed-width-integers as implemented here.
*
* Internally, this type combines multiple smaller integers into a bigger integer.
*/
template<typename T, int S> struct UIntF {
static_assert(std::is_unsigned_v<T>);
static_assert(S >= 1);
/**
* Array of smaller integers that make up the bigger integer. The first element is the least
* significant digit.
*/
std::array<T, S> v;
/** Allow default construction. Note that the value is not initialized in this case. */
UIntF() = default;
/** Construct from a specific integer. */
explicit UIntF(uint64_t value);
/** Construct from a string. */
explicit UIntF(StringRefNull str, int base = 10);
/** Convert to a normal integer. Note that this may lose digits. */
explicit operator uint64_t() const;
/** Convert to floating point. This may lose precision. */
explicit operator double() const;
explicit operator float() const;
/* See `BLI_fixed_width_int_str.hh`. */
#ifdef WITH_GMP
/** Update value based on the integer encoded in the string. */
void set_from_str(StringRefNull str, int base = 10);
/** Convert to a string. */
std::string to_string(int base = 10) const;
#endif
};
/**
* A signed fixed width integer. It's mostly the same as #UIntF, but signed.
*/
template<typename T, int S> struct IntF {
static_assert(std::is_unsigned_v<T>);
static_assert(S >= 1);
/**
* Array of smaller integers that make up the bigger integer. The first element is the least
* significant digit.
*/
std::array<T, S> v;
/** Allow default construction. Note that the value is not initialized in this case. */
IntF() = default;
/** Construct from a specific integer. */
explicit IntF(int64_t value);
/** Support casting unsigned to signed fixed-width-int. */
explicit IntF(const UIntF<T, S> &value);
/** Construct from a string. */
explicit IntF(StringRefNull str, int base = 10);
/** Convert to a normal integer. Note that this may lose digits. */
explicit operator int64_t() const;
/** Convert to floating point. This may lose precision. */
explicit operator double() const;
explicit operator float() const;
/** Support casting from signed to unsigned fixed-width-int. */
explicit operator UIntF<T, S>() const;
/* See `BLI_fixed_width_int_str.hh`. */
#ifdef WITH_GMP
/** Update value based on the integer encoded in the string. */
void set_from_str(const StringRefNull str, const int base = 10);
/** Convert to a string. */
std::string to_string(int base = 10) const;
#endif
};
template<typename T> struct DoubleUIntType {
using type = void;
};
template<> struct DoubleUIntType<uint8_t> {
using type = uint16_t;
};
template<> struct DoubleUIntType<uint16_t> {
using type = uint32_t;
};
template<> struct DoubleUIntType<uint32_t> {
using type = uint64_t;
};
#ifndef _MSC_VER
template<> struct DoubleUIntType<uint64_t> {
using type = __uint128_t;
};
#endif
/** Maps unsigned integer types to a type that's twice the size. E.g. uint16_t to uint32_t. */
template<typename T> using double_uint_type = typename DoubleUIntType<T>::type;
using UInt64_8 = UIntF<uint8_t, 8>;
using UInt64_16 = UIntF<uint16_t, 4>;
using UInt64_32 = UIntF<uint32_t, 2>;
using Int64_8 = IntF<uint8_t, 8>;
using Int64_16 = IntF<uint16_t, 4>;
using Int64_32 = IntF<uint32_t, 2>;
using UInt128_8 = UIntF<uint8_t, 16>;
using UInt128_16 = UIntF<uint16_t, 8>;
using UInt128_32 = UIntF<uint32_t, 4>;
using UInt128_64 = UIntF<uint64_t, 2>;
using UInt256_8 = UIntF<uint8_t, 32>;
using UInt256_16 = UIntF<uint16_t, 16>;
using UInt256_32 = UIntF<uint32_t, 8>;
using UInt256_64 = UIntF<uint64_t, 4>;
using Int128_8 = IntF<uint8_t, 16>;
using Int128_16 = IntF<uint16_t, 8>;
using Int128_32 = IntF<uint32_t, 4>;
using Int128_64 = IntF<uint64_t, 2>;
using Int256_8 = IntF<uint8_t, 32>;
using Int256_16 = IntF<uint16_t, 16>;
using Int256_32 = IntF<uint32_t, 8>;
using Int256_64 = IntF<uint64_t, 4>;
#ifdef _MSC_VER
using UInt128 = UInt128_32;
using UInt256 = UInt256_32;
using Int128 = Int128_32;
using Int256 = Int256_32;
#else
using UInt128 = UInt128_64;
using UInt256 = UInt256_64;
using Int128 = Int128_64;
using Int256 = Int256_64;
#endif
template<typename T, int S> inline UIntF<T, S>::UIntF(const uint64_t value)
{
constexpr int Count = std::min(S, int(sizeof(decltype(value)) / sizeof(T)));
constexpr int BitsPerT = 8 * sizeof(T);
for (int i = 0; i < Count; i++) {
this->v[i] = T(value >> (BitsPerT * i));
}
for (int i = Count; i < S; i++) {
this->v[i] = 0;
}
}
template<typename T, int S> inline IntF<T, S>::IntF(const int64_t value)
{
constexpr int Count = std::min(S, int(sizeof(decltype(value)) / sizeof(T)));
constexpr int BitsPerT = 8 * sizeof(T);
for (int i = 0; i < Count; i++) {
this->v[i] = T(value >> (BitsPerT * i));
}
const T sign_extend_fill = value < 0 ? T(-1) : T(0);
for (int i = Count; i < S; i++) {
this->v[i] = sign_extend_fill;
}
}
template<typename T, int S> inline IntF<T, S>::IntF(const UIntF<T, S> &value) : v(value.v) {}
#ifdef WITH_GMP
template<typename T, int S> UIntF<T, S>::UIntF(const StringRefNull str, const int base)
{
this->set_from_str(str, base);
}
template<typename T, int S> IntF<T, S>::IntF(const StringRefNull str, const int base)
{
this->set_from_str(str, base);
}
#endif /* WITH_GMP */
template<typename T, int S> inline UIntF<T, S>::operator uint64_t() const
{
constexpr int Count = std::min(S, int(sizeof(uint64_t) / sizeof(T)));
constexpr int BitsPerT = 8 * sizeof(T);
uint64_t result = 0;
for (int i = 0; i < Count; i++) {
result |= uint64_t(this->v[i]) << (BitsPerT * i);
}
return result;
}
template<typename T, int S> inline UIntF<T, S>::operator double() const
{
double result = double(this->v[0]);
for (int i = 1; i < S; i++) {
const T a = this->v[i];
if (a == 0) {
continue;
}
result += ldexp(a, 8 * sizeof(T) * i);
}
return result;
}
template<typename T, int S> inline UIntF<T, S>::operator float() const
{
return float(double(*this));
}
template<typename T, int S> inline IntF<T, S>::operator int64_t() const
{
return int64_t(uint64_t(UIntF<T, S>(*this)));
}
template<typename T, int S> inline IntF<T, S>::operator double() const
{
if (is_negative(*this)) {
return -double(-*this);
}
double result = double(this->v[0]);
for (int i = 1; i < S; i++) {
const T a = this->v[i];
if (a == 0) {
continue;
}
result += ldexp(a, 8 * sizeof(T) * i);
}
return result;
}
template<typename T, int S> inline IntF<T, S>::operator float() const
{
return float(double(*this));
}
template<typename T, int S> inline IntF<T, S>::operator UIntF<T, S>() const
{
UIntF<T, S> result;
result.v = this->v;
return result;
}
/**
* Adds two fixed-width-integer together using the standard addition with carry algorithm taught
* in schools. The main difference is that the digits here are not 0 to 9, but 0 to max(T).
*
* Due to the design of two's-complement numbers, this works for signed and unsigned
* fixed-width-integer. The overflow behavior is wrap-around.
*
* \tparam T: Type for individual digits.
* \tparam T2: Integer type that is twice as large as T.
* \tparam S: Number of digits of type T in each fixed-width-integer.
*/
template<typename T, typename T2, int S>
inline void generic_add(T *__restrict dst, const T *a, const T *b)
{
constexpr int shift = 8 * sizeof(T);
T2 carry = 0;
unroll<S>([&](auto i) {
const T2 ai = T2(a[i]);
const T2 bi = T2(b[i]);
const T2 ri = ai + bi + carry;
dst[i] = T(ri);
carry = ri >> shift;
});
}
/**
* Similar to #generic_add, but for subtraction.
*/
template<typename T, typename T2, int S>
inline void generic_sub(T *__restrict dst, const T *a, const T *b)
{
T2 carry = 0;
unroll<S>([&](auto i) {
const T2 ai = T2(a[i]);
const T2 bi = T2(b[i]);
const T2 ri = ai - bi - carry;
dst[i] = T(ri);
carry = ri > ai;
});
}
/** Similar to #generic_add, but for unsigned multiplication. */
template<typename T, typename T2, int S>
inline void generic_unsigned_mul(T *__restrict dst, const T *a, const T *b)
{
constexpr int shift = 8 * sizeof(T);
T2 r[S] = {};
for (int i = 0; i < S; i++) {
const T2 bi = T2(b[i]);
T2 carry = 0;
for (int j = 0; j < S - i; j++) {
const T2 rji = T2(a[j]) * bi + carry;
carry = rji >> shift;
r[i + j] += T2(T(rji));
}
}
T2 carry = 0;
for (int i = 0; i < S; i++) {
const T2 ri = r[i] + carry;
carry = ri >> shift;
dst[i] = T(ri);
}
}
template<typename T, int Size>
inline UIntF<T, Size> operator+(const UIntF<T, Size> &a, const UIntF<T, Size> &b)
requires(!std::is_void_v<double_uint_type<T>>)
{
UIntF<T, Size> result;
generic_add<T, double_uint_type<T>, Size>(result.v.data(), a.v.data(), b.v.data());
return result;
}
template<typename T, int Size>
inline IntF<T, Size> operator+(const IntF<T, Size> &a, const IntF<T, Size> &b)
requires(!std::is_void_v<double_uint_type<T>>)
{
IntF<T, Size> result;
generic_add<T, double_uint_type<T>, Size>(result.v.data(), a.v.data(), b.v.data());
return result;
}
template<typename T, int Size>
inline UIntF<T, Size> operator-(const UIntF<T, Size> &a, const UIntF<T, Size> &b)
{
UIntF<T, Size> result;
generic_sub<T, double_uint_type<T>, Size>(result.v.data(), a.v.data(), b.v.data());
return result;
}
template<typename T, int Size>
inline IntF<T, Size> operator-(const IntF<T, Size> &a, const IntF<T, Size> &b)
{
IntF<T, Size> result;
generic_sub<T, double_uint_type<T>, Size>(result.v.data(), a.v.data(), b.v.data());
return result;
}
template<typename T, int Size>
inline UIntF<T, Size> operator*(const UIntF<T, Size> &a, const UIntF<T, Size> &b)
requires(!std::is_void_v<double_uint_type<T>>)
{
UIntF<T, Size> result;
generic_unsigned_mul<T, double_uint_type<T>, Size>(result.v.data(), a.v.data(), b.v.data());
return result;
}
/**
* Using this function is faster than using the comparison operator. Only a single bit has to be
* checked to determine if the value is negative.
*/
template<typename T, int Size> bool is_negative(const IntF<T, Size> &a)
{
return (a.v[Size - 1] & (T(1) << (sizeof(T) * 8 - 1))) != 0;
}
template<typename T, int Size> inline bool is_zero(const UIntF<T, Size> &a)
{
bool result = true;
unroll<Size>([&](auto i) { result &= (a.v[i] == 0); });
return result;
}
template<typename T, int Size> inline bool is_zero(const IntF<T, Size> &a)
{
bool result = true;
unroll<Size>([&](auto i) { result &= (a.v[i] == 0); });
return result;
}
template<typename T, int Size>
inline IntF<T, Size> operator*(const IntF<T, Size> &a, const IntF<T, Size> &b)
requires(!std::is_void_v<double_uint_type<T>>)
{
using UIntF = UIntF<T, Size>;
using IntF = IntF<T, Size>;
/* Signed multiplication is implemented in terms of unsigned multiplication. */
const bool is_negative_a = is_negative(a);
const bool is_negative_b = is_negative(b);
if (is_negative_a && is_negative_b) {
return IntF(UIntF(-a) * UIntF(-b));
}
if (is_negative_a) {
return -IntF(UIntF(-a) * UIntF(b));
}
if (is_negative_b) {
return -IntF(UIntF(a) * UIntF(-b));
}
return IntF(UIntF(a) * UIntF(b));
}
template<typename T, int Size> inline IntF<T, Size> operator-(const IntF<T, Size> &a)
{
IntF<T, Size> result;
for (int i = 0; i < Size; i++) {
result.v[i] = ~a.v[i];
}
return result + IntF<T, Size>(1);
}
template<typename T, int Size> inline void operator+=(UIntF<T, Size> &a, const UIntF<T, Size> &b)
{
a = a + b;
}
template<typename T, int Size> inline void operator+=(IntF<T, Size> &a, const IntF<T, Size> &b)
{
a = a + b;
}
template<typename T, int Size> inline void operator-=(UIntF<T, Size> &a, const UIntF<T, Size> &b)
{
a = a - b;
}
template<typename T, int Size> inline void operator-=(IntF<T, Size> &a, const IntF<T, Size> &b)
{
a = a - b;
}
template<typename T, int Size> inline void operator*=(UIntF<T, Size> &a, const UIntF<T, Size> &b)
{
a = a * b;
}
template<typename T, int Size> inline void operator*=(IntF<T, Size> &a, const IntF<T, Size> &b)
{
a = a * b;
}
template<typename T, int Size>
inline bool operator==(const IntF<T, Size> &a, const IntF<T, Size> &b)
{
return a.v == b.v;
}
template<typename T, int Size>
inline bool operator==(const UIntF<T, Size> &a, const UIntF<T, Size> &b)
{
return a.v == b.v;
}
template<typename T, int Size>
inline bool operator!=(const IntF<T, Size> &a, const IntF<T, Size> &b)
{
return a.v != b.v;
}
template<typename T, int Size>
inline bool operator!=(const UIntF<T, Size> &a, const UIntF<T, Size> &b)
{
return a.v != b.v;
}
template<typename T, size_t Size>
inline int compare_reversed_order(const std::array<T, Size> &a, const std::array<T, Size> &b)
{
for (int i = Size - 1; i >= 0; i--) {
if (a[i] < b[i]) {
return -1;
}
if (a[i] > b[i]) {
return 1;
}
}
return 0;
}
template<typename T, int Size>
inline bool operator<(const IntF<T, Size> &a, const IntF<T, Size> &b)
{
const bool is_negative_a = is_negative(a);
const bool is_negative_b = is_negative(b);
if (is_negative_a == is_negative_b) {
return compare_reversed_order(a.v, b.v) < 0;
}
return is_negative_a;
}
template<typename T, int Size>
inline bool operator<=(const IntF<T, Size> &a, const IntF<T, Size> &b)
{
const bool is_negative_a = is_negative(a);
const bool is_negative_b = is_negative(b);
if (is_negative_a == is_negative_b) {
return compare_reversed_order(a.v, b.v) <= 0;
}
return is_negative_a;
}
template<typename T, int Size>
inline bool operator>(const IntF<T, Size> &a, const IntF<T, Size> &b)
{
const bool is_negative_a = is_negative(a);
const bool is_negative_b = is_negative(b);
if (is_negative_a == is_negative_b) {
return compare_reversed_order(a.v, b.v) > 0;
}
return is_negative_b;
}
template<typename T, int Size>
inline bool operator>=(const IntF<T, Size> &a, const IntF<T, Size> &b)
{
const bool is_negative_a = is_negative(a);
const bool is_negative_b = is_negative(b);
if (is_negative_a == is_negative_b) {
return compare_reversed_order(a.v, b.v) >= 0;
}
return is_negative_b;
}
template<typename T, int Size>
inline bool operator<(const UIntF<T, Size> &a, const UIntF<T, Size> &b)
{
return compare_reversed_order(a.v, b.v) < 0;
}
template<typename T, int Size>
inline bool operator<=(const UIntF<T, Size> &a, const UIntF<T, Size> &b)
{
return compare_reversed_order(a.v, b.v) <= 0;
}
template<typename T, int Size>
inline bool operator>(const UIntF<T, Size> &a, const UIntF<T, Size> &b)
{
return compare_reversed_order(a.v, b.v) > 0;
}
template<typename T, int Size>
inline bool operator>=(const UIntF<T, Size> &a, const UIntF<T, Size> &b)
{
return compare_reversed_order(a.v, b.v) >= 0;
}
} // namespace blender::fixed_width_int

View File

@@ -0,0 +1,79 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bli
*/
#pragma once
/** Conversions to and from strings use GMP internally currently. */
#ifdef WITH_GMP
# include <gmpxx.h>
# include "BLI_array.hh"
# include "BLI_fixed_width_int.hh"
namespace blender::fixed_width_int {
template<typename T, int S>
inline void UIntF<T, S>::set_from_str(const StringRefNull str, const int base)
{
mpz_t x;
mpz_init(x);
mpz_set_str(x, str.c_str(), base);
for (int i = 0; i < S; i++) {
static_assert(sizeof(T) <= sizeof(decltype(mpz_get_ui(x))));
this->v[i] = T(mpz_get_ui(x));
mpz_div_2exp(x, x, 8 * sizeof(T));
}
mpz_clear(x);
}
template<typename T, int S>
inline void IntF<T, S>::set_from_str(const StringRefNull str, const int base)
{
if (str[0] == '-') {
const UIntF<T, S> unsigned_value(str.c_str() + 1, base);
this->v = unsigned_value.v;
*this = -*this;
}
else {
const UIntF<T, S> unsigned_value(str.c_str(), base);
this->v = unsigned_value.v;
}
}
template<typename T, int S> inline std::string UIntF<T, S>::to_string(const int base) const
{
mpz_t x;
mpz_init(x);
for (int i = S - 1; i >= 0; i--) {
static_assert(sizeof(T) <= sizeof(decltype(mpz_get_ui(x))));
mpz_mul_2exp(x, x, 8 * sizeof(T));
mpz_add_ui(x, x, this->v[i]);
}
/* Add 2 because of possible +/- sign and null terminator. */
/* Also see https://gmplib.org/manual/Converting-Integers. */
const int str_size = mpz_sizeinbase(x, base) + 2;
Array<char, 1024> str(str_size);
mpz_get_str(str.data(), base, x);
mpz_clear(x);
return std::string(str.data());
}
template<typename T, int S> inline std::string IntF<T, S>::to_string(const int base) const
{
if (is_negative(*this)) {
std::string str = UIntF<T, S>(-*this).to_string(base);
str.insert(str.begin(), '-');
return str;
}
return UIntF<T, S>(*this).to_string();
}
} // namespace blender::fixed_width_int
#endif /* WITH_GMP */

View File

@@ -0,0 +1,47 @@
/* SPDX-FileCopyrightText: 1991 1992 1993 Free Software Foundation, Inc.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bli
* \note The canonical source of this file is maintained with the GNU C Library.
* Bugs can be reported to <bug-glibc@prep.ai.mit.edu>.
*/
#if defined WIN32 && !defined _LIBC
# undef __P
# define __P(protos) protos
/* We #undef these before defining them because some losing systems
* (HP-UX A.08.07 for example) define these in <unistd.h>. */
# undef FNM_PATHNAME
# undef FNM_NOESCAPE
# undef FNM_PERIOD
/* Bits set in the FLAGS argument to `fnmatch'. */
# define FNM_PATHNAME (1 << 0) /* No wildcard can ever match `/'. */
# define FNM_NOESCAPE (1 << 1) /* Backslashes don't quote special chars. */
# define FNM_PERIOD (1 << 2) /* Leading `.' is matched only explicitly. */
# if !defined(_POSIX_C_SOURCE) || _POSIX_C_SOURCE < 2 || defined(_GNU_SOURCE)
# define FNM_FILE_NAME FNM_PATHNAME /* Preferred GNU name. */
# define FNM_LEADING_DIR (1 << 3) /* Ignore `/...' after a match. */
# define FNM_CASEFOLD (1 << 4) /* Compare without regard to case. */
# endif
/* Value returned by `fnmatch' if STRING does not match PATTERN. */
# define FNM_NOMATCH 1
/* Match STRING against the filename pattern PATTERN,
* returning zero if it matches, FNM_NOMATCH if not. */
extern int fnmatch __P((const char *__pattern, const char *__string, int __flags));
#else
# ifndef _GNU_SOURCE
# define _GNU_SOURCE
# endif
# include <fnmatch.h> // IWYU pragma: export
#endif /* defined WIN32 && !defined _LIBC */

View File

@@ -0,0 +1,165 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include <type_traits>
#include <utility>
#include "BLI_build_config.h"
#include "BLI_utildefines.h"
namespace blender {
/** \file
* \ingroup bli
*
* A `FunctionRef<Signature>` is a non-owning reference to some callable object with a specific
* signature. It can be used to pass some callback to another function.
*
* A `FunctionRef` is small and cheap to copy. Therefore it should generally be passed by value.
*
* Example signatures:
* `FunctionRef<void()>` - A function without parameters and void return type.
* `FunctionRef<int(float)>` - A function with a float parameter and an int return value.
* `FunctionRef<int(int, int)>` - A function with two int parameters and an int return value.
*
* There are multiple ways to achieve that, so here is a comparison of the different approaches:
* 1. Pass function pointer and user data (as void *) separately:
* - The only method that is compatible with C interfaces.
* - Is cumbersome to work with in many cases, because one has to keep track of two parameters.
* - Not type safe at all, because of the void pointer.
* - It requires workarounds when one wants to pass a lambda into a function.
* 2. Using `std::function`:
* - It works well with most callables and is easy to use.
* - Owns the callable, so it can be returned from a function more safely than other methods.
* - Requires that the callable is copyable.
* - Requires an allocation when the callable is too large (typically > 16 bytes).
* 3. Using a template for the callable type:
* - Most efficient solution at runtime, because compiler knows the exact callable at the place
* where it is called.
* - Works well with all callables.
* - Requires the function to be in a header file.
* - It's difficult to constrain the signature of the function.
* 4. Using `FunctionRef`:
* - Second most efficient solution at runtime.
* - It's easy to constrain the signature of the callable.
* - Does not require the function to be in a header file.
* - Works well with all callables.
* - It's a non-owning reference, so it *cannot* be stored safely in general.
*
* The fact that this is a non-owning reference makes `FunctionRef` very well suited for some use
* cases, but one has to be a bit more careful when using it to make sure that the referenced
* callable is not destructed.
*
* In particular, one must not construct a `FunctionRef` variable from a lambda directly as shown
* below. This is because the lambda object goes out of scope after the line finished executing and
* will be destructed. Calling the reference afterwards invokes undefined behavior.
*
* Don't:
* FunctionRef<int()> ref = []() { return 0; };
* Do:
* auto f = []() { return 0; };
* FuntionRef<int()> ref = f;
*
* It is fine to pass a lambda directly to a function:
*
* void some_function(FunctionRef<int()> f);
* some_function([]() { return 0; });
*/
template<typename Function> class FunctionRef;
template<typename Ret, typename... Params> class FunctionRef<Ret(Params...)> {
private:
/**
* A function pointer that knows how to call the referenced callable with the given parameters.
*/
Ret (*callback_)(intptr_t callable, Params... params) = nullptr;
/**
* A pointer to the referenced callable object. This can be a C function, a lambda object or any
* other callable.
*
* The value does not need to be initialized because it is not used unless `callback_` is set as
* well, in which case it will be initialized as well.
*
* Use `intptr_t` to avoid warnings when casting to function pointers.
*/
intptr_t callable_;
template<typename Callable> static Ret callback_fn(intptr_t callable, Params... params)
{
return (*reinterpret_cast<Callable *>(callable))(std::forward<Params>(params)...);
}
public:
FunctionRef() = default;
FunctionRef(std::nullptr_t) {}
/**
* A `FunctionRef` itself is a callable as well. However, we don't want that this
* constructor is called when `Callable` is a `FunctionRef`. If we would allow this, it
* would be easy to accidentally create a `FunctionRef` that internally calls another
* `FunctionRef`. Usually, when assigning a `FunctionRef` to another, we want that both
* contain a reference to the same underlying callable afterwards.
*
* It is still possible to reference another `FunctionRef` by first wrapping it in
* another lambda.
*/
template<typename Callable>
FunctionRef(Callable &&callable)
requires(!std::is_same_v<std::remove_cv_t<std::remove_reference_t<Callable>>, FunctionRef> &&
std::is_invocable_r_v<Ret, Callable, Params...>)
: callback_(callback_fn<typename std::remove_reference_t<Callable>>),
callable_(intptr_t(&callable))
{
if constexpr (std::is_constructible_v<bool, Callable>) {
/* For some types, the compiler can be sure that the callable is always truthy. Good!
* Then the entire check can be optimized away. */
#if COMPILER_CLANG || COMPILER_GCC
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Waddress"
# if COMPILER_GCC
# pragma GCC diagnostic ignored "-Wnonnull-compare"
# endif
#endif
/* Make sure the #FunctionRef is falsy if the callback is falsy.
* That can happen when passing in null or empty std::function. */
const bool is_truthy = bool(callable);
if (!is_truthy) {
callback_ = nullptr;
callable_ = 0;
}
#if COMPILER_CLANG || COMPILER_GCC
# pragma GCC diagnostic pop
#endif
}
}
/**
* Call the referenced function and forward all parameters to it.
*
* This invokes undefined behavior if the `FunctionRef` does not reference a function currently.
*/
Ret operator()(Params... params) const
{
BLI_assert(callback_ != nullptr);
return callback_(callable_, std::forward<Params>(params)...);
}
/**
* Returns true, when the `FunctionRef` references a function currently.
* If this returns false, the `FunctionRef` must not be called.
*/
operator bool() const
{
/* Just checking `callback_` is enough to determine if the `FunctionRef` is in a state that it
* can be called in. */
return callback_ != nullptr;
}
};
} // namespace blender

View File

@@ -0,0 +1,260 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bli
*
* This is a generic counterpart to #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 #Array supports:
* - Small object optimization / inline buffer.
* - Exception safety and various more specific constructors.
*/
#include "BLI_allocator.hh"
#include "BLI_cpp_type.hh"
#include "BLI_generic_span.hh"
namespace blender {
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;
BLI_NO_UNIQUE_ADDRESS 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, size, NoInitialization{}, allocator)
{
type_->default_construct_n(data_, size_);
}
GArray(const CPPType &type,
const int64_t size,
NoInitialization /*not_init_tag*/,
Allocator allocator = {})
: GArray(type, allocator)
{
BLI_assert(size >= 0);
size_ = size;
data_ = this->allocate(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)
{
/* 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)
: type_(other.type_), data_(other.data_), size_(other.size_), allocator_(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(size_ == 0 || type_ != nullptr);
return GSpan(type_, data_, size_);
}
operator GMutableSpan()
{
BLI_assert(size_ == 0 || 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;
}
if (this->data_) {
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(size_t(size) * item_size, alignment, AT);
}
void deallocate(void *ptr)
{
allocator_.deallocate(ptr);
}
};
} // namespace blender

View File

@@ -0,0 +1,60 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bli
*/
#include <cstdint>
#include <memory>
#include "BLI_assert.h"
namespace blender {
/**
* A #GenericKey allows different kinds of keys to be used in the same data-structure like a #Set
* or #Map.
*
* Typically, the key is stored as `std::reference_wrapper<const GenericKey>` in the
* data-structure. That implies that one has to make sure that the key is not destructed while it's
* still in use.
*/
class GenericKey {
public:
virtual ~GenericKey() = default;
/** The hash function has to be implemented by the non-abstract subclass. */
virtual uint64_t hash() const = 0;
/**
* Check if the other key is equal to this one. Usually that involves a dynamic_cast to check if
* it has the same type.
*/
virtual bool equal_to(const GenericKey &other) const = 0;
/**
* For efficiency, it can be good to not always allocate the key if it's just used for lookup.
* This method allows the key to be converted into a heap-allocated version that can be stored
* safely if necessary.
*/
virtual std::unique_ptr<GenericKey> to_storable() const = 0;
friend bool operator==(const GenericKey &a, const GenericKey &b)
{
const bool are_equal = a.equal_to(b);
/* Ensure that equality check is symmetric. */
BLI_assert(are_equal == b.equal_to(a));
return are_equal;
}
friend bool operator!=(const GenericKey &a, const GenericKey &b)
{
return !(a == b);
}
};
} // namespace blender

View File

@@ -0,0 +1,50 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "BLI_generic_key.hh"
#include "BLI_string_ref.hh"
#include "BLI_utility_mixins.hh"
namespace blender {
/** Utility class that to easy create a #GenericKey from a string. */
class GenericStringKey : public GenericKey, NonMovable {
private:
std::string value_;
/** This may reference the string stored in value_. */
StringRef value_ref_;
public:
GenericStringKey(StringRef value) : value_ref_(value) {}
uint64_t hash() const override
{
return get_default_hash(value_ref_);
}
friend bool operator==(const GenericStringKey &a, const GenericStringKey &b)
{
return a.value_ref_ == b.value_ref_;
}
bool equal_to(const GenericKey &other) const override
{
if (const auto *other_typed = dynamic_cast<const GenericStringKey *>(&other)) {
return value_ref_ == other_typed->value_ref_;
}
return false;
}
std::unique_ptr<GenericKey> to_storable() const override
{
auto storable_key = std::make_unique<GenericStringKey>("");
storable_key->value_ = value_ref_;
storable_key->value_ref_ = storable_key->value_;
return storable_key;
}
};
} // namespace blender

View File

@@ -0,0 +1,139 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bli
*/
#pragma once
#include "BLI_cpp_type.hh"
namespace blender {
/**
* A generic non-const pointer whose type is only known at runtime.
*/
class GMutablePointer {
private:
const CPPType *type_ = nullptr;
void *data_ = nullptr;
public:
GMutablePointer() = default;
GMutablePointer(const CPPType *type, void *data = nullptr) : type_(type), data_(data)
{
/* If there is data, there has to be a type. */
BLI_assert(data_ == nullptr || type_ != nullptr);
}
GMutablePointer(const CPPType &type, void *data = nullptr) : GMutablePointer(&type, data) {}
template<typename T>
GMutablePointer(T *data)
requires(!std::is_void_v<T>)
: GMutablePointer(&CPPType::get<T>(), data)
{
}
void *get() const
{
return data_;
}
const CPPType *type() const
{
return type_;
}
operator bool() const
{
return data_ != nullptr;
}
template<typename T> T *get() const
{
BLI_assert(this->is_type<T>());
return static_cast<T *>(data_);
}
template<typename T> bool is_type() const
{
return type_ != nullptr && type_->is<T>();
}
template<typename T> T relocate_out()
{
BLI_assert(this->is_type<T>());
T value;
type_->relocate_assign(data_, &value);
data_ = nullptr;
type_ = nullptr;
return value;
}
void destruct()
{
BLI_assert(data_ != nullptr);
type_->destruct(data_);
}
};
/**
* A generic const pointer whose type is only known at runtime.
*/
class GPointer {
private:
const CPPType *type_ = nullptr;
const void *data_ = nullptr;
public:
GPointer() = default;
GPointer(GMutablePointer ptr) : type_(ptr.type()), data_(ptr.get()) {}
GPointer(const CPPType *type, const void *data = nullptr) : type_(type), data_(data)
{
/* If there is data, there has to be a type. */
BLI_assert(data_ == nullptr || type_ != nullptr);
}
GPointer(const CPPType &type, const void *data = nullptr) : type_(&type), data_(data) {}
template<typename T>
GPointer(T *data)
requires(!std::is_void_v<T>)
: GPointer(&CPPType::get<T>(), data)
{
}
operator bool() const
{
return data_ != nullptr;
}
const void *get() const
{
return data_;
}
const CPPType *type() const
{
return type_;
}
template<typename T> const T *get() const
{
BLI_assert(this->is_type<T>());
return static_cast<const T *>(data_);
}
template<typename T> bool is_type() const
{
return type_ != nullptr && type_->is<T>();
}
};
} // namespace blender

View File

@@ -0,0 +1,281 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bli
*/
#include "BLI_cpp_type.hh"
#include "BLI_span.hh"
namespace blender {
/**
* A generic span. It behaves just like a Span<T>, but the type is only known at run-time.
*/
class GSpan {
protected:
const CPPType *type_ = nullptr;
const void *data_ = nullptr;
int64_t size_ = 0;
public:
GSpan() = default;
GSpan(const CPPType *type, const void *buffer, int64_t size)
: type_(type), data_(buffer), size_(size)
{
BLI_assert(size >= 0);
BLI_assert(buffer != nullptr || size == 0);
BLI_assert(size == 0 || type != nullptr);
BLI_assert(type == nullptr || type->pointer_has_valid_alignment(buffer));
}
GSpan(const CPPType &type, const void *buffer, int64_t size) : GSpan(&type, buffer, size) {}
GSpan(const CPPType &type) : type_(&type) {}
GSpan(const CPPType *type) : type_(type) {}
template<typename T>
GSpan(Span<T> array)
: GSpan(CPPType::get<T>(), static_cast<const void *>(array.data()), array.size())
{
}
template<typename T>
GSpan(MutableSpan<T> array)
: GSpan(CPPType::get<T>(), static_cast<const void *>(array.data()), array.size())
{
}
const CPPType &type() const
{
BLI_assert(type_ != nullptr);
return *type_;
}
const CPPType *type_ptr() const
{
return type_;
}
bool is_empty() const
{
return size_ == 0;
}
int64_t size() const
{
return size_;
}
int64_t size_in_bytes() const
{
return type_->size * size_;
}
const void *data() const
{
return data_;
}
const void *operator[](int64_t index) const
{
BLI_assert(index < size_);
return POINTER_OFFSET(data_, type_->size * index);
}
template<typename T> Span<T> typed() const
{
BLI_assert(size_ == 0 || type_ != nullptr);
BLI_assert(type_ == nullptr || type_->is<T>());
return Span<T>(static_cast<const T *>(data_), size_);
}
GSpan slice(const int64_t start, int64_t size) const
{
BLI_assert(start >= 0);
BLI_assert(size >= 0);
BLI_assert(start + size <= size_ || size == 0);
return GSpan(type_, POINTER_OFFSET(data_, type_->size * start), size);
}
GSpan slice(const IndexRange range) const
{
return this->slice(range.start(), range.size());
}
GSpan drop_front(const int64_t n) const
{
BLI_assert(n >= 0);
const int64_t new_size = std::max<int64_t>(0, size_ - n);
return GSpan(*type_, POINTER_OFFSET(data_, type_->size * n), new_size);
}
GSpan drop_back(const int64_t n) const
{
BLI_assert(n >= 0);
const int64_t new_size = std::max<int64_t>(0, size_ - n);
return GSpan(*type_, data_, new_size);
}
GSpan take_front(const int64_t n) const
{
BLI_assert(n >= 0);
const int64_t new_size = std::min<int64_t>(size_, n);
return GSpan(*type_, data_, new_size);
}
GSpan take_back(const int64_t n) const
{
BLI_assert(n >= 0);
const int64_t new_size = std::min<int64_t>(size_, n);
return GSpan(*type_, POINTER_OFFSET(data_, type_->size * (size_ - new_size)), new_size);
}
};
/**
* A generic mutable span. It behaves just like a MutableSpan<T>, but the type is only
* known at run-time.
*/
class GMutableSpan {
protected:
const CPPType *type_ = nullptr;
void *data_ = nullptr;
int64_t size_ = 0;
public:
GMutableSpan() = default;
GMutableSpan(const CPPType *type, void *buffer, int64_t size)
: type_(type), data_(buffer), size_(size)
{
BLI_assert(size >= 0);
BLI_assert(buffer != nullptr || size == 0);
BLI_assert(size == 0 || type != nullptr);
BLI_assert(type == nullptr || type->pointer_has_valid_alignment(buffer));
}
GMutableSpan(const CPPType &type, void *buffer, int64_t size) : GMutableSpan(&type, buffer, size)
{
}
GMutableSpan(const CPPType &type) : type_(&type) {}
GMutableSpan(const CPPType *type) : type_(type) {}
template<typename T>
GMutableSpan(MutableSpan<T> array)
: GMutableSpan(CPPType::get<T>(), static_cast<void *>(array.begin()), array.size())
{
}
operator GSpan() const
{
return GSpan(type_, data_, size_);
}
const CPPType &type() const
{
BLI_assert(type_ != nullptr);
return *type_;
}
const CPPType *type_ptr() const
{
return type_;
}
bool is_empty() const
{
return size_ == 0;
}
int64_t size() const
{
return size_;
}
int64_t size_in_bytes() const
{
return type_->size * size_;
}
void *data() const
{
return data_;
}
void *operator[](int64_t index) const
{
BLI_assert(index >= 0);
BLI_assert(index < size_);
return POINTER_OFFSET(data_, type_->size * index);
}
template<typename T> MutableSpan<T> typed() const
{
BLI_assert(size_ == 0 || type_ != nullptr);
BLI_assert(type_ == nullptr || type_->is<T>());
return MutableSpan<T>(static_cast<T *>(data_), size_);
}
GMutableSpan slice(const int64_t start, int64_t size) const
{
BLI_assert(start >= 0);
BLI_assert(size >= 0);
BLI_assert(start + size <= size_ || size == 0);
return GMutableSpan(type_, POINTER_OFFSET(data_, type_->size * start), size);
}
GMutableSpan slice(IndexRange range) const
{
return this->slice(range.start(), range.size());
}
GMutableSpan drop_front(const int64_t n) const
{
BLI_assert(n >= 0);
const int64_t new_size = std::max<int64_t>(0, size_ - n);
return GMutableSpan(*type_, POINTER_OFFSET(data_, type_->size * n), new_size);
}
GMutableSpan drop_back(const int64_t n) const
{
BLI_assert(n >= 0);
const int64_t new_size = std::max<int64_t>(0, size_ - n);
return GMutableSpan(*type_, data_, new_size);
}
GMutableSpan take_front(const int64_t n) const
{
BLI_assert(n >= 0);
const int64_t new_size = std::min<int64_t>(size_, n);
return GMutableSpan(*type_, data_, new_size);
}
GMutableSpan take_back(const int64_t n) const
{
BLI_assert(n >= 0);
const int64_t new_size = std::min<int64_t>(size_, n);
return GMutableSpan(*type_, POINTER_OFFSET(data_, type_->size * (size_ - new_size)), new_size);
}
/**
* Copy all values from another span into this span. This invokes undefined behavior when the
* destination contains uninitialized data and T is not trivially copy constructible.
* The size of both spans is expected to be the same.
*/
void copy_from(GSpan values)
{
BLI_assert(type_ == &values.type());
BLI_assert(size_ == values.size());
type_->copy_assign_n(values.data(), data_, size_);
}
};
} // namespace blender

View File

@@ -0,0 +1,115 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bli
*/
#pragma once
#include "BLI_generic_pointer.hh"
#include "BLI_linear_allocator.hh"
#include "BLI_map.hh"
namespace blender {
/**
* This is a map that stores key-value-pairs. What makes it special is that the type of values does
* not have to be known at compile time. There just has to be a corresponding CPPType.
*/
template<typename Key> class GValueMap {
private:
/* Used to allocate values owned by this container. */
LinearAllocator<> &allocator_;
Map<Key, GMutablePointer> values_;
public:
GValueMap(LinearAllocator<> &allocator) : allocator_(allocator) {}
~GValueMap()
{
/* Destruct all values that are still in the map. */
for (GMutablePointer value : values_.values()) {
value.destruct();
}
}
/* Add a value to the container. The container becomes responsible for destructing the value that
* is passed in. The caller remains responsible for freeing the value after it has been
* destructed. */
template<typename ForwardKey> void add_new_direct(ForwardKey &&key, GMutablePointer value)
{
values_.add_new_as(std::forward<ForwardKey>(key), value);
}
/* Add a value to the container that is move constructed from the given value. The caller remains
* responsible for destructing and freeing the given value. */
template<typename ForwardKey> void add_new_by_move(ForwardKey &&key, GMutablePointer value)
{
const CPPType &type = *value.type();
void *buffer = allocator_.allocate(type);
type.move_construct(value.get(), buffer);
values_.add_new_as(std::forward<ForwardKey>(key), GMutablePointer{type, buffer});
}
/* Add a value to the container that is copy constructed from the given value. The caller remains
* responsible for destructing and freeing the given value. */
template<typename ForwardKey> void add_new_by_copy(ForwardKey &&key, GPointer value)
{
const CPPType &type = *value.type();
void *buffer = allocator_.allocate(type);
type.copy_construct(value.get(), buffer);
values_.add_new_as(std::forward<ForwardKey>(key), GMutablePointer{type, buffer});
}
/* Add a value to the container. */
template<typename ForwardKey, typename T> void add_new(ForwardKey &&key, T &&value)
{
if constexpr (std::is_rvalue_reference_v<T>) {
this->add_new_by_move(std::forward<ForwardKey>(key), &value);
}
else {
this->add_new_by_copy(std::forward<ForwardKey>(key), &value);
}
}
/* Remove the value for the given name from the container and remove it. The caller is
* responsible for freeing it. The lifetime of the referenced memory might be bound to lifetime
* of the container. */
template<typename ForwardKey> GMutablePointer extract(const ForwardKey &key)
{
return values_.pop_as(key);
}
template<typename ForwardKey> GPointer lookup(const ForwardKey &key) const
{
return values_.lookup_as(key);
}
/* Remove the value for the given name from the container and remove it. */
template<typename T, typename ForwardKey> T extract(const ForwardKey &key)
{
GMutablePointer value = values_.pop_as(key);
const CPPType &type = *value.type();
BLI_assert(type.is<T>());
T return_value;
type.relocate_assign(value.get(), &return_value);
return return_value;
}
template<typename T, typename ForwardKey> const T &lookup(const ForwardKey &key) const
{
GMutablePointer value = values_.lookup_as(key);
BLI_assert(value.is_type<T>());
BLI_assert(value.get() != nullptr);
return *static_cast<const T *>(value.get());
}
template<typename ForwardKey> bool contains(const ForwardKey &key) const
{
return values_.contains_as(key);
}
};
} // namespace blender

View File

@@ -0,0 +1,149 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bli
*
* A`GVectorArray` is a container for a fixed amount of dynamically growing vectors with a generic
* data type. Its main use case is to store many small vectors with few separate allocations. Using
* this structure is generally more efficient than allocating each vector separately.
*/
#include "BLI_array.hh"
#include "BLI_generic_virtual_vector_array.hh"
#include "BLI_linear_allocator.hh"
namespace blender {
/* An array of vectors containing elements of a generic type. */
class GVectorArray : NonCopyable, NonMovable {
private:
struct Item {
void *start = nullptr;
int64_t length = 0;
int64_t capacity = 0;
};
/* Use a linear allocator to pack many small vectors together. Currently, memory from reallocated
* vectors is not reused. This can be improved in the future. */
LinearAllocator<> allocator_;
/* The data type of individual elements. */
const CPPType &type_;
/* The size of an individual element. This is inlined from `type_.size()` for easier access. */
const int64_t element_size_;
/* The individual vectors. */
Array<Item> items_;
public:
GVectorArray() = delete;
GVectorArray(const CPPType &type, int64_t array_size);
~GVectorArray();
int64_t size() const
{
return items_.size();
}
bool is_empty() const
{
return items_.is_empty();
}
const CPPType &type() const
{
return type_;
}
void append(int64_t index, const void *value);
/* Add multiple elements to a single vector. */
void extend(int64_t index, const GVArray &values);
void extend(int64_t index, GSpan values);
/* Add multiple elements to multiple vectors. */
void extend(const IndexMask &mask, const GVVectorArray &values);
void extend(const IndexMask &mask, const GVectorArray &values);
void clear(const IndexMask &mask);
GMutableSpan operator[](int64_t index);
GSpan operator[](int64_t index) const;
private:
void realloc_to_at_least(Item &item, int64_t min_capacity);
};
/* A non-owning typed mutable reference to an `GVectorArray`. It simplifies access when the type of
* the data is known at compile time. */
template<typename T> class GVectorArray_TypedMutableRef {
private:
GVectorArray *vector_array_;
public:
GVectorArray_TypedMutableRef(GVectorArray &vector_array) : vector_array_(&vector_array)
{
BLI_assert(vector_array_->type().is<T>());
}
int64_t size() const
{
return vector_array_->size();
}
bool is_empty() const
{
return vector_array_->is_empty();
}
void append(const int64_t index, const T &value)
{
vector_array_->append(index, &value);
}
void extend(const int64_t index, const Span<T> values)
{
vector_array_->extend(index, values);
}
void extend(const int64_t index, const VArray<T> &values)
{
vector_array_->extend(index, values);
}
MutableSpan<T> operator[](const int64_t index)
{
return (*vector_array_)[index].typed<T>();
}
};
/* A generic virtual vector array implementation for a `GVectorArray`. */
class GVVectorArray_For_GVectorArray : public GVVectorArray {
private:
const GVectorArray &vector_array_;
public:
GVVectorArray_For_GVectorArray(const GVectorArray &vector_array)
: GVVectorArray(vector_array.type(), vector_array.size()), vector_array_(vector_array)
{
}
protected:
int64_t get_vector_size_impl(const int64_t index) const override
{
return vector_array_[index].size();
}
void get_vector_element_impl(const int64_t index,
const int64_t index_in_vector,
void *r_value) const override
{
type_->copy_assign(vector_array_[index][index_in_vector], r_value);
}
};
} // namespace blender

View File

@@ -0,0 +1,994 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bli
*
* A generic virtual array is the same as a virtual array, except for the fact that the data type
* is only known at runtime.
*/
#include "BLI_generic_array.hh"
#include "BLI_generic_span.hh"
#include "BLI_virtual_array.hh"
namespace blender {
/* -------------------------------------------------------------------- */
/** \name #GVArrayImpl and #GVMutableArrayImpl.
* \{ */
class GVArray;
class GVArrayImpl;
class GVMutableArray;
class GVMutableArrayImpl;
/* A generically typed version of #VArrayImpl. */
class GVArrayImpl {
protected:
const CPPType *type_;
int64_t size_;
public:
GVArrayImpl(const CPPType &type, int64_t size);
virtual ~GVArrayImpl() = default;
const CPPType &type() const;
int64_t size() const;
virtual void get(int64_t index, void *r_value) const;
virtual void get_to_uninitialized(int64_t index, void *r_value) const = 0;
virtual CommonVArrayInfo common_info() const;
virtual void materialize(const IndexMask &mask, void *dst, bool dst_is_uninitialized) const;
virtual void materialize_compressed(const IndexMask &mask,
void *dst,
bool dst_is_uninitialized) const;
virtual bool try_assign_VArray(void *varray) const;
};
/* A generic version of #VMutableArrayImpl. */
class GVMutableArrayImpl : public GVArrayImpl {
public:
GVMutableArrayImpl(const CPPType &type, int64_t size) : GVArrayImpl(type, size) {}
virtual void set_by_copy(int64_t index, const void *value);
virtual void set_by_relocate(int64_t index, void *value);
virtual void set_by_move(int64_t index, void *value) = 0;
virtual void set_all(const void *src);
virtual bool try_assign_VMutableArray(void *varray) const;
};
/** \} */
/* -------------------------------------------------------------------- */
/** \name #GVArray and #GVMutableArray
* \{ */
namespace blenlib_detail {
struct GVArrayAnyExtraInfo {
const GVArrayImpl *(*get_varray)(const void *buffer) =
[](const void * /*buffer*/) -> const GVArrayImpl * { return nullptr; };
template<typename StorageT> static constexpr GVArrayAnyExtraInfo get();
};
} // namespace blenlib_detail
class GVMutableArray;
/**
* Utility class to reduce code duplication between #GVArray and #GVMutableArray.
* It pretty much follows #VArrayCommon. Don't use this class outside of this header.
*/
class GVArrayCommon {
protected:
AnyDerived<const GVArrayImpl, 40> impl_;
GVArrayCommon() = default;
GVArrayCommon(const GVArrayImpl *impl);
GVArrayCommon(std::shared_ptr<const GVArrayImpl> impl);
template<typename ImplT, typename... Args> void emplace(Args &&...args);
public:
const CPPType &type() const;
operator bool() const;
int64_t size() const;
bool is_empty() const;
IndexRange index_range() const;
template<typename T> bool try_assign_VArray(VArray<T> &varray) const;
bool may_have_ownership() const;
void materialize(void *dst) const;
void materialize(const IndexMask &mask, void *dst) const;
void materialize_to_uninitialized(void *dst) const;
void materialize_to_uninitialized(const IndexMask &mask, void *dst) const;
void materialize_compressed(const IndexMask &mask, void *dst) const;
void materialize_compressed_to_uninitialized(const IndexMask &mask, void *dst) const;
CommonVArrayInfo common_info() const;
/**
* Returns true when the virtual array is stored as a span internally.
*/
bool is_span() const;
/**
* Returns the internally used span of the virtual array. This invokes undefined behavior if the
* virtual array is not stored as a span internally.
*/
GSpan get_internal_span() const;
/**
* Returns true when the virtual array returns the same value for every index.
*/
bool is_single() const;
/**
* Copies the value that is used for every element into `r_value`, which is expected to point to
* initialized memory. This invokes undefined behavior if the virtual array would not return the
* same value for every index.
*/
void get_internal_single(void *r_value) const;
/**
* Same as `get_internal_single`, but `r_value` points to initialized memory.
*/
void get_internal_single_to_uninitialized(void *r_value) const;
void get(int64_t index, void *r_value) const;
/**
* Returns a copy of the value at the given index. Usually a typed virtual array should
* be used instead, but sometimes this is simpler when only a few indices are needed.
*/
template<typename T> T get(int64_t index) const;
void get_to_uninitialized(int64_t index, void *r_value) const;
};
/** Generic version of #VArray. */
class GVArray : public GVArrayCommon {
private:
friend GVMutableArray;
public:
GVArray() = default;
GVArray(const GVArrayImpl *impl);
GVArray(std::shared_ptr<const GVArrayImpl> impl);
GVArray(varray_tag::span /*tag*/, GSpan span);
GVArray(varray_tag::single_ref /*tag*/, const CPPType &type, int64_t size, const void *value);
GVArray(varray_tag::single /*tag*/, const CPPType &type, int64_t size, const void *value);
template<typename T> GVArray(const VArray<T> &varray);
template<typename T> GVArray(VArray<T> &&varray);
template<typename T> VArray<T> typed() const;
template<typename ImplT, typename... Args> static GVArray from(Args &&...args);
static GVArray from_single(const CPPType &type, int64_t size, const void *value);
static GVArray from_single_ref(const CPPType &type, int64_t size, const void *value);
static GVArray from_single_default(const CPPType &type, int64_t size);
static GVArray from_span(GSpan span);
static GVArray from_garray(GArray<> array);
static GVArray from_empty(const CPPType &type);
template<typename GetToUninitFn>
static GVArray from_func(const CPPType &type, int64_t size, GetToUninitFn &&get_to_uninit);
static GVArray from_std_func(const CPPType &type,
int64_t size,
std::function<void(int64_t index, void *r_value)> get_to_uninit);
GVArray slice(IndexRange slice) const;
const GVArrayImpl *get_implementation() const
{
return impl_.get();
}
};
/** Generic version of #VMutableArray. */
class GVMutableArray : public GVArrayCommon {
public:
GVMutableArray() = default;
GVMutableArray(GVMutableArrayImpl *impl);
GVMutableArray(std::shared_ptr<GVMutableArrayImpl> impl);
template<typename T> GVMutableArray(const VMutableArray<T> &varray);
template<typename T> VMutableArray<T> typed() const;
template<typename ImplT, typename... Args> static GVMutableArray from(Args &&...args);
static GVMutableArray from_span(GMutableSpan span);
operator GVArray() const &;
operator GVArray() && noexcept;
GMutableSpan get_internal_span() const;
template<typename T> bool try_assign_VMutableArray(VMutableArray<T> &varray) const;
void set_by_copy(int64_t index, const void *value);
void set_by_move(int64_t index, void *value);
void set_by_relocate(int64_t index, void *value);
void fill(const void *value);
/**
* Copy the values from the source buffer to all elements in the virtual array.
*/
void set_all(const void *src);
GVMutableArrayImpl *get_implementation() const;
private:
GVMutableArrayImpl *get_impl() const;
};
/** \} */
/* -------------------------------------------------------------------- */
/** \name #GVArraySpan and #GMutableVArraySpan.
* \{ */
/* A generic version of VArraySpan. */
class GVArraySpan : public GSpan {
private:
GVArray varray_;
void *owned_data_ = nullptr;
public:
GVArraySpan();
GVArraySpan(GVArray varray);
template<typename T> GVArraySpan(VArray<T> varray) : GVArraySpan(GVArray(varray)) {}
GVArraySpan(GVArraySpan &&other);
~GVArraySpan();
GVArraySpan &operator=(GVArraySpan &&other);
};
/* A generic version of MutableVArraySpan. */
class GMutableVArraySpan : public GMutableSpan, NonCopyable, NonMovable {
private:
GVMutableArray varray_;
void *owned_data_ = nullptr;
bool save_has_been_called_ = false;
bool show_not_saved_warning_ = true;
public:
GMutableVArraySpan();
GMutableVArraySpan(GVMutableArray varray, bool copy_values_to_span = true);
GMutableVArraySpan(GMutableVArraySpan &&other);
~GMutableVArraySpan();
GMutableVArraySpan &operator=(GMutableVArraySpan &&other);
const GVMutableArray &varray() const;
void save();
void disable_not_applied_warning();
};
/** \} */
/* -------------------------------------------------------------------- */
/** \name Conversions between generic and typed virtual arrays.
* \{ */
/* Used to convert a typed virtual array into a generic one. */
template<typename T> class GVArrayImpl_For_VArray : public GVArrayImpl {
protected:
VArray<T> varray_;
public:
GVArrayImpl_For_VArray(VArray<T> varray)
: GVArrayImpl(CPPType::get<T>(), varray.size()), varray_(std::move(varray))
{
}
protected:
void get(const int64_t index, void *r_value) const override
{
*static_cast<T *>(r_value) = varray_[index];
}
void get_to_uninitialized(const int64_t index, void *r_value) const override
{
new (r_value) T(varray_[index]);
}
void materialize(const IndexMask &mask,
void *dst,
const bool dst_is_uninitialized) const override
{
varray_.get_implementation()->materialize(mask, static_cast<T *>(dst), dst_is_uninitialized);
}
void materialize_compressed(const IndexMask &mask,
void *dst,
const bool dst_is_uninitialized) const override
{
varray_.get_implementation()->materialize_compressed(
mask, static_cast<T *>(dst), dst_is_uninitialized);
}
bool try_assign_VArray(void *varray) const override
{
*static_cast<VArray<T> *>(varray) = varray_;
return true;
}
CommonVArrayInfo common_info() const override
{
return varray_.common_info();
}
};
/* Used to convert any generic virtual array into a typed one. */
template<typename T> class VArrayImpl_For_GVArray : public VArrayImpl<T> {
protected:
GVArray varray_;
public:
VArrayImpl_For_GVArray(GVArray varray) : VArrayImpl<T>(varray.size()), varray_(std::move(varray))
{
BLI_assert(varray_);
BLI_assert(varray_.type().template is<T>());
}
protected:
T get(const int64_t index) const override
{
T value;
varray_.get(index, &value);
return value;
}
CommonVArrayInfo common_info() const override
{
return varray_.common_info();
}
bool try_assign_GVArray(GVArray &varray) const override
{
varray = varray_;
return true;
}
void materialize(const IndexMask &mask, T *dst, const bool dst_is_uninitialized) const override
{
varray_.get_implementation()->materialize(mask, dst, dst_is_uninitialized);
}
void materialize_compressed(const IndexMask &mask,
T *dst,
const bool dst_is_uninitialized) const override
{
varray_.get_implementation()->materialize_compressed(mask, dst, dst_is_uninitialized);
}
};
/* Used to convert any typed virtual mutable array into a generic one. */
template<typename T> class GVMutableArrayImpl_For_VMutableArray : public GVMutableArrayImpl {
protected:
VMutableArray<T> varray_;
public:
GVMutableArrayImpl_For_VMutableArray(VMutableArray<T> varray)
: GVMutableArrayImpl(CPPType::get<T>(), varray.size()), varray_(std::move(varray))
{
}
protected:
void get(const int64_t index, void *r_value) const override
{
*static_cast<T *>(r_value) = varray_[index];
}
void get_to_uninitialized(const int64_t index, void *r_value) const override
{
new (r_value) T(varray_[index]);
}
CommonVArrayInfo common_info() const override
{
return varray_.common_info();
}
void set_by_copy(const int64_t index, const void *value) override
{
const T &value_ = *static_cast<const T *>(value);
varray_.set(index, value_);
}
void set_by_relocate(const int64_t index, void *value) override
{
T &value_ = *static_cast<T *>(value);
varray_.set(index, std::move(value_));
value_.~T();
}
void set_by_move(const int64_t index, void *value) override
{
T &value_ = *static_cast<T *>(value);
varray_.set(index, std::move(value_));
}
void set_all(const void *src) override
{
varray_.set_all(Span(static_cast<const T *>(src), size_));
}
void materialize(const IndexMask &mask,
void *dst,
const bool dst_is_uninitialized) const override
{
varray_.get_implementation()->materialize(mask, static_cast<T *>(dst), dst_is_uninitialized);
}
void materialize_compressed(const IndexMask &mask,
void *dst,
const bool dst_is_uninitialized) const override
{
varray_.get_implementation()->materialize_compressed(
mask, static_cast<T *>(dst), dst_is_uninitialized);
}
bool try_assign_VArray(void *varray) const override
{
*static_cast<VArray<T> *>(varray) = varray_;
return true;
}
bool try_assign_VMutableArray(void *varray) const override
{
*static_cast<VMutableArray<T> *>(varray) = varray_;
return true;
}
};
/* Used to convert an generic mutable virtual array into a typed one. */
template<typename T> class VMutableArrayImpl_For_GVMutableArray : public VMutableArrayImpl<T> {
protected:
GVMutableArray varray_;
public:
VMutableArrayImpl_For_GVMutableArray(GVMutableArray varray)
: VMutableArrayImpl<T>(varray.size()), varray_(varray)
{
BLI_assert(varray_);
BLI_assert(varray_.type().template is<T>());
}
private:
T get(const int64_t index) const override
{
T value;
varray_.get(index, &value);
return value;
}
void set(const int64_t index, T value) override
{
varray_.set_by_relocate(index, &value);
}
CommonVArrayInfo common_info() const override
{
return varray_.common_info();
}
bool try_assign_GVArray(GVArray &varray) const override
{
varray = varray_;
return true;
}
bool try_assign_GVMutableArray(GVMutableArray &varray) const override
{
varray = varray_;
return true;
}
void materialize(const IndexMask &mask, T *dst, const bool dst_is_uninitialized) const override
{
varray_.get_implementation()->materialize(mask, dst, dst_is_uninitialized);
}
void materialize_compressed(const IndexMask &mask,
T *dst,
const bool dst_is_uninitialized) const override
{
varray_.get_implementation()->materialize_compressed(mask, dst, dst_is_uninitialized);
}
};
/** \} */
/* -------------------------------------------------------------------- */
/** \name #GVArrayImpl_For_GSpan.
* \{ */
class GVArrayImpl_For_GSpan : public GVMutableArrayImpl {
protected:
void *data_ = nullptr;
const int64_t element_size_;
public:
GVArrayImpl_For_GSpan(const GMutableSpan span)
: GVMutableArrayImpl(span.type(), span.size()),
data_(span.data()),
element_size_(span.type().size)
{
}
protected:
GVArrayImpl_For_GSpan(const CPPType &type, int64_t size)
: GVMutableArrayImpl(type, size), element_size_(type.size)
{
}
public:
void get(int64_t index, void *r_value) const override;
void get_to_uninitialized(int64_t index, void *r_value) const override;
void set_by_copy(int64_t index, const void *value) override;
void set_by_move(int64_t index, void *value) override;
void set_by_relocate(int64_t index, void *value) override;
CommonVArrayInfo common_info() const override;
void materialize(const IndexMask &mask, void *dst, bool dst_is_uninitialized) const override;
void materialize_compressed(const IndexMask &mask,
void *dst,
bool dst_is_uninitialized) const override;
};
class GVArrayImpl_For_GSpan_final final : public GVArrayImpl_For_GSpan {
public:
using GVArrayImpl_For_GSpan::GVArrayImpl_For_GSpan;
private:
CommonVArrayInfo common_info() const override;
};
template<> inline constexpr bool is_trivial_extended_v<GVArrayImpl_For_GSpan_final> = true;
/** \} */
/* -------------------------------------------------------------------- */
/** \name #GVArrayImpl_For_SingleValueRef.
* \{ */
class GVArrayImpl_For_SingleValueRef : public GVArrayImpl {
protected:
const void *value_ = nullptr;
public:
GVArrayImpl_For_SingleValueRef(const CPPType &type, const int64_t size, const void *value)
: GVArrayImpl(type, size), value_(value)
{
}
protected:
GVArrayImpl_For_SingleValueRef(const CPPType &type, const int64_t size) : GVArrayImpl(type, size)
{
}
void get(const int64_t index, void *r_value) const override;
void get_to_uninitialized(const int64_t index, void *r_value) const override;
CommonVArrayInfo common_info() const override;
void materialize(const IndexMask &mask, void *dst, bool dst_is_uninitialized) const override;
void materialize_compressed(const IndexMask &mask,
void *dst,
bool dst_is_uninitialized) const override;
};
class GVArrayImpl_For_SingleValueRef_final final : public GVArrayImpl_For_SingleValueRef {
public:
using GVArrayImpl_For_SingleValueRef::GVArrayImpl_For_SingleValueRef;
private:
CommonVArrayInfo common_info() const override;
};
template<>
inline constexpr bool is_trivial_extended_v<GVArrayImpl_For_SingleValueRef_final> = true;
/** \} */
/* -------------------------------------------------------------------- */
/** \name #GVArrayImpl_For_Func.
* \{ */
template<typename GetToUninitFn> class GVArrayImpl_For_Func final : public GVArrayImpl {
private:
GetToUninitFn get_to_uninit_;
public:
GVArrayImpl_For_Func(const CPPType &type, const int64_t size, GetToUninitFn get_to_uninit)
: GVArrayImpl(type, size), get_to_uninit_(std::move(get_to_uninit))
{
}
void get(const int64_t index, void *r_value) const override
{
if (!type_->is_trivially_destructible) {
type_->destruct(r_value);
}
return get_to_uninit_(index, r_value);
}
void get_to_uninitialized(const int64_t index, void *r_value) const override
{
return get_to_uninit_(index, r_value);
}
};
/** \} */
/* -------------------------------------------------------------------- */
/** \name Inline methods for #GVArrayImpl.
* \{ */
inline GVArrayImpl::GVArrayImpl(const CPPType &type, const int64_t size)
: type_(&type), size_(size)
{
BLI_assert(size_ >= 0);
}
inline const CPPType &GVArrayImpl::type() const
{
return *type_;
}
inline int64_t GVArrayImpl::size() const
{
return size_;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Inline methods for #GVMutableArrayImpl.
* \{ */
inline void GVMutableArray::set_by_copy(const int64_t index, const void *value)
{
BLI_assert(index >= 0);
BLI_assert(index < this->size());
this->get_impl()->set_by_copy(index, value);
}
inline void GVMutableArray::set_by_move(const int64_t index, void *value)
{
BLI_assert(index >= 0);
BLI_assert(index < this->size());
this->get_impl()->set_by_move(index, value);
}
inline void GVMutableArray::set_by_relocate(const int64_t index, void *value)
{
BLI_assert(index >= 0);
BLI_assert(index < this->size());
this->get_impl()->set_by_relocate(index, value);
}
template<typename T>
inline bool GVMutableArray::try_assign_VMutableArray(VMutableArray<T> &varray) const
{
BLI_assert(impl_->type().is<T>());
return this->get_impl()->try_assign_VMutableArray(&varray);
}
inline GVMutableArrayImpl *GVMutableArray::get_impl() const
{
return const_cast<GVMutableArrayImpl *>(static_cast<const GVMutableArrayImpl *>(impl_.get()));
}
inline GVMutableArrayImpl *GVMutableArray::get_implementation() const
{
return this->get_impl();
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Inline methods for #GVArrayCommon.
* \{ */
template<typename ImplT, typename... Args> inline void GVArrayCommon::emplace(Args &&...args)
{
impl_.emplace<ImplT>(std::forward<Args>(args)...);
}
/* Copies the value at the given index into the provided storage. The `r_value` pointer is
* expected to point to initialized memory. */
inline void GVArrayCommon::get(const int64_t index, void *r_value) const
{
BLI_assert(index >= 0);
BLI_assert(index < this->size());
impl_->get(index, r_value);
}
template<typename T> inline T GVArrayCommon::get(const int64_t index) const
{
BLI_assert(index >= 0);
BLI_assert(index < this->size());
BLI_assert(this->type().is<T>());
T value{};
impl_->get(index, &value);
return value;
}
/* Same as `get`, but `r_value` is expected to point to uninitialized memory. */
inline void GVArrayCommon::get_to_uninitialized(const int64_t index, void *r_value) const
{
BLI_assert(index >= 0);
BLI_assert(index < this->size());
impl_->get_to_uninitialized(index, r_value);
}
template<typename T> inline bool GVArrayCommon::try_assign_VArray(VArray<T> &varray) const
{
BLI_assert(impl_->type().is<T>());
return impl_->try_assign_VArray(&varray);
}
inline const CPPType &GVArrayCommon::type() const
{
return impl_->type();
}
inline GVArrayCommon::operator bool() const
{
return impl_;
}
inline CommonVArrayInfo GVArrayCommon::common_info() const
{
return impl_->common_info();
}
inline int64_t GVArrayCommon::size() const
{
if (!impl_) {
return 0;
}
return impl_->size();
}
inline bool GVArrayCommon::is_empty() const
{
return this->size() == 0;
}
/** \} */
/** To be used with #call_with_devirtualized_parameters. */
template<typename T, bool UseSingle, bool UseSpan> struct GVArrayDevirtualizer {
const GVArrayImpl &varray_impl;
template<typename Fn> bool devirtualize(const Fn &fn) const
{
const CommonVArrayInfo info = this->varray_impl.common_info();
const int64_t size = this->varray_impl.size();
if constexpr (UseSingle) {
if (info.type == CommonVArrayInfo::Type::Single) {
return fn(SingleAsSpan<T>(*static_cast<const T *>(info.data), size));
}
}
if constexpr (UseSpan) {
if (info.type == CommonVArrayInfo::Type::Span) {
return fn(Span<T>(static_cast<const T *>(info.data), size));
}
}
return false;
}
};
/* -------------------------------------------------------------------- */
/** \name Inline methods for #GVArray.
* \{ */
inline GVArray::GVArray(varray_tag::span /*tag*/, const GSpan span)
{
/* Use const-cast because the underlying virtual array implementation is shared between const
* and non const data. */
GMutableSpan mutable_span{span.type(), const_cast<void *>(span.data()), span.size()};
this->emplace<GVArrayImpl_For_GSpan_final>(mutable_span);
}
inline GVArray::GVArray(varray_tag::single_ref /*tag*/,
const CPPType &type,
const int64_t size,
const void *value)
{
this->emplace<GVArrayImpl_For_SingleValueRef_final>(type, size, value);
}
namespace blenlib_detail {
template<typename StorageT> constexpr GVArrayAnyExtraInfo GVArrayAnyExtraInfo::get()
{
static_assert(std::is_base_of_v<GVArrayImpl, StorageT> ||
is_same_any_v<StorageT, const GVArrayImpl *, std::shared_ptr<const GVArrayImpl>>);
if constexpr (std::is_base_of_v<GVArrayImpl, StorageT>) {
return {[](const void *buffer) {
return static_cast<const GVArrayImpl *>(static_cast<const StorageT *>(buffer));
}};
}
else if constexpr (std::is_same_v<StorageT, const GVArrayImpl *>) {
return {[](const void *buffer) { return *static_cast<const StorageT *>(buffer); }};
}
else if constexpr (std::is_same_v<StorageT, std::shared_ptr<const GVArrayImpl>>) {
return {[](const void *buffer) { return (static_cast<const StorageT *>(buffer))->get(); }};
}
else {
BLI_assert_unreachable();
return {};
}
}
} // namespace blenlib_detail
template<typename ImplT, typename... Args> inline GVArray GVArray::from(Args &&...args)
{
static_assert(std::is_base_of_v<GVArrayImpl, ImplT>);
GVArray varray;
varray.template emplace<ImplT>(std::forward<Args>(args)...);
return varray;
}
template<typename T> inline GVArray::GVArray(const VArray<T> &varray) : GVArray(VArray<T>(varray))
{
}
template<typename T> inline GVArray::GVArray(VArray<T> &&varray)
{
if (!varray) {
return;
}
const CommonVArrayInfo info = varray.common_info();
if (info.type == CommonVArrayInfo::Type::Single) {
*this = GVArray::from_single(CPPType::get<T>(), varray.size(), info.data);
return;
}
/* Need to check for ownership, because otherwise the referenced data can be destructed when
* #this is destructed. */
if (info.type == CommonVArrayInfo::Type::Span && !info.may_have_ownership) {
*this = GVArray::from_span(GSpan(CPPType::get<T>(), info.data, varray.size()));
return;
}
if (varray.try_assign_GVArray(*this)) {
return;
}
*this = GVArray::from<GVArrayImpl_For_VArray<T>>(std::move(varray));
}
inline GVArray::GVArray(const GVArrayImpl *impl) : GVArrayCommon(impl) {}
inline GVArray::GVArray(std::shared_ptr<const GVArrayImpl> impl) : GVArrayCommon(std::move(impl))
{
}
inline GVArray GVArray::from_single(const CPPType &type, const int64_t size, const void *value)
{
return GVArray(varray_tag::single{}, type, size, value);
}
inline GVArray GVArray::from_single_ref(const CPPType &type, const int64_t size, const void *value)
{
return GVArray(varray_tag::single_ref{}, type, size, value);
}
inline GVArray GVArray::from_single_default(const CPPType &type, const int64_t size)
{
return GVArray::from_single_ref(type, size, type.default_value());
}
inline GVArray GVArray::from_span(GSpan span)
{
return GVArray(varray_tag::span{}, span);
}
inline GVArray GVArray::from_empty(const CPPType &type)
{
return GVArray::from_span(GSpan(type));
}
inline GVArray GVArray::from_std_func(
const CPPType &type,
int64_t size,
std::function<void(int64_t index, void *r_value)> get_to_uninit)
{
return GVArray::from_func(type, size, std::move(get_to_uninit));
}
template<typename T> inline VArray<T> GVArray::typed() const
{
if (!*this) {
return {};
}
BLI_assert(impl_->type().is<T>());
const CommonVArrayInfo info = this->common_info();
if (info.type == CommonVArrayInfo::Type::Single) {
return VArray<T>::from_single(*static_cast<const T *>(info.data), this->size());
}
/* Need to check for ownership, because otherwise the referenced data can be destructed when
* #this is destructed. */
if (info.type == CommonVArrayInfo::Type::Span && !info.may_have_ownership) {
return VArray<T>::from_span(Span<T>(static_cast<const T *>(info.data), this->size()));
}
VArray<T> varray;
if (this->try_assign_VArray(varray)) {
return varray;
}
return VArray<T>::template from<VArrayImpl_For_GVArray<T>>(*this);
}
template<typename GetToUninitFn>
inline GVArray GVArray::from_func(const CPPType &type, int64_t size, GetToUninitFn &&get_to_uninit)
{
return GVArray::from<GVArrayImpl_For_Func<GetToUninitFn>>(
type, size, std::forward<GetToUninitFn>(get_to_uninit));
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Inline methods for #GVMutableArray.
* \{ */
template<typename ImplT, typename... Args>
inline GVMutableArray GVMutableArray::from(Args &&...args)
{
static_assert(std::is_base_of_v<GVMutableArrayImpl, ImplT>);
GVMutableArray varray;
varray.emplace<ImplT>(std::forward<Args>(args)...);
return varray;
}
template<typename T> inline GVMutableArray::GVMutableArray(const VMutableArray<T> &varray)
{
if (!varray) {
return;
}
const CommonVArrayInfo info = varray.common_info();
if (info.type == CommonVArrayInfo::Type::Span && !info.may_have_ownership) {
*this = GVMutableArray::from_span(
GMutableSpan(CPPType::get<T>(), const_cast<void *>(info.data), varray.size()));
return;
}
if (varray.try_assign_GVMutableArray(*this)) {
return;
}
*this = GVMutableArray::from<GVMutableArrayImpl_For_VMutableArray<T>>(varray);
}
template<typename T> inline VMutableArray<T> GVMutableArray::typed() const
{
if (!*this) {
return {};
}
BLI_assert(this->type().is<T>());
const CommonVArrayInfo info = this->common_info();
if (info.type == CommonVArrayInfo::Type::Span && !info.may_have_ownership) {
return VMutableArray<T>::from_span(
MutableSpan<T>(const_cast<T *>(static_cast<const T *>(info.data)), this->size()));
}
VMutableArray<T> varray;
if (this->try_assign_VMutableArray(varray)) {
return varray;
}
return VMutableArray<T>::template from<VMutableArrayImpl_For_GVMutableArray<T>>(*this);
}
/** \} */
} // namespace blender

View File

@@ -0,0 +1,173 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bli
*
* A generic virtual vector array is essentially the same as a virtual vector array, but its data
* type is only known at runtime.
*/
#include "BLI_generic_virtual_array.hh"
#include "BLI_virtual_vector_array.hh"
namespace blender {
/* A generically typed version of `VVectorArray`. */
class GVVectorArray {
protected:
const CPPType *type_;
int64_t size_;
public:
GVVectorArray(const CPPType &type, const int64_t size) : type_(&type), size_(size) {}
virtual ~GVVectorArray() = default;
/* Returns the number of vectors in the vector array. */
int64_t size() const
{
return size_;
}
/* Returns true when there is no vector in the vector array. */
bool is_empty() const
{
return size_ == 0;
}
const CPPType &type() const
{
return *type_;
}
/* Returns the size of the vector at the given index. */
int64_t get_vector_size(const int64_t index) const
{
BLI_assert(index >= 0);
BLI_assert(index < size_);
return this->get_vector_size_impl(index);
}
/* Copies an element from one of the vectors into `r_value`, which is expected to point to
* initialized memory. */
void get_vector_element(const int64_t index, const int64_t index_in_vector, void *r_value) const
{
BLI_assert(index >= 0);
BLI_assert(index < size_);
BLI_assert(index_in_vector >= 0);
BLI_assert(index_in_vector < this->get_vector_size(index));
this->get_vector_element_impl(index, index_in_vector, r_value);
}
/* Returns true when the same vector is used at every index. */
bool is_single_vector() const
{
if (size_ == 1) {
return true;
}
return this->is_single_vector_impl();
}
protected:
virtual int64_t get_vector_size_impl(int64_t index) const = 0;
virtual void get_vector_element_impl(int64_t index,
int64_t index_in_vector,
void *r_value) const = 0;
virtual bool is_single_vector_impl() const
{
return false;
}
};
class GVArray_For_GVVectorArrayIndex : public GVArrayImpl {
private:
const GVVectorArray &vector_array_;
const int64_t index_;
public:
GVArray_For_GVVectorArrayIndex(const GVVectorArray &vector_array, const int64_t index)
: GVArrayImpl(vector_array.type(), vector_array.get_vector_size(index)),
vector_array_(vector_array),
index_(index)
{
}
protected:
void get(int64_t index_in_vector, void *r_value) const override;
void get_to_uninitialized(int64_t index_in_vector, void *r_value) const override;
};
class GVVectorArray_For_SingleGVArray : public GVVectorArray {
private:
GVArray varray_;
public:
GVVectorArray_For_SingleGVArray(GVArray varray, const int64_t size)
: GVVectorArray(varray.type(), size), varray_(std::move(varray))
{
}
protected:
int64_t get_vector_size_impl(int64_t index) const override;
void get_vector_element_impl(int64_t index,
int64_t index_in_vector,
void *r_value) const override;
bool is_single_vector_impl() const override;
};
class GVVectorArray_For_SingleGSpan : public GVVectorArray {
private:
const GSpan span_;
public:
GVVectorArray_For_SingleGSpan(const GSpan span, const int64_t size)
: GVVectorArray(span.type(), size), span_(span)
{
}
protected:
int64_t get_vector_size_impl(int64_t /*index*/) const override;
void get_vector_element_impl(int64_t /*index*/,
int64_t index_in_vector,
void *r_value) const override;
bool is_single_vector_impl() const override;
};
template<typename T> class VVectorArray_For_GVVectorArray : public VVectorArray<T> {
private:
const GVVectorArray &vector_array_;
public:
VVectorArray_For_GVVectorArray(const GVVectorArray &vector_array)
: VVectorArray<T>(vector_array.size()), vector_array_(vector_array)
{
}
protected:
int64_t get_vector_size_impl(const int64_t index) const override
{
return vector_array_.get_vector_size(index);
}
T get_vector_element_impl(const int64_t index, const int64_t index_in_vector) const override
{
T value;
vector_array_.get_vector_element(index, index_in_vector, &value);
return value;
}
bool is_single_vector_impl() const override
{
return vector_array_.is_single_vector();
}
};
} // namespace blender

View File

@@ -0,0 +1,644 @@
/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bli
*
* GHash is a hash-map implementation (unordered key, value pairs).
*
* This is also used to implement a 'set' (see #GSet below).
*/
#include "BLI_compiler_attrs.h"
#include "BLI_compiler_compat.h"
#include "BLI_sys_types.h" /* for bool */
namespace blender {
#define _GHASH_INTERNAL_ATTR
#ifndef GHASH_INTERNAL_API
# ifdef __GNUC__
# undef _GHASH_INTERNAL_ATTR
# define _GHASH_INTERNAL_ATTR __attribute__((deprecated)) /* not deprecated, just private. */
# endif
#endif
/* -------------------------------------------------------------------- */
/** \name GHash Types
* \{ */
typedef unsigned int (*GHashHashFP)(const void *key);
/** returns false when equal */
typedef bool (*GHashCmpFP)(const void *a, const void *b);
typedef void (*GHashKeyFreeFP)(void *key);
typedef void (*GHashValFreeFP)(void *val);
typedef void *(*GHashKeyCopyFP)(const void *key);
typedef void *(*GHashValCopyFP)(const void *val);
struct GHash;
struct GHashIterator {
GHash *gh;
struct Entry *curEntry;
unsigned int curBucket;
};
struct GHashIterState {
unsigned int curr_bucket _GHASH_INTERNAL_ATTR;
};
enum {
GHASH_FLAG_ALLOW_DUPES = (1 << 0), /* Only checked for in debug mode */
GHASH_FLAG_ALLOW_SHRINK = (1 << 1), /* Allow to shrink buckets' size. */
#ifdef GHASH_INTERNAL_API
/* Internal usage only */
/* Whether the GHash is actually used as GSet (no value storage). */
GHASH_FLAG_IS_GSET = (1 << 16),
#endif
};
/** \} */
/* -------------------------------------------------------------------- */
/** \name GHash API
*
* Defined in `BLI_ghash.c`
* \{ */
/**
* Creates a new, empty GHash.
*
* \param hashfp: Hash callback.
* \param cmpfp: Comparison callback.
* \param info: Identifier string for the GHash.
* \param nentries_reserve: Optionally reserve the number of members that the hash will hold.
* Use this to avoid resizing buckets if the size is known or can be closely approximated.
* \return An empty GHash.
*/
GHash *BLI_ghash_new_ex(GHashHashFP hashfp,
GHashCmpFP cmpfp,
const char *info,
unsigned int nentries_reserve) ATTR_MALLOC ATTR_WARN_UNUSED_RESULT;
/**
* Wraps #BLI_ghash_new_ex with zero entries reserved.
*/
GHash *BLI_ghash_new(GHashHashFP hashfp,
GHashCmpFP cmpfp,
const char *info) ATTR_MALLOC ATTR_WARN_UNUSED_RESULT;
/**
* Copy given GHash. Keys and values are also copied if relevant callback is provided,
* else pointers remain the same.
*/
GHash *BLI_ghash_copy(const GHash *gh,
GHashKeyCopyFP keycopyfp,
GHashValCopyFP valcopyfp) ATTR_MALLOC ATTR_WARN_UNUSED_RESULT;
/**
* Frees the GHash and its members.
*
* \param gh: The GHash to free.
* \param keyfreefp: Optional callback to free the key.
* \param valfreefp: Optional callback to free the value.
*/
void BLI_ghash_free(GHash *gh, GHashKeyFreeFP keyfreefp, GHashValFreeFP valfreefp);
/**
* Reserve given amount of entries (resize \a gh accordingly if needed).
*/
void BLI_ghash_reserve(GHash *gh, unsigned int nentries_reserve);
/**
* Insert a key/value pair into the \a gh.
*
* \note Duplicates are not checked,
* the caller is expected to ensure elements are unique unless
* GHASH_FLAG_ALLOW_DUPES flag is set.
*/
void BLI_ghash_insert(GHash *gh, void *key, void *val);
/**
* Inserts a new value to a key that may already be in ghash.
*
* Avoids #BLI_ghash_remove, #BLI_ghash_insert calls (double lookups)
*
* \returns true if a new key has been added.
*/
bool BLI_ghash_reinsert(
GHash *gh, void *key, void *val, GHashKeyFreeFP keyfreefp, GHashValFreeFP valfreefp);
/**
* Replaces the key of an item in the \a gh.
*
* Use when a key is re-allocated or its memory location is changed.
*
* \returns The previous key or NULL if not found, the caller may free if it's needed.
*/
void *BLI_ghash_replace_key(GHash *gh, void *key);
/**
* Lookup the value of \a key in \a gh.
*
* \param key: The key to lookup.
* \returns the value for \a key or NULL.
*
* \note When NULL is a valid value, use #BLI_ghash_lookup_p to differentiate a missing key
* from a key with a NULL value. (Avoids calling #BLI_ghash_haskey before #BLI_ghash_lookup)
*/
void *BLI_ghash_lookup(const GHash *gh, const void *key) ATTR_WARN_UNUSED_RESULT;
/**
* A version of #BLI_ghash_lookup which accepts a fallback argument.
*/
void *BLI_ghash_lookup_default(const GHash *gh,
const void *key,
void *val_default) ATTR_WARN_UNUSED_RESULT;
/**
* Lookup a pointer to the value of \a key in \a gh.
*
* \param key: The key to lookup.
* \returns the pointer to value for \a key or NULL.
*
* \note This has 2 main benefits over #BLI_ghash_lookup.
* - A NULL return always means that \a key isn't in \a gh.
* - The value can be modified in-place without further function calls (faster).
*/
void **BLI_ghash_lookup_p(GHash *gh, const void *key) ATTR_WARN_UNUSED_RESULT;
/**
* Ensure \a key is exists in \a gh.
*
* This handles the common situation where the caller needs ensure a key is added to \a gh,
* constructing a new value in the case the key isn't found.
* Otherwise use the existing value.
*
* Such situations typically incur multiple lookups, however this function
* avoids them by ensuring the key is added,
* returning a pointer to the value so it can be used or initialized by the caller.
*
* \returns true when the value didn't need to be added.
* (when false, the caller _must_ initialize the value).
*/
bool BLI_ghash_ensure_p(GHash *gh, void *key, void ***r_val) ATTR_WARN_UNUSED_RESULT;
/**
* A version of #BLI_ghash_ensure_p that allows caller to re-assign the key.
* Typically used when the key is to be duplicated.
*
* \warning Caller _must_ write to \a r_key when returning false.
*/
bool BLI_ghash_ensure_p_ex(GHash *gh, const void *key, void ***r_key, void ***r_val)
ATTR_WARN_UNUSED_RESULT;
/**
* Remove \a key from \a gh, or return false if the key wasn't found.
*
* \param key: The key to remove.
* \param keyfreefp: Optional callback to free the key.
* \param valfreefp: Optional callback to free the value.
* \return true if \a key was removed from \a gh.
*/
bool BLI_ghash_remove(GHash *gh,
const void *key,
GHashKeyFreeFP keyfreefp,
GHashValFreeFP valfreefp);
/**
* Wraps #BLI_ghash_clear_ex with zero entries reserved.
*/
void BLI_ghash_clear(GHash *gh, GHashKeyFreeFP keyfreefp, GHashValFreeFP valfreefp);
/**
* Reset \a gh clearing all entries.
*
* \param keyfreefp: Optional callback to free the key.
* \param valfreefp: Optional callback to free the value.
* \param nentries_reserve: Optionally reserve the number of members that the hash will hold.
*/
void BLI_ghash_clear_ex(GHash *gh,
GHashKeyFreeFP keyfreefp,
GHashValFreeFP valfreefp,
unsigned int nentries_reserve);
/**
* Remove \a key from \a gh, returning the value or NULL if the key wasn't found.
*
* \param key: The key to remove.
* \param keyfreefp: Optional callback to free the key.
* \return the value of \a key int \a gh or NULL.
*/
void *BLI_ghash_popkey(GHash *gh,
const void *key,
GHashKeyFreeFP keyfreefp) ATTR_WARN_UNUSED_RESULT;
/**
* \return true if the \a key is in \a gh.
*/
bool BLI_ghash_haskey(const GHash *gh, const void *key) ATTR_WARN_UNUSED_RESULT;
/**
* Remove a random entry from \a gh, returning true
* if a key/value pair could be removed, false otherwise.
*
* \param state: Used for efficient removal.
* \param r_key: The removed key.
* \param r_val: The removed value.
* \return true if there was something to pop, false if ghash was already empty.
*/
bool BLI_ghash_pop(GHash *gh, GHashIterState *state, void **r_key, void **r_val)
ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/**
* \return size of the GHash.
*/
unsigned int BLI_ghash_len(const GHash *gh) ATTR_WARN_UNUSED_RESULT;
/**
* Sets a GHash flag.
*/
void BLI_ghash_flag_set(GHash *gh, unsigned int flag);
/**
* Clear a GHash flag.
*/
void BLI_ghash_flag_clear(GHash *gh, unsigned int flag);
/** \} */
/* -------------------------------------------------------------------- */
/** \name GHash Iterator
* \{ */
/**
* Create a new GHashIterator. The hash table must not be mutated
* while the iterator is in use, and the iterator will step exactly
* #BLI_ghash_len(gh) times before becoming done.
*
* \param gh: The GHash to iterate over.
* \return Pointer to a new iterator.
*/
GHashIterator *BLI_ghashIterator_new(GHash *gh) ATTR_MALLOC ATTR_WARN_UNUSED_RESULT;
/**
* Init an already allocated GHashIterator. The hash table must not
* be mutated while the iterator is in use, and the iterator will
* step exactly #BLI_ghash_len(gh) times before becoming done.
*
* \param ghi: The GHashIterator to initialize.
* \param gh: The GHash to iterate over.
*/
void BLI_ghashIterator_init(GHashIterator *ghi, GHash *gh);
/**
* Free a GHashIterator.
*
* \param ghi: The iterator to free.
*/
void BLI_ghashIterator_free(GHashIterator *ghi);
/**
* Steps the iterator to the next index.
*
* \param ghi: The iterator.
*/
void BLI_ghashIterator_step(GHashIterator *ghi);
BLI_INLINE void *BLI_ghashIterator_getKey(GHashIterator *ghi) ATTR_WARN_UNUSED_RESULT;
BLI_INLINE void *BLI_ghashIterator_getValue(GHashIterator *ghi) ATTR_WARN_UNUSED_RESULT;
BLI_INLINE void **BLI_ghashIterator_getValue_p(GHashIterator *ghi) ATTR_WARN_UNUSED_RESULT;
BLI_INLINE bool BLI_ghashIterator_done(const GHashIterator *ghi) ATTR_WARN_UNUSED_RESULT;
struct _gh_Entry {
void *next, *key, *val;
};
BLI_INLINE void *BLI_ghashIterator_getKey(GHashIterator *ghi)
{
return (reinterpret_cast<struct _gh_Entry *>(ghi->curEntry))->key;
}
BLI_INLINE void *BLI_ghashIterator_getValue(GHashIterator *ghi)
{
return (reinterpret_cast<struct _gh_Entry *>(ghi->curEntry))->val;
}
BLI_INLINE void **BLI_ghashIterator_getValue_p(GHashIterator *ghi)
{
return &(reinterpret_cast<struct _gh_Entry *>(ghi->curEntry))->val;
}
BLI_INLINE bool BLI_ghashIterator_done(const GHashIterator *ghi)
{
return !ghi->curEntry;
}
/* disallow further access */
#ifdef __GNUC__
# pragma GCC poison _gh_Entry
#else
# define _gh_Entry void
#endif
#define GHASH_ITER(gh_iter_, ghash_) \
for (BLI_ghashIterator_init(&gh_iter_, ghash_); BLI_ghashIterator_done(&gh_iter_) == false; \
BLI_ghashIterator_step(&gh_iter_))
#define GHASH_ITER_INDEX(gh_iter_, ghash_, i_) \
for (BLI_ghashIterator_init(&gh_iter_, ghash_), i_ = 0; \
BLI_ghashIterator_done(&gh_iter_) == false; \
BLI_ghashIterator_step(&gh_iter_), i_++)
/** \} */
/* -------------------------------------------------------------------- */
/** \name GSet Types
* A "set" implementation (unordered collection of unique elements).
*
* Internally this is a 'GHash' without any keys,
* which is why this API's are in the same header & source file.
* \{ */
struct GSet;
typedef GHashHashFP GSetHashFP;
typedef GHashCmpFP GSetCmpFP;
typedef GHashKeyFreeFP GSetKeyFreeFP;
typedef GHashKeyCopyFP GSetKeyCopyFP;
typedef GHashIterState GSetIterState;
/** \} */
/** \name GSet Public API
*
* Use ghash API to give 'set' functionality
* \{ */
GSet *BLI_gset_new_ex(GSetHashFP hashfp,
GSetCmpFP cmpfp,
const char *info,
unsigned int nentries_reserve) ATTR_MALLOC ATTR_WARN_UNUSED_RESULT;
GSet *BLI_gset_new(GSetHashFP hashfp,
GSetCmpFP cmpfp,
const char *info) ATTR_MALLOC ATTR_WARN_UNUSED_RESULT;
/**
* Copy given GSet. Keys are also copied if callback is provided, else pointers remain the same.
*/
GSet *BLI_gset_copy(const GSet *gs, GSetKeyCopyFP keycopyfp) ATTR_MALLOC ATTR_WARN_UNUSED_RESULT;
unsigned int BLI_gset_len(const GSet *gs) ATTR_WARN_UNUSED_RESULT;
void BLI_gset_flag_set(GSet *gs, unsigned int flag);
void BLI_gset_flag_clear(GSet *gs, unsigned int flag);
void BLI_gset_free(GSet *gs, GSetKeyFreeFP keyfreefp);
/**
* Adds the key to the set (no checks for unique keys!).
* Matching #BLI_ghash_insert
*/
void BLI_gset_insert(GSet *gs, void *key);
/**
* A version of BLI_gset_insert which checks first if the key is in the set.
* \returns true if a new key has been added.
*
* \note GHash has no equivalent to this because typically the value would be different.
*/
bool BLI_gset_add(GSet *gs, void *key);
/**
* Set counterpart to #BLI_ghash_ensure_p_ex.
* similar to BLI_gset_add, except it returns the key pointer.
*
* \warning Caller _must_ write to \a r_key when returning false.
*/
bool BLI_gset_ensure_p_ex(GSet *gs, const void *key, void ***r_key);
/**
* Adds the key to the set (duplicates are managed).
* Matching #BLI_ghash_reinsert
*
* \returns true if a new key has been added.
*/
bool BLI_gset_reinsert(GSet *gs, void *key, GSetKeyFreeFP keyfreefp);
/**
* Replaces the key to the set if it's found.
* Matching #BLI_ghash_replace_key
*
* \returns The old key or NULL if not found.
*/
void *BLI_gset_replace_key(GSet *gs, void *key);
bool BLI_gset_haskey(const GSet *gs, const void *key) ATTR_WARN_UNUSED_RESULT;
/**
* Remove a random entry from \a gs, returning true if a key could be removed, false otherwise.
*
* \param state: Used for efficient removal.
* \param r_key: The removed key.
* \return true if there was something to pop, false if gset was already empty.
*/
bool BLI_gset_pop(GSet *gs, GSetIterState *state, void **r_key) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL();
bool BLI_gset_remove(GSet *gs, const void *key, GSetKeyFreeFP keyfreefp);
void BLI_gset_clear_ex(GSet *gs, GSetKeyFreeFP keyfreefp, unsigned int nentries_reserve);
void BLI_gset_clear(GSet *gs, GSetKeyFreeFP keyfreefp);
/* When set's are used for key & value. */
/**
* Returns the pointer to the key if it's found.
*/
void *BLI_gset_lookup(const GSet *gs, const void *key) ATTR_WARN_UNUSED_RESULT;
/**
* Returns the pointer to the key if it's found, removing it from the GSet.
* \note Caller must handle freeing.
*/
void *BLI_gset_pop_key(GSet *gs, const void *key) ATTR_WARN_UNUSED_RESULT;
/** \} */
/* -------------------------------------------------------------------- */
/** \name GSet Iterator
* \{ */
/* Rely on inline API for now. */
/** Use a GSet specific type so we can cast but compiler sees as different */
struct GSetIterator {
GHashIterator _ghi
#if defined(__GNUC__) && !defined(__clang__)
__attribute__((deprecated))
#endif
;
};
BLI_INLINE GSetIterator *BLI_gsetIterator_new(GSet *gs)
{
return reinterpret_cast<GSetIterator *>(BLI_ghashIterator_new(reinterpret_cast<GHash *>(gs)));
}
BLI_INLINE void BLI_gsetIterator_init(GSetIterator *gsi, GSet *gs)
{
BLI_ghashIterator_init(reinterpret_cast<GHashIterator *>(gsi), reinterpret_cast<GHash *>(gs));
}
BLI_INLINE void BLI_gsetIterator_free(GSetIterator *gsi)
{
BLI_ghashIterator_free(reinterpret_cast<GHashIterator *>(gsi));
}
BLI_INLINE void *BLI_gsetIterator_getKey(GSetIterator *gsi)
{
return BLI_ghashIterator_getKey(reinterpret_cast<GHashIterator *>(gsi));
}
BLI_INLINE void BLI_gsetIterator_step(GSetIterator *gsi)
{
BLI_ghashIterator_step(reinterpret_cast<GHashIterator *>(gsi));
}
BLI_INLINE bool BLI_gsetIterator_done(const GSetIterator *gsi)
{
return BLI_ghashIterator_done(reinterpret_cast<const GHashIterator *>(gsi));
}
#define GSET_ITER(gs_iter_, gset_) \
for (BLI_gsetIterator_init(&gs_iter_, gset_); BLI_gsetIterator_done(&gs_iter_) == false; \
BLI_gsetIterator_step(&gs_iter_))
#define GSET_ITER_INDEX(gs_iter_, gset_, i_) \
for (BLI_gsetIterator_init(&gs_iter_, gset_), i_ = 0; \
BLI_gsetIterator_done(&gs_iter_) == false; \
BLI_gsetIterator_step(&gs_iter_), i_++)
/** \} */
/* -------------------------------------------------------------------- */
/** \name GHash/GSet Debugging API's
* \{ */
/* For testing, debugging only */
#ifdef GHASH_INTERNAL_API
/**
* \return number of buckets in the GHash.
*/
int BLI_ghash_buckets_len(const GHash *gh);
int BLI_gset_buckets_len(const GSet *gs);
/**
* Measure how well the hash function performs (1.0 is approx as good as random distribution),
* and return a few other stats like load,
* variance of the distribution of the entries in the buckets, etc.
*
* Smaller is better!
*/
double BLI_ghash_calc_quality_ex(const GHash *gh,
double *r_load,
double *r_variance,
double *r_prop_empty_buckets,
double *r_prop_overloaded_buckets,
int *r_biggest_bucket);
double BLI_gset_calc_quality_ex(const GSet *gs,
double *r_load,
double *r_variance,
double *r_prop_empty_buckets,
double *r_prop_overloaded_buckets,
int *r_biggest_bucket);
double BLI_ghash_calc_quality(const GHash *gh);
double BLI_gset_calc_quality(const GSet *gs);
#endif /* GHASH_INTERNAL_API */
/** \} */
/* -------------------------------------------------------------------- */
/** \name GHash/GSet Macros
* \{ */
#define GHASH_FOREACH_BEGIN(type, var, what) \
do { \
GHashIterator gh_iter##var; \
GHASH_ITER (gh_iter##var, what) { \
type var = (type)(BLI_ghashIterator_getValue(&gh_iter##var));
#define GHASH_FOREACH_END() \
} \
} \
while (0)
#define GSET_FOREACH_BEGIN(type, var, what) \
do { \
GSetIterator gh_iter##var; \
GSET_ITER (gh_iter##var, what) { \
type var = (type)(BLI_gsetIterator_getKey(&gh_iter##var));
#define GSET_FOREACH_END() \
} \
} \
while (0)
/** \} */
/* -------------------------------------------------------------------- */
/** \name GHash/GSet Utils
*
* Defined in `BLI_ghash_utils.cc`
* \{ */
/**
* Callbacks for GHash (`BLI_ghashutil_`)
*
* \note '_p' suffix denotes void pointer arg,
* so we can have functions that take correctly typed args too.
*/
unsigned int BLI_ghashutil_ptrhash(const void *key);
bool BLI_ghashutil_ptrcmp(const void *a, const void *b);
/**
* This function implements the widely used `djb` hash apparently posted
* by Daniel Bernstein to `comp.lang.c` some time ago. The 32 bit
* unsigned hash value starts at 5381 and for each byte 'c' in the
* string, is updated: `hash = hash * 33 + c`.
* This function uses the signed value of each byte.
*
* NOTE: this is the same hash method that glib 2.34.0 uses.
*/
unsigned int BLI_ghashutil_strhash_n(const char *key, size_t n);
#define BLI_ghashutil_strhash(key) \
(CHECK_TYPE_ANY(key, char *, const char *), BLI_ghashutil_strhash_p(key))
unsigned int BLI_ghashutil_strhash_p(const void *ptr);
unsigned int BLI_ghashutil_strhash_p_murmur(const void *ptr);
bool BLI_ghashutil_strcmp(const void *a, const void *b);
#define BLI_ghashutil_inthash(key) \
(CHECK_TYPE_ANY(&(key), int *, const int *), BLI_ghashutil_uinthash((unsigned int)key))
unsigned int BLI_ghashutil_uinthash(unsigned int key);
unsigned int BLI_ghashutil_inthash_p(const void *ptr);
unsigned int BLI_ghashutil_inthash_p_murmur(const void *ptr);
unsigned int BLI_ghashutil_inthash_p_simple(const void *ptr);
bool BLI_ghashutil_intcmp(const void *a, const void *b);
size_t BLI_ghashutil_combine_hash(size_t hash_a, size_t hash_b);
unsigned int BLI_ghashutil_uinthash_v4(const unsigned int key[4]);
#define BLI_ghashutil_inthash_v4(key) \
(CHECK_TYPE_ANY(key, int *, const int *), BLI_ghashutil_uinthash_v4((const unsigned int *)key))
#define BLI_ghashutil_inthash_v4_p ((GSetHashFP)BLI_ghashutil_uinthash_v4)
#define BLI_ghashutil_uinthash_v4_p ((GSetHashFP)BLI_ghashutil_uinthash_v4)
unsigned int BLI_ghashutil_uinthash_v4_murmur(const unsigned int key[4]);
#define BLI_ghashutil_inthash_v4_murmur(key) \
(CHECK_TYPE_ANY(key, int *, const int *), \
BLI_ghashutil_uinthash_v4_murmur((const unsigned int *)key))
#define BLI_ghashutil_inthash_v4_p_murmur ((GSetHashFP)BLI_ghashutil_uinthash_v4_murmur)
#define BLI_ghashutil_uinthash_v4_p_murmur ((GSetHashFP)BLI_ghashutil_uinthash_v4_murmur)
bool BLI_ghashutil_uinthash_v4_cmp(const void *a, const void *b);
#define BLI_ghashutil_inthash_v4_cmp BLI_ghashutil_uinthash_v4_cmp
struct GHashPair {
const void *first;
const void *second;
};
GHashPair *BLI_ghashutil_pairalloc(const void *first, const void *second);
unsigned int BLI_ghashutil_pairhash(const void *ptr);
bool BLI_ghashutil_paircmp(const void *a, const void *b);
void BLI_ghashutil_pairfree(void *ptr);
/**
* Wrapper GHash Creation Functions
*/
GHash *BLI_ghash_ptr_new_ex(const char *info,
unsigned int nentries_reserve) ATTR_MALLOC ATTR_WARN_UNUSED_RESULT;
GHash *BLI_ghash_ptr_new(const char *info) ATTR_MALLOC ATTR_WARN_UNUSED_RESULT;
GHash *BLI_ghash_str_new_ex(const char *info,
unsigned int nentries_reserve) ATTR_MALLOC ATTR_WARN_UNUSED_RESULT;
GHash *BLI_ghash_str_new(const char *info) ATTR_MALLOC ATTR_WARN_UNUSED_RESULT;
GHash *BLI_ghash_int_new_ex(const char *info,
unsigned int nentries_reserve) ATTR_MALLOC ATTR_WARN_UNUSED_RESULT;
GHash *BLI_ghash_int_new(const char *info) ATTR_MALLOC ATTR_WARN_UNUSED_RESULT;
GHash *BLI_ghash_pair_new_ex(const char *info,
unsigned int nentries_reserve) ATTR_MALLOC ATTR_WARN_UNUSED_RESULT;
GHash *BLI_ghash_pair_new(const char *info) ATTR_MALLOC ATTR_WARN_UNUSED_RESULT;
GSet *BLI_gset_ptr_new_ex(const char *info,
unsigned int nentries_reserve) ATTR_MALLOC ATTR_WARN_UNUSED_RESULT;
GSet *BLI_gset_ptr_new(const char *info);
GSet *BLI_gset_str_new_ex(const char *info,
unsigned int nentries_reserve) ATTR_MALLOC ATTR_WARN_UNUSED_RESULT;
GSet *BLI_gset_str_new(const char *info);
GSet *BLI_gset_pair_new_ex(const char *info,
unsigned int nentries_reserve) ATTR_MALLOC ATTR_WARN_UNUSED_RESULT;
GSet *BLI_gset_pair_new(const char *info) ATTR_MALLOC ATTR_WARN_UNUSED_RESULT;
GSet *BLI_gset_int_new_ex(const char *info,
unsigned int nentries_reserve) ATTR_MALLOC ATTR_WARN_UNUSED_RESULT;
GSet *BLI_gset_int_new(const char *info) ATTR_MALLOC ATTR_WARN_UNUSED_RESULT;
/** \} */
} // namespace blender

View File

@@ -0,0 +1,44 @@
/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bli
*/
#include "BLI_utildefines.h"
namespace blender {
struct GSQueue;
GSQueue *BLI_gsqueue_new(size_t elem_size);
/**
* Returns true if the queue is empty, false otherwise.
*/
bool BLI_gsqueue_is_empty(const GSQueue *queue);
size_t BLI_gsqueue_len(const GSQueue *queue);
/**
* Retrieves and removes the first element from the queue.
* The value is copies to \a r_item, which must be at least \a elem_size bytes.
*
* Does not reduce amount of allocated memory.
*/
void BLI_gsqueue_pop(GSQueue *queue, void *r_item);
/**
* Copies the source value onto the end of the queue
*
* \note This copies #GSQueue.elem_size bytes from \a item,
* (the pointer itself is not stored).
*
* \param item: source data to be copied to the queue.
*/
void BLI_gsqueue_push(GSQueue *queue, const void *item);
/**
* Free the queue's data and the queue itself.
*/
void BLI_gsqueue_free(GSQueue *queue);
} // namespace blender

View File

@@ -0,0 +1,110 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bli
*/
#include "BLI_utildefines.h"
namespace blender {
/**
* Jenkins Lookup3 Hash Functions.
* Source: http://burtleburtle.net/bob/c/lookup3.c
*/
#define rot(x, k) (((x) << (k)) | ((x) >> (32 - (k))))
#define final(a, b, c) \
{ \
c ^= b; \
c -= rot(b, 14); \
a ^= c; \
a -= rot(c, 11); \
b ^= a; \
b -= rot(a, 25); \
c ^= b; \
c -= rot(b, 16); \
a ^= c; \
a -= rot(c, 4); \
b ^= a; \
b -= rot(a, 14); \
c ^= b; \
c -= rot(b, 24); \
} \
((void)0)
BLI_INLINE unsigned int BLI_hash_int_3d(unsigned int kx, unsigned int ky, unsigned int kz)
{
unsigned int a, b, c;
a = b = c = 0xdeadbeef + (3 << 2) + 13;
c += kz;
b += ky;
a += kx;
final(a, b, c);
return c;
}
BLI_INLINE unsigned int BLI_hash_int_2d(unsigned int kx, unsigned int ky)
{
unsigned int a, b, c;
a = b = c = 0xdeadbeef + (2 << 2) + 13;
a += kx;
b += ky;
final(a, b, c);
return c;
}
#undef final
#undef rot
BLI_INLINE unsigned int BLI_hash_string(const char *str)
{
unsigned int i = 0, c;
while ((c = *str++)) {
i = i * 37 + c;
}
return i;
}
BLI_INLINE float BLI_hash_int_2d_to_float(uint32_t kx, uint32_t ky)
{
return float(BLI_hash_int_2d(kx, ky)) / float(0xFFFFFFFFu);
}
BLI_INLINE float BLI_hash_int_3d_to_float(uint32_t kx, uint32_t ky, uint32_t kz)
{
return float(BLI_hash_int_3d(kx, ky, kz)) / float(0xFFFFFFFFu);
}
BLI_INLINE unsigned int BLI_hash_int(unsigned int k)
{
return BLI_hash_int_2d(k, 0);
}
BLI_INLINE float BLI_hash_int_01(unsigned int k)
{
return float(BLI_hash_int(k)) * (1.0f / float(0xFFFFFFFF));
}
BLI_INLINE void BLI_hash_pointer_to_color(const void *ptr, int *r, int *g, int *b)
{
size_t val = reinterpret_cast<size_t>(ptr);
const size_t hash_a = BLI_hash_int(val & 0x0000ffff);
const size_t hash_b = BLI_hash_int(uint((val & 0xffff0000) >> 16));
const size_t hash = hash_a ^ (hash_b + 0x9e3779b9 + (hash_a << 6) + (hash_a >> 2));
*r = (hash & 0xff0000) >> 16;
*g = (hash & 0x00ff00) >> 8;
*b = hash & 0x0000ff;
}
} // namespace blender

View File

@@ -0,0 +1,287 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bli
*
* A specialization of `DefaultHash<T>` provides a hash function for values of type T.
* This hash function is used by default in hash table implementations in blenlib.
*
* The actual hash function is in the `operator()` method of `DefaultHash<T>`. The following code
* computes the hash of some value using DefaultHash.
*
* T value = ...;
* DefaultHash<T> hash_function;
* uint32_t hash = hash_function(value);
*
* Hash table implementations like Set support heterogeneous key lookups. That means that
* one can do a lookup with a key of type A in a hash table that stores keys of type B. This is
* commonly done when B is std::string, because the conversion from e.g. a #StringRef to
* std::string can be costly and is unnecessary. To make this work, values of type A and B that
* compare equal have to have the same hash value. This is achieved by defining potentially
* multiple `operator()` in a specialization of #DefaultHash. All those methods have to compute the
* same hash for values that compare equal.
*
* The computed hash is an unsigned 64 bit integer. Ideally, the hash function would generate
* uniformly random hash values for a set of keys. However, in many cases trivial hash functions
* are faster and produce a good enough distribution. In general it is better when more information
* is in the lower bits of the hash. By choosing a good probing strategy, the effects of a bad hash
* function are less noticeable though. In this context a good probing strategy is one that takes
* all bits of the hash into account eventually. One has to check on a case by case basis to see if
* a better but more expensive or trivial hash function works better.
*
* There are three main ways to provide a hash table implementation with a custom hash function.
*
* - When you want to provide a default hash function for your own custom type: Add a `hash()`
* member function to it. The function should return `uint64_t` and take no arguments. This
* method will be called by the default implementation of #DefaultHash. It will automatically be
* used by hash table implementations.
*
* - When you want to provide a default hash function for a type that you cannot modify: Add a new
* specialization to the #DefaultHash struct. This can be done by writing code like below in
* either global or `blender` namespace.
*
* template<> struct DefaultHash<TheType> {
* uint64_t operator()(const TheType &value) const {
* return ...;
* }
* };
*
* - When you want to provide a different hash function for a type that already has a default hash
* function: Implement a struct like the one below and pass it as template parameter to the hash
* table explicitly.
*
* struct MyCustomHash {
* uint64_t operator()(const TheType &value) const {
* return ...;
* }
* };
*/
#include <bit>
#include <memory>
#include <string>
#include <utility>
#include "BLI_hash_fwd.hh"
#include "BLI_string_ref.hh"
namespace blender {
/**
* If there is no other specialization of #DefaultHash for a given type, look for a hash function
* on the type itself. Implementing a `hash()` method on a type is often significantly easier than
* specializing #DefaultHash.
*
* To support heterogeneous lookup, a type can also implement a static `hash_as(const OtherType &)`
* function.
*
* In the case of an enum type, the default hash is just to cast the enum value to an integer.
*/
template<typename T> struct DefaultHash {
constexpr uint64_t operator()(const T &value) const
{
if constexpr (std::is_enum_v<T>) {
/* For enums use the value as hash directly. */
return uint64_t(value);
}
else {
/* Try to call the `hash()` function on the value. */
/* If this results in a compiler error, no hash function for the type has been found. */
return value.hash();
}
}
template<typename U> constexpr uint64_t operator()(const U &value) const
{
/* Try calling the static `T::hash_as(value)` function with the given value. The returned hash
* should be "compatible" with `T::hash()`. Usually that means that if `value` is converted to
* `T` its hash does not change. */
/* If this results in a compiler error, no hash function for the heterogeneous lookup has been
* found. */
return T::hash_as(value);
}
};
/**
* Use the same hash function for const and non const variants of a type.
*/
template<typename T> struct DefaultHash<const T> {
constexpr uint64_t operator()(const T &value) const
{
return DefaultHash<T>{}(value);
}
};
#define TRIVIAL_DEFAULT_INT_HASH(TYPE) \
template<> struct DefaultHash<TYPE> { \
constexpr uint64_t operator()(TYPE value) const \
{ \
return uint64_t(value); \
} \
}
/**
* We cannot make any assumptions about the distribution of keys, so use a trivial hash function by
* default. The default probing strategy is designed to take all bits of the hash into account
* to avoid worst case behavior when the lower bits are all zero. Special hash functions can be
* implemented when more knowledge about a specific key distribution is available.
*/
TRIVIAL_DEFAULT_INT_HASH(int8_t);
TRIVIAL_DEFAULT_INT_HASH(uint8_t);
TRIVIAL_DEFAULT_INT_HASH(int16_t);
TRIVIAL_DEFAULT_INT_HASH(uint16_t);
TRIVIAL_DEFAULT_INT_HASH(int32_t);
TRIVIAL_DEFAULT_INT_HASH(uint32_t);
TRIVIAL_DEFAULT_INT_HASH(int64_t);
TRIVIAL_DEFAULT_INT_HASH(uint64_t);
/**
* One should try to avoid using floats as keys in hash tables, but sometimes it is convenient.
*/
template<> struct DefaultHash<float> {
constexpr uint64_t operator()(const float value) const
{
/* Make sure +0 and -0 hash to the same value. */
if (value == 0.0f) {
return 0;
}
/* Explicit `uint64_t` cast to suppress CPPCHECK warning. */
return uint64_t(std::bit_cast<uint32_t>(value));
}
};
template<> struct DefaultHash<double> {
constexpr uint64_t operator()(const double value) const
{
/* Make sure +0 and -0 hash to the same value. */
if (value == 0.0) {
return 0;
}
return std::bit_cast<uint64_t>(value);
}
};
template<> struct DefaultHash<bool> {
constexpr uint64_t operator()(bool value) const
{
return uint64_t((value != false) * 1298191);
}
};
constexpr uint64_t hash_string(StringRef str)
{
uint64_t hash = 5381;
for (char c : str) {
hash = hash * 33 + c;
}
return hash;
}
template<> struct DefaultHash<std::string> {
/**
* Take a #StringRef as parameter to support heterogeneous lookups in hash table implementations
* when std::string is used as key.
*/
constexpr uint64_t operator()(StringRef value) const
{
return hash_string(value);
}
};
template<> struct DefaultHash<StringRef> {
constexpr uint64_t operator()(StringRef value) const
{
return hash_string(value);
}
};
template<> struct DefaultHash<StringRefNull> {
constexpr uint64_t operator()(StringRef value) const
{
return hash_string(value);
}
};
template<> struct DefaultHash<std::string_view> {
constexpr uint64_t operator()(StringRef value) const
{
return hash_string(value);
}
};
/**
* While we cannot guarantee that the lower 4 bits of a pointer are zero, it is often the case.
*/
template<typename T> struct DefaultHash<T *> {
constexpr uint64_t operator()(const T *value) const
{
uintptr_t ptr = uintptr_t(value);
uint64_t hash = uint64_t(ptr >> 4);
return hash;
}
};
namespace blenlib_detail {
static constexpr std::array<uint64_t, 5> default_hash_factors = {
19349669, 83492791, 3632623, 8789800933, 7235126189};
template<size_t... I, typename... Args>
constexpr uint64_t get_default_hash_array(std::index_sequence<I...> /*indices*/,
const Args &...args)
{
static_assert(sizeof...(Args) == sizeof...(I));
static_assert(sizeof...(Args) <= default_hash_factors.size());
return (0 ^ ... ^ (default_hash_factors[I] * DefaultHash<std::decay_t<Args>>{}(args)));
}
} // namespace blenlib_detail
template<typename T, typename... Args>
constexpr uint64_t get_default_hash(const T &v, const Args &...args)
{
return DefaultHash<std::decay_t<T>>{}(v) ^
blenlib_detail::get_default_hash_array(std::make_index_sequence<sizeof...(Args)>(),
args...);
}
/** Support hashing different kinds of pointer types. */
template<typename T> struct PointerHashes {
template<typename U> constexpr uint64_t operator()(const U &value) const
{
return get_default_hash(&*value);
}
};
template<typename T> struct DefaultHash<std::unique_ptr<T>> : public PointerHashes<T> {};
template<typename T> struct DefaultHash<std::shared_ptr<T>> : public PointerHashes<T> {};
template<typename T> struct DefaultHash<std::reference_wrapper<T>> {
constexpr uint64_t operator()(const std::reference_wrapper<T> &value) const
{
return get_default_hash(value.get());
}
};
template<typename T1, typename T2> struct DefaultHash<std::pair<T1, T2>> {
constexpr uint64_t operator()(const std::pair<T1, T2> &value) const
{
return get_default_hash(value.first, value.second);
}
};
/**
* Special overload for function pointers to avoid adding const to them which causes a warning with
* MSVC.
*/
template<typename Ret, typename... Args> struct DefaultHash<Ret (*)(Args...)> {
constexpr uint64_t operator()(Ret (*fn)(Args...)) const
{
return get_default_hash(reinterpret_cast<const void *>(fn));
}
};
} // namespace blender

View File

@@ -0,0 +1,14 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include <cstdint>
namespace blender {
template<typename T, typename... Args>
constexpr uint64_t get_default_hash(const T &v, const Args &...args);
} // namespace blender

View File

@@ -0,0 +1,33 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bli
*/
#include <cstdio>
#include "BLI_sys_types.h"
namespace blender {
/**
* Compute MD5 message digest for 'len' bytes beginning at 'buffer'.
* The result is always in little endian byte order,
* so that a byte-wise output yields to the wanted ASCII representation of the message digest.
*/
void *BLI_hash_md5_buffer(const char *buffer, size_t len, void *resblock);
/**
* Compute MD5 message digest for bytes read from 'stream'.
* The resulting message digest number will be written into the 16 bytes beginning at 'resblock'.
* \return Non-zero if an error occurred.
*/
int BLI_hash_md5_stream(FILE *stream, void *resblock);
char *BLI_hash_md5_to_hexdigest(const void *resblock, char r_hex_digest[33]);
} // namespace blender

View File

@@ -0,0 +1,35 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bli
*/
#include "BLI_sys_types.h"
namespace blender {
struct BLI_HashMurmur2A {
uint32_t hash;
uint32_t tail;
uint32_t count;
uint32_t size;
};
void BLI_hash_mm2a_init(BLI_HashMurmur2A *mm2, uint32_t seed);
void BLI_hash_mm2a_add(BLI_HashMurmur2A *mm2, const unsigned char *data, size_t len);
void BLI_hash_mm2a_add_int(BLI_HashMurmur2A *mm2, int data);
uint32_t BLI_hash_mm2a_end(BLI_HashMurmur2A *mm2);
/**
* Non-incremental version, quicker for small keys.
*/
uint32_t BLI_hash_mm2(const unsigned char *data, size_t len, uint32_t seed);
} // namespace blender

View File

@@ -0,0 +1,17 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bli
*/
#include "BLI_sys_types.h"
namespace blender {
uint32_t BLI_hash_mm3(const unsigned char *data, size_t len, uint32_t seed);
} // namespace blender

View File

@@ -0,0 +1,322 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bli
*
* This file contains code that can be shared between different hash table implementations.
*/
#include <algorithm>
#include "BLI_utildefines.h"
#include "BLI_vector.hh"
namespace blender {
/* -------------------------------------------------------------------- */
/** \name Constexpr Utility Functions
*
* Those should eventually be de-duplicated with functions in BLI_math_base.h.
* \{ */
template<typename IntT> constexpr IntT ceil_division(const IntT x, const IntT y)
{
BLI_assert(x >= 0);
BLI_assert(y >= 0);
return x / y + ((x % y) != 0);
}
template<typename IntT> constexpr IntT floor_division(const IntT x, const IntT y)
{
BLI_assert(x >= 0);
BLI_assert(y >= 0);
return x / y;
}
constexpr int64_t ceil_division_by_fraction(const int64_t x,
const int64_t numerator,
const int64_t denominator)
{
return int64_t(ceil_division(uint64_t(x) * uint64_t(denominator), uint64_t(numerator)));
}
constexpr int64_t floor_multiplication_with_fraction(const int64_t x,
const int64_t numerator,
const int64_t denominator)
{
return int64_t((uint64_t(x) * uint64_t(numerator) / uint64_t(denominator)));
}
constexpr int64_t total_slot_amount_for_usable_slots(const int64_t min_usable_slots,
const int64_t max_load_factor_numerator,
const int64_t max_load_factor_denominator)
{
return power_of_2_max(ceil_division_by_fraction(
min_usable_slots, max_load_factor_numerator, max_load_factor_denominator));
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Load Factor
*
* This is an abstraction for a fractional load factor. The hash table using this class is assumed
* to use arrays with a size that is a power of two.
*
* \{ */
class LoadFactor {
private:
uint8_t numerator_;
uint8_t denominator_;
public:
constexpr LoadFactor(uint8_t numerator, uint8_t denominator)
: numerator_(numerator), denominator_(denominator)
{
BLI_assert(numerator > 0);
BLI_assert(numerator < denominator);
}
constexpr void compute_total_and_usable_slots(int64_t min_total_slots,
int64_t min_usable_slots,
int64_t *r_total_slots,
int64_t *r_usable_slots) const
{
BLI_assert(is_power_of_2(int(min_total_slots)));
int64_t total_slots = this->compute_total_slots(min_usable_slots, numerator_, denominator_);
total_slots = std::max(total_slots, min_total_slots);
const int64_t usable_slots = floor_multiplication_with_fraction(
total_slots, numerator_, denominator_);
BLI_assert(min_usable_slots <= usable_slots);
*r_total_slots = total_slots;
*r_usable_slots = usable_slots;
}
static constexpr int64_t compute_total_slots(int64_t min_usable_slots,
uint8_t numerator,
uint8_t denominator)
{
return total_slot_amount_for_usable_slots(min_usable_slots, numerator, denominator);
}
};
/** \} */
/* -------------------------------------------------------------------- */
/** \name Intrusive Key Info
*
* A hash table slot has to maintain state about whether the slot is empty, occupied or removed.
* Usually, this state information is stored in its own variable. While it only needs two bits in
* theory, in practice often 4 or 8 bytes are used, due to alignment requirements.
*
* One solution to deal with this problem is to embed the state information in the key. That means,
* two values of the key type are selected to indicate whether the slot is empty or removed.
*
* The classes below tell a slot implementation which special key values it can use. They can be
* used as #KeyInfo in slot types like #IntrusiveSetSlot and #IntrusiveMapSlot.
*
* A #KeyInfo type has to implement a couple of static methods that are descried in
* #TemplatedKeyInfo.
*
* \{ */
/**
* The template arguments EmptyValue and RemovedValue define which special are used. This can be
* used when a hash table has integer keys and there are two specific integers that will never be
* used as keys.
*/
template<typename Key, Key EmptyValue, Key RemovedValue> struct TemplatedKeyInfo {
/**
* Get the value that indicates that the slot is empty. This is used to indicate new slots.
*/
static Key get_empty()
{
return EmptyValue;
}
/**
* Modify the given key so that it represents a removed slot.
*/
static void remove(Key &key)
{
key = RemovedValue;
}
/**
* Return true, when the given key indicates that the slot is empty.
*/
static bool is_empty(const Key &key)
{
return key == EmptyValue;
}
/**
* Return true, when the given key indicates that the slot is removed.
*/
static bool is_removed(const Key &key)
{
return key == RemovedValue;
}
/**
* Return true, when the key is valid, i.e. it can be contained in an occupied slot.
*/
static bool is_not_empty_or_removed(const Key &key)
{
return key != EmptyValue && key != RemovedValue;
}
};
/**
* `0xffff...ffff` indicates an empty slot.
* `0xffff...fffe` indicates a removed slot.
*
* Those specific values are used, because with them a single comparison is enough to check whether
* a slot is occupied. The keys `0x0000...0000` and `0x0000...0001` also satisfy this constraint.
* However, nullptr is much more likely to be used as valid key.
*/
template<typename Pointer> struct PointerKeyInfo {
static Pointer get_empty()
{
return Pointer(UINTPTR_MAX);
}
static void remove(Pointer &pointer)
{
pointer = Pointer(UINTPTR_MAX - 1);
}
static bool is_empty(Pointer pointer)
{
return uintptr_t(pointer) == UINTPTR_MAX;
}
static bool is_removed(Pointer pointer)
{
return uintptr_t(pointer) == UINTPTR_MAX - 1;
}
static bool is_not_empty_or_removed(Pointer pointer)
{
return uintptr_t(pointer) < UINTPTR_MAX - 1;
}
};
/** \} */
/* -------------------------------------------------------------------- */
/** \name Hash Table Stats
*
* A utility class that makes it easier for hash table implementations to provide statistics to the
* developer. These statistics can be helpful when trying to figure out why a hash table is slow.
*
* To use this utility, a hash table has to implement various methods, that are mentioned below.
*
* \{ */
class HashTableStats {
private:
Vector<int64_t> keys_by_collision_count_;
int64_t total_collisions_;
float average_collisions_;
int64_t size_;
int64_t capacity_;
int64_t removed_amount_;
float load_factor_;
float removed_load_factor_;
int64_t size_per_element_;
int64_t size_in_bytes_;
const void *address_;
public:
/**
* Requires that the hash table has the following methods:
* - count_collisions(key) -> int64_t
* - size() -> int64_t
* - capacity() -> int64_t
* - removed_amount() -> int64_t
* - size_per_element() -> int64_t
* - size_in_bytes() -> int64_t
*/
template<typename HashTable, typename Keys>
HashTableStats(const HashTable &hash_table, const Keys &keys)
{
total_collisions_ = 0;
size_ = hash_table.size();
capacity_ = hash_table.capacity();
removed_amount_ = hash_table.removed_amount();
size_per_element_ = hash_table.size_per_element();
size_in_bytes_ = hash_table.size_in_bytes();
address_ = static_cast<const void *>(&hash_table);
for (const auto &key : keys) {
int64_t collisions = hash_table.count_collisions(key);
if (keys_by_collision_count_.size() <= collisions) {
keys_by_collision_count_.append_n_times(0,
collisions - keys_by_collision_count_.size() + 1);
}
keys_by_collision_count_[collisions]++;
total_collisions_ += collisions;
}
average_collisions_ = (size_ == 0) ? 0 : float(total_collisions_) / float(size_);
load_factor_ = float(size_) / float(capacity_);
removed_load_factor_ = float(removed_amount_) / float(capacity_);
}
void print(const char *name) const;
};
/** \} */
/**
* This struct provides an equality operator that returns true for all objects that compare equal
* when one would use the `==` operator. This is different from std::equal_to<T>, because that
* requires the parameters to be of type T. Our hash tables support lookups using other types
* without conversion, therefore DefaultEquality needs to be more generic.
*/
template<typename T> struct DefaultEquality {
template<typename T1, typename T2> bool operator()(const T1 &a, const T2 &b) const
{
return a == b;
}
};
/**
* Support comparing different kinds of raw and smart pointers.
*/
struct PointerComparison {
template<typename T1, typename T2> bool operator()(const T1 &a, const T2 &b) const
{
return &*a == &*b;
}
};
template<typename T> struct DefaultEquality<std::unique_ptr<T>> : public PointerComparison {};
template<typename T> struct DefaultEquality<std::shared_ptr<T>> : public PointerComparison {};
struct SequenceComparison {
template<typename T1, typename T2> bool operator()(const T1 &a, const T2 &b) const
{
const auto a_begin = a.begin();
const auto a_end = a.end();
const auto b_begin = b.begin();
const auto b_end = b.end();
if (a_end - a_begin != b_end - b_begin) {
return false;
}
return std::equal(a_begin, a_end, b_begin);
}
};
template<typename T, int64_t InlineBufferCapacity, typename Allocator>
struct DefaultEquality<Vector<T, InlineBufferCapacity, Allocator>> : public SequenceComparison {};
} // namespace blender

View File

@@ -0,0 +1,79 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bli
* \brief A min-heap / priority queue ADT
*/
#include <stdbool.h>
#include <stddef.h>
#include "BLI_compiler_attrs.h"
namespace blender {
struct Heap;
struct HeapNode;
typedef void (*HeapFreeFP)(void *ptr);
/**
* Creates a new heap. Removed nodes are recycled, so memory usage will not shrink.
*
* \note Use when the size of the heap is known in advance.
*/
Heap *BLI_heap_new_ex(unsigned int reserve_num) ATTR_WARN_UNUSED_RESULT;
Heap *BLI_heap_new() ATTR_WARN_UNUSED_RESULT;
void BLI_heap_clear(Heap *heap, HeapFreeFP ptrfreefp) ATTR_NONNULL(1);
void BLI_heap_free(Heap *heap, HeapFreeFP ptrfreefp) ATTR_NONNULL(1);
/**
* Insert heap node with a value (often a 'cost') and pointer into the heap,
* duplicate values are allowed.
*/
HeapNode *BLI_heap_insert(Heap *heap, float value, void *ptr) ATTR_NONNULL(1);
/**
* Convenience function since this is a common pattern.
*/
void BLI_heap_insert_or_update(Heap *heap, HeapNode **node_p, float value, void *ptr)
ATTR_NONNULL(1, 2);
void BLI_heap_remove(Heap *heap, HeapNode *node) ATTR_NONNULL(1, 2);
bool BLI_heap_is_empty(const Heap *heap) ATTR_NONNULL(1);
unsigned int BLI_heap_len(const Heap *heap) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1);
/**
* Return the top node of the heap.
* This is the node with the lowest value.
*/
HeapNode *BLI_heap_top(const Heap *heap) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1);
/**
* Return the value of top node of the heap.
* This is the node with the lowest value.
*/
float BLI_heap_top_value(const Heap *heap) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1);
/**
* Pop the top node off the heap and return its pointer.
*/
void *BLI_heap_pop_min(Heap *heap) ATTR_NONNULL(1);
/**
* Can be used to avoid #BLI_heap_remove, #BLI_heap_insert calls,
* balancing the tree still has a performance cost,
* but is often much less than remove/insert, difference is most noticeable with large heaps.
*/
void BLI_heap_node_value_update(Heap *heap, HeapNode *node, float value) ATTR_NONNULL(1, 2);
void BLI_heap_node_value_update_ptr(Heap *heap, HeapNode *node, float value, void *ptr)
ATTR_NONNULL(1, 2);
/**
* Return the value or pointer of a heap node.
*/
float BLI_heap_node_value(const HeapNode *heap) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1);
void *BLI_heap_node_ptr(const HeapNode *heap) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1);
/**
* Only for checking internal errors (gtest).
*/
bool BLI_heap_is_valid(const Heap *heap);
} // namespace blender

View File

@@ -0,0 +1,46 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bli
* \brief A min-heap / priority queue ADT
*/
#include "BLI_compiler_attrs.h"
#include "BLI_sys_types.h"
namespace blender {
struct HeapSimple;
typedef void (*HeapSimpleFreeFP)(void *ptr);
/**
* Creates a new simple heap, which only supports insertion and removal from top.
*
* \note Use when the size of the heap is known in advance.
*/
HeapSimple *BLI_heapsimple_new_ex(unsigned int reserve_num) ATTR_WARN_UNUSED_RESULT;
HeapSimple *BLI_heapsimple_new() ATTR_WARN_UNUSED_RESULT;
void BLI_heapsimple_clear(HeapSimple *heap, HeapSimpleFreeFP ptrfreefp) ATTR_NONNULL(1);
void BLI_heapsimple_free(HeapSimple *heap, HeapSimpleFreeFP ptrfreefp) ATTR_NONNULL(1);
/**
* Insert heap node with a value (often a 'cost') and pointer into the heap,
* duplicate values are allowed.
*/
void BLI_heapsimple_insert(HeapSimple *heap, float value, void *ptr) ATTR_NONNULL(1);
bool BLI_heapsimple_is_empty(const HeapSimple *heap) ATTR_NONNULL(1);
uint BLI_heapsimple_len(const HeapSimple *heap) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1);
/**
* Return the lowest value of the heap.
*/
float BLI_heapsimple_top_value(const HeapSimple *heap) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1);
/**
* Pop the top node off the heap and return its pointer.
*/
void *BLI_heapsimple_pop_min(HeapSimple *heap) ATTR_NONNULL(1);
} // namespace blender

View File

@@ -0,0 +1,26 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bli
*
* This file only exists to forward declare `ImplicitSharingInfo` in C code.
*/
namespace blender {
#ifdef __cplusplus
class ImplicitSharingInfo;
using ImplicitSharingInfoHandle = ImplicitSharingInfo;
#else
struct ImplicitSharingInfoHandle;
#endif
} // namespace blender

View File

@@ -0,0 +1,315 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bli
*/
#include <atomic>
#include "BLI_assert.h"
#include "BLI_utility_mixins.hh"
#include "MEM_guardedalloc.h"
namespace blender {
/**
* #ImplicitSharingInfo is the core data structure for implicit sharing in Blender. Implicit
* sharing is a technique that avoids copying data when it is not necessary. This results in better
* memory usage and performance. Only read-only data can be shared, because otherwise multiple
* owners might want to change the data in conflicting ways.
*
* To determine whether data is shared, #ImplicitSharingInfo keeps a user count. If the count is 1,
* the data only has a single owner and is therefore mutable. If some code wants to modify data
* that is currently shared, it has to make a copy first.
* This behavior is also called "copy on write".
*
* In addition to containing the reference count, #ImplicitSharingInfo also knows how to destruct
* the referenced data. This is important because the code freeing the data in the end might not
* know how it was allocated (for example, it doesn't know whether an array was allocated using the
* system or guarded allocator).
*
* #ImplicitSharingInfo can be used in two ways:
* - It can be allocated separately from the referenced data. This is used when the shared data is
* e.g. a plain data array.
* - It can be embedded into another struct. For that it's best to use #ImplicitSharingMixin.
*/
class ImplicitSharingInfo : NonCopyable, NonMovable {
private:
/**
* Number of users that want to own the shared data. This can be in multiple states:
* - 0: The data is expired and likely freed. It must not be accessed anymore. The
* #ImplicitSharingInfo may still be alive when there are weak users.
* - 1: The data is mutable by the single owner.
* - >1: The data is shared and therefore immutable.
*/
mutable std::atomic<int> strong_users_ = 1;
/**
* Number of users that only keep a reference to the `ImplicitSharingInfo` but don't need to own
* the shared data. One additional weak user is added as long as there is at least one strong
* user. Together with the `version_` below this adds an efficient way to detect if data has been
* changed.
*/
mutable std::atomic<int> weak_users_ = 1;
/**
* The data referenced by an #ImplicitSharingInfo can change over time. This version is
* incremented whenever the referenced data is about to be changed. This allows checking if the
* data has been changed between points in time.
*/
mutable std::atomic<int64_t> version_ = 0;
public:
virtual ~ImplicitSharingInfo()
{
BLI_assert(strong_users_ == 0);
BLI_assert(weak_users_ == 0);
}
/** Whether the resource can be modified in place because there is only one owner. */
bool is_mutable() const
{
return strong_users_.load(std::memory_order_relaxed) == 1;
}
/**
* Weak users don't protect the referenced data from being freed. If the data is freed while
* there is still a weak referenced, this returns true.
*/
bool is_expired() const
{
return strong_users_.load(std::memory_order_acquire) == 0;
}
/** Call when the data has a new additional owner. */
void add_user() const
{
BLI_assert(!this->is_expired());
strong_users_.fetch_add(1, std::memory_order_relaxed);
}
/**
* Adding a weak owner prevents the #ImplicitSharingInfo from being freed but not the referenced
* data.
*
* \note Unlike std::shared_ptr a weak user cannot be turned into a strong user. This is
* because some code might change the referenced data assuming that there is only one strong user
* while a new strong user is added by another thread.
*/
void add_weak_user() const
{
weak_users_.fetch_add(1, std::memory_order_relaxed);
}
/**
* Call this when making sure that the referenced data is mutable, which also implies that it is
* about to be modified. This allows other code to detect whether data has not been changed very
* efficiently.
*/
void tag_ensured_mutable() const
{
BLI_assert(this->is_mutable());
/* This might not need an atomic increment when the #version method below is only called when
* the code calling it is a strong user of this sharing info. Better be safe and use an atomic
* for now. */
version_.fetch_add(1, std::memory_order_acq_rel);
}
/**
* Get a version number that is increased when the data is modified. It can be used to detect if
* data has been changed.
*/
int64_t version() const
{
return version_.load(std::memory_order_acquire);
}
int strong_users() const
{
return strong_users_.load(std::memory_order_acquire);
}
/**
* Call when the data is no longer needed. This might just decrement the user count, or it might
* also delete the data if this was the last user.
*/
void remove_user_and_delete_if_last() const
{
const int old_user_count = strong_users_.fetch_sub(1, std::memory_order_acq_rel);
BLI_assert(old_user_count >= 1);
const bool was_last_user = old_user_count == 1;
if (was_last_user) {
const int old_weak_user_count = weak_users_.load(std::memory_order_acquire);
BLI_assert(old_weak_user_count >= 1);
if (old_weak_user_count == 1) {
/* If the weak user count is 1 it means that there is no actual weak user. The 1 just
* indicates that there was still at least one strong user. */
weak_users_ = 0;
const_cast<ImplicitSharingInfo *>(this)->delete_self_with_data();
}
else {
/* There is still at least one actual weak user, so don't free the sharing info yet. The
* data can be freed though. */
const_cast<ImplicitSharingInfo *>(this)->delete_data_only();
/* Also remove the "fake" weak user that indicated that there was at least one strong
* user. */
this->remove_weak_user_and_delete_if_last();
}
}
}
/**
* This might just decrement the weak user count or might delete the data. Should be used in
* conjunction with #add_weak_user.
*/
void remove_weak_user_and_delete_if_last() const
{
const int old_weak_user_count = weak_users_.fetch_sub(1, std::memory_order_acq_rel);
BLI_assert(old_weak_user_count >= 1);
const bool was_last_weak_user = old_weak_user_count == 1;
if (was_last_weak_user) {
/* It's possible that the data has been freed before already, but now it is definitely freed
* together with the sharing info. */
const_cast<ImplicitSharingInfo *>(this)->delete_self_with_data();
}
}
private:
/** Has to free the #ImplicitSharingInfo and the referenced data. The data might have been freed
* before by #delete_data_only already. This case should be handled here. */
virtual void delete_self_with_data() = 0;
/** Can free the referenced data but the #ImplicitSharingInfo still has to be kept alive. */
virtual void delete_data_only() {}
};
/**
* Makes it easy to embed implicit-sharing behavior into a struct. Structs that derive from this
* class can be used with #ImplicitSharingPtr.
*/
class ImplicitSharingMixin : public ImplicitSharingInfo {
private:
void delete_self_with_data() override
{
/* Can't use `delete this` here, because we don't know what allocator was used. */
this->delete_self();
}
virtual void delete_self() = 0;
};
/**
* Utility for creating an allocated shared resource, to be used like:
* `new ImplicitSharedValue<T>(args);`
*/
template<typename T> class ImplicitSharedValue : public ImplicitSharingInfo {
public:
T data;
template<typename... Args>
ImplicitSharedValue(Args &&...args) : data(std::forward<Args>(args)...)
{
}
MEM_CXX_CLASS_ALLOC_FUNCS("ImplicitSharedValue");
private:
void delete_self_with_data() override
{
delete this;
}
};
/**
* Utility that contains sharing information and the data that is shared.
*/
struct ImplicitSharingInfoAndData {
const ImplicitSharingInfo *sharing_info = nullptr;
const void *data = nullptr;
};
namespace implicit_sharing {
namespace detail {
void *resize_trivial_array_impl(void *old_data,
int64_t old_size,
int64_t new_size,
int64_t alignment,
const ImplicitSharingInfo **sharing_info);
void *make_trivial_data_mutable_impl(void *old_data,
int64_t size,
int64_t alignment,
const ImplicitSharingInfo **sharing_info);
} // namespace detail
/**
* Copy shared data from the source to the destination, adding a user count.
* \note Does not free any existing data in the destination.
*/
template<typename T>
void copy_shared_pointer(T *src_ptr,
const ImplicitSharingInfo *src_sharing_info,
T **r_dst_ptr,
const ImplicitSharingInfo **r_dst_sharing_info)
{
*r_dst_ptr = src_ptr;
*r_dst_sharing_info = src_sharing_info;
if (*r_dst_ptr) {
BLI_assert(*r_dst_sharing_info != nullptr);
(*r_dst_sharing_info)->add_user();
}
}
/**
* Remove this reference to the shared data and remove dangling pointers.
*/
template<typename T> void free_shared_data(T **data, const ImplicitSharingInfo **sharing_info)
{
if (*sharing_info) {
BLI_assert(*data != nullptr);
(*sharing_info)->remove_user_and_delete_if_last();
}
*data = nullptr;
*sharing_info = nullptr;
}
/**
* Create an implicit sharing object that takes ownership of the data, allowing it to be shared.
* When it is no longer used, the data is freed with #MEM_delete, so it must be a trivial type.
*/
const ImplicitSharingInfo *info_for_mem_free(void *data);
/**
* Make data mutable (single-user) if it is shared. For trivially-copyable data only.
*/
template<typename T>
void make_trivial_data_mutable(T **data,
const ImplicitSharingInfo **sharing_info,
const int64_t size)
{
*data = static_cast<T *>(
detail::make_trivial_data_mutable_impl(*data, sizeof(T) * size, alignof(T), sharing_info));
}
/**
* Resize an array of shared data. For trivially-copyable data only. Any new values are not
* initialized.
*/
template<typename T>
void resize_trivial_array(T **data,
const ImplicitSharingInfo **sharing_info,
int64_t old_size,
int64_t new_size)
{
*data = static_cast<T *>(detail::resize_trivial_array_impl(
*data, sizeof(T) * old_size, sizeof(T) * new_size, alignof(T), sharing_info));
}
} // namespace implicit_sharing
} // namespace blender

View File

@@ -0,0 +1,262 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bli
*/
#include <memory>
#include <utility>
#include "BLI_implicit_sharing.hh"
namespace blender {
/**
* #ImplicitSharingPtr is a smart pointer that manages implicit sharing. It's designed to work with
* types that derive from #ImplicitSharingMixin. It is fairly similar to #std::shared_ptr but
* requires the reference count to be embedded in the data.
*/
template<typename T = ImplicitSharingInfo, bool IsStrong = true> class ImplicitSharingPtr {
private:
const T *data_ = nullptr;
template<typename U, bool OtherIsStrong> friend class ImplicitSharingPtr;
public:
using element_type = T;
ImplicitSharingPtr() = default;
explicit ImplicitSharingPtr(const T *data) : data_(data) {}
/* Implicit conversion from nullptr. */
ImplicitSharingPtr(std::nullptr_t) : data_(nullptr) {}
ImplicitSharingPtr(const ImplicitSharingPtr &other) : data_(other.data_)
{
this->add_user(data_);
}
ImplicitSharingPtr(ImplicitSharingPtr &&other) : data_(other.data_)
{
other.data_ = nullptr;
}
template<typename U>
ImplicitSharingPtr(const ImplicitSharingPtr<U, IsStrong> &other)
requires std::is_base_of_v<T, U>
: data_(static_cast<const U *>(other.data_))
{
this->add_user(data_);
}
template<typename U>
ImplicitSharingPtr(ImplicitSharingPtr<U, IsStrong> &&other)
requires std::is_base_of_v<T, U>
: data_(static_cast<U *>(other.data_))
{
other.data_ = nullptr;
}
~ImplicitSharingPtr()
{
this->remove_user_and_delete_if_last(data_);
}
ImplicitSharingPtr &operator=(const ImplicitSharingPtr &other)
{
if (this == &other) {
return *this;
}
this->remove_user_and_delete_if_last(data_);
data_ = other.data_;
this->add_user(data_);
return *this;
}
ImplicitSharingPtr &operator=(ImplicitSharingPtr &&other)
{
if (this == &other) {
return *this;
}
this->remove_user_and_delete_if_last(data_);
data_ = other.data_;
other.data_ = nullptr;
return *this;
}
const T *operator->() const
{
BLI_assert(data_ != nullptr);
return data_;
}
const T &operator*() const
{
BLI_assert(data_ != nullptr);
return *data_;
}
operator bool() const
{
return data_ != nullptr;
}
const T *get() const
{
return data_;
}
const T *release()
{
const T *data = data_;
data_ = nullptr;
return data;
}
void reset()
{
this->remove_user_and_delete_if_last(data_);
data_ = nullptr;
}
bool has_value() const
{
return data_ != nullptr;
}
uint64_t hash() const
{
return get_default_hash(data_);
}
/**
* If there is only a single user of the data, return a mutable reference to it directly.
* Otherwise call #copy which is expected to return a new implicitly shared pointer that always
* has a single user.
*/
T &ensure_mutable_inplace()
{
BLI_assert(data_);
if (!data_->is_mutable()) {
/* The data is shared and therefore immutable. Make a mutable copy. */
*this = data_->copy();
}
BLI_assert(data_->is_mutable());
data_->tag_ensured_mutable();
return const_cast<T &>(*data_);
}
static uint64_t hash_as(const T *data)
{
return get_default_hash(data);
}
friend bool operator==(const ImplicitSharingPtr &a, const ImplicitSharingPtr &b) = default;
friend bool operator==(const T *a, const ImplicitSharingPtr &b)
{
return a == b.data_;
}
friend bool operator==(const ImplicitSharingPtr &a, const T *b)
{
return a.data_ == b;
}
private:
static void add_user(const T *data)
{
if (data != nullptr) {
if constexpr (IsStrong) {
data->add_user();
}
else {
data->add_weak_user();
}
}
}
static void remove_user_and_delete_if_last(const T *data)
{
if (data != nullptr) {
if constexpr (IsStrong) {
data->remove_user_and_delete_if_last();
}
else {
data->remove_weak_user_and_delete_if_last();
}
}
}
};
using WeakImplicitSharingPtr = ImplicitSharingPtr<ImplicitSharingInfo, false>;
/**
* Utility struct to allow used #ImplicitSharingPtr when it's necessary to type-erase the backing
* storage for user-exposed data. For example, #Vector, or #std::vector might be used to
* store an implicitly shared array that is only accessed with #Span or #MutableSpan.
*
* This class handles RAII for the sharing info and the exposed data pointer.
* Retrieving the data with write access and type safety must be handled elsewhere.
*/
class ImplicitSharingPtrAndData {
public:
ImplicitSharingPtr<> sharing_info;
const void *data = nullptr;
ImplicitSharingPtrAndData() = default;
ImplicitSharingPtrAndData(ImplicitSharingPtr<> sharing_info, const void *data)
: sharing_info(std::move(sharing_info)), data(data)
{
}
ImplicitSharingPtrAndData(const ImplicitSharingPtrAndData &other) = default;
ImplicitSharingPtrAndData(ImplicitSharingPtrAndData &&other)
: sharing_info(std::move(other.sharing_info)), data(std::exchange(other.data, nullptr))
{
}
ImplicitSharingPtrAndData &operator=(const ImplicitSharingPtrAndData &other)
{
if (this == &other) {
return *this;
}
std::destroy_at(this);
new (this) ImplicitSharingPtrAndData(other);
return *this;
}
ImplicitSharingPtrAndData &operator=(ImplicitSharingPtrAndData &&other)
{
if (this == &other) {
return *this;
}
std::destroy_at(this);
new (this) ImplicitSharingPtrAndData(std::move(other));
return *this;
}
~ImplicitSharingPtrAndData()
{
this->data = nullptr;
}
bool has_value() const
{
return this->sharing_info.has_value();
}
};
template<typename T> static constexpr bool is_ImplicitSharingPtr_strong_v = false;
template<typename T>
static constexpr bool is_ImplicitSharingPtr_strong_v<ImplicitSharingPtr<T, true>> = true;
} // namespace blender

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,98 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bli
*/
#pragma once
#include "BLI_index_mask.hh"
#include "BLI_resource_scope.hh"
namespace blender::index_mask {
struct AtomicExpr;
struct UnionExpr;
struct IntersectionExpr;
struct DifferenceExpr;
struct Expr {
enum class Type {
Atomic,
Union,
Intersection,
Difference,
};
Type type;
int index;
Vector<const Expr *> terms;
int expression_array_size() const;
const AtomicExpr &as_atomic() const;
const UnionExpr &as_union() const;
const IntersectionExpr &as_intersection() const;
const DifferenceExpr &as_difference() const;
};
struct AtomicExpr : public Expr {
const IndexMask *mask;
};
struct UnionExpr : public Expr {};
struct IntersectionExpr : public Expr {};
struct DifferenceExpr : public Expr {};
class ExprBuilder {
private:
ResourceScope scope_;
int expr_count_ = 0;
public:
using Term = std::variant<const Expr *, const IndexMask *, IndexRange>;
const UnionExpr &merge(const Span<Term> terms);
const DifferenceExpr &subtract(const Term &main_term, const Span<Term> subtract_terms);
const IntersectionExpr &intersect(const Span<Term> terms);
private:
const Expr &term_to_expr(const Term &term);
};
IndexMask evaluate_expression(const Expr &expression, LinearAllocator<> &memory);
inline int Expr::expression_array_size() const
{
return this->index + 1;
}
inline const AtomicExpr &Expr::as_atomic() const
{
BLI_assert(this->type == Type::Atomic);
return static_cast<const AtomicExpr &>(*this);
}
inline const UnionExpr &Expr::as_union() const
{
BLI_assert(this->type == Type::Union);
return static_cast<const UnionExpr &>(*this);
}
inline const IntersectionExpr &Expr::as_intersection() const
{
BLI_assert(this->type == Type::Intersection);
return static_cast<const IntersectionExpr &>(*this);
}
inline const DifferenceExpr &Expr::as_difference() const
{
BLI_assert(this->type == Type::Difference);
return static_cast<const DifferenceExpr &>(*this);
}
} // namespace blender::index_mask

View File

@@ -0,0 +1,23 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bli
*/
#pragma once
namespace blender {
namespace index_mask {
class IndexMask;
class IndexMaskMemory;
} // namespace index_mask
using index_mask::IndexMask;
using index_mask::IndexMaskMemory;
} // namespace blender

View File

@@ -0,0 +1,373 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bli
*
* A `IndexRange` wraps an interval of non-negative integers. It can be used to reference
* consecutive elements in an array. Furthermore, it can make for loops more convenient and less
* error prone, especially when using nested loops.
*
* I'd argue that the second loop is more readable and less error prone than the first one. That is
* not necessarily always the case, but often it is.
*
* \code{.cc}
* for (int64_t i = 0; i < 10; i++) {
* for (int64_t j = 0; j < 20; j++) {
* for (int64_t k = 0; k < 30; k++) {
*
* for (int64_t i : IndexRange(10)) {
* for (int64_t j : IndexRange(20)) {
* for (int64_t k : IndexRange(30)) {
* \endcode
*
* Some containers like Vector have an index_range() method. This will return the
* IndexRange that contains all indices that can be used to access the container. This is
* particularly useful when you want to iterate over the indices and the elements (much like
* Python's enumerate(), just worse). Again, I think the second example here is better:
*
* \code{.cc}
* for (int64_t i = 0; i < my_vector_with_a_long_name.size(); i++) {
* do_something(i, my_vector_with_a_long_name[i]);
*
* for (int64_t i : my_vector_with_a_long_name.index_range()) {
* do_something(i, my_vector_with_a_long_name[i]);
* \endcode
*
* Ideally this could be could be even closer to Python's enumerate(). We might get that in the
* future with newer C++ versions.
*/
#include <algorithm>
#include <iosfwd>
#include "BLI_assert.h"
#include "BLI_random_access_iterator_mixin.hh"
namespace blender {
template<typename T> class Span;
class IndexRange {
private:
int64_t start_ = 0;
int64_t size_ = 0;
public:
constexpr IndexRange() = default;
constexpr explicit IndexRange(int64_t size) : size_(size)
{
BLI_assert(size >= 0);
}
constexpr IndexRange(const int64_t start, const int64_t size) : start_(start), size_(size)
{
BLI_assert(start >= 0);
BLI_assert(size >= 0);
}
constexpr static IndexRange from_begin_size(const int64_t begin, const int64_t size)
{
return IndexRange(begin, size);
}
constexpr static IndexRange from_begin_end(const int64_t begin, const int64_t end)
{
return IndexRange(begin, end - begin);
}
constexpr static IndexRange from_begin_end_inclusive(const int64_t begin, const int64_t last)
{
return IndexRange(begin, last - begin + 1);
}
constexpr static IndexRange from_end_size(const int64_t end, const int64_t size)
{
return IndexRange(end - size, size);
}
constexpr static IndexRange from_single(const int64_t index)
{
return IndexRange(index, 1);
}
class Iterator : public iterator::RandomAccessIteratorMixin<Iterator> {
public:
using value_type = int64_t;
using pointer = const int64_t *;
using reference = int64_t;
private:
int64_t current_;
public:
constexpr explicit Iterator(int64_t current) : current_(current) {}
constexpr int64_t operator*() const
{
return current_;
}
const int64_t &iter_prop() const
{
return current_;
}
};
constexpr Iterator begin() const
{
return Iterator(start_);
}
constexpr Iterator end() const
{
return Iterator(start_ + size_);
}
/**
* Access an element in the range.
*/
constexpr int64_t operator[](int64_t index) const
{
BLI_assert(index >= 0);
BLI_assert(index < this->size());
return start_ + index;
}
/**
* Two ranges compare equal when they contain the same numbers.
*/
constexpr friend bool operator==(IndexRange a, IndexRange b)
{
return (a.size_ == b.size_) && (a.start_ == b.start_ || a.size_ == 0);
}
constexpr friend bool operator!=(IndexRange a, IndexRange b)
{
return !(a == b);
}
/**
* Get the amount of numbers in the range.
*/
constexpr int64_t size() const
{
return size_;
}
constexpr IndexRange index_range() const
{
return IndexRange(size_);
}
/**
* Returns true if the size is zero.
*/
constexpr bool is_empty() const
{
return size_ == 0;
}
/**
* Creates a new index range with the same beginning but a different end.
*/
constexpr IndexRange with_new_end(const int64_t new_end) const
{
return IndexRange::from_begin_end(start_, new_end);
}
/**
* Create a new range starting at the end of the current one.
*/
constexpr IndexRange after(int64_t n) const
{
BLI_assert(n >= 0);
return IndexRange(start_ + size_, n);
}
/**
* Create a new range that ends at the start of the current one.
*/
constexpr IndexRange before(int64_t n) const
{
BLI_assert(n >= 0);
return IndexRange(start_ - n, n);
}
/**
* Get the first element in the range.
* Asserts when the range is empty.
*/
constexpr int64_t first() const
{
BLI_assert(this->size() > 0);
return start_;
}
/**
* Get the nth last element in the range.
* Asserts when the range is empty or when n is negative.
*/
constexpr int64_t last(const int64_t n = 0) const
{
BLI_assert(n >= 0);
BLI_assert(n < size_);
BLI_assert(this->size() > 0);
return start_ + size_ - 1 - n;
}
/**
* Get the element one before the beginning. The returned value is undefined when the range is
* empty, and the range must start after zero already.
*/
constexpr int64_t one_before_start() const
{
BLI_assert(start_ > 0);
return start_ - 1;
}
/**
* Get the element one after the end. The returned value is undefined when the range is empty.
*/
constexpr int64_t one_after_last() const
{
return start_ + size_;
}
/**
* Get the first element in the range. The returned value is undefined when the range is empty.
*/
constexpr int64_t start() const
{
return start_;
}
/**
* Returns true when the range contains a certain number, otherwise false.
*/
constexpr bool contains(int64_t value) const
{
return value >= start_ && value < start_ + size_;
}
/**
* Returns true when all indices in the given range are also in the current range.
*/
constexpr bool contains(const IndexRange range) const
{
if (range.is_empty()) {
return true;
}
if (range.start_ < start_) {
return false;
}
if (range.start_ + range.size_ > start_ + size_) {
return false;
}
return true;
}
/**
* Returns a new range, that contains a sub-interval of the current one.
*/
constexpr IndexRange slice(int64_t start, int64_t size) const
{
BLI_assert(start >= 0);
BLI_assert(size >= 0);
int64_t new_start = start_ + start;
BLI_assert(new_start + size <= start_ + size_ || size == 0);
return IndexRange(new_start, size);
}
constexpr IndexRange slice(IndexRange range) const
{
return this->slice(range.start(), range.size());
}
/**
* Returns a new IndexRange that contains the intersection of the current one with the given
* range. Returns empty range if there are no overlapping indices. The returned range is always
* a valid slice of this range.
*/
constexpr IndexRange intersect(IndexRange other) const
{
const int64_t old_end = start_ + size_;
const int64_t new_start = std::min(old_end, std::max(start_, other.start_));
const int64_t new_end = std::max(new_start, std::min(old_end, other.start_ + other.size_));
return IndexRange(new_start, new_end - new_start);
}
/**
* Returns a new IndexRange with n elements removed from the beginning of the range.
* This invokes undefined behavior when n is negative.
*/
constexpr IndexRange drop_front(int64_t n) const
{
BLI_assert(n >= 0);
const int64_t new_size = std::max<int64_t>(0, size_ - n);
return IndexRange(start_ + n, new_size);
}
/**
* Returns a new IndexRange with n elements removed from the end of the range.
* This invokes undefined behavior when n is negative.
*/
constexpr IndexRange drop_back(int64_t n) const
{
BLI_assert(n >= 0);
const int64_t new_size = std::max<int64_t>(0, size_ - n);
return IndexRange(start_, new_size);
}
/**
* Returns a new IndexRange that only contains the first n elements. This invokes undefined
* behavior when n is negative.
*/
constexpr IndexRange take_front(int64_t n) const
{
BLI_assert(n >= 0);
const int64_t new_size = std::min<int64_t>(size_, n);
return IndexRange(start_, new_size);
}
/**
* Returns a new IndexRange that only contains the last n elements. This invokes undefined
* behavior when n is negative.
*/
constexpr IndexRange take_back(int64_t n) const
{
BLI_assert(n >= 0);
const int64_t new_size = std::min<int64_t>(size_, n);
return IndexRange(start_ + size_ - new_size, new_size);
}
/**
* Move the range forward or backward within the larger array. The amount may be negative,
* but its absolute value cannot be greater than the existing start of the range.
*/
constexpr IndexRange shift(int64_t n) const
{
return IndexRange(start_ + n, size_);
}
friend std::ostream &operator<<(std::ostream &stream, IndexRange range);
};
struct AlignedIndexRanges {
IndexRange prefix;
IndexRange aligned;
IndexRange suffix;
};
/**
* Split a range into three parts so that the boundaries of the middle part are aligned to some
* power of two.
*
* This can be used when an algorithm can be optimized on aligned indices/memory. The algorithm
* then needs a slow path for the beginning and end, and a fast path for the aligned elements.
*/
AlignedIndexRanges split_index_range_by_alignment(const IndexRange range, const int64_t alignment);
} // namespace blender

View File

@@ -0,0 +1,125 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bli
*/
#pragma once
#include <array>
#include "BLI_index_range.hh"
#include "BLI_index_ranges_builder_fwd.hh"
#include "BLI_span.hh"
#include "BLI_utility_mixins.hh"
namespace blender {
/**
* A data structure that is designed to allow building many index ranges efficiently.
*
* One first has to add individual indices or ranges in ascending order. Internally, consecutive
* indices and ranges are automatically joined.
*
* \note This data structure has a pre-defined capacity and can not automatically grow once that
* capacity is reached. Use #IndexRangesBuilderBuffer to control the capacity.
*/
template<typename T> class IndexRangesBuilder : NonCopyable, NonMovable {
private:
/** The current pointer into #data_. It's changed whenever a new range starts. */
T *c_;
/** Structure: [-1, start, end, start, end, start, end, ...]. */
MutableSpan<T> data_;
public:
IndexRangesBuilder(MutableSpan<T> data) : data_(data)
{
static_assert(std::is_signed_v<T>);
/* Set the first value to -1 so that when the first index is added, it is detected as the start
* of a new range. */
data_[0] = -1;
c_ = data_.data();
}
/** Add a new index. It has to be larger than any previously added index. */
bool add(const T index)
{
return this->add_range(index, index + 1);
}
/**
* Add a range of indices. It has to start after any previously added index.
* By design, this is branchless and requires O(1) time.
*/
bool add_range(const T start, const T end)
{
/* Indices have to be added in ascending order. */
BLI_assert(start >= *c_);
BLI_assert(start >= 0);
BLI_assert(start < end);
const bool is_new_range = start > *c_;
/* Check that the capacity is not overflown. */
BLI_assert(!is_new_range || this->size() < this->capacity());
/* This is designed to either append to the last range or start a new range.
* It is intentionally branchless for more predictable performance on unpredictable data. */
c_ += is_new_range;
*c_ = start;
c_ += is_new_range;
*c_ = end;
return is_new_range;
}
/** Number of collected ranges. */
int64_t size() const
{
return (c_ - data_.data()) / 2;
}
/** How many ranges this container can hold at most. */
int64_t capacity() const
{
return data_.size() / 2;
}
/** True if there are no ranges yet. */
bool is_empty() const
{
return c_ == data_.data();
}
IndexRange index_range() const
{
return IndexRange(this->size());
}
/** Get the i-th collected #IndexRange. */
IndexRange operator[](const int64_t i) const
{
const T start = data_[size_t(1) + 2 * size_t(i)];
const T end = data_[size_t(2) + 2 * size_t(i)];
return IndexRange::from_begin_end(start, end);
}
static constexpr int64_t buffer_size_for_ranges_num(const int64_t ranges_num)
{
/* Two values for each range (start, end) and the dummy prefix value. */
return ranges_num * 2 + 1;
}
};
template<typename T, int64_t MaxRangesNum> struct IndexRangesBuilderBuffer {
std::array<T, size_t(IndexRangesBuilder<T>::buffer_size_for_ranges_num(MaxRangesNum))> data;
operator MutableSpan<T>()
{
return this->data;
}
};
} // namespace blender

View File

@@ -0,0 +1,15 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bli
*/
#pragma once
namespace blender {
template<typename T> class IndexRangesBuilder;
} // namespace blender

Some files were not shown because too many files have changed in this diff Show More