Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'source/blender')
-rw-r--r--source/blender/blenkernel/BKE_blender_version.h6
-rw-r--r--source/blender/blenkernel/BKE_simulation.h1
-rw-r--r--source/blender/blenkernel/intern/curve.c12
-rw-r--r--source/blender/blenkernel/intern/customdata.c12
-rw-r--r--source/blender/blenkernel/intern/font.c2
-rw-r--r--source/blender/blenkernel/intern/image.c7
-rw-r--r--source/blender/blenkernel/intern/lib_query.c3
-rw-r--r--source/blender/blenkernel/intern/node.c18
-rw-r--r--source/blender/blenkernel/intern/object_dupli.c2
-rw-r--r--source/blender/blenkernel/intern/simulation.cc9
-rw-r--r--source/blender/blenlib/BLI_span.hh71
-rw-r--r--source/blender/blenlib/BLI_vector.hh2
-rw-r--r--source/blender/blenlib/CMakeLists.txt25
-rw-r--r--source/blender/blenlib/tests/BLI_array_test.cc176
-rw-r--r--source/blender/blenlib/tests/BLI_disjoint_set_test.cc36
-rw-r--r--source/blender/blenlib/tests/BLI_edgehash_test.cc408
-rw-r--r--source/blender/blenlib/tests/BLI_index_mask_test.cc43
-rw-r--r--source/blender/blenlib/tests/BLI_index_range_test.cc143
-rw-r--r--source/blender/blenlib/tests/BLI_linear_allocator_test.cc118
-rw-r--r--source/blender/blenlib/tests/BLI_map_test.cc590
-rw-r--r--source/blender/blenlib/tests/BLI_math_base_safe_test.cc37
-rw-r--r--source/blender/blenlib/tests/BLI_memory_utils_test.cc159
-rw-r--r--source/blender/blenlib/tests/BLI_set_test.cc565
-rw-r--r--source/blender/blenlib/tests/BLI_span_test.cc311
-rw-r--r--source/blender/blenlib/tests/BLI_stack_cxx_test.cc188
-rw-r--r--source/blender/blenlib/tests/BLI_string_ref_test.cc277
-rw-r--r--source/blender/blenlib/tests/BLI_vector_set_test.cc164
-rw-r--r--source/blender/blenlib/tests/BLI_vector_test.cc639
-rw-r--r--source/blender/blenloader/intern/readfile.c2
-rw-r--r--source/blender/blenloader/intern/versioning_260.c2
-rw-r--r--source/blender/blenloader/intern/writefile.c2
-rw-r--r--source/blender/bmesh/tools/bmesh_bevel.c2
-rw-r--r--source/blender/depsgraph/intern/builder/deg_builder_nodes.cc18
-rw-r--r--source/blender/depsgraph/intern/builder/deg_builder_nodes.h2
-rw-r--r--source/blender/depsgraph/intern/builder/deg_builder_relations.cc53
-rw-r--r--source/blender/depsgraph/intern/builder/deg_builder_relations.h2
-rw-r--r--source/blender/depsgraph/intern/depsgraph_tag.cc1
-rw-r--r--source/blender/depsgraph/intern/eval/deg_eval_copy_on_write.cc5
-rw-r--r--source/blender/editors/curve/editfont.c28
-rw-r--r--source/blender/editors/gpencil/gpencil_interpolate.c26
-rw-r--r--source/blender/editors/include/ED_buttons.h4
-rw-r--r--source/blender/editors/space_buttons/space_buttons.c93
-rw-r--r--source/blender/editors/space_node/node_edit.c4
-rw-r--r--source/blender/editors/space_node/node_relationships.c4
-rw-r--r--source/blender/editors/transform/transform.h1
-rw-r--r--source/blender/editors/transform/transform_snap.c30
-rw-r--r--source/blender/functions/FN_array_spans.hh2
-rw-r--r--source/blender/functions/FN_multi_function_params.hh46
-rw-r--r--source/blender/functions/FN_spans.hh28
-rw-r--r--source/blender/functions/intern/multi_function_builder.cc4
-rw-r--r--source/blender/functions/intern/multi_function_network_evaluation.cc20
-rw-r--r--source/blender/functions/intern/multi_function_network_optimization.cc2
-rw-r--r--source/blender/makesdna/DNA_curve_types.h9
-rw-r--r--source/blender/makesdna/DNA_simulation_types.h8
-rw-r--r--source/blender/makesdna/intern/dna_rename_defs.h1
-rw-r--r--source/blender/makesrna/intern/rna_curve.c4
-rw-r--r--source/blender/makesrna/intern/rna_nodetree.c15
-rw-r--r--source/blender/makesrna/intern/rna_space.c97
-rw-r--r--source/blender/modifiers/intern/MOD_simulation.cc3
-rw-r--r--source/blender/nodes/CMakeLists.txt2
-rw-r--r--source/blender/nodes/NOD_node_tree_dependencies.hh79
-rw-r--r--source/blender/nodes/intern/node_tree_dependencies.cc57
-rw-r--r--source/blender/python/generic/py_capi_utils.c26
-rw-r--r--source/blender/python/intern/bpy_interface.c12
-rw-r--r--source/blender/simulation/CMakeLists.txt3
-rw-r--r--source/blender/simulation/SIM_simulation_update.hh4
-rw-r--r--source/blender/simulation/intern/particle_allocator.cc2
-rw-r--r--source/blender/simulation/intern/particle_mesh_emitter.cc147
-rw-r--r--source/blender/simulation/intern/particle_mesh_emitter.hh47
-rw-r--r--source/blender/simulation/intern/simulation_collect_influences.cc108
-rw-r--r--source/blender/simulation/intern/simulation_collect_influences.hh2
-rw-r--r--source/blender/simulation/intern/simulation_solver.cc66
-rw-r--r--source/blender/simulation/intern/simulation_solver.hh246
-rw-r--r--source/blender/simulation/intern/simulation_solver_influences.hh270
-rw-r--r--source/blender/simulation/intern/simulation_update.cc95
-rw-r--r--source/blender/simulation/intern/time_interval.hh2
-rw-r--r--source/blender/windowmanager/xr/intern/wm_xr_session.c4
77 files changed, 5056 insertions, 658 deletions
diff --git a/source/blender/blenkernel/BKE_blender_version.h b/source/blender/blenkernel/BKE_blender_version.h
index 77d2a973c50..800a3a426b7 100644
--- a/source/blender/blenkernel/BKE_blender_version.h
+++ b/source/blender/blenkernel/BKE_blender_version.h
@@ -32,15 +32,15 @@ extern "C" {
*/
/* Blender major and minor version. */
-#define BLENDER_VERSION 290
+#define BLENDER_VERSION 291
/* Blender patch version for bugfix releases. */
#define BLENDER_VERSION_PATCH 0
/** Blender release cycle stage: alpha/beta/rc/release. */
-#define BLENDER_VERSION_CYCLE beta
+#define BLENDER_VERSION_CYCLE alpha
/* Blender file format version. */
#define BLENDER_FILE_VERSION BLENDER_VERSION
-#define BLENDER_FILE_SUBVERSION 7
+#define BLENDER_FILE_SUBVERSION 0
/* Minimum Blender version that supports reading file written with the current
* version. Older Blender versions will test this and show a warning if the file
diff --git a/source/blender/blenkernel/BKE_simulation.h b/source/blender/blenkernel/BKE_simulation.h
index 5aa71b6381d..6cbe77e8de3 100644
--- a/source/blender/blenkernel/BKE_simulation.h
+++ b/source/blender/blenkernel/BKE_simulation.h
@@ -32,6 +32,7 @@ void *BKE_simulation_add(struct Main *bmain, const char *name);
void BKE_simulation_data_update(struct Depsgraph *depsgraph,
struct Scene *scene,
struct Simulation *simulation);
+void BKE_simulation_update_dependencies(struct Simulation *simulation, struct Main *bmain);
SimulationState *BKE_simulation_state_add(Simulation *simulation,
const char *type,
diff --git a/source/blender/blenkernel/intern/curve.c b/source/blender/blenkernel/intern/curve.c
index d242337d5a7..3dc7ae39893 100644
--- a/source/blender/blenkernel/intern/curve.c
+++ b/source/blender/blenkernel/intern/curve.c
@@ -223,7 +223,7 @@ void BKE_curve_init(Curve *cu, const short curve_type)
cu->vfont->id.us += 4;
cu->str = MEM_malloc_arrayN(12, sizeof(unsigned char), "str");
BLI_strncpy(cu->str, "Text", 12);
- cu->len = cu->len_wchar = cu->pos = 4;
+ cu->len = cu->len_char32 = cu->pos = 4;
cu->strinfo = MEM_calloc_arrayN(12, sizeof(CharInfo), "strinfo new");
cu->totbox = cu->actbox = 1;
cu->tb = MEM_calloc_arrayN(MAXTEXTBOX, sizeof(TextBox), "textbox");
@@ -5437,7 +5437,7 @@ void BKE_curve_material_index_remove(Curve *cu, int index)
if (curvetype == OB_FONT) {
struct CharInfo *info = cu->strinfo;
int i;
- for (i = cu->len_wchar - 1; i >= 0; i--, info++) {
+ for (i = cu->len_char32 - 1; i >= 0; i--, info++) {
if (info->mat_nr && info->mat_nr >= index) {
info->mat_nr--;
}
@@ -5461,7 +5461,7 @@ bool BKE_curve_material_index_used(Curve *cu, int index)
if (curvetype == OB_FONT) {
struct CharInfo *info = cu->strinfo;
int i;
- for (i = cu->len_wchar - 1; i >= 0; i--, info++) {
+ for (i = cu->len_char32 - 1; i >= 0; i--, info++) {
if (info->mat_nr == index) {
return true;
}
@@ -5487,7 +5487,7 @@ void BKE_curve_material_index_clear(Curve *cu)
if (curvetype == OB_FONT) {
struct CharInfo *info = cu->strinfo;
int i;
- for (i = cu->len_wchar - 1; i >= 0; i--, info++) {
+ for (i = cu->len_char32 - 1; i >= 0; i--, info++) {
info->mat_nr = 0;
}
}
@@ -5509,7 +5509,7 @@ bool BKE_curve_material_index_validate(Curve *cu)
CharInfo *info = cu->strinfo;
const int max_idx = max_ii(0, cu->totcol); /* OB_FONT use 1 as first mat index, not 0!!! */
int i;
- for (i = cu->len_wchar - 1; i >= 0; i--, info++) {
+ for (i = cu->len_char32 - 1; i >= 0; i--, info++) {
if (info->mat_nr > max_idx) {
info->mat_nr = 0;
is_valid = false;
@@ -5557,7 +5557,7 @@ void BKE_curve_material_remap(Curve *cu, const unsigned int *remap, unsigned int
}
else {
strinfo = cu->strinfo;
- charinfo_len = cu->len_wchar;
+ charinfo_len = cu->len_char32;
}
for (i = 0; i <= charinfo_len; i++) {
diff --git a/source/blender/blenkernel/intern/customdata.c b/source/blender/blenkernel/intern/customdata.c
index 22d4af6fa39..7bf11d86a63 100644
--- a/source/blender/blenkernel/intern/customdata.c
+++ b/source/blender/blenkernel/intern/customdata.c
@@ -2643,11 +2643,13 @@ static CustomDataLayer *customData_add_layer__internal(CustomData *data,
}
if (alloctype == CD_DUPLICATE && layerdata) {
- if (typeInfo->copy) {
- typeInfo->copy(layerdata, newlayerdata, totelem);
- }
- else {
- memcpy(newlayerdata, layerdata, (size_t)totelem * typeInfo->size);
+ if (totelem > 0) {
+ if (typeInfo->copy) {
+ typeInfo->copy(layerdata, newlayerdata, totelem);
+ }
+ else {
+ memcpy(newlayerdata, layerdata, (size_t)totelem * typeInfo->size);
+ }
}
}
else if (alloctype == CD_DEFAULT) {
diff --git a/source/blender/blenkernel/intern/font.c b/source/blender/blenkernel/intern/font.c
index dfa5ff6975f..958acf0589b 100644
--- a/source/blender/blenkernel/intern/font.c
+++ b/source/blender/blenkernel/intern/font.c
@@ -797,7 +797,7 @@ static bool vfont_to_curve(Object *ob,
}
else {
char32_t *mem_tmp;
- slen = cu->len_wchar;
+ slen = cu->len_char32;
/* Create unicode string */
mem_tmp = MEM_malloc_arrayN((slen + 1), sizeof(*mem_tmp), "convertedmem");
diff --git a/source/blender/blenkernel/intern/image.c b/source/blender/blenkernel/intern/image.c
index 9365ee040c2..e42a1c58985 100644
--- a/source/blender/blenkernel/intern/image.c
+++ b/source/blender/blenkernel/intern/image.c
@@ -57,6 +57,7 @@
#include "DNA_packedFile_types.h"
#include "DNA_scene_types.h"
#include "DNA_sequence_types.h"
+#include "DNA_simulation_types.h"
#include "DNA_world_types.h"
#include "BLI_blenlib.h"
@@ -3240,6 +3241,12 @@ static void image_walk_id_all_users(
if (scene->nodetree && scene->use_nodes && !skip_nested_nodes) {
image_walk_ntree_all_users(scene->nodetree, &scene->id, customdata, callback);
}
+ break;
+ }
+ case ID_SIM: {
+ Simulation *simulation = (Simulation *)id;
+ image_walk_ntree_all_users(simulation->nodetree, &simulation->id, customdata, callback);
+ break;
}
default:
break;
diff --git a/source/blender/blenkernel/intern/lib_query.c b/source/blender/blenkernel/intern/lib_query.c
index 00a42b12e07..0f81d45c10f 100644
--- a/source/blender/blenkernel/intern/lib_query.c
+++ b/source/blender/blenkernel/intern/lib_query.c
@@ -412,6 +412,8 @@ bool BKE_library_id_can_use_idtype(ID *id_owner, const short id_type_used)
return ELEM(id_type_used, ID_MA);
case ID_VO:
return ELEM(id_type_used, ID_MA);
+ case ID_SIM:
+ return ELEM(id_type_used, ID_OB, ID_IM);
case ID_IM:
case ID_VF:
case ID_TXT:
@@ -422,7 +424,6 @@ bool BKE_library_id_can_use_idtype(ID *id_owner, const short id_type_used)
case ID_PAL:
case ID_PC:
case ID_CF:
- case ID_SIM:
/* Those types never use/reference other IDs... */
return false;
case ID_IP:
diff --git a/source/blender/blenkernel/intern/node.c b/source/blender/blenkernel/intern/node.c
index 20d65e52b09..ca1354e9fea 100644
--- a/source/blender/blenkernel/intern/node.c
+++ b/source/blender/blenkernel/intern/node.c
@@ -61,6 +61,7 @@
#include "BKE_lib_query.h"
#include "BKE_main.h"
#include "BKE_node.h"
+#include "BKE_simulation.h"
#include "BLI_ghash.h"
#include "BLI_threads.h"
@@ -2493,6 +2494,7 @@ ID *BKE_node_tree_find_owner_ID(Main *bmain, struct bNodeTree *ntree)
&bmain->textures,
&bmain->scenes,
&bmain->linestyles,
+ &bmain->simulations,
NULL};
for (int i = 0; lists[i] != NULL; i++) {
@@ -3637,6 +3639,16 @@ void ntreeUpdateAllUsers(Main *main, ID *ngroup)
FOREACH_NODETREE_END;
}
+static void ntreeUpdateSimulationDependencies(Main *main, bNodeTree *simulation_ntree)
+{
+ FOREACH_NODETREE_BEGIN (main, ntree, owner_id) {
+ if (GS(owner_id->name) == ID_SIM && ntree == simulation_ntree) {
+ BKE_simulation_update_dependencies((Simulation *)owner_id, main);
+ }
+ }
+ FOREACH_NODETREE_END;
+}
+
void ntreeUpdateTree(Main *bmain, bNodeTree *ntree)
{
bNode *node;
@@ -3679,7 +3691,6 @@ void ntreeUpdateTree(Main *bmain, bNodeTree *ntree)
ntreeInterfaceTypeUpdate(ntree);
}
- /* XXX hack, should be done by depsgraph!! */
if (bmain) {
ntreeUpdateAllUsers(bmain, &ntree->id);
}
@@ -3695,6 +3706,11 @@ void ntreeUpdateTree(Main *bmain, bNodeTree *ntree)
ntree_validate_links(ntree);
}
+ if (bmain != NULL && ntree->typeinfo == ntreeType_Simulation &&
+ (ntree->id.flag & LIB_EMBEDDED_DATA)) {
+ ntreeUpdateSimulationDependencies(bmain, ntree);
+ }
+
/* clear update flags */
for (node = ntree->nodes.first; node; node = node->next) {
node->update = 0;
diff --git a/source/blender/blenkernel/intern/object_dupli.c b/source/blender/blenkernel/intern/object_dupli.c
index f498e147110..413acde62d5 100644
--- a/source/blender/blenkernel/intern/object_dupli.c
+++ b/source/blender/blenkernel/intern/object_dupli.c
@@ -523,7 +523,7 @@ static void make_duplis_font(const DupliContext *ctx)
/* Safety check even if it might fail badly when called for original object. */
const bool is_eval_curve = DEG_is_evaluated_id(&cu->id);
- /* advance matching BLI_strncpy_wchar_from_utf8 */
+ /* Advance matching BLI_str_utf8_as_utf32. */
for (a = 0; a < text_len; a++, ct++) {
/* XXX That G.main is *really* ugly, but not sure what to do here...
diff --git a/source/blender/blenkernel/intern/simulation.cc b/source/blender/blenkernel/intern/simulation.cc
index 95340e4e29c..b1c7f50a2af 100644
--- a/source/blender/blenkernel/intern/simulation.cc
+++ b/source/blender/blenkernel/intern/simulation.cc
@@ -112,6 +112,7 @@ static void simulation_copy_data(Main *bmain, ID *id_dst, const ID *id_src, cons
BKE_simulation_state_copy_data(state_src, state_dst);
}
+ BLI_listbase_clear(&simulation_dst->persistent_data_handles);
BLI_duplicatelist(&simulation_dst->persistent_data_handles,
&simulation_src->persistent_data_handles);
}
@@ -284,6 +285,14 @@ void BKE_simulation_data_update(Depsgraph *depsgraph, Scene *scene, Simulation *
blender::sim::update_simulation_in_depsgraph(depsgraph, scene, simulation);
}
+void BKE_simulation_update_dependencies(Simulation *simulation, Main *bmain)
+{
+ bool dependencies_changed = blender::sim::update_simulation_dependencies(simulation);
+ if (dependencies_changed) {
+ DEG_relations_tag_update(bmain);
+ }
+}
+
using StateTypeMap = blender::Map<std::string, std::unique_ptr<SimulationStateType>>;
template<typename T>
diff --git a/source/blender/blenlib/BLI_span.hh b/source/blender/blenlib/BLI_span.hh
index 2d875fe73be..81b86f647f6 100644
--- a/source/blender/blenlib/BLI_span.hh
+++ b/source/blender/blenlib/BLI_span.hh
@@ -87,7 +87,7 @@ namespace blender {
*/
template<typename T> class Span {
private:
- const T *start_ = nullptr;
+ const T *data_ = nullptr;
int64_t size_ = 0;
public:
@@ -96,13 +96,13 @@ template<typename T> class Span {
*/
Span() = default;
- Span(const T *start, int64_t size) : start_(start), size_(size)
+ Span(const T *start, int64_t size) : data_(start), size_(size)
{
BLI_assert(size >= 0);
}
template<typename U, typename std::enable_if_t<is_convertible_pointer_v<U, T>> * = nullptr>
- Span(const U *start, int64_t size) : start_((const T *)start), size_(size)
+ Span(const U *start, int64_t size) : data_((const T *)start), size_(size)
{
BLI_assert(size >= 0);
}
@@ -136,7 +136,7 @@ template<typename T> class Span {
* Span<Derived *> -> Span<Base *>
*/
template<typename U, typename std::enable_if_t<is_convertible_pointer_v<U, T>> * = nullptr>
- Span(Span<U> array) : start_((T *)array.data()), size_(array.size())
+ Span(Span<U> array) : data_((T *)array.data()), size_(array.size())
{
}
@@ -149,7 +149,7 @@ template<typename T> class Span {
BLI_assert(start >= 0);
BLI_assert(size >= 0);
BLI_assert(start + size <= this->size() || size == 0);
- return Span(start_ + start, size);
+ return Span(data_ + start, size);
}
Span slice(IndexRange range) const
@@ -207,17 +207,17 @@ template<typename T> class Span {
*/
const T *data() const
{
- return start_;
+ return data_;
}
const T *begin() const
{
- return start_;
+ return data_;
}
const T *end() const
{
- return start_ + size_;
+ return data_ + size_;
}
/**
@@ -228,7 +228,7 @@ template<typename T> class Span {
{
BLI_assert(index >= 0);
BLI_assert(index < size_);
- return start_[index];
+ return data_[index];
}
/**
@@ -300,7 +300,7 @@ template<typename T> class Span {
const T &first() const
{
BLI_assert(size_ > 0);
- return start_[0];
+ return data_[0];
}
/**
@@ -310,7 +310,7 @@ template<typename T> class Span {
const T &last() const
{
BLI_assert(size_ > 0);
- return start_[size_ - 1];
+ return data_[size_ - 1];
}
/**
@@ -320,7 +320,7 @@ template<typename T> class Span {
T get(int64_t index, const T &fallback) const
{
if (index < size_ && index >= 0) {
- return start_[index];
+ return data_[index];
}
return fallback;
}
@@ -336,9 +336,9 @@ template<typename T> class Span {
BLI_assert(size_ < 1000);
for (int64_t i = 0; i < size_; i++) {
- const T &value = start_[i];
+ const T &value = data_[i];
for (int64_t j = i + 1; j < size_; j++) {
- if (value == start_[j]) {
+ if (value == data_[j]) {
return true;
}
}
@@ -358,7 +358,7 @@ template<typename T> class Span {
BLI_assert(size_ < 1000);
for (int64_t i = 0; i < size_; i++) {
- const T &value = start_[i];
+ const T &value = data_[i];
if (other.contains(value)) {
return true;
}
@@ -383,7 +383,7 @@ template<typename T> class Span {
int64_t first_index_try(const T &search_value) const
{
for (int64_t i = 0; i < size_; i++) {
- if (start_[i] == search_value) {
+ if (data_[i] == search_value) {
return i;
}
}
@@ -406,7 +406,7 @@ template<typename T> class Span {
{
BLI_assert((size_ * sizeof(T)) % sizeof(NewT) == 0);
int64_t new_size = size_ * sizeof(T) / sizeof(NewT);
- return Span<NewT>(reinterpret_cast<const NewT *>(start_), new_size);
+ return Span<NewT>(reinterpret_cast<const NewT *>(data_), new_size);
}
/**
@@ -439,13 +439,13 @@ template<typename T> class Span {
*/
template<typename T> class MutableSpan {
private:
- T *start_;
+ T *data_;
int64_t size_;
public:
MutableSpan() = default;
- MutableSpan(T *start, const int64_t size) : start_(start), size_(size)
+ MutableSpan(T *start, const int64_t size) : data_(start), size_(size)
{
}
@@ -459,7 +459,7 @@ template<typename T> class MutableSpan {
operator Span<T>() const
{
- return Span<T>(start_, size_);
+ return Span<T>(data_, size_);
}
/**
@@ -475,7 +475,7 @@ template<typename T> class MutableSpan {
*/
void fill(const T &value)
{
- initialized_fill_n(start_, size_, value);
+ initialized_fill_n(data_, size_, value);
}
/**
@@ -486,7 +486,7 @@ template<typename T> class MutableSpan {
{
for (int64_t i : indices) {
BLI_assert(i < size_);
- start_[i] = value;
+ data_[i] = value;
}
}
@@ -496,23 +496,23 @@ template<typename T> class MutableSpan {
*/
T *data() const
{
- return start_;
+ return data_;
}
T *begin() const
{
- return start_;
+ return data_;
}
T *end() const
{
- return start_ + size_;
+ return data_ + size_;
}
T &operator[](const int64_t index) const
{
BLI_assert(index < this->size());
- return start_[index];
+ return data_[index];
}
/**
@@ -522,7 +522,7 @@ template<typename T> class MutableSpan {
MutableSpan slice(const int64_t start, const int64_t length) const
{
BLI_assert(start + length <= this->size());
- return MutableSpan(start_ + start, length);
+ return MutableSpan(data_ + start, length);
}
/**
@@ -571,7 +571,7 @@ template<typename T> class MutableSpan {
*/
Span<T> as_span() const
{
- return Span<T>(start_, size_);
+ return Span<T>(data_, size_);
}
/**
@@ -590,7 +590,7 @@ template<typename T> class MutableSpan {
T &last() const
{
BLI_assert(size_ > 0);
- return start_[size_ - 1];
+ return data_[size_ - 1];
}
/**
@@ -609,13 +609,24 @@ template<typename T> class MutableSpan {
}
/**
+ * 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(Span<T> values)
+ {
+ BLI_assert(size_ == values.size());
+ initialized_copy_n(values.data(), size_, data_);
+ }
+
+ /**
* Returns a new span to the same underlying memory buffer. No conversions are done.
*/
template<typename NewT> MutableSpan<NewT> cast() const
{
BLI_assert((size_ * sizeof(T)) % sizeof(NewT) == 0);
int64_t new_size = size_ * sizeof(T) / sizeof(NewT);
- return MutableSpan<NewT>(reinterpret_cast<NewT *>(start_), new_size);
+ return MutableSpan<NewT>(reinterpret_cast<NewT *>(data_), new_size);
}
};
diff --git a/source/blender/blenlib/BLI_vector.hh b/source/blender/blenlib/BLI_vector.hh
index 1bb674093bb..7eac511bf4a 100644
--- a/source/blender/blenlib/BLI_vector.hh
+++ b/source/blender/blenlib/BLI_vector.hh
@@ -561,7 +561,7 @@ class Vector {
/**
* Return a reference to the last element in the vector.
- * This will assert when the vector is empty.
+ * This invokes undefined behavior when the vector is empty.
*/
const T &last() const
{
diff --git a/source/blender/blenlib/CMakeLists.txt b/source/blender/blenlib/CMakeLists.txt
index c6e04d4147a..d7d1d701bc8 100644
--- a/source/blender/blenlib/CMakeLists.txt
+++ b/source/blender/blenlib/CMakeLists.txt
@@ -340,3 +340,28 @@ set_source_files_properties(
)
blender_add_lib(bf_blenlib "${SRC}" "${INC}" "${INC_SYS}" "${LIB}")
+
+if(WITH_GTESTS)
+ set(TEST_SRC
+ tests/BLI_array_test.cc
+ tests/BLI_disjoint_set_test.cc
+ tests/BLI_edgehash_test.cc
+ tests/BLI_index_mask_test.cc
+ tests/BLI_index_range_test.cc
+ tests/BLI_linear_allocator_test.cc
+ tests/BLI_map_test.cc
+ tests/BLI_math_base_safe_test.cc
+ tests/BLI_memory_utils_test.cc
+ tests/BLI_set_test.cc
+ tests/BLI_span_test.cc
+ tests/BLI_stack_cxx_test.cc
+ tests/BLI_string_ref_test.cc
+ tests/BLI_vector_set_test.cc
+ tests/BLI_vector_test.cc
+ )
+ set(TEST_LIB
+ bf_blenlib
+ )
+ include(GTestTesting)
+ blender_add_test_lib(bf_bli_tests "${TEST_SRC}" "${INC};${TEST_INC}" "${INC_SYS}" "${LIB};${TEST_LIB}")
+endif()
diff --git a/source/blender/blenlib/tests/BLI_array_test.cc b/source/blender/blenlib/tests/BLI_array_test.cc
new file mode 100644
index 00000000000..7348a6f93f3
--- /dev/null
+++ b/source/blender/blenlib/tests/BLI_array_test.cc
@@ -0,0 +1,176 @@
+/* Apache License, Version 2.0 */
+
+#include "BLI_array.hh"
+#include "BLI_strict_flags.h"
+#include "testing/testing.h"
+
+namespace blender::tests {
+
+TEST(array, DefaultConstructor)
+{
+ Array<int> array;
+ EXPECT_EQ(array.size(), 0);
+ EXPECT_TRUE(array.is_empty());
+}
+
+TEST(array, SizeConstructor)
+{
+ Array<int> array(5);
+ EXPECT_EQ(array.size(), 5);
+ EXPECT_FALSE(array.is_empty());
+}
+
+TEST(array, FillConstructor)
+{
+ Array<int> array(5, 8);
+ EXPECT_EQ(array.size(), 5);
+ EXPECT_EQ(array[0], 8);
+ EXPECT_EQ(array[1], 8);
+ EXPECT_EQ(array[2], 8);
+ EXPECT_EQ(array[3], 8);
+ EXPECT_EQ(array[4], 8);
+}
+
+TEST(array, InitializerListConstructor)
+{
+ Array<int> array = {4, 5, 6, 7};
+ EXPECT_EQ(array.size(), 4);
+ EXPECT_EQ(array[0], 4);
+ EXPECT_EQ(array[1], 5);
+ EXPECT_EQ(array[2], 6);
+ EXPECT_EQ(array[3], 7);
+}
+
+TEST(array, SpanConstructor)
+{
+ int stackarray[4] = {6, 7, 8, 9};
+ Span<int> span(stackarray, ARRAY_SIZE(stackarray));
+ Array<int> array(span);
+ EXPECT_EQ(array.size(), 4);
+ EXPECT_EQ(array[0], 6);
+ EXPECT_EQ(array[1], 7);
+ EXPECT_EQ(array[2], 8);
+ EXPECT_EQ(array[3], 9);
+}
+
+TEST(array, CopyConstructor)
+{
+ Array<int> array = {5, 6, 7, 8};
+ Array<int> new_array(array);
+
+ EXPECT_EQ(array.size(), 4);
+ EXPECT_EQ(new_array.size(), 4);
+ EXPECT_NE(array.data(), new_array.data());
+ EXPECT_EQ(new_array[0], 5);
+ EXPECT_EQ(new_array[1], 6);
+ EXPECT_EQ(new_array[2], 7);
+ EXPECT_EQ(new_array[3], 8);
+}
+
+TEST(array, MoveConstructor)
+{
+ Array<int> array = {5, 6, 7, 8};
+ Array<int> new_array(std::move(array));
+
+ EXPECT_EQ(array.size(), 0); /* NOLINT: bugprone-use-after-move */
+ EXPECT_EQ(new_array.size(), 4);
+ EXPECT_EQ(new_array[0], 5);
+ EXPECT_EQ(new_array[1], 6);
+ EXPECT_EQ(new_array[2], 7);
+ EXPECT_EQ(new_array[3], 8);
+}
+
+TEST(array, CopyAssignment)
+{
+ Array<int> array = {1, 2, 3};
+ Array<int> new_array = {4};
+ EXPECT_EQ(new_array.size(), 1);
+ new_array = array;
+ EXPECT_EQ(new_array.size(), 3);
+ EXPECT_EQ(array.size(), 3);
+ EXPECT_NE(array.data(), new_array.data());
+ EXPECT_EQ(new_array[0], 1);
+ EXPECT_EQ(new_array[1], 2);
+ EXPECT_EQ(new_array[2], 3);
+}
+
+TEST(array, MoveAssignment)
+{
+ Array<int> array = {1, 2, 3};
+ Array<int> new_array = {4};
+ EXPECT_EQ(new_array.size(), 1);
+ new_array = std::move(array);
+ EXPECT_EQ(new_array.size(), 3);
+ EXPECT_EQ(array.size(), 0); /* NOLINT: bugprone-use-after-move */
+ EXPECT_EQ(new_array[0], 1);
+ EXPECT_EQ(new_array[1], 2);
+ EXPECT_EQ(new_array[2], 3);
+}
+
+/**
+ * Tests that the trivially constructible types are not zero-initialized. We do not want that for
+ * performance reasons.
+ */
+TEST(array, TrivialTypeSizeConstructor)
+{
+ Array<char, 1> *array = new Array<char, 1>(1);
+ char *ptr = &(*array)[0];
+ array->~Array();
+
+ const char magic = 42;
+ *ptr = magic;
+ EXPECT_EQ(*ptr, magic);
+
+ new (array) Array<char, 1>(1);
+ EXPECT_EQ((*array)[0], magic);
+ EXPECT_EQ(*ptr, magic);
+ delete array;
+}
+
+struct ConstructibleType {
+ char value;
+
+ ConstructibleType()
+ {
+ value = 42;
+ }
+};
+
+TEST(array, NoInitializationSizeConstructor)
+{
+ using MyArray = Array<ConstructibleType>;
+
+ TypedBuffer<MyArray> buffer;
+ memset((void *)&buffer, 100, sizeof(MyArray));
+
+ /* Doing this to avoid some compiler optimization. */
+ for (int64_t i : IndexRange(sizeof(MyArray))) {
+ EXPECT_EQ(((char *)buffer.ptr())[i], 100);
+ }
+
+ {
+ MyArray &array = *new (buffer) MyArray(1, NoInitialization());
+ EXPECT_EQ(array[0].value, 100);
+ array.clear_without_destruct();
+ array.~Array();
+ }
+ {
+ MyArray &array = *new (buffer) MyArray(1);
+ EXPECT_EQ(array[0].value, 42);
+ array.~Array();
+ }
+}
+
+TEST(array, Fill)
+{
+ Array<int> array(5);
+ array.fill(3);
+ EXPECT_EQ(array.size(), 5u);
+ EXPECT_EQ(array[0], 3);
+ EXPECT_EQ(array[1], 3);
+ EXPECT_EQ(array[2], 3);
+ EXPECT_EQ(array[3], 3);
+ EXPECT_EQ(array[4], 3);
+}
+
+} // namespace blender::tests
diff --git a/source/blender/blenlib/tests/BLI_disjoint_set_test.cc b/source/blender/blenlib/tests/BLI_disjoint_set_test.cc
new file mode 100644
index 00000000000..f30ee610b2a
--- /dev/null
+++ b/source/blender/blenlib/tests/BLI_disjoint_set_test.cc
@@ -0,0 +1,36 @@
+/* Apache License, Version 2.0 */
+
+#include "BLI_disjoint_set.hh"
+#include "BLI_strict_flags.h"
+
+#include "testing/testing.h"
+
+namespace blender::tests {
+
+TEST(disjoint_set, Test)
+{
+ DisjointSet disjoint_set(6);
+ EXPECT_FALSE(disjoint_set.in_same_set(1, 2));
+ EXPECT_FALSE(disjoint_set.in_same_set(5, 3));
+ EXPECT_TRUE(disjoint_set.in_same_set(2, 2));
+ EXPECT_EQ(disjoint_set.find_root(3), 3);
+
+ disjoint_set.join(1, 2);
+
+ EXPECT_TRUE(disjoint_set.in_same_set(1, 2));
+ EXPECT_FALSE(disjoint_set.in_same_set(0, 1));
+
+ disjoint_set.join(3, 4);
+
+ EXPECT_FALSE(disjoint_set.in_same_set(2, 3));
+ EXPECT_TRUE(disjoint_set.in_same_set(3, 4));
+
+ disjoint_set.join(1, 4);
+
+ EXPECT_TRUE(disjoint_set.in_same_set(1, 4));
+ EXPECT_TRUE(disjoint_set.in_same_set(1, 3));
+ EXPECT_TRUE(disjoint_set.in_same_set(2, 4));
+ EXPECT_FALSE(disjoint_set.in_same_set(0, 4));
+}
+
+} // namespace blender::tests
diff --git a/source/blender/blenlib/tests/BLI_edgehash_test.cc b/source/blender/blenlib/tests/BLI_edgehash_test.cc
new file mode 100644
index 00000000000..7106033df36
--- /dev/null
+++ b/source/blender/blenlib/tests/BLI_edgehash_test.cc
@@ -0,0 +1,408 @@
+/* Apache License, Version 2.0 */
+
+#include "testing/testing.h"
+#include <algorithm>
+#include <random>
+#include <vector>
+
+#include "BLI_edgehash.h"
+#include "BLI_utildefines.h"
+
+#define VALUE_1 POINTER_FROM_INT(1)
+#define VALUE_2 POINTER_FROM_INT(2)
+#define VALUE_3 POINTER_FROM_INT(3)
+
+TEST(edgehash, InsertIncreasesLength)
+{
+ EdgeHash *eh = BLI_edgehash_new(__func__);
+
+ ASSERT_EQ(BLI_edgehash_len(eh), 0);
+ BLI_edgehash_insert(eh, 1, 2, VALUE_1);
+ ASSERT_EQ(BLI_edgehash_len(eh), 1);
+
+ BLI_edgehash_free(eh, nullptr);
+}
+
+TEST(edgehash, ReinsertNewIncreasesLength)
+{
+ EdgeHash *eh = BLI_edgehash_new(__func__);
+
+ ASSERT_EQ(BLI_edgehash_len(eh), 0);
+ BLI_edgehash_reinsert(eh, 1, 2, VALUE_1);
+ ASSERT_EQ(BLI_edgehash_len(eh), 1);
+
+ BLI_edgehash_free(eh, nullptr);
+}
+
+TEST(edgehash, ReinsertExistingDoesNotIncreaseLength)
+{
+ EdgeHash *eh = BLI_edgehash_new(__func__);
+
+ ASSERT_EQ(BLI_edgehash_len(eh), 0);
+ BLI_edgehash_reinsert(eh, 1, 2, VALUE_1);
+ ASSERT_EQ(BLI_edgehash_len(eh), 1);
+ BLI_edgehash_reinsert(eh, 1, 2, VALUE_2);
+ ASSERT_EQ(BLI_edgehash_len(eh), 1);
+ BLI_edgehash_reinsert(eh, 2, 1, VALUE_2);
+ ASSERT_EQ(BLI_edgehash_len(eh), 1);
+
+ BLI_edgehash_free(eh, nullptr);
+}
+
+TEST(edgehash, ReinsertCanChangeValue)
+{
+ EdgeHash *eh = BLI_edgehash_new(__func__);
+
+ BLI_edgehash_insert(eh, 1, 2, VALUE_1);
+ ASSERT_EQ(BLI_edgehash_lookup(eh, 1, 2), VALUE_1);
+ BLI_edgehash_reinsert(eh, 2, 1, VALUE_2);
+ ASSERT_EQ(BLI_edgehash_lookup(eh, 1, 2), VALUE_2);
+ BLI_edgehash_reinsert(eh, 1, 2, VALUE_3);
+ ASSERT_EQ(BLI_edgehash_lookup(eh, 2, 1), VALUE_3);
+
+ BLI_edgehash_free(eh, nullptr);
+}
+
+TEST(edgehash, LookupExisting)
+{
+ EdgeHash *eh = BLI_edgehash_new(__func__);
+
+ BLI_edgehash_insert(eh, 1, 2, VALUE_1);
+ ASSERT_EQ(BLI_edgehash_lookup(eh, 1, 2), VALUE_1);
+ ASSERT_EQ(BLI_edgehash_lookup(eh, 2, 1), VALUE_1);
+
+ BLI_edgehash_free(eh, nullptr);
+}
+
+TEST(edgehash, LookupNonExisting)
+{
+ EdgeHash *eh = BLI_edgehash_new(__func__);
+
+ ASSERT_EQ(BLI_edgehash_lookup(eh, 1, 2), nullptr);
+
+ BLI_edgehash_free(eh, nullptr);
+}
+
+TEST(edgehash, LookupNonExistingWithDefault)
+{
+ EdgeHash *eh = BLI_edgehash_new(__func__);
+
+ ASSERT_EQ(BLI_edgehash_lookup_default(eh, 1, 2, VALUE_1), VALUE_1);
+
+ BLI_edgehash_free(eh, nullptr);
+}
+
+TEST(edgehash, LookupExistingWithDefault)
+{
+ EdgeHash *eh = BLI_edgehash_new(__func__);
+
+ BLI_edgehash_insert(eh, 1, 2, VALUE_1);
+ ASSERT_EQ(BLI_edgehash_lookup_default(eh, 1, 2, VALUE_2), VALUE_1);
+
+ BLI_edgehash_free(eh, nullptr);
+}
+
+TEST(edgehash, LookupPExisting)
+{
+ EdgeHash *eh = BLI_edgehash_new(__func__);
+
+ void *value = VALUE_1;
+ BLI_edgehash_insert(eh, 1, 2, value);
+ void **value_p = BLI_edgehash_lookup_p(eh, 1, 2);
+ ASSERT_EQ(*value_p, VALUE_1);
+ *value_p = VALUE_2;
+ ASSERT_EQ(BLI_edgehash_lookup(eh, 1, 2), VALUE_2);
+
+ BLI_edgehash_free(eh, nullptr);
+}
+
+TEST(edgehash, LookupPNonExisting)
+{
+ EdgeHash *eh = BLI_edgehash_new(__func__);
+
+ ASSERT_EQ(BLI_edgehash_lookup_p(eh, 1, 2), nullptr);
+
+ BLI_edgehash_free(eh, nullptr);
+}
+
+TEST(edgehash, EnsurePNonExisting)
+{
+ EdgeHash *eh = BLI_edgehash_new(__func__);
+
+ void **value_p;
+ bool existed = BLI_edgehash_ensure_p(eh, 1, 2, &value_p);
+ ASSERT_FALSE(existed);
+ *value_p = VALUE_1;
+ ASSERT_EQ(BLI_edgehash_lookup(eh, 1, 2), VALUE_1);
+
+ BLI_edgehash_free(eh, nullptr);
+}
+
+TEST(edgehash, EnsurePExisting)
+{
+ EdgeHash *eh = BLI_edgehash_new(__func__);
+
+ BLI_edgehash_insert(eh, 1, 2, VALUE_1);
+ void **value_p;
+ bool existed = BLI_edgehash_ensure_p(eh, 1, 2, &value_p);
+ ASSERT_TRUE(existed);
+ ASSERT_EQ(*value_p, VALUE_1);
+ *value_p = VALUE_2;
+ ASSERT_EQ(BLI_edgehash_lookup(eh, 1, 2), VALUE_2);
+
+ BLI_edgehash_free(eh, nullptr);
+}
+
+TEST(edgehash, RemoveExistingDecreasesLength)
+{
+ EdgeHash *eh = BLI_edgehash_new(__func__);
+
+ BLI_edgehash_insert(eh, 1, 2, VALUE_1);
+ ASSERT_EQ(BLI_edgehash_len(eh), 1);
+ bool has_been_removed = BLI_edgehash_remove(eh, 1, 2, nullptr);
+ ASSERT_EQ(BLI_edgehash_len(eh), 0);
+ ASSERT_TRUE(has_been_removed);
+
+ BLI_edgehash_free(eh, nullptr);
+}
+
+TEST(edgehash, RemoveNonExistingDoesNotDecreaseLength)
+{
+ EdgeHash *eh = BLI_edgehash_new(__func__);
+
+ BLI_edgehash_insert(eh, 1, 2, VALUE_1);
+ ASSERT_EQ(BLI_edgehash_len(eh), 1);
+ bool has_been_removed = BLI_edgehash_remove(eh, 4, 5, nullptr);
+ ASSERT_EQ(BLI_edgehash_len(eh), 1);
+ ASSERT_FALSE(has_been_removed);
+
+ BLI_edgehash_free(eh, nullptr);
+}
+
+TEST(edgehash, PopKeyTwice)
+{
+ EdgeHash *eh = BLI_edgehash_new(__func__);
+
+ BLI_edgehash_insert(eh, 1, 2, VALUE_1);
+ ASSERT_EQ(BLI_edgehash_popkey(eh, 1, 2), VALUE_1);
+ ASSERT_EQ(BLI_edgehash_popkey(eh, 1, 2), nullptr);
+
+ BLI_edgehash_free(eh, nullptr);
+}
+
+TEST(edgehash, LookupInvertedIndices)
+{
+ EdgeHash *eh = BLI_edgehash_new(__func__);
+
+ BLI_edgehash_insert(eh, 1, 2, VALUE_1);
+ ASSERT_EQ(BLI_edgehash_lookup(eh, 2, 1), VALUE_1);
+
+ BLI_edgehash_free(eh, nullptr);
+}
+
+TEST(edgehash, HasKeyExisting)
+{
+ EdgeHash *eh = BLI_edgehash_new(__func__);
+
+ BLI_edgehash_insert(eh, 1, 2, VALUE_1);
+ ASSERT_TRUE(BLI_edgehash_haskey(eh, 1, 2));
+ ASSERT_TRUE(BLI_edgehash_haskey(eh, 2, 1));
+
+ BLI_edgehash_free(eh, nullptr);
+}
+
+TEST(edgehash, HasKeyNonExisting)
+{
+ EdgeHash *eh = BLI_edgehash_new(__func__);
+
+ ASSERT_FALSE(BLI_edgehash_haskey(eh, 1, 2));
+
+ BLI_edgehash_free(eh, nullptr);
+}
+
+TEST(edgehash, ClearSetsLengthToZero)
+{
+ EdgeHash *eh = BLI_edgehash_new(__func__);
+
+ BLI_edgehash_insert(eh, 1, 2, VALUE_1);
+ BLI_edgehash_insert(eh, 1, 2, VALUE_2);
+ ASSERT_EQ(BLI_edgehash_len(eh), 2);
+ BLI_edgehash_clear(eh, nullptr);
+ ASSERT_EQ(BLI_edgehash_len(eh), 0);
+
+ BLI_edgehash_free(eh, nullptr);
+}
+
+TEST(edgehash, IteratorFindsAllValues)
+{
+ EdgeHash *eh = BLI_edgehash_new(__func__);
+
+ BLI_edgehash_insert(eh, 1, 2, VALUE_1);
+ BLI_edgehash_insert(eh, 1, 3, VALUE_2);
+ BLI_edgehash_insert(eh, 1, 4, VALUE_3);
+
+ EdgeHashIterator *ehi = BLI_edgehashIterator_new(eh);
+ auto a = BLI_edgehashIterator_getValue(ehi);
+ BLI_edgehashIterator_step(ehi);
+ auto b = BLI_edgehashIterator_getValue(ehi);
+ BLI_edgehashIterator_step(ehi);
+ auto c = BLI_edgehashIterator_getValue(ehi);
+ BLI_edgehashIterator_step(ehi);
+
+ ASSERT_NE(a, b);
+ ASSERT_NE(b, c);
+ ASSERT_NE(a, c);
+ ASSERT_TRUE(ELEM(a, VALUE_1, VALUE_2, VALUE_3));
+ ASSERT_TRUE(ELEM(b, VALUE_1, VALUE_2, VALUE_3));
+ ASSERT_TRUE(ELEM(c, VALUE_1, VALUE_2, VALUE_3));
+
+ BLI_edgehashIterator_free(ehi);
+ BLI_edgehash_free(eh, nullptr);
+}
+
+TEST(edgehash, IterateIsDone)
+{
+ EdgeHash *eh = BLI_edgehash_new(__func__);
+
+ BLI_edgehash_insert(eh, 1, 2, VALUE_1);
+ BLI_edgehash_insert(eh, 1, 3, VALUE_2);
+ BLI_edgehash_insert(eh, 1, 4, VALUE_3);
+
+ EdgeHashIterator *ehi = BLI_edgehashIterator_new(eh);
+ ASSERT_FALSE(BLI_edgehashIterator_isDone(ehi));
+ BLI_edgehashIterator_step(ehi);
+ ASSERT_FALSE(BLI_edgehashIterator_isDone(ehi));
+ BLI_edgehashIterator_step(ehi);
+ ASSERT_FALSE(BLI_edgehashIterator_isDone(ehi));
+ BLI_edgehashIterator_step(ehi);
+ ASSERT_TRUE(BLI_edgehashIterator_isDone(ehi));
+
+ BLI_edgehashIterator_free(ehi);
+ BLI_edgehash_free(eh, nullptr);
+}
+
+TEST(edgehash, DoubleRemove)
+{
+ EdgeHash *eh = BLI_edgehash_new(__func__);
+
+ BLI_edgehash_insert(eh, 1, 2, VALUE_1);
+ BLI_edgehash_insert(eh, 1, 3, VALUE_2);
+ BLI_edgehash_insert(eh, 1, 4, VALUE_3);
+ ASSERT_EQ(BLI_edgehash_len(eh), 3);
+
+ BLI_edgehash_remove(eh, 1, 2, nullptr);
+ BLI_edgehash_remove(eh, 1, 3, nullptr);
+ ASSERT_EQ(BLI_edgehash_len(eh), 1);
+
+ BLI_edgehash_free(eh, nullptr);
+}
+
+struct Edge {
+ uint v1, v2;
+};
+
+TEST(edgehash, StressTest)
+{
+ std::srand(0);
+ int amount = 10000;
+
+ std::vector<Edge> edges;
+ for (int i = 0; i < amount; i++) {
+ edges.push_back({(uint)i, amount + (uint)std::rand() % 12345});
+ }
+
+ EdgeHash *eh = BLI_edgehash_new(__func__);
+
+ /* first insert all the edges */
+ for (int i = 0; i < edges.size(); i++) {
+ BLI_edgehash_insert(eh, edges[i].v1, edges[i].v2, POINTER_FROM_INT(i));
+ }
+
+ std::vector<Edge> shuffled = edges;
+ std::shuffle(shuffled.begin(), shuffled.end(), std::default_random_engine());
+
+ /* then remove half of them */
+ int remove_until = shuffled.size() / 2;
+ for (int i = 0; i < remove_until; i++) {
+ BLI_edgehash_remove(eh, shuffled[i].v2, shuffled[i].v1, nullptr);
+ }
+
+ ASSERT_EQ(BLI_edgehash_len(eh), edges.size() - remove_until);
+
+ /* check if the right ones have been removed */
+ for (int i = 0; i < shuffled.size(); i++) {
+ bool haskey = BLI_edgehash_haskey(eh, shuffled[i].v1, shuffled[i].v2);
+ if (i < remove_until) {
+ ASSERT_FALSE(haskey);
+ }
+ else {
+ ASSERT_TRUE(haskey);
+ }
+ }
+
+ /* reinsert all edges */
+ for (int i = 0; i < edges.size(); i++) {
+ BLI_edgehash_reinsert(eh, edges[i].v1, edges[i].v2, POINTER_FROM_INT(i));
+ }
+
+ ASSERT_EQ(BLI_edgehash_len(eh), edges.size());
+
+ /* pop all edges */
+ for (int i = 0; i < edges.size(); i++) {
+ int value = POINTER_AS_INT(BLI_edgehash_popkey(eh, edges[i].v1, edges[i].v2));
+ ASSERT_EQ(i, value);
+ }
+
+ ASSERT_EQ(BLI_edgehash_len(eh), 0);
+
+ BLI_edgehash_free(eh, nullptr);
+}
+
+TEST(edgeset, AddNonExistingIncreasesLength)
+{
+ EdgeSet *es = BLI_edgeset_new(__func__);
+
+ ASSERT_EQ(BLI_edgeset_len(es), 0);
+ BLI_edgeset_add(es, 1, 2);
+ ASSERT_EQ(BLI_edgeset_len(es), 1);
+ BLI_edgeset_add(es, 1, 3);
+ ASSERT_EQ(BLI_edgeset_len(es), 2);
+ BLI_edgeset_add(es, 1, 4);
+ ASSERT_EQ(BLI_edgeset_len(es), 3);
+
+ BLI_edgeset_free(es);
+}
+
+TEST(edgeset, AddExistingDoesNotIncreaseLength)
+{
+ EdgeSet *es = BLI_edgeset_new(__func__);
+
+ ASSERT_EQ(BLI_edgeset_len(es), 0);
+ BLI_edgeset_add(es, 1, 2);
+ ASSERT_EQ(BLI_edgeset_len(es), 1);
+ BLI_edgeset_add(es, 2, 1);
+ ASSERT_EQ(BLI_edgeset_len(es), 1);
+ BLI_edgeset_add(es, 1, 2);
+ ASSERT_EQ(BLI_edgeset_len(es), 1);
+
+ BLI_edgeset_free(es);
+}
+
+TEST(edgeset, HasKeyNonExisting)
+{
+ EdgeSet *es = BLI_edgeset_new(__func__);
+
+ ASSERT_FALSE(BLI_edgeset_haskey(es, 1, 2));
+
+ BLI_edgeset_free(es);
+}
+
+TEST(edgeset, HasKeyExisting)
+{
+ EdgeSet *es = BLI_edgeset_new(__func__);
+
+ BLI_edgeset_insert(es, 1, 2);
+ ASSERT_TRUE(BLI_edgeset_haskey(es, 1, 2));
+
+ BLI_edgeset_free(es);
+}
diff --git a/source/blender/blenlib/tests/BLI_index_mask_test.cc b/source/blender/blenlib/tests/BLI_index_mask_test.cc
new file mode 100644
index 00000000000..4d6060e51c9
--- /dev/null
+++ b/source/blender/blenlib/tests/BLI_index_mask_test.cc
@@ -0,0 +1,43 @@
+/* Apache License, Version 2.0 */
+
+#include "BLI_index_mask.hh"
+#include "testing/testing.h"
+
+namespace blender::tests {
+
+TEST(index_mask, DefaultConstructor)
+{
+ IndexMask mask;
+ EXPECT_EQ(mask.min_array_size(), 0);
+ EXPECT_EQ(mask.size(), 0);
+}
+
+TEST(index_mask, ArrayConstructor)
+{
+ [](IndexMask mask) {
+ EXPECT_EQ(mask.size(), 4);
+ EXPECT_EQ(mask.min_array_size(), 8);
+ EXPECT_FALSE(mask.is_range());
+ EXPECT_EQ(mask[0], 3);
+ EXPECT_EQ(mask[1], 5);
+ EXPECT_EQ(mask[2], 6);
+ EXPECT_EQ(mask[3], 7);
+ }({3, 5, 6, 7});
+}
+
+TEST(index_mask, RangeConstructor)
+{
+ IndexMask mask = IndexRange(3, 5);
+ EXPECT_EQ(mask.size(), 5);
+ EXPECT_EQ(mask.min_array_size(), 8);
+ EXPECT_EQ(mask.last(), 7);
+ EXPECT_TRUE(mask.is_range());
+ EXPECT_EQ(mask.as_range().first(), 3);
+ EXPECT_EQ(mask.as_range().last(), 7);
+ Span<int64_t> indices = mask.indices();
+ EXPECT_EQ(indices[0], 3);
+ EXPECT_EQ(indices[1], 4);
+ EXPECT_EQ(indices[2], 5);
+}
+
+} // namespace blender::tests
diff --git a/source/blender/blenlib/tests/BLI_index_range_test.cc b/source/blender/blenlib/tests/BLI_index_range_test.cc
new file mode 100644
index 00000000000..d472ded0f18
--- /dev/null
+++ b/source/blender/blenlib/tests/BLI_index_range_test.cc
@@ -0,0 +1,143 @@
+/* Apache License, Version 2.0 */
+
+#include "BLI_index_range.hh"
+#include "BLI_strict_flags.h"
+#include "BLI_vector.hh"
+#include "testing/testing.h"
+
+namespace blender::tests {
+
+TEST(index_range, DefaultConstructor)
+{
+ IndexRange range;
+ EXPECT_EQ(range.size(), 0);
+
+ Vector<int64_t> vector;
+ for (int64_t value : range) {
+ vector.append(value);
+ }
+ EXPECT_EQ(vector.size(), 0);
+}
+
+TEST(index_range, SingleElementRange)
+{
+ IndexRange range(4, 1);
+ EXPECT_EQ(range.size(), 1);
+ EXPECT_EQ(*range.begin(), 4);
+
+ Vector<int64_t> vector;
+ for (int64_t value : range) {
+ vector.append(value);
+ }
+
+ EXPECT_EQ(vector.size(), 1);
+ EXPECT_EQ(vector[0], 4);
+}
+
+TEST(index_range, MultipleElementRange)
+{
+ IndexRange range(6, 4);
+ EXPECT_EQ(range.size(), 4);
+
+ Vector<int64_t> vector;
+ for (int64_t value : range) {
+ vector.append(value);
+ }
+
+ EXPECT_EQ(vector.size(), 4);
+ for (int i = 0; i < 4; i++) {
+ EXPECT_EQ(vector[i], i + 6);
+ }
+}
+
+TEST(index_range, SubscriptOperator)
+{
+ IndexRange range(5, 5);
+ EXPECT_EQ(range[0], 5);
+ EXPECT_EQ(range[1], 6);
+ EXPECT_EQ(range[2], 7);
+}
+
+TEST(index_range, Before)
+{
+ IndexRange range = IndexRange(5, 5).before(3);
+ EXPECT_EQ(range[0], 2);
+ EXPECT_EQ(range[1], 3);
+ EXPECT_EQ(range[2], 4);
+ EXPECT_EQ(range.size(), 3);
+}
+
+TEST(index_range, After)
+{
+ IndexRange range = IndexRange(5, 5).after(4);
+ EXPECT_EQ(range[0], 10);
+ EXPECT_EQ(range[1], 11);
+ EXPECT_EQ(range[2], 12);
+ EXPECT_EQ(range[3], 13);
+ EXPECT_EQ(range.size(), 4);
+}
+
+TEST(index_range, Contains)
+{
+ IndexRange range = IndexRange(5, 3);
+ EXPECT_TRUE(range.contains(5));
+ EXPECT_TRUE(range.contains(6));
+ EXPECT_TRUE(range.contains(7));
+ EXPECT_FALSE(range.contains(4));
+ EXPECT_FALSE(range.contains(8));
+}
+
+TEST(index_range, First)
+{
+ IndexRange range = IndexRange(5, 3);
+ EXPECT_EQ(range.first(), 5);
+}
+
+TEST(index_range, Last)
+{
+ IndexRange range = IndexRange(5, 3);
+ EXPECT_EQ(range.last(), 7);
+}
+
+TEST(index_range, OneAfterEnd)
+{
+ IndexRange range = IndexRange(5, 3);
+ EXPECT_EQ(range.one_after_last(), 8);
+}
+
+TEST(index_range, Start)
+{
+ IndexRange range = IndexRange(6, 2);
+ EXPECT_EQ(range.start(), 6);
+}
+
+TEST(index_range, Slice)
+{
+ IndexRange range = IndexRange(5, 15);
+ IndexRange slice = range.slice(2, 6);
+ EXPECT_EQ(slice.size(), 6);
+ EXPECT_EQ(slice.first(), 7);
+ EXPECT_EQ(slice.last(), 12);
+}
+
+TEST(index_range, SliceRange)
+{
+ IndexRange range = IndexRange(5, 15);
+ IndexRange slice = range.slice(IndexRange(3, 5));
+ EXPECT_EQ(slice.size(), 5);
+ EXPECT_EQ(slice.first(), 8);
+ EXPECT_EQ(slice.last(), 12);
+}
+
+TEST(index_range, AsSpan)
+{
+ IndexRange range = IndexRange(4, 6);
+ Span<int64_t> span = range.as_span();
+ EXPECT_EQ(span.size(), 6);
+ EXPECT_EQ(span[0], 4);
+ EXPECT_EQ(span[1], 5);
+ EXPECT_EQ(span[2], 6);
+ EXPECT_EQ(span[3], 7);
+}
+
+} // namespace blender::tests
diff --git a/source/blender/blenlib/tests/BLI_linear_allocator_test.cc b/source/blender/blenlib/tests/BLI_linear_allocator_test.cc
new file mode 100644
index 00000000000..44b70d1f55d
--- /dev/null
+++ b/source/blender/blenlib/tests/BLI_linear_allocator_test.cc
@@ -0,0 +1,118 @@
+/* Apache License, Version 2.0 */
+
+#include "BLI_linear_allocator.hh"
+#include "BLI_strict_flags.h"
+#include "testing/testing.h"
+
+namespace blender::tests {
+
+static bool is_aligned(void *ptr, uint alignment)
+{
+ BLI_assert(is_power_of_2_i((int)alignment));
+ return (POINTER_AS_UINT(ptr) & (alignment - 1)) == 0;
+}
+
+TEST(linear_allocator, AllocationAlignment)
+{
+ LinearAllocator<> allocator;
+
+ EXPECT_TRUE(is_aligned(allocator.allocate(10, 4), 4));
+ EXPECT_TRUE(is_aligned(allocator.allocate(10, 4), 4));
+ EXPECT_TRUE(is_aligned(allocator.allocate(10, 4), 4));
+ EXPECT_TRUE(is_aligned(allocator.allocate(10, 8), 8));
+ EXPECT_TRUE(is_aligned(allocator.allocate(10, 4), 4));
+ EXPECT_TRUE(is_aligned(allocator.allocate(10, 16), 16));
+ EXPECT_TRUE(is_aligned(allocator.allocate(10, 4), 4));
+ EXPECT_TRUE(is_aligned(allocator.allocate(10, 64), 64));
+ EXPECT_TRUE(is_aligned(allocator.allocate(10, 64), 64));
+ EXPECT_TRUE(is_aligned(allocator.allocate(10, 8), 8));
+ EXPECT_TRUE(is_aligned(allocator.allocate(10, 128), 128));
+}
+
+TEST(linear_allocator, PackedAllocation)
+{
+ LinearAllocator<> allocator;
+ blender::AlignedBuffer<256, 32> buffer;
+ allocator.provide_buffer(buffer);
+
+ uintptr_t ptr1 = (uintptr_t)allocator.allocate(10, 4); /* 0 - 10 */
+ uintptr_t ptr2 = (uintptr_t)allocator.allocate(10, 4); /* 12 - 22 */
+ uintptr_t ptr3 = (uintptr_t)allocator.allocate(8, 32); /* 32 - 40 */
+ uintptr_t ptr4 = (uintptr_t)allocator.allocate(16, 8); /* 40 - 56 */
+ uintptr_t ptr5 = (uintptr_t)allocator.allocate(1, 8); /* 56 - 57 */
+ uintptr_t ptr6 = (uintptr_t)allocator.allocate(1, 4); /* 60 - 61 */
+ uintptr_t ptr7 = (uintptr_t)allocator.allocate(1, 1); /* 61 - 62 */
+
+ EXPECT_EQ(ptr2 - ptr1, 12); /* 12 - 0 = 12 */
+ EXPECT_EQ(ptr3 - ptr2, 20); /* 32 - 12 = 20 */
+ EXPECT_EQ(ptr4 - ptr3, 8); /* 40 - 32 = 8 */
+ EXPECT_EQ(ptr5 - ptr4, 16); /* 56 - 40 = 16 */
+ EXPECT_EQ(ptr6 - ptr5, 4); /* 60 - 56 = 4 */
+ EXPECT_EQ(ptr7 - ptr6, 1); /* 61 - 60 = 1 */
+}
+
+TEST(linear_allocator, CopyString)
+{
+ LinearAllocator<> allocator;
+ blender::AlignedBuffer<256, 1> buffer;
+ allocator.provide_buffer(buffer);
+
+ StringRefNull ref1 = allocator.copy_string("Hello");
+ StringRefNull ref2 = allocator.copy_string("World");
+
+ EXPECT_EQ(ref1, "Hello");
+ EXPECT_EQ(ref2, "World");
+ EXPECT_EQ(ref2.data() - ref1.data(), 6);
+}
+
+TEST(linear_allocator, AllocateArray)
+{
+ LinearAllocator<> allocator;
+
+ MutableSpan<int> span = allocator.allocate_array<int>(5);
+ EXPECT_EQ(span.size(), 5);
+}
+
+TEST(linear_allocator, Construct)
+{
+ LinearAllocator<> allocator;
+
+ std::array<int, 5> values = {1, 2, 3, 4, 5};
+ Vector<int> *vector = allocator.construct<Vector<int>>(values);
+ EXPECT_EQ(vector->size(), 5);
+ EXPECT_EQ((*vector)[3], 4);
+ vector->~Vector();
+}
+
+TEST(linear_allocator, ConstructElementsAndPointerArray)
+{
+ LinearAllocator<> allocator;
+
+ std::array<int, 7> values = {1, 2, 3, 4, 5, 6, 7};
+ Span<Vector<int> *> vectors = allocator.construct_elements_and_pointer_array<Vector<int>>(
+ 5, values);
+
+ EXPECT_EQ(vectors.size(), 5);
+ EXPECT_EQ(vectors[3]->size(), 7);
+ EXPECT_EQ((*vectors[2])[5], 6);
+
+ for (Vector<int> *vector : vectors) {
+ vector->~Vector();
+ }
+}
+
+TEST(linear_allocator, ConstructArrayCopy)
+{
+ LinearAllocator<> allocator;
+
+ Vector<int> values = {1, 2, 3};
+ MutableSpan<int> span1 = allocator.construct_array_copy(values.as_span());
+ MutableSpan<int> span2 = allocator.construct_array_copy(values.as_span());
+ EXPECT_NE(span1.data(), span2.data());
+ EXPECT_EQ(span1.size(), 3);
+ EXPECT_EQ(span2.size(), 3);
+ EXPECT_EQ(span1[1], 2);
+ EXPECT_EQ(span2[2], 3);
+}
+
+} // namespace blender::tests
diff --git a/source/blender/blenlib/tests/BLI_map_test.cc b/source/blender/blenlib/tests/BLI_map_test.cc
new file mode 100644
index 00000000000..fe7b0f01279
--- /dev/null
+++ b/source/blender/blenlib/tests/BLI_map_test.cc
@@ -0,0 +1,590 @@
+/* Apache License, Version 2.0 */
+
+#include "BLI_map.hh"
+#include "BLI_rand.h"
+#include "BLI_set.hh"
+#include "BLI_strict_flags.h"
+#include "BLI_timeit.hh"
+#include "BLI_vector.hh"
+#include "testing/testing.h"
+
+namespace blender::tests {
+
+TEST(map, DefaultConstructor)
+{
+ Map<int, float> map;
+ EXPECT_EQ(map.size(), 0);
+ EXPECT_TRUE(map.is_empty());
+}
+
+TEST(map, AddIncreasesSize)
+{
+ Map<int, float> map;
+ EXPECT_EQ(map.size(), 0);
+ EXPECT_TRUE(map.is_empty());
+ map.add(2, 5.0f);
+ EXPECT_EQ(map.size(), 1);
+ EXPECT_FALSE(map.is_empty());
+ map.add(6, 2.0f);
+ EXPECT_EQ(map.size(), 2);
+ EXPECT_FALSE(map.is_empty());
+}
+
+TEST(map, Contains)
+{
+ Map<int, float> map;
+ EXPECT_FALSE(map.contains(4));
+ map.add(5, 6.0f);
+ EXPECT_FALSE(map.contains(4));
+ map.add(4, 2.0f);
+ EXPECT_TRUE(map.contains(4));
+}
+
+TEST(map, LookupExisting)
+{
+ Map<int, float> map;
+ map.add(2, 6.0f);
+ map.add(4, 1.0f);
+ EXPECT_EQ(map.lookup(2), 6.0f);
+ EXPECT_EQ(map.lookup(4), 1.0f);
+}
+
+TEST(map, LookupNotExisting)
+{
+ Map<int, float> map;
+ map.add(2, 4.0f);
+ map.add(1, 1.0f);
+ EXPECT_EQ(map.lookup_ptr(0), nullptr);
+ EXPECT_EQ(map.lookup_ptr(5), nullptr);
+}
+
+TEST(map, AddMany)
+{
+ Map<int, int> map;
+ for (int i = 0; i < 100; i++) {
+ map.add(i * 30, i);
+ map.add(i * 31, i);
+ }
+}
+
+TEST(map, PopItem)
+{
+ Map<int, float> map;
+ map.add(2, 3.0f);
+ map.add(1, 9.0f);
+ EXPECT_TRUE(map.contains(2));
+ EXPECT_TRUE(map.contains(1));
+
+ EXPECT_EQ(map.pop(1), 9.0f);
+ EXPECT_TRUE(map.contains(2));
+ EXPECT_FALSE(map.contains(1));
+
+ EXPECT_EQ(map.pop(2), 3.0f);
+ EXPECT_FALSE(map.contains(2));
+ EXPECT_FALSE(map.contains(1));
+}
+
+TEST(map, PopTry)
+{
+ Map<int, int> map;
+ map.add(1, 5);
+ map.add(2, 7);
+ EXPECT_EQ(map.size(), 2);
+ std::optional<int> value = map.pop_try(4);
+ EXPECT_EQ(map.size(), 2);
+ EXPECT_FALSE(value.has_value());
+ value = map.pop_try(2);
+ EXPECT_EQ(map.size(), 1);
+ EXPECT_TRUE(value.has_value());
+ EXPECT_EQ(*value, 7);
+ EXPECT_EQ(*map.pop_try(1), 5);
+ EXPECT_EQ(map.size(), 0);
+}
+
+TEST(map, PopDefault)
+{
+ Map<int, int> map;
+ map.add(1, 4);
+ map.add(2, 7);
+ map.add(3, 8);
+ EXPECT_EQ(map.size(), 3);
+ EXPECT_EQ(map.pop_default(4, 10), 10);
+ EXPECT_EQ(map.size(), 3);
+ EXPECT_EQ(map.pop_default(1, 10), 4);
+ EXPECT_EQ(map.size(), 2);
+ EXPECT_EQ(map.pop_default(2, 20), 7);
+ EXPECT_EQ(map.size(), 1);
+ EXPECT_EQ(map.pop_default(2, 20), 20);
+ EXPECT_EQ(map.size(), 1);
+ EXPECT_EQ(map.pop_default(3, 0), 8);
+ EXPECT_EQ(map.size(), 0);
+}
+
+TEST(map, PopItemMany)
+{
+ Map<int, int> map;
+ for (int i = 0; i < 100; i++) {
+ map.add_new(i, i);
+ }
+ for (int i = 25; i < 80; i++) {
+ EXPECT_EQ(map.pop(i), i);
+ }
+ for (int i = 0; i < 100; i++) {
+ EXPECT_EQ(map.contains(i), i < 25 || i >= 80);
+ }
+}
+
+TEST(map, ValueIterator)
+{
+ Map<int, float> map;
+ map.add(3, 5.0f);
+ map.add(1, 2.0f);
+ map.add(7, -2.0f);
+
+ blender::Set<float> values;
+
+ int iterations = 0;
+ for (float value : map.values()) {
+ values.add(value);
+ iterations++;
+ }
+
+ EXPECT_EQ(iterations, 3);
+ EXPECT_TRUE(values.contains(5.0f));
+ EXPECT_TRUE(values.contains(-2.0f));
+ EXPECT_TRUE(values.contains(2.0f));
+}
+
+TEST(map, KeyIterator)
+{
+ Map<int, float> map;
+ map.add(6, 3.0f);
+ map.add(2, 4.0f);
+ map.add(1, 3.0f);
+
+ blender::Set<int> keys;
+
+ int iterations = 0;
+ for (int key : map.keys()) {
+ keys.add(key);
+ iterations++;
+ }
+
+ EXPECT_EQ(iterations, 3);
+ EXPECT_TRUE(keys.contains(1));
+ EXPECT_TRUE(keys.contains(2));
+ EXPECT_TRUE(keys.contains(6));
+}
+
+TEST(map, ItemIterator)
+{
+ Map<int, float> map;
+ map.add(5, 3.0f);
+ map.add(2, 9.0f);
+ map.add(1, 0.0f);
+
+ blender::Set<int> keys;
+ blender::Set<float> values;
+
+ int iterations = 0;
+ const Map<int, float> &const_map = map;
+ for (auto item : const_map.items()) {
+ keys.add(item.key);
+ values.add(item.value);
+ iterations++;
+ }
+
+ EXPECT_EQ(iterations, 3);
+ EXPECT_TRUE(keys.contains(5));
+ EXPECT_TRUE(keys.contains(2));
+ EXPECT_TRUE(keys.contains(1));
+ EXPECT_TRUE(values.contains(3.0f));
+ EXPECT_TRUE(values.contains(9.0f));
+ EXPECT_TRUE(values.contains(0.0f));
+}
+
+TEST(map, MutableValueIterator)
+{
+ Map<int, int> map;
+ map.add(3, 6);
+ map.add(2, 1);
+
+ for (int &value : map.values()) {
+ value += 10;
+ }
+
+ EXPECT_EQ(map.lookup(3), 16);
+ EXPECT_EQ(map.lookup(2), 11);
+}
+
+TEST(map, MutableItemIterator)
+{
+ Map<int, int> map;
+ map.add(3, 6);
+ map.add(2, 1);
+
+ for (auto item : map.items()) {
+ item.value += item.key;
+ }
+
+ EXPECT_EQ(map.lookup(3), 9.0f);
+ EXPECT_EQ(map.lookup(2), 3.0f);
+}
+
+TEST(map, MutableItemToItemConversion)
+{
+ Map<int, int> map;
+ map.add(3, 6);
+ map.add(2, 1);
+
+ Vector<int> keys, values;
+ for (Map<int, int>::Item item : map.items()) {
+ keys.append(item.key);
+ values.append(item.value);
+ }
+
+ EXPECT_EQ(keys.size(), 2);
+ EXPECT_EQ(values.size(), 2);
+ EXPECT_TRUE(keys.contains(3));
+ EXPECT_TRUE(keys.contains(2));
+ EXPECT_TRUE(values.contains(6));
+ EXPECT_TRUE(values.contains(1));
+}
+
+static float return_42()
+{
+ return 42.0f;
+}
+
+TEST(map, LookupOrAddCB_SeparateFunction)
+{
+ Map<int, float> map;
+ EXPECT_EQ(map.lookup_or_add_cb(0, return_42), 42.0f);
+ EXPECT_EQ(map.lookup(0), 42);
+
+ map.keys();
+}
+
+TEST(map, LookupOrAddCB_Lambdas)
+{
+ Map<int, float> map;
+ auto lambda1 = []() { return 11.0f; };
+ EXPECT_EQ(map.lookup_or_add_cb(0, lambda1), 11.0f);
+ auto lambda2 = []() { return 20.0f; };
+ EXPECT_EQ(map.lookup_or_add_cb(1, lambda2), 20.0f);
+
+ EXPECT_EQ(map.lookup_or_add_cb(0, lambda2), 11.0f);
+ EXPECT_EQ(map.lookup_or_add_cb(1, lambda1), 20.0f);
+}
+
+TEST(map, AddOrModify)
+{
+ Map<int, float> map;
+ auto create_func = [](float *value) {
+ *value = 10.0f;
+ return true;
+ };
+ auto modify_func = [](float *value) {
+ *value += 5;
+ return false;
+ };
+ EXPECT_TRUE(map.add_or_modify(1, create_func, modify_func));
+ EXPECT_EQ(map.lookup(1), 10.0f);
+ EXPECT_FALSE(map.add_or_modify(1, create_func, modify_func));
+ EXPECT_EQ(map.lookup(1), 15.0f);
+}
+
+TEST(map, AddOverwrite)
+{
+ Map<int, float> map;
+ EXPECT_FALSE(map.contains(3));
+ EXPECT_TRUE(map.add_overwrite(3, 6.0f));
+ EXPECT_EQ(map.lookup(3), 6.0f);
+ EXPECT_FALSE(map.add_overwrite(3, 7.0f));
+ EXPECT_EQ(map.lookup(3), 7.0f);
+ EXPECT_FALSE(map.add(3, 8.0f));
+ EXPECT_EQ(map.lookup(3), 7.0f);
+}
+
+TEST(map, LookupOrAddDefault)
+{
+ Map<int, float> map;
+ map.lookup_or_add_default(3) = 6;
+ EXPECT_EQ(map.lookup(3), 6);
+ map.lookup_or_add_default(5) = 2;
+ EXPECT_EQ(map.lookup(5), 2);
+ map.lookup_or_add_default(3) += 4;
+ EXPECT_EQ(map.lookup(3), 10);
+}
+
+TEST(map, LookupOrAdd)
+{
+ Map<int, int> map;
+ EXPECT_EQ(map.lookup_or_add(6, 4), 4);
+ EXPECT_EQ(map.lookup_or_add(6, 5), 4);
+ map.lookup_or_add(6, 4) += 10;
+ EXPECT_EQ(map.lookup(6), 14);
+}
+
+TEST(map, MoveConstructorSmall)
+{
+ Map<int, float> map1;
+ map1.add(1, 2.0f);
+ map1.add(4, 1.0f);
+ Map<int, float> map2(std::move(map1));
+ EXPECT_EQ(map2.size(), 2);
+ EXPECT_EQ(map2.lookup(1), 2.0f);
+ EXPECT_EQ(map2.lookup(4), 1.0f);
+ EXPECT_EQ(map1.size(), 0); /* NOLINT: bugprone-use-after-move */
+ EXPECT_EQ(map1.lookup_ptr(4), nullptr);
+}
+
+TEST(map, MoveConstructorLarge)
+{
+ Map<int, int> map1;
+ for (int i = 0; i < 100; i++) {
+ map1.add_new(i, i);
+ }
+ Map<int, int> map2(std::move(map1));
+ EXPECT_EQ(map2.size(), 100);
+ EXPECT_EQ(map2.lookup(1), 1);
+ EXPECT_EQ(map2.lookup(4), 4);
+ EXPECT_EQ(map1.size(), 0); /* NOLINT: bugprone-use-after-move */
+ EXPECT_EQ(map1.lookup_ptr(4), nullptr);
+}
+
+TEST(map, MoveAssignment)
+{
+ Map<int, float> map1;
+ map1.add(1, 2.0f);
+ map1.add(4, 1.0f);
+ Map<int, float> map2;
+ map2 = std::move(map1);
+ EXPECT_EQ(map2.size(), 2);
+ EXPECT_EQ(map2.lookup(1), 2.0f);
+ EXPECT_EQ(map2.lookup(4), 1.0f);
+ EXPECT_EQ(map1.size(), 0); /* NOLINT: bugprone-use-after-move */
+ EXPECT_EQ(map1.lookup_ptr(4), nullptr);
+}
+
+TEST(map, CopyAssignment)
+{
+ Map<int, float> map1;
+ map1.add(1, 2.0f);
+ map1.add(4, 1.0f);
+ Map<int, float> map2;
+ map2 = map1;
+ EXPECT_EQ(map2.size(), 2);
+ EXPECT_EQ(map2.lookup(1), 2.0f);
+ EXPECT_EQ(map2.lookup(4), 1.0f);
+ EXPECT_EQ(map1.size(), 2);
+ EXPECT_EQ(*map1.lookup_ptr(4), 1.0f);
+}
+
+TEST(map, Clear)
+{
+ Map<int, float> map;
+ map.add(1, 1.0f);
+ map.add(2, 5.0f);
+
+ EXPECT_EQ(map.size(), 2);
+ EXPECT_TRUE(map.contains(1));
+ EXPECT_TRUE(map.contains(2));
+
+ map.clear();
+
+ EXPECT_EQ(map.size(), 0);
+ EXPECT_FALSE(map.contains(1));
+ EXPECT_FALSE(map.contains(2));
+}
+
+TEST(map, UniquePtrValue)
+{
+ auto value1 = std::unique_ptr<int>(new int());
+ auto value2 = std::unique_ptr<int>(new int());
+ auto value3 = std::unique_ptr<int>(new int());
+
+ int *value1_ptr = value1.get();
+
+ Map<int, std::unique_ptr<int>> map;
+ map.add_new(1, std::move(value1));
+ map.add(2, std::move(value2));
+ map.add_overwrite(3, std::move(value3));
+ map.lookup_or_add_cb(4, []() { return std::unique_ptr<int>(new int()); });
+ map.add_new(5, std::unique_ptr<int>(new int()));
+ map.add(6, std::unique_ptr<int>(new int()));
+ map.add_overwrite(7, std::unique_ptr<int>(new int()));
+ map.lookup_or_add(8, std::unique_ptr<int>(new int()));
+ map.pop_default(9, std::unique_ptr<int>(new int()));
+
+ EXPECT_EQ(map.lookup(1).get(), value1_ptr);
+ EXPECT_EQ(map.lookup_ptr(100), nullptr);
+}
+
+TEST(map, Remove)
+{
+ Map<int, int> map;
+ map.add(2, 4);
+ EXPECT_EQ(map.size(), 1);
+ EXPECT_FALSE(map.remove(3));
+ EXPECT_EQ(map.size(), 1);
+ EXPECT_TRUE(map.remove(2));
+ EXPECT_EQ(map.size(), 0);
+}
+
+TEST(map, PointerKeys)
+{
+ char a, b, c, d;
+
+ Map<char *, int> map;
+ EXPECT_TRUE(map.add(&a, 5));
+ EXPECT_FALSE(map.add(&a, 4));
+ map.add_new(&b, 1);
+ map.add_new(&c, 1);
+ EXPECT_EQ(map.size(), 3);
+ EXPECT_TRUE(map.remove(&b));
+ EXPECT_TRUE(map.add(&b, 8));
+ EXPECT_FALSE(map.remove(&d));
+ EXPECT_TRUE(map.remove(&a));
+ EXPECT_TRUE(map.remove(&b));
+ EXPECT_TRUE(map.remove(&c));
+ EXPECT_TRUE(map.is_empty());
+}
+
+TEST(map, ConstKeysAndValues)
+{
+ Map<const std::string, const std::string> map;
+ map.reserve(10);
+ map.add("45", "643");
+ EXPECT_TRUE(map.contains("45"));
+ EXPECT_FALSE(map.contains("54"));
+}
+
+TEST(map, ForeachItem)
+{
+ Map<int, int> map;
+ map.add(3, 4);
+ map.add(1, 8);
+
+ Vector<int> keys;
+ Vector<int> values;
+ map.foreach_item([&](int key, int value) {
+ keys.append(key);
+ values.append(value);
+ });
+
+ EXPECT_EQ(keys.size(), 2);
+ EXPECT_EQ(values.size(), 2);
+ EXPECT_EQ(keys.first_index_of(3), values.first_index_of(4));
+ EXPECT_EQ(keys.first_index_of(1), values.first_index_of(8));
+}
+
+/**
+ * Set this to 1 to activate the benchmark. It is disabled by default, because it prints a lot.
+ */
+#if 0
+template<typename MapT>
+BLI_NOINLINE void benchmark_random_ints(StringRef name, int amount, int factor)
+{
+ RNG *rng = BLI_rng_new(0);
+ Vector<int> values;
+ for (int i = 0; i < amount; i++) {
+ values.append(BLI_rng_get_int(rng) * factor);
+ }
+ BLI_rng_free(rng);
+
+ MapT map;
+ {
+ SCOPED_TIMER(name + " Add");
+ for (int value : values) {
+ map.add(value, value);
+ }
+ }
+ int count = 0;
+ {
+ SCOPED_TIMER(name + " Contains");
+ for (int value : values) {
+ count += map.contains(value);
+ }
+ }
+ {
+ SCOPED_TIMER(name + " Remove");
+ for (int value : values) {
+ count += map.remove(value);
+ }
+ }
+
+ /* Print the value for simple error checking and to avoid some compiler optimizations. */
+ std::cout << "Count: " << count << "\n";
+}
+
+TEST(map, Benchmark)
+{
+ for (int i = 0; i < 3; i++) {
+ benchmark_random_ints<blender::Map<int, int>>("blender::Map ", 1000000, 1);
+ benchmark_random_ints<blender::StdUnorderedMapWrapper<int, int>>("std::unordered_map", 1000000, 1);
+ }
+ std::cout << "\n";
+ for (int i = 0; i < 3; i++) {
+ uint32_t factor = (3 << 10);
+ benchmark_random_ints<blender::Map<int, int>>("blender::Map ", 1000000, factor);
+ benchmark_random_ints<blender::StdUnorderedMapWrapper<int, int>>(
+ "std::unordered_map", 1000000, factor);
+ }
+}
+
+/**
+ * Timer 'blender::Map Add' took 61.7616 ms
+ * Timer 'blender::Map Contains' took 18.4989 ms
+ * Timer 'blender::Map Remove' took 20.5864 ms
+ * Count: 1999755
+ * Timer 'std::unordered_map Add' took 188.674 ms
+ * Timer 'std::unordered_map Contains' took 44.3741 ms
+ * Timer 'std::unordered_map Remove' took 169.52 ms
+ * Count: 1999755
+ * Timer 'blender::Map Add' took 37.9196 ms
+ * Timer 'blender::Map Contains' took 16.7361 ms
+ * Timer 'blender::Map Remove' took 20.9568 ms
+ * Count: 1999755
+ * Timer 'std::unordered_map Add' took 166.09 ms
+ * Timer 'std::unordered_map Contains' took 40.6133 ms
+ * Timer 'std::unordered_map Remove' took 142.85 ms
+ * Count: 1999755
+ * Timer 'blender::Map Add' took 37.3053 ms
+ * Timer 'blender::Map Contains' took 16.6731 ms
+ * Timer 'blender::Map Remove' took 18.8304 ms
+ * Count: 1999755
+ * Timer 'std::unordered_map Add' took 170.964 ms
+ * Timer 'std::unordered_map Contains' took 38.1824 ms
+ * Timer 'std::unordered_map Remove' took 140.263 ms
+ * Count: 1999755
+ *
+ * Timer 'blender::Map Add' took 50.1131 ms
+ * Timer 'blender::Map Contains' took 25.0491 ms
+ * Timer 'blender::Map Remove' took 32.4225 ms
+ * Count: 1889920
+ * Timer 'std::unordered_map Add' took 150.129 ms
+ * Timer 'std::unordered_map Contains' took 34.6999 ms
+ * Timer 'std::unordered_map Remove' took 120.907 ms
+ * Count: 1889920
+ * Timer 'blender::Map Add' took 50.4438 ms
+ * Timer 'blender::Map Contains' took 25.2677 ms
+ * Timer 'blender::Map Remove' took 32.3047 ms
+ * Count: 1889920
+ * Timer 'std::unordered_map Add' took 144.015 ms
+ * Timer 'std::unordered_map Contains' took 36.3387 ms
+ * Timer 'std::unordered_map Remove' took 119.109 ms
+ * Count: 1889920
+ * Timer 'blender::Map Add' took 48.6995 ms
+ * Timer 'blender::Map Contains' took 25.1846 ms
+ * Timer 'blender::Map Remove' took 33.0283 ms
+ * Count: 1889920
+ * Timer 'std::unordered_map Add' took 143.494 ms
+ * Timer 'std::unordered_map Contains' took 34.8905 ms
+ * Timer 'std::unordered_map Remove' took 122.739 ms
+ * Count: 1889920
+ */
+
+#endif /* Benchmark */
+
+} // namespace blender::tests
diff --git a/source/blender/blenlib/tests/BLI_math_base_safe_test.cc b/source/blender/blenlib/tests/BLI_math_base_safe_test.cc
new file mode 100644
index 00000000000..2e3e083cf92
--- /dev/null
+++ b/source/blender/blenlib/tests/BLI_math_base_safe_test.cc
@@ -0,0 +1,37 @@
+/* Apache License, Version 2.0 */
+
+#include "testing/testing.h"
+
+#include "BLI_math_base_safe.h"
+
+TEST(math_base, SafePowf)
+{
+ EXPECT_FLOAT_EQ(safe_powf(4.0f, 3.0f), 64.0f);
+ EXPECT_FLOAT_EQ(safe_powf(3.2f, 5.6f), 674.2793796f);
+ EXPECT_FLOAT_EQ(safe_powf(4.0f, -2.0f), 0.0625f);
+ EXPECT_FLOAT_EQ(safe_powf(6.0f, -3.2f), 0.003235311f);
+ EXPECT_FLOAT_EQ(safe_powf(-4.0f, 6), 4096.0f);
+ EXPECT_FLOAT_EQ(safe_powf(-3.0f, 5.5), 0.0f);
+ EXPECT_FLOAT_EQ(safe_powf(-2.5f, -4.0f), 0.0256f);
+ EXPECT_FLOAT_EQ(safe_powf(-3.7f, -4.5f), 0.0f);
+}
+
+TEST(math_base, SafeModf)
+{
+ EXPECT_FLOAT_EQ(safe_modf(3.4, 2.2f), 1.2f);
+ EXPECT_FLOAT_EQ(safe_modf(3.4, -2.2f), 1.2f);
+ EXPECT_FLOAT_EQ(safe_modf(-3.4, -2.2f), -1.2f);
+ EXPECT_FLOAT_EQ(safe_modf(-3.4, 0.0f), 0.0f);
+ EXPECT_FLOAT_EQ(safe_modf(0.0f, 3.0f), 0.0f);
+ EXPECT_FLOAT_EQ(safe_modf(55.0f, 10.0f), 5.0f);
+}
+
+TEST(math_base, SafeLogf)
+{
+ EXPECT_FLOAT_EQ(safe_logf(3.3f, 2.5f), 1.302995247f);
+ EXPECT_FLOAT_EQ(safe_logf(0.0f, 3.0f), 0.0f);
+ EXPECT_FLOAT_EQ(safe_logf(3.0f, 0.0f), 0.0f);
+ EXPECT_FLOAT_EQ(safe_logf(-2.0f, 4.3f), 0.0f);
+ EXPECT_FLOAT_EQ(safe_logf(2.0f, -4.3f), 0.0f);
+ EXPECT_FLOAT_EQ(safe_logf(-2.0f, -4.3f), 0.0f);
+}
diff --git a/source/blender/blenlib/tests/BLI_memory_utils_test.cc b/source/blender/blenlib/tests/BLI_memory_utils_test.cc
new file mode 100644
index 00000000000..f3cb02b63d7
--- /dev/null
+++ b/source/blender/blenlib/tests/BLI_memory_utils_test.cc
@@ -0,0 +1,159 @@
+/* Apache License, Version 2.0 */
+
+#include "BLI_float3.hh"
+#include "BLI_memory_utils.hh"
+#include "BLI_strict_flags.h"
+#include "testing/testing.h"
+
+namespace blender::tests {
+
+struct MyValue {
+ static inline int alive = 0;
+
+ MyValue()
+ {
+ if (alive == 15) {
+ throw std::exception();
+ }
+
+ alive++;
+ }
+
+ MyValue(const MyValue &UNUSED(other))
+ {
+ if (alive == 15) {
+ throw std::exception();
+ }
+
+ alive++;
+ }
+
+ ~MyValue()
+ {
+ alive--;
+ }
+};
+
+TEST(memory_utils, DefaultConstructN_ActuallyCallsConstructor)
+{
+ constexpr int amount = 10;
+ TypedBuffer<MyValue, amount> buffer;
+
+ EXPECT_EQ(MyValue::alive, 0);
+ default_construct_n(buffer.ptr(), amount);
+ EXPECT_EQ(MyValue::alive, amount);
+ destruct_n(buffer.ptr(), amount);
+ EXPECT_EQ(MyValue::alive, 0);
+}
+
+TEST(memory_utils, DefaultConstructN_StrongExceptionSafety)
+{
+ constexpr int amount = 20;
+ TypedBuffer<MyValue, amount> buffer;
+
+ EXPECT_EQ(MyValue::alive, 0);
+ EXPECT_THROW(default_construct_n(buffer.ptr(), amount), std::exception);
+ EXPECT_EQ(MyValue::alive, 0);
+}
+
+TEST(memory_utils, UninitializedCopyN_ActuallyCopies)
+{
+ constexpr int amount = 5;
+ TypedBuffer<MyValue, amount> buffer1;
+ TypedBuffer<MyValue, amount> buffer2;
+
+ EXPECT_EQ(MyValue::alive, 0);
+ default_construct_n(buffer1.ptr(), amount);
+ EXPECT_EQ(MyValue::alive, amount);
+ uninitialized_copy_n(buffer1.ptr(), amount, buffer2.ptr());
+ EXPECT_EQ(MyValue::alive, 2 * amount);
+ destruct_n(buffer1.ptr(), amount);
+ EXPECT_EQ(MyValue::alive, amount);
+ destruct_n(buffer2.ptr(), amount);
+ EXPECT_EQ(MyValue::alive, 0);
+}
+
+TEST(memory_utils, UninitializedCopyN_StrongExceptionSafety)
+{
+ constexpr int amount = 10;
+ TypedBuffer<MyValue, amount> buffer1;
+ TypedBuffer<MyValue, amount> buffer2;
+
+ EXPECT_EQ(MyValue::alive, 0);
+ default_construct_n(buffer1.ptr(), amount);
+ EXPECT_EQ(MyValue::alive, amount);
+ EXPECT_THROW(uninitialized_copy_n(buffer1.ptr(), amount, buffer2.ptr()), std::exception);
+ EXPECT_EQ(MyValue::alive, amount);
+ destruct_n(buffer1.ptr(), amount);
+ EXPECT_EQ(MyValue::alive, 0);
+}
+
+TEST(memory_utils, UninitializedFillN_ActuallyCopies)
+{
+ constexpr int amount = 10;
+ TypedBuffer<MyValue, amount> buffer;
+
+ EXPECT_EQ(MyValue::alive, 0);
+ {
+ MyValue value;
+ EXPECT_EQ(MyValue::alive, 1);
+ uninitialized_fill_n(buffer.ptr(), amount, value);
+ EXPECT_EQ(MyValue::alive, 1 + amount);
+ destruct_n(buffer.ptr(), amount);
+ EXPECT_EQ(MyValue::alive, 1);
+ }
+ EXPECT_EQ(MyValue::alive, 0);
+}
+
+TEST(memory_utils, UninitializedFillN_StrongExceptionSafety)
+{
+ constexpr int amount = 20;
+ TypedBuffer<MyValue, amount> buffer;
+
+ EXPECT_EQ(MyValue::alive, 0);
+ {
+ MyValue value;
+ EXPECT_EQ(MyValue::alive, 1);
+ EXPECT_THROW(uninitialized_fill_n(buffer.ptr(), amount, value), std::exception);
+ EXPECT_EQ(MyValue::alive, 1);
+ }
+ EXPECT_EQ(MyValue::alive, 0);
+}
+
+class TestBaseClass {
+ virtual void mymethod(){};
+};
+
+class TestChildClass : public TestBaseClass {
+ void mymethod() override
+ {
+ }
+};
+
+static_assert(is_convertible_pointer_v<int *, int *>);
+static_assert(is_convertible_pointer_v<int *, const int *>);
+static_assert(is_convertible_pointer_v<int *, int *const>);
+static_assert(is_convertible_pointer_v<int *, const int *const>);
+static_assert(!is_convertible_pointer_v<const int *, int *>);
+static_assert(!is_convertible_pointer_v<int, int *>);
+static_assert(!is_convertible_pointer_v<int *, int>);
+static_assert(is_convertible_pointer_v<TestBaseClass *, const TestBaseClass *>);
+static_assert(!is_convertible_pointer_v<const TestBaseClass *, TestBaseClass *>);
+static_assert(is_convertible_pointer_v<TestChildClass *, TestBaseClass *>);
+static_assert(!is_convertible_pointer_v<TestBaseClass *, TestChildClass *>);
+static_assert(is_convertible_pointer_v<const TestChildClass *, const TestBaseClass *>);
+static_assert(!is_convertible_pointer_v<TestBaseClass, const TestChildClass *>);
+static_assert(!is_convertible_pointer_v<float3, float *>);
+static_assert(!is_convertible_pointer_v<float *, float3>);
+static_assert(!is_convertible_pointer_v<int **, int *>);
+static_assert(!is_convertible_pointer_v<int *, int **>);
+static_assert(is_convertible_pointer_v<int **, int **>);
+static_assert(is_convertible_pointer_v<const int **, const int **>);
+static_assert(!is_convertible_pointer_v<const int **, int **>);
+static_assert(!is_convertible_pointer_v<int *const *, int **>);
+static_assert(!is_convertible_pointer_v<int *const *const, int **>);
+static_assert(is_convertible_pointer_v<int **, int **const>);
+static_assert(is_convertible_pointer_v<int **, int *const *>);
+static_assert(is_convertible_pointer_v<int **, int const *const *>);
+
+} // namespace blender::tests
diff --git a/source/blender/blenlib/tests/BLI_set_test.cc b/source/blender/blenlib/tests/BLI_set_test.cc
new file mode 100644
index 00000000000..7bd0b258df8
--- /dev/null
+++ b/source/blender/blenlib/tests/BLI_set_test.cc
@@ -0,0 +1,565 @@
+/* Apache License, Version 2.0 */
+
+#include <set>
+#include <unordered_set>
+
+#include "BLI_ghash.h"
+#include "BLI_rand.h"
+#include "BLI_set.hh"
+#include "BLI_strict_flags.h"
+#include "BLI_timeit.hh"
+#include "BLI_vector.hh"
+#include "testing/testing.h"
+
+namespace blender {
+namespace tests {
+
+TEST(set, DefaultConstructor)
+{
+ Set<int> set;
+ EXPECT_EQ(set.size(), 0);
+ EXPECT_TRUE(set.is_empty());
+}
+
+TEST(set, ContainsNotExistant)
+{
+ Set<int> set;
+ EXPECT_FALSE(set.contains(3));
+}
+
+TEST(set, ContainsExistant)
+{
+ Set<int> set;
+ EXPECT_FALSE(set.contains(5));
+ EXPECT_TRUE(set.is_empty());
+ set.add(5);
+ EXPECT_TRUE(set.contains(5));
+ EXPECT_FALSE(set.is_empty());
+}
+
+TEST(set, AddMany)
+{
+ Set<int> set;
+ for (int i = 0; i < 100; i++) {
+ set.add(i);
+ }
+
+ for (int i = 50; i < 100; i++) {
+ EXPECT_TRUE(set.contains(i));
+ }
+ for (int i = 100; i < 150; i++) {
+ EXPECT_FALSE(set.contains(i));
+ }
+}
+
+TEST(set, InitializerListConstructor)
+{
+ Set<int> set = {4, 5, 6};
+ EXPECT_EQ(set.size(), 3);
+ EXPECT_TRUE(set.contains(4));
+ EXPECT_TRUE(set.contains(5));
+ EXPECT_TRUE(set.contains(6));
+ EXPECT_FALSE(set.contains(2));
+ EXPECT_FALSE(set.contains(3));
+}
+
+TEST(set, CopyConstructor)
+{
+ Set<int> set = {3};
+ EXPECT_TRUE(set.contains(3));
+ EXPECT_FALSE(set.contains(4));
+
+ Set<int> set2(set);
+ set2.add(4);
+ EXPECT_TRUE(set2.contains(3));
+ EXPECT_TRUE(set2.contains(4));
+
+ EXPECT_FALSE(set.contains(4));
+}
+
+TEST(set, MoveConstructor)
+{
+ Set<int> set = {1, 2, 3};
+ EXPECT_EQ(set.size(), 3);
+ Set<int> set2(std::move(set));
+ EXPECT_EQ(set.size(), 0); /* NOLINT: bugprone-use-after-move */
+ EXPECT_EQ(set2.size(), 3);
+}
+
+TEST(set, CopyAssignment)
+{
+ Set<int> set = {3};
+ EXPECT_TRUE(set.contains(3));
+ EXPECT_FALSE(set.contains(4));
+
+ Set<int> set2;
+ set2 = set;
+ set2.add(4);
+ EXPECT_TRUE(set2.contains(3));
+ EXPECT_TRUE(set2.contains(4));
+
+ EXPECT_FALSE(set.contains(4));
+}
+
+TEST(set, MoveAssignment)
+{
+ Set<int> set = {1, 2, 3};
+ EXPECT_EQ(set.size(), 3);
+ Set<int> set2;
+ set2 = std::move(set);
+ EXPECT_EQ(set.size(), 0); /* NOLINT: bugprone-use-after-move */
+ EXPECT_EQ(set2.size(), 3);
+}
+
+TEST(set, RemoveContained)
+{
+ Set<int> set = {3, 4, 5};
+ EXPECT_TRUE(set.contains(3));
+ EXPECT_TRUE(set.contains(4));
+ EXPECT_TRUE(set.contains(5));
+ set.remove_contained(4);
+ EXPECT_TRUE(set.contains(3));
+ EXPECT_FALSE(set.contains(4));
+ EXPECT_TRUE(set.contains(5));
+ set.remove_contained(3);
+ EXPECT_FALSE(set.contains(3));
+ EXPECT_FALSE(set.contains(4));
+ EXPECT_TRUE(set.contains(5));
+ set.remove_contained(5);
+ EXPECT_FALSE(set.contains(3));
+ EXPECT_FALSE(set.contains(4));
+ EXPECT_FALSE(set.contains(5));
+}
+
+TEST(set, RemoveContainedMany)
+{
+ Set<int> set;
+ for (int i = 0; i < 1000; i++) {
+ set.add(i);
+ }
+ for (int i = 100; i < 1000; i++) {
+ set.remove_contained(i);
+ }
+ for (int i = 900; i < 1000; i++) {
+ set.add(i);
+ }
+
+ for (int i = 0; i < 1000; i++) {
+ if (i < 100 || i >= 900) {
+ EXPECT_TRUE(set.contains(i));
+ }
+ else {
+ EXPECT_FALSE(set.contains(i));
+ }
+ }
+}
+
+TEST(set, Intersects)
+{
+ Set<int> a = {3, 4, 5, 6};
+ Set<int> b = {1, 2, 5};
+ EXPECT_TRUE(Set<int>::Intersects(a, b));
+ EXPECT_FALSE(Set<int>::Disjoint(a, b));
+}
+
+TEST(set, Disjoint)
+{
+ Set<int> a = {5, 6, 7, 8};
+ Set<int> b = {2, 3, 4, 9};
+ EXPECT_FALSE(Set<int>::Intersects(a, b));
+ EXPECT_TRUE(Set<int>::Disjoint(a, b));
+}
+
+TEST(set, AddMultiple)
+{
+ Set<int> a;
+ a.add_multiple({5, 7});
+ EXPECT_TRUE(a.contains(5));
+ EXPECT_TRUE(a.contains(7));
+ EXPECT_FALSE(a.contains(4));
+ a.add_multiple({2, 4, 7});
+ EXPECT_TRUE(a.contains(4));
+ EXPECT_TRUE(a.contains(2));
+ EXPECT_EQ(a.size(), 4);
+}
+
+TEST(set, AddMultipleNew)
+{
+ Set<int> a;
+ a.add_multiple_new({5, 6});
+ EXPECT_TRUE(a.contains(5));
+ EXPECT_TRUE(a.contains(6));
+}
+
+TEST(set, Iterator)
+{
+ Set<int> set = {1, 3, 2, 5, 4};
+ blender::Vector<int> vec;
+ for (int value : set) {
+ vec.append(value);
+ }
+ EXPECT_EQ(vec.size(), 5);
+ EXPECT_TRUE(vec.contains(1));
+ EXPECT_TRUE(vec.contains(3));
+ EXPECT_TRUE(vec.contains(2));
+ EXPECT_TRUE(vec.contains(5));
+ EXPECT_TRUE(vec.contains(4));
+}
+
+TEST(set, OftenAddRemoveContained)
+{
+ Set<int> set;
+ for (int i = 0; i < 100; i++) {
+ set.add(42);
+ EXPECT_EQ(set.size(), 1);
+ set.remove_contained(42);
+ EXPECT_EQ(set.size(), 0);
+ }
+}
+
+TEST(set, UniquePtrValues)
+{
+ Set<std::unique_ptr<int>> set;
+ set.add_new(std::unique_ptr<int>(new int()));
+ auto value1 = std::unique_ptr<int>(new int());
+ set.add_new(std::move(value1));
+ set.add(std::unique_ptr<int>(new int()));
+
+ EXPECT_EQ(set.size(), 3);
+}
+
+TEST(set, Clear)
+{
+ Set<int> set = {3, 4, 6, 7};
+ EXPECT_EQ(set.size(), 4);
+ set.clear();
+ EXPECT_EQ(set.size(), 0);
+}
+
+TEST(set, StringSet)
+{
+ Set<std::string> set;
+ set.add("hello");
+ set.add("world");
+ EXPECT_EQ(set.size(), 2);
+ EXPECT_TRUE(set.contains("hello"));
+ EXPECT_TRUE(set.contains("world"));
+ EXPECT_FALSE(set.contains("world2"));
+}
+
+TEST(set, PointerSet)
+{
+ int a, b, c;
+ Set<int *> set;
+ set.add(&a);
+ set.add(&b);
+ EXPECT_EQ(set.size(), 2);
+ EXPECT_TRUE(set.contains(&a));
+ EXPECT_TRUE(set.contains(&b));
+ EXPECT_FALSE(set.contains(&c));
+}
+
+TEST(set, Remove)
+{
+ Set<int> set = {1, 2, 3, 4, 5, 6};
+ EXPECT_EQ(set.size(), 6);
+ EXPECT_TRUE(set.remove(2));
+ EXPECT_EQ(set.size(), 5);
+ EXPECT_FALSE(set.contains(2));
+ EXPECT_FALSE(set.remove(2));
+ EXPECT_EQ(set.size(), 5);
+ EXPECT_TRUE(set.remove(5));
+ EXPECT_EQ(set.size(), 4);
+}
+
+struct Type1 {
+ uint32_t value;
+};
+
+struct Type2 {
+ uint32_t value;
+};
+
+static bool operator==(const Type1 &a, const Type1 &b)
+{
+ return a.value == b.value;
+}
+static bool operator==(const Type2 &a, const Type1 &b)
+{
+ return a.value == b.value;
+}
+
+} // namespace tests
+
+/* This has to be defined in ::blender namespace. */
+template<> struct DefaultHash<tests::Type1> {
+ uint32_t operator()(const tests::Type1 &value) const
+ {
+ return value.value;
+ }
+
+ uint32_t operator()(const tests::Type2 &value) const
+ {
+ return value.value;
+ }
+};
+
+namespace tests {
+
+TEST(set, ContainsAs)
+{
+ Set<Type1> set;
+ set.add(Type1{5});
+ EXPECT_TRUE(set.contains_as(Type1{5}));
+ EXPECT_TRUE(set.contains_as(Type2{5}));
+ EXPECT_FALSE(set.contains_as(Type1{6}));
+ EXPECT_FALSE(set.contains_as(Type2{6}));
+}
+
+TEST(set, ContainsAsString)
+{
+ Set<std::string> set;
+ set.add("test");
+ EXPECT_TRUE(set.contains_as("test"));
+ EXPECT_TRUE(set.contains_as(StringRef("test")));
+ EXPECT_FALSE(set.contains_as("string"));
+ EXPECT_FALSE(set.contains_as(StringRef("string")));
+}
+
+TEST(set, RemoveContainedAs)
+{
+ Set<Type1> set;
+ set.add(Type1{5});
+ EXPECT_TRUE(set.contains_as(Type2{5}));
+ set.remove_contained_as(Type2{5});
+ EXPECT_FALSE(set.contains_as(Type2{5}));
+}
+
+TEST(set, RemoveAs)
+{
+ Set<Type1> set;
+ set.add(Type1{5});
+ EXPECT_TRUE(set.contains_as(Type2{5}));
+ set.remove_as(Type2{6});
+ EXPECT_TRUE(set.contains_as(Type2{5}));
+ set.remove_as(Type2{5});
+ EXPECT_FALSE(set.contains_as(Type2{5}));
+ set.remove_as(Type2{5});
+ EXPECT_FALSE(set.contains_as(Type2{5}));
+}
+
+TEST(set, AddAs)
+{
+ Set<std::string> set;
+ EXPECT_TRUE(set.add_as("test"));
+ EXPECT_TRUE(set.add_as(StringRef("qwe")));
+ EXPECT_FALSE(set.add_as(StringRef("test")));
+ EXPECT_FALSE(set.add_as("qwe"));
+}
+
+template<uint N> struct EqualityIntModN {
+ bool operator()(uint a, uint b) const
+ {
+ return (a % N) == (b % N);
+ }
+};
+
+template<uint N> struct HashIntModN {
+ uint64_t operator()(uint value) const
+ {
+ return value % N;
+ }
+};
+
+TEST(set, CustomizeHashAndEquality)
+{
+ Set<uint, 0, DefaultProbingStrategy, HashIntModN<10>, EqualityIntModN<10>> set;
+ set.add(4);
+ EXPECT_TRUE(set.contains(4));
+ EXPECT_TRUE(set.contains(14));
+ EXPECT_TRUE(set.contains(104));
+ EXPECT_FALSE(set.contains(5));
+ set.add(55);
+ EXPECT_TRUE(set.contains(5));
+ EXPECT_TRUE(set.contains(14));
+ set.remove(1004);
+ EXPECT_FALSE(set.contains(14));
+}
+
+TEST(set, IntrusiveIntKey)
+{
+ Set<int,
+ 2,
+ DefaultProbingStrategy,
+ DefaultHash<int>,
+ DefaultEquality,
+ IntegerSetSlot<int, 100, 200>>
+ set;
+ EXPECT_TRUE(set.add(4));
+ EXPECT_TRUE(set.add(3));
+ EXPECT_TRUE(set.add(11));
+ EXPECT_TRUE(set.add(8));
+ EXPECT_FALSE(set.add(3));
+ EXPECT_FALSE(set.add(4));
+ EXPECT_TRUE(set.remove(4));
+ EXPECT_FALSE(set.remove(7));
+ EXPECT_TRUE(set.add(4));
+ EXPECT_TRUE(set.remove(4));
+}
+
+struct MyKeyType {
+ uint32_t key;
+ int32_t attached_data;
+
+ uint64_t hash() const
+ {
+ return key;
+ }
+
+ friend bool operator==(const MyKeyType &a, const MyKeyType &b)
+ {
+ return a.key == b.key;
+ }
+};
+
+TEST(set, LookupKey)
+{
+ Set<MyKeyType> set;
+ set.add({1, 10});
+ set.add({2, 20});
+ EXPECT_EQ(set.lookup_key({1, 30}).attached_data, 10);
+ EXPECT_EQ(set.lookup_key({2, 0}).attached_data, 20);
+}
+
+TEST(set, LookupKeyDefault)
+{
+ Set<MyKeyType> set;
+ set.add({1, 10});
+ set.add({2, 20});
+
+ MyKeyType fallback{5, 50};
+ EXPECT_EQ(set.lookup_key_default({1, 66}, fallback).attached_data, 10);
+ EXPECT_EQ(set.lookup_key_default({4, 40}, fallback).attached_data, 50);
+}
+
+TEST(set, LookupKeyPtr)
+{
+ Set<MyKeyType> set;
+ set.add({1, 10});
+ set.add({2, 20});
+ EXPECT_EQ(set.lookup_key_ptr({1, 50})->attached_data, 10);
+ EXPECT_EQ(set.lookup_key_ptr({2, 50})->attached_data, 20);
+ EXPECT_EQ(set.lookup_key_ptr({3, 50}), nullptr);
+}
+
+/**
+ * Set this to 1 to activate the benchmark. It is disabled by default, because it prints a lot.
+ */
+#if 0
+template<typename SetT>
+BLI_NOINLINE void benchmark_random_ints(StringRef name, int amount, int factor)
+{
+ RNG *rng = BLI_rng_new(0);
+ Vector<int> values;
+ for (int i = 0; i < amount; i++) {
+ values.append(BLI_rng_get_int(rng) * factor);
+ }
+ BLI_rng_free(rng);
+
+ SetT set;
+ {
+ SCOPED_TIMER(name + " Add");
+ for (int value : values) {
+ set.add(value);
+ }
+ }
+ int count = 0;
+ {
+ SCOPED_TIMER(name + " Contains");
+ for (int value : values) {
+ count += set.contains(value);
+ }
+ }
+ {
+ SCOPED_TIMER(name + " Remove");
+ for (int value : values) {
+ count += set.remove(value);
+ }
+ }
+
+ /* Print the value for simple error checking and to avoid some compiler optimizations. */
+ std::cout << "Count: " << count << "\n";
+}
+
+TEST(set, Benchmark)
+{
+ for (int i = 0; i < 3; i++) {
+ benchmark_random_ints<blender::Set<int>>("blender::Set ", 100000, 1);
+ benchmark_random_ints<blender::StdUnorderedSetWrapper<int>>("std::unordered_set", 100000, 1);
+ }
+ std::cout << "\n";
+ for (int i = 0; i < 3; i++) {
+ uint32_t factor = (3 << 10);
+ benchmark_random_ints<blender::Set<int>>("blender::Set ", 100000, factor);
+ benchmark_random_ints<blender::StdUnorderedSetWrapper<int>>("std::unordered_set", 100000, factor);
+ }
+}
+
+/**
+ * Output of the rudimentary benchmark above on my hardware.
+ *
+ * Timer 'blender::Set Add' took 5.5573 ms
+ * Timer 'blender::Set Contains' took 0.807384 ms
+ * Timer 'blender::Set Remove' took 0.953436 ms
+ * Count: 199998
+ * Timer 'std::unordered_set Add' took 12.551 ms
+ * Timer 'std::unordered_set Contains' took 2.3323 ms
+ * Timer 'std::unordered_set Remove' took 5.07082 ms
+ * Count: 199998
+ * Timer 'blender::Set Add' took 2.62526 ms
+ * Timer 'blender::Set Contains' took 0.407499 ms
+ * Timer 'blender::Set Remove' took 0.472981 ms
+ * Count: 199998
+ * Timer 'std::unordered_set Add' took 6.26945 ms
+ * Timer 'std::unordered_set Contains' took 1.17236 ms
+ * Timer 'std::unordered_set Remove' took 3.77402 ms
+ * Count: 199998
+ * Timer 'blender::Set Add' took 2.59152 ms
+ * Timer 'blender::Set Contains' took 0.415254 ms
+ * Timer 'blender::Set Remove' took 0.477559 ms
+ * Count: 199998
+ * Timer 'std::unordered_set Add' took 6.28129 ms
+ * Timer 'std::unordered_set Contains' took 1.17562 ms
+ * Timer 'std::unordered_set Remove' took 3.77811 ms
+ * Count: 199998
+ *
+ * Timer 'blender::Set Add' took 3.16514 ms
+ * Timer 'blender::Set Contains' took 0.732895 ms
+ * Timer 'blender::Set Remove' took 1.08171 ms
+ * Count: 198790
+ * Timer 'std::unordered_set Add' took 6.57377 ms
+ * Timer 'std::unordered_set Contains' took 1.17008 ms
+ * Timer 'std::unordered_set Remove' took 3.7946 ms
+ * Count: 198790
+ * Timer 'blender::Set Add' took 3.11439 ms
+ * Timer 'blender::Set Contains' took 0.740159 ms
+ * Timer 'blender::Set Remove' took 1.06749 ms
+ * Count: 198790
+ * Timer 'std::unordered_set Add' took 6.35597 ms
+ * Timer 'std::unordered_set Contains' took 1.17713 ms
+ * Timer 'std::unordered_set Remove' took 3.77826 ms
+ * Count: 198790
+ * Timer 'blender::Set Add' took 3.09876 ms
+ * Timer 'blender::Set Contains' took 0.742072 ms
+ * Timer 'blender::Set Remove' took 1.06622 ms
+ * Count: 198790
+ * Timer 'std::unordered_set Add' took 6.4469 ms
+ * Timer 'std::unordered_set Contains' took 1.16515 ms
+ * Timer 'std::unordered_set Remove' took 3.80639 ms
+ * Count: 198790
+ */
+
+#endif /* Benchmark */
+
+} // namespace tests
+} // namespace blender
diff --git a/source/blender/blenlib/tests/BLI_span_test.cc b/source/blender/blenlib/tests/BLI_span_test.cc
new file mode 100644
index 00000000000..587497624f4
--- /dev/null
+++ b/source/blender/blenlib/tests/BLI_span_test.cc
@@ -0,0 +1,311 @@
+/* Apache License, Version 2.0 */
+
+#include "BLI_span.hh"
+#include "BLI_strict_flags.h"
+#include "BLI_vector.hh"
+#include "testing/testing.h"
+
+namespace blender::tests {
+
+TEST(span, FromSmallVector)
+{
+ Vector<int> a = {1, 2, 3};
+ Span<int> a_span = a;
+ EXPECT_EQ(a_span.size(), 3);
+ EXPECT_EQ(a_span[0], 1);
+ EXPECT_EQ(a_span[1], 2);
+ EXPECT_EQ(a_span[2], 3);
+}
+
+TEST(span, AddConstToPointer)
+{
+ int a = 0;
+ std::vector<int *> vec = {&a};
+ Span<int *> span = vec;
+ Span<const int *> const_span = span;
+ EXPECT_EQ(const_span.size(), 1);
+}
+
+TEST(span, IsReferencing)
+{
+ int array[] = {3, 5, 8};
+ MutableSpan<int> span(array, ARRAY_SIZE(array));
+ EXPECT_EQ(span.size(), 3);
+ EXPECT_EQ(span[1], 5);
+ array[1] = 10;
+ EXPECT_EQ(span[1], 10);
+}
+
+TEST(span, DropBack)
+{
+ Vector<int> a = {4, 5, 6, 7};
+ auto slice = Span<int>(a).drop_back(2);
+ EXPECT_EQ(slice.size(), 2);
+ EXPECT_EQ(slice[0], 4);
+ EXPECT_EQ(slice[1], 5);
+}
+
+TEST(span, DropBackAll)
+{
+ Vector<int> a = {4, 5, 6, 7};
+ auto slice = Span<int>(a).drop_back(a.size());
+ EXPECT_EQ(slice.size(), 0);
+}
+
+TEST(span, DropFront)
+{
+ Vector<int> a = {4, 5, 6, 7};
+ auto slice = Span<int>(a).drop_front(1);
+ EXPECT_EQ(slice.size(), 3);
+ EXPECT_EQ(slice[0], 5);
+ EXPECT_EQ(slice[1], 6);
+ EXPECT_EQ(slice[2], 7);
+}
+
+TEST(span, DropFrontAll)
+{
+ Vector<int> a = {4, 5, 6, 7};
+ auto slice = Span<int>(a).drop_front(a.size());
+ EXPECT_EQ(slice.size(), 0);
+}
+
+TEST(span, TakeFront)
+{
+ Vector<int> a = {4, 5, 6, 7};
+ auto slice = Span<int>(a).take_front(2);
+ EXPECT_EQ(slice.size(), 2);
+ EXPECT_EQ(slice[0], 4);
+ EXPECT_EQ(slice[1], 5);
+}
+
+TEST(span, TakeBack)
+{
+ Vector<int> a = {5, 6, 7, 8};
+ auto slice = Span<int>(a).take_back(2);
+ EXPECT_EQ(slice.size(), 2);
+ EXPECT_EQ(slice[0], 7);
+ EXPECT_EQ(slice[1], 8);
+}
+
+TEST(span, Slice)
+{
+ Vector<int> a = {4, 5, 6, 7};
+ auto slice = Span<int>(a).slice(1, 2);
+ EXPECT_EQ(slice.size(), 2);
+ EXPECT_EQ(slice[0], 5);
+ EXPECT_EQ(slice[1], 6);
+}
+
+TEST(span, SliceEmpty)
+{
+ Vector<int> a = {4, 5, 6, 7};
+ auto slice = Span<int>(a).slice(2, 0);
+ EXPECT_EQ(slice.size(), 0);
+}
+
+TEST(span, SliceRange)
+{
+ Vector<int> a = {1, 2, 3, 4, 5};
+ auto slice = Span<int>(a).slice(IndexRange(2, 2));
+ EXPECT_EQ(slice.size(), 2);
+ EXPECT_EQ(slice[0], 3);
+ EXPECT_EQ(slice[1], 4);
+}
+
+TEST(span, Contains)
+{
+ Vector<int> a = {4, 5, 6, 7};
+ Span<int> a_span = a;
+ EXPECT_TRUE(a_span.contains(4));
+ EXPECT_TRUE(a_span.contains(5));
+ EXPECT_TRUE(a_span.contains(6));
+ EXPECT_TRUE(a_span.contains(7));
+ EXPECT_FALSE(a_span.contains(3));
+ EXPECT_FALSE(a_span.contains(8));
+}
+
+TEST(span, Count)
+{
+ Vector<int> a = {2, 3, 4, 3, 3, 2, 2, 2, 2};
+ Span<int> a_span = a;
+ EXPECT_EQ(a_span.count(1), 0);
+ EXPECT_EQ(a_span.count(2), 5);
+ EXPECT_EQ(a_span.count(3), 3);
+ EXPECT_EQ(a_span.count(4), 1);
+ EXPECT_EQ(a_span.count(5), 0);
+}
+
+static void test_ref_from_initializer_list(Span<int> span)
+{
+ EXPECT_EQ(span.size(), 4);
+ EXPECT_EQ(span[0], 3);
+ EXPECT_EQ(span[1], 6);
+ EXPECT_EQ(span[2], 8);
+ EXPECT_EQ(span[3], 9);
+}
+
+TEST(span, FromInitializerList)
+{
+ test_ref_from_initializer_list({3, 6, 8, 9});
+}
+
+TEST(span, FromVector)
+{
+ std::vector<int> a = {1, 2, 3, 4};
+ Span<int> a_span(a);
+ EXPECT_EQ(a_span.size(), 4);
+ EXPECT_EQ(a_span[0], 1);
+ EXPECT_EQ(a_span[1], 2);
+ EXPECT_EQ(a_span[2], 3);
+ EXPECT_EQ(a_span[3], 4);
+}
+
+TEST(span, FromArray)
+{
+ std::array<int, 2> a = {5, 6};
+ Span<int> a_span(a);
+ EXPECT_EQ(a_span.size(), 2);
+ EXPECT_EQ(a_span[0], 5);
+ EXPECT_EQ(a_span[1], 6);
+}
+
+TEST(span, Fill)
+{
+ std::array<int, 5> a = {4, 5, 6, 7, 8};
+ MutableSpan<int> a_span(a);
+ a_span.fill(1);
+ EXPECT_EQ(a[0], 1);
+ EXPECT_EQ(a[1], 1);
+ EXPECT_EQ(a[2], 1);
+ EXPECT_EQ(a[3], 1);
+ EXPECT_EQ(a[4], 1);
+}
+
+TEST(span, FillIndices)
+{
+ std::array<int, 5> a = {0, 0, 0, 0, 0};
+ MutableSpan<int> a_span(a);
+ a_span.fill_indices({0, 2, 3}, 1);
+ EXPECT_EQ(a[0], 1);
+ EXPECT_EQ(a[1], 0);
+ EXPECT_EQ(a[2], 1);
+ EXPECT_EQ(a[3], 1);
+ EXPECT_EQ(a[4], 0);
+}
+
+TEST(span, SizeInBytes)
+{
+ std::array<int, 10> a;
+ Span<int> a_span(a);
+ EXPECT_EQ(a_span.size_in_bytes(), (int64_t)sizeof(a));
+ EXPECT_EQ(a_span.size_in_bytes(), 40);
+}
+
+TEST(span, FirstLast)
+{
+ std::array<int, 4> a = {6, 7, 8, 9};
+ Span<int> a_span(a);
+ EXPECT_EQ(a_span.first(), 6);
+ EXPECT_EQ(a_span.last(), 9);
+}
+
+TEST(span, FirstLast_OneElement)
+{
+ int a = 3;
+ Span<int> a_span(&a, 1);
+ EXPECT_EQ(a_span.first(), 3);
+ EXPECT_EQ(a_span.last(), 3);
+}
+
+TEST(span, Get)
+{
+ std::array<int, 3> a = {5, 6, 7};
+ Span<int> a_span(a);
+ EXPECT_EQ(a_span.get(0, 42), 5);
+ EXPECT_EQ(a_span.get(1, 42), 6);
+ EXPECT_EQ(a_span.get(2, 42), 7);
+ EXPECT_EQ(a_span.get(3, 42), 42);
+ EXPECT_EQ(a_span.get(4, 42), 42);
+}
+
+TEST(span, ContainsPtr)
+{
+ std::array<int, 3> a = {5, 6, 7};
+ int other = 10;
+ Span<int> a_span(a);
+ EXPECT_TRUE(a_span.contains_ptr(&a[0] + 0));
+ EXPECT_TRUE(a_span.contains_ptr(&a[0] + 1));
+ EXPECT_TRUE(a_span.contains_ptr(&a[0] + 2));
+ EXPECT_FALSE(a_span.contains_ptr(&a[0] + 3));
+ EXPECT_FALSE(a_span.contains_ptr(&a[0] - 1));
+ EXPECT_FALSE(a_span.contains_ptr(&other));
+}
+
+TEST(span, FirstIndex)
+{
+ std::array<int, 5> a = {4, 5, 4, 2, 5};
+ Span<int> a_span(a);
+
+ EXPECT_EQ(a_span.first_index(4), 0);
+ EXPECT_EQ(a_span.first_index(5), 1);
+ EXPECT_EQ(a_span.first_index(2), 3);
+}
+
+TEST(span, CastSameSize)
+{
+ int value = 0;
+ std::array<int *, 4> a = {&value, nullptr, nullptr, nullptr};
+ Span<int *> a_span = a;
+ Span<float *> new_a_span = a_span.cast<float *>();
+
+ EXPECT_EQ(a_span.size(), 4);
+ EXPECT_EQ(new_a_span.size(), 4);
+
+ EXPECT_EQ(a_span[0], &value);
+ EXPECT_EQ(new_a_span[0], (float *)&value);
+}
+
+TEST(span, CastSmallerSize)
+{
+ std::array<uint32_t, 4> a = {3, 4, 5, 6};
+ Span<uint32_t> a_span = a;
+ Span<uint16_t> new_a_span = a_span.cast<uint16_t>();
+
+ EXPECT_EQ(a_span.size(), 4);
+ EXPECT_EQ(new_a_span.size(), 8);
+}
+
+TEST(span, CastLargerSize)
+{
+ std::array<uint16_t, 4> a = {4, 5, 6, 7};
+ Span<uint16_t> a_span = a;
+ Span<uint32_t> new_a_span = a_span.cast<uint32_t>();
+
+ EXPECT_EQ(a_span.size(), 4);
+ EXPECT_EQ(new_a_span.size(), 2);
+}
+
+TEST(span, VoidPointerSpan)
+{
+ int a;
+ float b;
+ double c;
+
+ auto func1 = [](Span<void *> span) { EXPECT_EQ(span.size(), 3); };
+ func1({&a, &b, &c});
+}
+
+TEST(span, CopyFrom)
+{
+ std::array<int, 4> src = {5, 6, 7, 8};
+ std::array<int, 4> dst = {1, 2, 3, 4};
+
+ EXPECT_EQ(dst[2], 3);
+ MutableSpan(dst).copy_from(src);
+ EXPECT_EQ(dst[0], 5);
+ EXPECT_EQ(dst[1], 6);
+ EXPECT_EQ(dst[2], 7);
+ EXPECT_EQ(dst[3], 8);
+}
+
+} // namespace blender::tests
diff --git a/source/blender/blenlib/tests/BLI_stack_cxx_test.cc b/source/blender/blenlib/tests/BLI_stack_cxx_test.cc
new file mode 100644
index 00000000000..3572e751b88
--- /dev/null
+++ b/source/blender/blenlib/tests/BLI_stack_cxx_test.cc
@@ -0,0 +1,188 @@
+/* Apache License, Version 2.0 */
+
+#include "BLI_stack.hh"
+#include "BLI_strict_flags.h"
+#include "BLI_vector.hh"
+#include "testing/testing.h"
+
+namespace blender::tests {
+
+TEST(stack, DefaultConstructor)
+{
+ Stack<int> stack;
+ EXPECT_EQ(stack.size(), 0);
+ EXPECT_TRUE(stack.is_empty());
+}
+
+TEST(stack, SpanConstructor)
+{
+ std::array<int, 3> array = {4, 7, 2};
+ Stack<int> stack(array);
+ EXPECT_EQ(stack.size(), 3);
+ EXPECT_EQ(stack.pop(), 2);
+ EXPECT_EQ(stack.pop(), 7);
+ EXPECT_EQ(stack.pop(), 4);
+ EXPECT_TRUE(stack.is_empty());
+}
+
+TEST(stack, CopyConstructor)
+{
+ Stack<int> stack1 = {1, 2, 3, 4, 5, 6, 7};
+ Stack<int> stack2 = stack1;
+ EXPECT_EQ(stack1.size(), 7);
+ EXPECT_EQ(stack2.size(), 7);
+ for (int i = 7; i >= 1; i--) {
+ EXPECT_FALSE(stack1.is_empty());
+ EXPECT_FALSE(stack2.is_empty());
+ EXPECT_EQ(stack1.pop(), i);
+ EXPECT_EQ(stack2.pop(), i);
+ }
+ EXPECT_TRUE(stack1.is_empty());
+ EXPECT_TRUE(stack2.is_empty());
+}
+
+TEST(stack, MoveConstructor)
+{
+ Stack<int> stack1 = {1, 2, 3, 4, 5, 6, 7};
+ Stack<int> stack2 = std::move(stack1);
+ EXPECT_EQ(stack1.size(), 0); /* NOLINT: bugprone-use-after-move */
+ EXPECT_EQ(stack2.size(), 7);
+ for (int i = 7; i >= 1; i--) {
+ EXPECT_EQ(stack2.pop(), i);
+ }
+}
+
+TEST(stack, CopyAssignment)
+{
+ Stack<int> stack1 = {1, 2, 3, 4, 5, 6, 7};
+ Stack<int> stack2 = {2, 3, 4, 5, 6, 7};
+ stack2 = stack1;
+
+ EXPECT_EQ(stack1.size(), 7);
+ EXPECT_EQ(stack2.size(), 7);
+ for (int i = 7; i >= 1; i--) {
+ EXPECT_FALSE(stack1.is_empty());
+ EXPECT_FALSE(stack2.is_empty());
+ EXPECT_EQ(stack1.pop(), i);
+ EXPECT_EQ(stack2.pop(), i);
+ }
+ EXPECT_TRUE(stack1.is_empty());
+ EXPECT_TRUE(stack2.is_empty());
+}
+
+TEST(stack, MoveAssignment)
+{
+ Stack<int> stack1 = {1, 2, 3, 4, 5, 6, 7};
+ Stack<int> stack2 = {5, 3, 7, 2, 2};
+ stack2 = std::move(stack1);
+ EXPECT_EQ(stack1.size(), 0); /* NOLINT: bugprone-use-after-move */
+ EXPECT_EQ(stack2.size(), 7);
+ for (int i = 7; i >= 1; i--) {
+ EXPECT_EQ(stack2.pop(), i);
+ }
+}
+
+TEST(stack, Push)
+{
+ Stack<int> stack;
+ EXPECT_EQ(stack.size(), 0);
+ stack.push(3);
+ EXPECT_EQ(stack.size(), 1);
+ stack.push(5);
+ EXPECT_EQ(stack.size(), 2);
+}
+
+TEST(stack, PushMultiple)
+{
+ Stack<int> stack;
+ EXPECT_EQ(stack.size(), 0);
+ stack.push_multiple({1, 2, 3});
+ EXPECT_EQ(stack.size(), 3);
+ EXPECT_EQ(stack.pop(), 3);
+ EXPECT_EQ(stack.pop(), 2);
+ EXPECT_EQ(stack.pop(), 1);
+}
+
+TEST(stack, PushPopMany)
+{
+ Stack<int> stack;
+ for (int i = 0; i < 1000; i++) {
+ stack.push(i);
+ EXPECT_EQ(stack.size(), static_cast<unsigned int>(i + 1));
+ }
+ for (int i = 999; i > 50; i--) {
+ EXPECT_EQ(stack.pop(), i);
+ EXPECT_EQ(stack.size(), static_cast<unsigned int>(i));
+ }
+ for (int i = 51; i < 5000; i++) {
+ stack.push(i);
+ EXPECT_EQ(stack.size(), static_cast<unsigned int>(i + 1));
+ }
+ for (int i = 4999; i >= 0; i--) {
+ EXPECT_EQ(stack.pop(), i);
+ EXPECT_EQ(stack.size(), static_cast<unsigned int>(i));
+ }
+}
+
+TEST(stack, PushMultipleAfterPop)
+{
+ Stack<int> stack;
+ for (int i = 0; i < 1000; i++) {
+ stack.push(i);
+ }
+ for (int i = 999; i >= 0; i--) {
+ EXPECT_EQ(stack.pop(), i);
+ }
+
+ Vector<int> values;
+ for (int i = 0; i < 5000; i++) {
+ values.append(i);
+ }
+ stack.push_multiple(values);
+ EXPECT_EQ(stack.size(), 5000);
+
+ for (int i = 4999; i >= 0; i--) {
+ EXPECT_EQ(stack.pop(), i);
+ }
+}
+
+TEST(stack, Pop)
+{
+ Stack<int> stack;
+ stack.push(4);
+ stack.push(6);
+ EXPECT_EQ(stack.pop(), 6);
+ EXPECT_EQ(stack.pop(), 4);
+}
+
+TEST(stack, Peek)
+{
+ Stack<int> stack;
+ stack.push(3);
+ stack.push(4);
+ EXPECT_EQ(stack.peek(), 4);
+ EXPECT_EQ(stack.peek(), 4);
+ stack.pop();
+ EXPECT_EQ(stack.peek(), 3);
+}
+
+TEST(stack, UniquePtrValues)
+{
+ Stack<std::unique_ptr<int>> stack;
+ stack.push(std::unique_ptr<int>(new int()));
+ stack.push(std::unique_ptr<int>(new int()));
+ std::unique_ptr<int> a = stack.pop();
+ std::unique_ptr<int> &b = stack.peek();
+ UNUSED_VARS(a, b);
+}
+
+TEST(stack, OveralignedValues)
+{
+ Stack<AlignedBuffer<1, 512>, 2> stack;
+ for (int i = 0; i < 100; i++) {
+ stack.push({});
+ EXPECT_EQ((uintptr_t)&stack.peek() % 512, 0);
+ }
+}
+
+} // namespace blender::tests
diff --git a/source/blender/blenlib/tests/BLI_string_ref_test.cc b/source/blender/blenlib/tests/BLI_string_ref_test.cc
new file mode 100644
index 00000000000..d08c8a77455
--- /dev/null
+++ b/source/blender/blenlib/tests/BLI_string_ref_test.cc
@@ -0,0 +1,277 @@
+/* Apache License, Version 2.0 */
+
+#include "BLI_strict_flags.h"
+#include "BLI_string_ref.hh"
+#include "BLI_vector.hh"
+#include "testing/testing.h"
+
+namespace blender::tests {
+
+TEST(string_ref_null, DefaultConstructor)
+{
+ StringRefNull ref;
+ EXPECT_EQ(ref.size(), 0);
+ EXPECT_EQ(ref[0], '\0');
+}
+
+TEST(string_ref_null, CStringConstructor)
+{
+ const char *str = "Hello";
+ StringRefNull ref(str);
+ EXPECT_EQ(ref.size(), 5);
+ EXPECT_EQ(ref.data(), str);
+}
+
+TEST(string_ref_null, CStringLengthConstructor)
+{
+ const char *str = "Hello";
+ StringRefNull ref(str, 5);
+ EXPECT_EQ(ref.size(), 5);
+ EXPECT_EQ(ref.data(), str);
+}
+
+TEST(string_ref, DefaultConstructor)
+{
+ StringRef ref;
+ EXPECT_EQ(ref.size(), 0);
+}
+
+TEST(string_ref, StartEndConstructor)
+{
+ const char *text = "hello world";
+ StringRef ref(text, text + 5);
+ EXPECT_EQ(ref.size(), 5);
+ EXPECT_TRUE(ref == "hello");
+ EXPECT_FALSE(ref == "hello ");
+}
+
+TEST(string_ref, StartEndConstructorNullptr)
+{
+ StringRef ref(nullptr, nullptr);
+ EXPECT_EQ(ref.size(), 0);
+ EXPECT_TRUE(ref == "");
+}
+
+TEST(string_ref, StartEndConstructorSame)
+{
+ const char *text = "hello world";
+ StringRef ref(text, text);
+ EXPECT_EQ(ref.size(), 0);
+ EXPECT_TRUE(ref == "");
+}
+
+TEST(string_ref, CStringConstructor)
+{
+ const char *str = "Test";
+ StringRef ref(str);
+ EXPECT_EQ(ref.size(), 4);
+ EXPECT_EQ(ref.data(), str);
+}
+
+TEST(string_ref, PointerWithLengthConstructor)
+{
+ const char *str = "Test";
+ StringRef ref(str, 2);
+ EXPECT_EQ(ref.size(), 2);
+ EXPECT_EQ(ref.data(), str);
+}
+
+TEST(string_ref, StdStringConstructor)
+{
+ std::string str = "Test";
+ StringRef ref(str);
+ EXPECT_EQ(ref.size(), 4);
+ EXPECT_EQ(ref.data(), str.data());
+}
+
+TEST(string_ref, SubscriptOperator)
+{
+ StringRef ref("hello");
+ EXPECT_EQ(ref.size(), 5);
+ EXPECT_EQ(ref[0], 'h');
+ EXPECT_EQ(ref[1], 'e');
+ EXPECT_EQ(ref[2], 'l');
+ EXPECT_EQ(ref[3], 'l');
+ EXPECT_EQ(ref[4], 'o');
+}
+
+TEST(string_ref, ToStdString)
+{
+ StringRef ref("test");
+ std::string str = ref;
+ EXPECT_EQ(str.size(), 4);
+ EXPECT_EQ(str, "test");
+}
+
+TEST(string_ref, Print)
+{
+ StringRef ref("test");
+ std::stringstream ss;
+ ss << ref;
+ ss << ref;
+ std::string str = ss.str();
+ EXPECT_EQ(str.size(), 8);
+ EXPECT_EQ(str, "testtest");
+}
+
+TEST(string_ref, Add)
+{
+ StringRef a("qwe");
+ StringRef b("asd");
+ std::string result = a + b;
+ EXPECT_EQ(result, "qweasd");
+}
+
+TEST(string_ref, AddCharPtr1)
+{
+ StringRef ref("test");
+ std::string result = ref + "qwe";
+ EXPECT_EQ(result, "testqwe");
+}
+
+TEST(string_ref, AddCharPtr2)
+{
+ StringRef ref("test");
+ std::string result = "qwe" + ref;
+ EXPECT_EQ(result, "qwetest");
+}
+
+TEST(string_ref, AddString1)
+{
+ StringRef ref("test");
+ std::string result = ref + std::string("asd");
+ EXPECT_EQ(result, "testasd");
+}
+
+TEST(string_ref, AddString2)
+{
+ StringRef ref("test");
+ std::string result = std::string("asd") + ref;
+ EXPECT_EQ(result, "asdtest");
+}
+
+TEST(string_ref, CompareEqual)
+{
+ StringRef ref1("test");
+ StringRef ref2("test");
+ StringRef ref3("other");
+ EXPECT_TRUE(ref1 == ref2);
+ EXPECT_FALSE(ref1 == ref3);
+ EXPECT_TRUE(ref1 != ref3);
+ EXPECT_FALSE(ref1 != ref2);
+}
+
+TEST(string_ref, CompareEqualCharPtr1)
+{
+ StringRef ref("test");
+ EXPECT_TRUE(ref == "test");
+ EXPECT_FALSE(ref == "other");
+ EXPECT_TRUE(ref != "other");
+ EXPECT_FALSE(ref != "test");
+}
+
+TEST(string_ref, CompareEqualCharPtr2)
+{
+ StringRef ref("test");
+ EXPECT_TRUE("test" == ref);
+ EXPECT_FALSE("other" == ref);
+ EXPECT_TRUE(ref != "other");
+ EXPECT_FALSE(ref != "test");
+}
+
+TEST(string_ref, CompareEqualString1)
+{
+ StringRef ref("test");
+ EXPECT_TRUE(ref == std::string("test"));
+ EXPECT_FALSE(ref == std::string("other"));
+ EXPECT_TRUE(ref != std::string("other"));
+ EXPECT_FALSE(ref != std::string("test"));
+}
+
+TEST(string_ref, CompareEqualString2)
+{
+ StringRef ref("test");
+ EXPECT_TRUE(std::string("test") == ref);
+ EXPECT_FALSE(std::string("other") == ref);
+ EXPECT_TRUE(std::string("other") != ref);
+ EXPECT_FALSE(std::string("test") != ref);
+}
+
+TEST(string_ref, Iterate)
+{
+ StringRef ref("test");
+ Vector<char> chars;
+ for (char c : ref) {
+ chars.append(c);
+ }
+ EXPECT_EQ(chars.size(), 4);
+ EXPECT_EQ(chars[0], 't');
+ EXPECT_EQ(chars[1], 'e');
+ EXPECT_EQ(chars[2], 's');
+ EXPECT_EQ(chars[3], 't');
+}
+
+TEST(string_ref, StartsWith)
+{
+ StringRef ref("test");
+ EXPECT_TRUE(ref.startswith(""));
+ EXPECT_TRUE(ref.startswith("t"));
+ EXPECT_TRUE(ref.startswith("te"));
+ EXPECT_TRUE(ref.startswith("tes"));
+ EXPECT_TRUE(ref.startswith("test"));
+ EXPECT_FALSE(ref.startswith("test "));
+ EXPECT_FALSE(ref.startswith("a"));
+}
+
+TEST(string_ref, EndsWith)
+{
+ StringRef ref("test");
+ EXPECT_TRUE(ref.endswith(""));
+ EXPECT_TRUE(ref.endswith("t"));
+ EXPECT_TRUE(ref.endswith("st"));
+ EXPECT_TRUE(ref.endswith("est"));
+ EXPECT_TRUE(ref.endswith("test"));
+ EXPECT_FALSE(ref.endswith(" test"));
+ EXPECT_FALSE(ref.endswith("a"));
+}
+
+TEST(string_ref, DropPrefixN)
+{
+ StringRef ref("test");
+ StringRef ref2 = ref.drop_prefix(2);
+ StringRef ref3 = ref2.drop_prefix(2);
+ EXPECT_EQ(ref2.size(), 2);
+ EXPECT_EQ(ref3.size(), 0);
+ EXPECT_EQ(ref2, "st");
+ EXPECT_EQ(ref3, "");
+}
+
+TEST(string_ref, DropPrefix)
+{
+ StringRef ref("test");
+ StringRef ref2 = ref.drop_prefix("tes");
+ EXPECT_EQ(ref2.size(), 1);
+ EXPECT_EQ(ref2, "t");
+}
+
+TEST(string_ref, Substr)
+{
+ StringRef ref("hello world");
+ EXPECT_EQ(ref.substr(0, 5), "hello");
+ EXPECT_EQ(ref.substr(4, 0), "");
+ EXPECT_EQ(ref.substr(3, 4), "lo w");
+ EXPECT_EQ(ref.substr(6, 5), "world");
+}
+
+TEST(string_ref, Copy)
+{
+ StringRef ref("hello");
+ char dst[10];
+ memset(dst, 0xFF, 10);
+ ref.copy(dst);
+ EXPECT_EQ(dst[5], '\0');
+ EXPECT_EQ(dst[6], 0xFF);
+ EXPECT_EQ(ref, dst);
+}
+
+} // namespace blender::tests
diff --git a/source/blender/blenlib/tests/BLI_vector_set_test.cc b/source/blender/blenlib/tests/BLI_vector_set_test.cc
new file mode 100644
index 00000000000..8f3db8d8403
--- /dev/null
+++ b/source/blender/blenlib/tests/BLI_vector_set_test.cc
@@ -0,0 +1,164 @@
+/* Apache License, Version 2.0 */
+
+#include "BLI_strict_flags.h"
+#include "BLI_vector_set.hh"
+#include "testing/testing.h"
+
+namespace blender::tests {
+
+TEST(vector_set, DefaultConstructor)
+{
+ VectorSet<int> set;
+ EXPECT_EQ(set.size(), 0);
+ EXPECT_TRUE(set.is_empty());
+}
+
+TEST(vector_set, InitializerListConstructor_WithoutDuplicates)
+{
+ VectorSet<int> set = {1, 4, 5};
+ EXPECT_EQ(set.size(), 3);
+ EXPECT_EQ(set[0], 1);
+ EXPECT_EQ(set[1], 4);
+ EXPECT_EQ(set[2], 5);
+}
+
+TEST(vector_set, InitializerListConstructor_WithDuplicates)
+{
+ VectorSet<int> set = {1, 3, 3, 2, 1, 5};
+ EXPECT_EQ(set.size(), 4);
+ EXPECT_EQ(set[0], 1);
+ EXPECT_EQ(set[1], 3);
+ EXPECT_EQ(set[2], 2);
+ EXPECT_EQ(set[3], 5);
+}
+
+TEST(vector_set, Copy)
+{
+ VectorSet<int> set1 = {1, 2, 3};
+ VectorSet<int> set2 = set1;
+ EXPECT_EQ(set1.size(), 3);
+ EXPECT_EQ(set2.size(), 3);
+ EXPECT_EQ(set1.index_of(2), 1);
+ EXPECT_EQ(set2.index_of(2), 1);
+}
+
+TEST(vector_set, CopyAssignment)
+{
+ VectorSet<int> set1 = {1, 2, 3};
+ VectorSet<int> set2 = {};
+ set2 = set1;
+ EXPECT_EQ(set1.size(), 3);
+ EXPECT_EQ(set2.size(), 3);
+ EXPECT_EQ(set1.index_of(2), 1);
+ EXPECT_EQ(set2.index_of(2), 1);
+}
+
+TEST(vector_set, Move)
+{
+ VectorSet<int> set1 = {1, 2, 3};
+ VectorSet<int> set2 = std::move(set1);
+ EXPECT_EQ(set1.size(), 0); /* NOLINT: bugprone-use-after-move */
+ EXPECT_EQ(set2.size(), 3);
+}
+
+TEST(vector_set, MoveAssignment)
+{
+ VectorSet<int> set1 = {1, 2, 3};
+ VectorSet<int> set2 = {};
+ set2 = std::move(set1);
+ EXPECT_EQ(set1.size(), 0); /* NOLINT: bugprone-use-after-move */
+ EXPECT_EQ(set2.size(), 3);
+}
+
+TEST(vector_set, AddNewIncreasesSize)
+{
+ VectorSet<int> set;
+ EXPECT_TRUE(set.is_empty());
+ EXPECT_EQ(set.size(), 0);
+ set.add(5);
+ EXPECT_FALSE(set.is_empty());
+ EXPECT_EQ(set.size(), 1);
+}
+
+TEST(vector_set, AddExistingDoesNotIncreaseSize)
+{
+ VectorSet<int> set;
+ EXPECT_EQ(set.size(), 0);
+ EXPECT_TRUE(set.add(5));
+ EXPECT_EQ(set.size(), 1);
+ EXPECT_FALSE(set.add(5));
+ EXPECT_EQ(set.size(), 1);
+}
+
+TEST(vector_set, Index)
+{
+ VectorSet<int> set = {3, 6, 4};
+ EXPECT_EQ(set.index_of(6), 1);
+ EXPECT_EQ(set.index_of(3), 0);
+ EXPECT_EQ(set.index_of(4), 2);
+}
+
+TEST(vector_set, IndexTry)
+{
+ VectorSet<int> set = {3, 6, 4};
+ EXPECT_EQ(set.index_of_try(5), -1);
+ EXPECT_EQ(set.index_of_try(3), 0);
+ EXPECT_EQ(set.index_of_try(6), 1);
+ EXPECT_EQ(set.index_of_try(2), -1);
+}
+
+TEST(vector_set, RemoveContained)
+{
+ VectorSet<int> set = {4, 5, 6, 7};
+ EXPECT_EQ(set.size(), 4);
+ set.remove_contained(5);
+ EXPECT_EQ(set.size(), 3);
+ EXPECT_EQ(set[0], 4);
+ EXPECT_EQ(set[1], 7);
+ EXPECT_EQ(set[2], 6);
+ set.remove_contained(6);
+ EXPECT_EQ(set.size(), 2);
+ EXPECT_EQ(set[0], 4);
+ EXPECT_EQ(set[1], 7);
+ set.remove_contained(4);
+ EXPECT_EQ(set.size(), 1);
+ EXPECT_EQ(set[0], 7);
+ set.remove_contained(7);
+ EXPECT_EQ(set.size(), 0);
+}
+
+TEST(vector_set, AddMultipleTimes)
+{
+ VectorSet<int> set;
+ for (int i = 0; i < 100; i++) {
+ EXPECT_FALSE(set.contains(i * 13));
+ set.add(i * 12);
+ set.add(i * 13);
+ EXPECT_TRUE(set.contains(i * 13));
+ }
+}
+
+TEST(vector_set, UniquePtrValue)
+{
+ VectorSet<std::unique_ptr<int>> set;
+ set.add_new(std::unique_ptr<int>(new int()));
+ set.add(std::unique_ptr<int>(new int()));
+ set.index_of_try(std::unique_ptr<int>(new int()));
+ std::unique_ptr<int> value = set.pop();
+ UNUSED_VARS(value);
+}
+
+TEST(vector_set, Remove)
+{
+ VectorSet<int> set;
+ EXPECT_TRUE(set.add(5));
+ EXPECT_TRUE(set.contains(5));
+ EXPECT_FALSE(set.remove(6));
+ EXPECT_TRUE(set.contains(5));
+ EXPECT_TRUE(set.remove(5));
+ EXPECT_FALSE(set.contains(5));
+ EXPECT_FALSE(set.remove(5));
+ EXPECT_FALSE(set.contains(5));
+}
+
+} // namespace blender::tests
diff --git a/source/blender/blenlib/tests/BLI_vector_test.cc b/source/blender/blenlib/tests/BLI_vector_test.cc
new file mode 100644
index 00000000000..f72dfc5deb8
--- /dev/null
+++ b/source/blender/blenlib/tests/BLI_vector_test.cc
@@ -0,0 +1,639 @@
+/* Apache License, Version 2.0 */
+
+#include "BLI_strict_flags.h"
+#include "BLI_vector.hh"
+#include "testing/testing.h"
+#include <forward_list>
+
+namespace blender::tests {
+
+TEST(vector, DefaultConstructor)
+{
+ Vector<int> vec;
+ EXPECT_EQ(vec.size(), 0);
+}
+
+TEST(vector, SizeConstructor)
+{
+ Vector<int> vec(3);
+ EXPECT_EQ(vec.size(), 3);
+}
+
+/**
+ * Tests that the trivially constructible types are not zero-initialized. We do not want that for
+ * performance reasons.
+ */
+TEST(vector, TrivialTypeSizeConstructor)
+{
+ Vector<char, 1> *vec = new Vector<char, 1>(1);
+ char *ptr = &(*vec)[0];
+ vec->~Vector();
+
+ const char magic = 42;
+ *ptr = magic;
+ EXPECT_EQ(*ptr, magic);
+
+ new (vec) Vector<char, 1>(1);
+ EXPECT_EQ((*vec)[0], magic);
+ EXPECT_EQ(*ptr, magic);
+ delete vec;
+}
+
+TEST(vector, SizeValueConstructor)
+{
+ Vector<int> vec(4, 10);
+ EXPECT_EQ(vec.size(), 4);
+ EXPECT_EQ(vec[0], 10);
+ EXPECT_EQ(vec[1], 10);
+ EXPECT_EQ(vec[2], 10);
+ EXPECT_EQ(vec[3], 10);
+}
+
+TEST(vector, InitializerListConstructor)
+{
+ Vector<int> vec = {1, 3, 4, 6};
+ EXPECT_EQ(vec.size(), 4);
+ EXPECT_EQ(vec[0], 1);
+ EXPECT_EQ(vec[1], 3);
+ EXPECT_EQ(vec[2], 4);
+ EXPECT_EQ(vec[3], 6);
+}
+
+TEST(vector, ConvertingConstructor)
+{
+ std::array<float, 5> values = {5.4f, 7.3f, -8.1f, 5.0f, 0.0f};
+ Vector<int> vec = values;
+ EXPECT_EQ(vec.size(), 5);
+ EXPECT_EQ(vec[0], 5);
+ EXPECT_EQ(vec[1], 7);
+ EXPECT_EQ(vec[2], -8);
+ EXPECT_EQ(vec[3], 5);
+ EXPECT_EQ(vec[4], 0);
+}
+
+struct TestListValue {
+ TestListValue *next, *prev;
+ int value;
+};
+
+TEST(vector, ListBaseConstructor)
+{
+ TestListValue *value1 = new TestListValue{0, 0, 4};
+ TestListValue *value2 = new TestListValue{0, 0, 5};
+ TestListValue *value3 = new TestListValue{0, 0, 6};
+
+ ListBase list = {NULL, NULL};
+ BLI_addtail(&list, value1);
+ BLI_addtail(&list, value2);
+ BLI_addtail(&list, value3);
+ Vector<TestListValue *> vec(list);
+
+ EXPECT_EQ(vec.size(), 3);
+ EXPECT_EQ(vec[0]->value, 4);
+ EXPECT_EQ(vec[1]->value, 5);
+ EXPECT_EQ(vec[2]->value, 6);
+
+ delete value1;
+ delete value2;
+ delete value3;
+}
+
+TEST(vector, ContainerConstructor)
+{
+ std::forward_list<int> list;
+ list.push_front(3);
+ list.push_front(1);
+ list.push_front(5);
+
+ Vector<int> vec = Vector<int>::FromContainer(list);
+ EXPECT_EQ(vec.size(), 3);
+ EXPECT_EQ(vec[0], 5);
+ EXPECT_EQ(vec[1], 1);
+ EXPECT_EQ(vec[2], 3);
+}
+
+TEST(vector, CopyConstructor)
+{
+ Vector<int> vec1 = {1, 2, 3};
+ Vector<int> vec2(vec1);
+ EXPECT_EQ(vec2.size(), 3);
+ EXPECT_EQ(vec2[0], 1);
+ EXPECT_EQ(vec2[1], 2);
+ EXPECT_EQ(vec2[2], 3);
+
+ vec1[1] = 5;
+ EXPECT_EQ(vec1[1], 5);
+ EXPECT_EQ(vec2[1], 2);
+}
+
+TEST(vector, CopyConstructor2)
+{
+ Vector<int, 2> vec1 = {1, 2, 3, 4};
+ Vector<int, 3> vec2(vec1);
+
+ EXPECT_EQ(vec1.size(), 4);
+ EXPECT_EQ(vec2.size(), 4);
+ EXPECT_NE(vec1.data(), vec2.data());
+ EXPECT_EQ(vec2[0], 1);
+ EXPECT_EQ(vec2[1], 2);
+ EXPECT_EQ(vec2[2], 3);
+ EXPECT_EQ(vec2[3], 4);
+}
+
+TEST(vector, CopyConstructor3)
+{
+ Vector<int, 20> vec1 = {1, 2, 3, 4};
+ Vector<int, 1> vec2(vec1);
+
+ EXPECT_EQ(vec1.size(), 4);
+ EXPECT_EQ(vec2.size(), 4);
+ EXPECT_NE(vec1.data(), vec2.data());
+ EXPECT_EQ(vec2[2], 3);
+}
+
+TEST(vector, CopyConstructor4)
+{
+ Vector<int, 5> vec1 = {1, 2, 3, 4};
+ Vector<int, 6> vec2(vec1);
+
+ EXPECT_EQ(vec1.size(), 4);
+ EXPECT_EQ(vec2.size(), 4);
+ EXPECT_NE(vec1.data(), vec2.data());
+ EXPECT_EQ(vec2[3], 4);
+}
+
+TEST(vector, MoveConstructor)
+{
+ Vector<int> vec1 = {1, 2, 3, 4};
+ Vector<int> vec2(std::move(vec1));
+
+ EXPECT_EQ(vec1.size(), 0); /* NOLINT: bugprone-use-after-move */
+ EXPECT_EQ(vec2.size(), 4);
+ EXPECT_EQ(vec2[0], 1);
+ EXPECT_EQ(vec2[1], 2);
+ EXPECT_EQ(vec2[2], 3);
+ EXPECT_EQ(vec2[3], 4);
+}
+
+TEST(vector, MoveConstructor2)
+{
+ Vector<int, 2> vec1 = {1, 2, 3, 4};
+ Vector<int, 3> vec2(std::move(vec1));
+
+ EXPECT_EQ(vec1.size(), 0); /* NOLINT: bugprone-use-after-move */
+ EXPECT_EQ(vec2.size(), 4);
+ EXPECT_EQ(vec2[0], 1);
+ EXPECT_EQ(vec2[1], 2);
+ EXPECT_EQ(vec2[2], 3);
+ EXPECT_EQ(vec2[3], 4);
+}
+
+TEST(vector, MoveConstructor3)
+{
+ Vector<int, 20> vec1 = {1, 2, 3, 4};
+ Vector<int, 1> vec2(std::move(vec1));
+
+ EXPECT_EQ(vec1.size(), 0); /* NOLINT: bugprone-use-after-move */
+ EXPECT_EQ(vec2.size(), 4);
+ EXPECT_EQ(vec2[2], 3);
+}
+
+TEST(vector, MoveConstructor4)
+{
+ Vector<int, 5> vec1 = {1, 2, 3, 4};
+ Vector<int, 6> vec2(std::move(vec1));
+
+ EXPECT_EQ(vec1.size(), 0); /* NOLINT: bugprone-use-after-move */
+ EXPECT_EQ(vec2.size(), 4);
+ EXPECT_EQ(vec2[3], 4);
+}
+
+TEST(vector, MoveAssignment)
+{
+ Vector<int> vec = {1, 2};
+ EXPECT_EQ(vec.size(), 2);
+ EXPECT_EQ(vec[0], 1);
+ EXPECT_EQ(vec[1], 2);
+
+ vec = Vector<int>({5});
+ EXPECT_EQ(vec.size(), 1);
+ EXPECT_EQ(vec[0], 5);
+}
+
+TEST(vector, CopyAssignment)
+{
+ Vector<int> vec1 = {1, 2, 3};
+ Vector<int> vec2 = {4, 5};
+ EXPECT_EQ(vec1.size(), 3);
+ EXPECT_EQ(vec2.size(), 2);
+
+ vec2 = vec1;
+ EXPECT_EQ(vec2.size(), 3);
+
+ vec1[0] = 7;
+ EXPECT_EQ(vec1[0], 7);
+ EXPECT_EQ(vec2[0], 1);
+}
+
+TEST(vector, Append)
+{
+ Vector<int> vec;
+ vec.append(3);
+ vec.append(6);
+ vec.append(7);
+ EXPECT_EQ(vec.size(), 3);
+ EXPECT_EQ(vec[0], 3);
+ EXPECT_EQ(vec[1], 6);
+ EXPECT_EQ(vec[2], 7);
+}
+
+TEST(vector, AppendAndGetIndex)
+{
+ Vector<int> vec;
+ EXPECT_EQ(vec.append_and_get_index(10), 0);
+ EXPECT_EQ(vec.append_and_get_index(10), 1);
+ EXPECT_EQ(vec.append_and_get_index(10), 2);
+ vec.append(10);
+ EXPECT_EQ(vec.append_and_get_index(10), 4);
+}
+
+TEST(vector, AppendNonDuplicates)
+{
+ Vector<int> vec;
+ vec.append_non_duplicates(4);
+ EXPECT_EQ(vec.size(), 1);
+ vec.append_non_duplicates(5);
+ EXPECT_EQ(vec.size(), 2);
+ vec.append_non_duplicates(4);
+ EXPECT_EQ(vec.size(), 2);
+}
+
+TEST(vector, ExtendNonDuplicates)
+{
+ Vector<int> vec;
+ vec.extend_non_duplicates({1, 2});
+ EXPECT_EQ(vec.size(), 2);
+ vec.extend_non_duplicates({3, 4});
+ EXPECT_EQ(vec.size(), 4);
+ vec.extend_non_duplicates({0, 1, 2, 3});
+ EXPECT_EQ(vec.size(), 5);
+}
+
+TEST(vector, Iterator)
+{
+ Vector<int> vec({1, 4, 9, 16});
+ int i = 1;
+ for (int value : vec) {
+ EXPECT_EQ(value, i * i);
+ i++;
+ }
+}
+
+TEST(vector, BecomeLarge)
+{
+ Vector<int, 4> vec;
+ for (int i = 0; i < 100; i++) {
+ vec.append(i * 5);
+ }
+ EXPECT_EQ(vec.size(), 100);
+ for (int i = 0; i < 100; i++) {
+ EXPECT_EQ(vec[i], static_cast<int>(i * 5));
+ }
+}
+
+static Vector<int> return_by_value_helper()
+{
+ return Vector<int>({3, 5, 1});
+}
+
+TEST(vector, ReturnByValue)
+{
+ Vector<int> vec = return_by_value_helper();
+ EXPECT_EQ(vec.size(), 3);
+ EXPECT_EQ(vec[0], 3);
+ EXPECT_EQ(vec[1], 5);
+ EXPECT_EQ(vec[2], 1);
+}
+
+TEST(vector, VectorOfVectors_Append)
+{
+ Vector<Vector<int>> vec;
+ EXPECT_EQ(vec.size(), 0);
+
+ Vector<int> v({1, 2});
+ vec.append(v);
+ vec.append({7, 8});
+ EXPECT_EQ(vec.size(), 2);
+ EXPECT_EQ(vec[0][0], 1);
+ EXPECT_EQ(vec[0][1], 2);
+ EXPECT_EQ(vec[1][0], 7);
+ EXPECT_EQ(vec[1][1], 8);
+}
+
+TEST(vector, RemoveLast)
+{
+ Vector<int> vec = {5, 6};
+ EXPECT_EQ(vec.size(), 2);
+ vec.remove_last();
+ EXPECT_EQ(vec.size(), 1);
+ vec.remove_last();
+ EXPECT_EQ(vec.size(), 0);
+}
+
+TEST(vector, IsEmpty)
+{
+ Vector<int> vec;
+ EXPECT_TRUE(vec.is_empty());
+ vec.append(1);
+ EXPECT_FALSE(vec.is_empty());
+ vec.remove_last();
+ EXPECT_TRUE(vec.is_empty());
+}
+
+TEST(vector, RemoveReorder)
+{
+ Vector<int> vec = {4, 5, 6, 7};
+ vec.remove_and_reorder(1);
+ EXPECT_EQ(vec[0], 4);
+ EXPECT_EQ(vec[1], 7);
+ EXPECT_EQ(vec[2], 6);
+ vec.remove_and_reorder(2);
+ EXPECT_EQ(vec[0], 4);
+ EXPECT_EQ(vec[1], 7);
+ vec.remove_and_reorder(0);
+ EXPECT_EQ(vec[0], 7);
+ vec.remove_and_reorder(0);
+ EXPECT_TRUE(vec.is_empty());
+}
+
+TEST(vector, RemoveFirstOccurrenceAndReorder)
+{
+ Vector<int> vec = {4, 5, 6, 7};
+ vec.remove_first_occurrence_and_reorder(5);
+ EXPECT_EQ(vec[0], 4);
+ EXPECT_EQ(vec[1], 7);
+ EXPECT_EQ(vec[2], 6);
+ vec.remove_first_occurrence_and_reorder(6);
+ EXPECT_EQ(vec[0], 4);
+ EXPECT_EQ(vec[1], 7);
+ vec.remove_first_occurrence_and_reorder(4);
+ EXPECT_EQ(vec[0], 7);
+ vec.remove_first_occurrence_and_reorder(7);
+ EXPECT_EQ(vec.size(), 0);
+}
+
+TEST(vector, Remove)
+{
+ Vector<int> vec = {1, 2, 3, 4, 5, 6};
+ vec.remove(3);
+ EXPECT_TRUE(std::equal(vec.begin(), vec.end(), Span<int>({1, 2, 3, 5, 6}).begin()));
+ vec.remove(0);
+ EXPECT_TRUE(std::equal(vec.begin(), vec.end(), Span<int>({2, 3, 5, 6}).begin()));
+ vec.remove(3);
+ EXPECT_TRUE(std::equal(vec.begin(), vec.end(), Span<int>({2, 3, 5}).begin()));
+ vec.remove(1);
+ EXPECT_TRUE(std::equal(vec.begin(), vec.end(), Span<int>({2, 5}).begin()));
+ vec.remove(1);
+ EXPECT_TRUE(std::equal(vec.begin(), vec.end(), Span<int>({2}).begin()));
+ vec.remove(0);
+ EXPECT_TRUE(std::equal(vec.begin(), vec.end(), Span<int>({}).begin()));
+}
+
+TEST(vector, ExtendSmallVector)
+{
+ Vector<int> a = {2, 3, 4};
+ Vector<int> b = {11, 12};
+ b.extend(a);
+ EXPECT_EQ(b.size(), 5);
+ EXPECT_EQ(b[0], 11);
+ EXPECT_EQ(b[1], 12);
+ EXPECT_EQ(b[2], 2);
+ EXPECT_EQ(b[3], 3);
+ EXPECT_EQ(b[4], 4);
+}
+
+TEST(vector, ExtendArray)
+{
+ int array[] = {3, 4, 5, 6};
+
+ Vector<int> a;
+ a.extend(array, 2);
+
+ EXPECT_EQ(a.size(), 2);
+ EXPECT_EQ(a[0], 3);
+ EXPECT_EQ(a[1], 4);
+}
+
+TEST(vector, Last)
+{
+ Vector<int> a{3, 5, 7};
+ EXPECT_EQ(a.last(), 7);
+}
+
+TEST(vector, AppendNTimes)
+{
+ Vector<int> a;
+ a.append_n_times(5, 3);
+ a.append_n_times(2, 2);
+ EXPECT_EQ(a.size(), 5);
+ EXPECT_EQ(a[0], 5);
+ EXPECT_EQ(a[1], 5);
+ EXPECT_EQ(a[2], 5);
+ EXPECT_EQ(a[3], 2);
+ EXPECT_EQ(a[4], 2);
+}
+
+TEST(vector, UniquePtrValue)
+{
+ Vector<std::unique_ptr<int>> vec;
+ vec.append(std::unique_ptr<int>(new int()));
+ vec.append(std::unique_ptr<int>(new int()));
+ vec.append(std::unique_ptr<int>(new int()));
+ vec.append(std::unique_ptr<int>(new int()));
+ EXPECT_EQ(vec.size(), 4);
+
+ std::unique_ptr<int> &a = vec.last();
+ std::unique_ptr<int> b = vec.pop_last();
+ vec.remove_and_reorder(0);
+ vec.remove(0);
+ EXPECT_EQ(vec.size(), 1);
+
+ UNUSED_VARS(a, b);
+}
+
+class TypeConstructMock {
+ public:
+ bool default_constructed = false;
+ bool copy_constructed = false;
+ bool move_constructed = false;
+ bool copy_assigned = false;
+ bool move_assigned = false;
+
+ TypeConstructMock() : default_constructed(true)
+ {
+ }
+
+ TypeConstructMock(const TypeConstructMock &UNUSED(other)) : copy_constructed(true)
+ {
+ }
+
+ TypeConstructMock(TypeConstructMock &&UNUSED(other)) noexcept : move_constructed(true)
+ {
+ }
+
+ TypeConstructMock &operator=(const TypeConstructMock &other)
+ {
+ if (this == &other) {
+ return *this;
+ }
+
+ copy_assigned = true;
+ return *this;
+ }
+
+ TypeConstructMock &operator=(TypeConstructMock &&other) noexcept
+ {
+ if (this == &other) {
+ return *this;
+ }
+
+ move_assigned = true;
+ return *this;
+ }
+};
+
+TEST(vector, SizeConstructorCallsDefaultConstructor)
+{
+ Vector<TypeConstructMock> vec(3);
+ EXPECT_TRUE(vec[0].default_constructed);
+ EXPECT_TRUE(vec[1].default_constructed);
+ EXPECT_TRUE(vec[2].default_constructed);
+}
+
+TEST(vector, SizeValueConstructorCallsCopyConstructor)
+{
+ Vector<TypeConstructMock> vec(3, TypeConstructMock());
+ EXPECT_TRUE(vec[0].copy_constructed);
+ EXPECT_TRUE(vec[1].copy_constructed);
+ EXPECT_TRUE(vec[2].copy_constructed);
+}
+
+TEST(vector, AppendCallsCopyConstructor)
+{
+ Vector<TypeConstructMock> vec;
+ TypeConstructMock value;
+ vec.append(value);
+ EXPECT_TRUE(vec[0].copy_constructed);
+}
+
+TEST(vector, AppendCallsMoveConstructor)
+{
+ Vector<TypeConstructMock> vec;
+ vec.append(TypeConstructMock());
+ EXPECT_TRUE(vec[0].move_constructed);
+}
+
+TEST(vector, SmallVectorCopyCallsCopyConstructor)
+{
+ Vector<TypeConstructMock, 2> src(2);
+ Vector<TypeConstructMock, 2> dst(src);
+ EXPECT_TRUE(dst[0].copy_constructed);
+ EXPECT_TRUE(dst[1].copy_constructed);
+}
+
+TEST(vector, LargeVectorCopyCallsCopyConstructor)
+{
+ Vector<TypeConstructMock, 2> src(5);
+ Vector<TypeConstructMock, 2> dst(src);
+ EXPECT_TRUE(dst[0].copy_constructed);
+ EXPECT_TRUE(dst[1].copy_constructed);
+}
+
+TEST(vector, SmallVectorMoveCallsMoveConstructor)
+{
+ Vector<TypeConstructMock, 2> src(2);
+ Vector<TypeConstructMock, 2> dst(std::move(src));
+ EXPECT_TRUE(dst[0].move_constructed);
+ EXPECT_TRUE(dst[1].move_constructed);
+}
+
+TEST(vector, LargeVectorMoveCallsNoConstructor)
+{
+ Vector<TypeConstructMock, 2> src(5);
+ Vector<TypeConstructMock, 2> dst(std::move(src));
+
+ EXPECT_TRUE(dst[0].default_constructed);
+ EXPECT_FALSE(dst[0].move_constructed);
+ EXPECT_FALSE(dst[0].copy_constructed);
+}
+
+TEST(vector, Resize)
+{
+ std::string long_string = "012345678901234567890123456789";
+ Vector<std::string> vec;
+ EXPECT_EQ(vec.size(), 0);
+ vec.resize(2);
+ EXPECT_EQ(vec.size(), 2);
+ EXPECT_EQ(vec[0], "");
+ EXPECT_EQ(vec[1], "");
+ vec.resize(5, long_string);
+ EXPECT_EQ(vec.size(), 5);
+ EXPECT_EQ(vec[0], "");
+ EXPECT_EQ(vec[1], "");
+ EXPECT_EQ(vec[2], long_string);
+ EXPECT_EQ(vec[3], long_string);
+ EXPECT_EQ(vec[4], long_string);
+ vec.resize(1);
+ EXPECT_EQ(vec.size(), 1);
+ EXPECT_EQ(vec[0], "");
+}
+
+TEST(vector, FirstIndexOf)
+{
+ Vector<int> vec = {2, 3, 5, 7, 5, 9};
+ EXPECT_EQ(vec.first_index_of(2), 0);
+ EXPECT_EQ(vec.first_index_of(5), 2);
+ EXPECT_EQ(vec.first_index_of(9), 5);
+}
+
+TEST(vector, FirstIndexTryOf)
+{
+ Vector<int> vec = {2, 3, 5, 7, 5, 9};
+ EXPECT_EQ(vec.first_index_of_try(2), 0);
+ EXPECT_EQ(vec.first_index_of_try(4), -1);
+ EXPECT_EQ(vec.first_index_of_try(5), 2);
+ EXPECT_EQ(vec.first_index_of_try(9), 5);
+ EXPECT_EQ(vec.first_index_of_try(1), -1);
+}
+
+TEST(vector, OveralignedValues)
+{
+ Vector<AlignedBuffer<1, 512>, 2> vec;
+ for (int i = 0; i < 100; i++) {
+ vec.append({});
+ EXPECT_EQ((uintptr_t)&vec.last() % 512, 0);
+ }
+}
+
+TEST(vector, ConstructVoidPointerVector)
+{
+ int a;
+ float b;
+ double c;
+ Vector<void *> vec = {&a, &b, &c};
+ EXPECT_EQ(vec.size(), 3);
+}
+
+TEST(vector, Fill)
+{
+ Vector<int> vec(5);
+ vec.fill(3);
+ EXPECT_EQ(vec.size(), 5u);
+ EXPECT_EQ(vec[0], 3);
+ EXPECT_EQ(vec[1], 3);
+ EXPECT_EQ(vec[2], 3);
+ EXPECT_EQ(vec[3], 3);
+ EXPECT_EQ(vec[4], 3);
+}
+
+} // namespace blender::tests
diff --git a/source/blender/blenloader/intern/readfile.c b/source/blender/blenloader/intern/readfile.c
index 9320187f2a0..db6d5136391 100644
--- a/source/blender/blenloader/intern/readfile.c
+++ b/source/blender/blenloader/intern/readfile.c
@@ -4162,7 +4162,7 @@ static void direct_link_curve(BlendDataReader *reader, Curve *cu)
direct_link_animdata(reader, cu->adt);
/* Protect against integer overflow vulnerability. */
- CLAMP(cu->len_wchar, 0, INT_MAX - 4);
+ CLAMP(cu->len_char32, 0, INT_MAX - 4);
BLO_read_pointer_array(reader, (void **)&cu->mat);
diff --git a/source/blender/blenloader/intern/versioning_260.c b/source/blender/blenloader/intern/versioning_260.c
index b3bf8991c3e..5e91fea3e20 100644
--- a/source/blender/blenloader/intern/versioning_260.c
+++ b/source/blender/blenloader/intern/versioning_260.c
@@ -2520,7 +2520,7 @@ void blo_do_versions_260(FileData *fd, Library *UNUSED(lib), Main *bmain)
for (cu = bmain->curves.first; cu; cu = cu->id.next) {
if (cu->str) {
- cu->len_wchar = BLI_strlen_utf8(cu->str);
+ cu->len_char32 = BLI_strlen_utf8(cu->str);
}
}
}
diff --git a/source/blender/blenloader/intern/writefile.c b/source/blender/blenloader/intern/writefile.c
index 46ac6b43c92..7f3fbee81e3 100644
--- a/source/blender/blenloader/intern/writefile.c
+++ b/source/blender/blenloader/intern/writefile.c
@@ -2007,7 +2007,7 @@ static void write_curve(BlendWriter *writer, Curve *cu, const void *id_address)
if (cu->vfont) {
BLO_write_raw(writer, cu->len + 1, cu->str);
- BLO_write_struct_array(writer, CharInfo, cu->len_wchar + 1, cu->strinfo);
+ BLO_write_struct_array(writer, CharInfo, cu->len_char32 + 1, cu->strinfo);
BLO_write_struct_array(writer, TextBox, cu->totbox, cu->tb);
}
else {
diff --git a/source/blender/bmesh/tools/bmesh_bevel.c b/source/blender/bmesh/tools/bmesh_bevel.c
index 588f716142d..39174a49283 100644
--- a/source/blender/bmesh/tools/bmesh_bevel.c
+++ b/source/blender/bmesh/tools/bmesh_bevel.c
@@ -917,7 +917,7 @@ static void math_layer_info_init(BevelParams *bp, BMesh *bm)
* segment (and its continuation into vmesh) can usually arbitrarily be
* the previous face or the next face.
* Or, for the center polygon of a corner, all of the faces around
- * the vertex are possible choices.
+ * the vertex are possibleface_component choices.
* If we just choose randomly, the resulting UV maps or material
* assignment can look ugly/inconsistent.
* Allow for the case when arguments are null.
diff --git a/source/blender/depsgraph/intern/builder/deg_builder_nodes.cc b/source/blender/depsgraph/intern/builder/deg_builder_nodes.cc
index c8309656f21..57bc673a521 100644
--- a/source/blender/depsgraph/intern/builder/deg_builder_nodes.cc
+++ b/source/blender/depsgraph/intern/builder/deg_builder_nodes.cc
@@ -1466,6 +1466,18 @@ void DepsgraphNodeBuilder::build_light(Light *lamp)
function_bind(BKE_light_eval, _1, lamp_cow));
}
+void DepsgraphNodeBuilder::build_nodetree_socket(bNodeSocket *socket)
+{
+ build_idproperties(socket->prop);
+
+ if (socket->type == SOCK_OBJECT) {
+ build_id((ID *)((bNodeSocketValueObject *)socket->default_value)->value);
+ }
+ else if (socket->type == SOCK_IMAGE) {
+ build_id((ID *)((bNodeSocketValueImage *)socket->default_value)->value);
+ }
+}
+
void DepsgraphNodeBuilder::build_nodetree(bNodeTree *ntree)
{
if (ntree == nullptr) {
@@ -1494,10 +1506,10 @@ void DepsgraphNodeBuilder::build_nodetree(bNodeTree *ntree)
LISTBASE_FOREACH (bNode *, bnode, &ntree->nodes) {
build_idproperties(bnode->prop);
LISTBASE_FOREACH (bNodeSocket *, socket, &bnode->inputs) {
- build_idproperties(socket->prop);
+ build_nodetree_socket(socket);
}
LISTBASE_FOREACH (bNodeSocket *, socket, &bnode->outputs) {
- build_idproperties(socket->prop);
+ build_nodetree_socket(socket);
}
ID *id = bnode->id;
@@ -1780,8 +1792,10 @@ void DepsgraphNodeBuilder::build_simulation(Simulation *simulation)
return;
}
add_id_node(&simulation->id);
+ build_idproperties(simulation->id.properties);
build_animdata(&simulation->id);
build_parameters(&simulation->id);
+ build_nodetree(simulation->nodetree);
Simulation *simulation_cow = get_cow_datablock(simulation);
Scene *scene_cow = get_cow_datablock(scene_);
diff --git a/source/blender/depsgraph/intern/builder/deg_builder_nodes.h b/source/blender/depsgraph/intern/builder/deg_builder_nodes.h
index 256fa3450a6..40f42705a52 100644
--- a/source/blender/depsgraph/intern/builder/deg_builder_nodes.h
+++ b/source/blender/depsgraph/intern/builder/deg_builder_nodes.h
@@ -31,6 +31,7 @@
#include "DEG_depsgraph.h"
+struct bNodeSocket;
struct CacheFile;
struct Camera;
struct Collection;
@@ -211,6 +212,7 @@ class DepsgraphNodeBuilder : public DepsgraphBuilder {
virtual void build_camera(Camera *camera);
virtual void build_light(Light *lamp);
virtual void build_nodetree(bNodeTree *ntree);
+ virtual void build_nodetree_socket(bNodeSocket *socket);
virtual void build_material(Material *ma);
virtual void build_materials(Material **materials, int num_materials);
virtual void build_freestyle_lineset(FreestyleLineSet *fls);
diff --git a/source/blender/depsgraph/intern/builder/deg_builder_relations.cc b/source/blender/depsgraph/intern/builder/deg_builder_relations.cc
index c5c509ee853..b8c98676ea6 100644
--- a/source/blender/depsgraph/intern/builder/deg_builder_relations.cc
+++ b/source/blender/depsgraph/intern/builder/deg_builder_relations.cc
@@ -2276,6 +2276,24 @@ void DepsgraphRelationBuilder::build_light(Light *lamp)
add_relation(lamp_parameters_key, shading_key, "Light Shading Parameters");
}
+void DepsgraphRelationBuilder::build_nodetree_socket(bNodeSocket *socket)
+{
+ build_idproperties(socket->prop);
+
+ if (socket->type == SOCK_OBJECT) {
+ Object *object = ((bNodeSocketValueObject *)socket->default_value)->value;
+ if (object != nullptr) {
+ build_object(object);
+ }
+ }
+ else if (socket->type == SOCK_IMAGE) {
+ Image *image = ((bNodeSocketValueImage *)socket->default_value)->value;
+ if (image != nullptr) {
+ build_image(image);
+ }
+ }
+}
+
void DepsgraphRelationBuilder::build_nodetree(bNodeTree *ntree)
{
if (ntree == nullptr) {
@@ -2292,10 +2310,10 @@ void DepsgraphRelationBuilder::build_nodetree(bNodeTree *ntree)
LISTBASE_FOREACH (bNode *, bnode, &ntree->nodes) {
build_idproperties(bnode->prop);
LISTBASE_FOREACH (bNodeSocket *, socket, &bnode->inputs) {
- build_idproperties(socket->prop);
+ build_nodetree_socket(socket);
}
LISTBASE_FOREACH (bNodeSocket *, socket, &bnode->outputs) {
- build_idproperties(socket->prop);
+ build_nodetree_socket(socket);
}
ID *id = bnode->id;
@@ -2602,13 +2620,40 @@ void DepsgraphRelationBuilder::build_simulation(Simulation *simulation)
if (built_map_.checkIsBuiltAndTag(simulation)) {
return;
}
+ build_idproperties(simulation->id.properties);
build_animdata(&simulation->id);
build_parameters(&simulation->id);
- OperationKey simulation_update_key(
+ build_nodetree(simulation->nodetree);
+ build_nested_nodetree(&simulation->id, simulation->nodetree);
+
+ OperationKey simulation_eval_key(
&simulation->id, NodeType::SIMULATION, OperationCode::SIMULATION_EVAL);
TimeSourceKey time_src_key;
- add_relation(time_src_key, simulation_update_key, "TimeSrc -> Simulation");
+ add_relation(time_src_key, simulation_eval_key, "TimeSrc -> Simulation");
+
+ OperationKey nodetree_key(
+ &simulation->nodetree->id, NodeType::PARAMETERS, OperationCode::PARAMETERS_EXIT);
+ add_relation(nodetree_key, simulation_eval_key, "NodeTree -> Simulation", 0);
+
+ LISTBASE_FOREACH (
+ PersistentDataHandleItem *, handle_item, &simulation->persistent_data_handles) {
+ if (handle_item->id == nullptr) {
+ continue;
+ }
+ build_id(handle_item->id);
+ if (GS(handle_item->id->name) == ID_OB) {
+ Object *object = (Object *)handle_item->id;
+ if (handle_item->flag & SIM_HANDLE_DEPENDS_ON_TRANSFORM) {
+ ComponentKey object_transform_key(&object->id, NodeType::TRANSFORM);
+ add_relation(object_transform_key, simulation_eval_key, "Object Transform -> Simulation");
+ }
+ if (handle_item->flag & SIM_HANDLE_DEPENDS_ON_GEOMETRY) {
+ ComponentKey object_geometry_key(&object->id, NodeType::GEOMETRY);
+ add_relation(object_geometry_key, simulation_eval_key, "Object Geometry -> Simulation");
+ }
+ }
+ }
}
void DepsgraphRelationBuilder::build_scene_sequencer(Scene *scene)
diff --git a/source/blender/depsgraph/intern/builder/deg_builder_relations.h b/source/blender/depsgraph/intern/builder/deg_builder_relations.h
index b4b0dc71f85..04f2a3f911d 100644
--- a/source/blender/depsgraph/intern/builder/deg_builder_relations.h
+++ b/source/blender/depsgraph/intern/builder/deg_builder_relations.h
@@ -46,6 +46,7 @@
#include "intern/node/deg_node_operation.h"
struct Base;
+struct bNodeSocket;
struct CacheFile;
struct Camera;
struct Collection;
@@ -275,6 +276,7 @@ class DepsgraphRelationBuilder : public DepsgraphBuilder {
virtual void build_camera(Camera *camera);
virtual void build_light(Light *lamp);
virtual void build_nodetree(bNodeTree *ntree);
+ virtual void build_nodetree_socket(bNodeSocket *socket);
virtual void build_material(Material *ma);
virtual void build_materials(Material **materials, int num_materials);
virtual void build_freestyle_lineset(FreestyleLineSet *fls);
diff --git a/source/blender/depsgraph/intern/depsgraph_tag.cc b/source/blender/depsgraph/intern/depsgraph_tag.cc
index 848275eb899..1863a333930 100644
--- a/source/blender/depsgraph/intern/depsgraph_tag.cc
+++ b/source/blender/depsgraph/intern/depsgraph_tag.cc
@@ -786,6 +786,7 @@ void DEG_graph_id_type_tag(Depsgraph *depsgraph, short id_type)
DEG_graph_id_type_tag(depsgraph, ID_LA);
DEG_graph_id_type_tag(depsgraph, ID_WO);
DEG_graph_id_type_tag(depsgraph, ID_SCE);
+ DEG_graph_id_type_tag(depsgraph, ID_SIM);
}
const int id_type_index = BKE_idtype_idcode_to_index(id_type);
deg::Depsgraph *deg_graph = reinterpret_cast<deg::Depsgraph *>(depsgraph);
diff --git a/source/blender/depsgraph/intern/eval/deg_eval_copy_on_write.cc b/source/blender/depsgraph/intern/eval/deg_eval_copy_on_write.cc
index 79d6c8d6a77..82cb311ec45 100644
--- a/source/blender/depsgraph/intern/eval/deg_eval_copy_on_write.cc
+++ b/source/blender/depsgraph/intern/eval/deg_eval_copy_on_write.cc
@@ -120,6 +120,7 @@ union NestedIDHackTempStorage {
Scene scene;
Tex tex;
World world;
+ Simulation simulation;
};
/* Set nested owned ID pointers to nullptr. */
@@ -137,6 +138,7 @@ void nested_id_hack_discard_pointers(ID *id_cow)
SPECIAL_CASE(ID_MA, Material, nodetree)
SPECIAL_CASE(ID_TE, Tex, nodetree)
SPECIAL_CASE(ID_WO, World, nodetree)
+ SPECIAL_CASE(ID_SIM, Simulation, nodetree)
SPECIAL_CASE(ID_CU, Curve, key)
SPECIAL_CASE(ID_LT, Lattice, key)
@@ -185,6 +187,7 @@ const ID *nested_id_hack_get_discarded_pointers(NestedIDHackTempStorage *storage
SPECIAL_CASE(ID_MA, Material, nodetree, material)
SPECIAL_CASE(ID_TE, Tex, nodetree, tex)
SPECIAL_CASE(ID_WO, World, nodetree, world)
+ SPECIAL_CASE(ID_SIM, Simulation, nodetree, simulation)
SPECIAL_CASE(ID_CU, Curve, key, curve)
SPECIAL_CASE(ID_LT, Lattice, key, lattice)
@@ -224,6 +227,7 @@ void nested_id_hack_restore_pointers(const ID *old_id, ID *new_id)
SPECIAL_CASE(ID_SCE, Scene, nodetree)
SPECIAL_CASE(ID_TE, Tex, nodetree)
SPECIAL_CASE(ID_WO, World, nodetree)
+ SPECIAL_CASE(ID_SIM, Simulation, nodetree)
SPECIAL_CASE(ID_CU, Curve, key)
SPECIAL_CASE(ID_LT, Lattice, key)
@@ -261,6 +265,7 @@ void ntree_hack_remap_pointers(const Depsgraph *depsgraph, ID *id_cow)
SPECIAL_CASE(ID_SCE, Scene, nodetree, bNodeTree)
SPECIAL_CASE(ID_TE, Tex, nodetree, bNodeTree)
SPECIAL_CASE(ID_WO, World, nodetree, bNodeTree)
+ SPECIAL_CASE(ID_SIM, Simulation, nodetree, bNodeTree)
SPECIAL_CASE(ID_CU, Curve, key, Key)
SPECIAL_CASE(ID_LT, Lattice, key, Key)
diff --git a/source/blender/editors/curve/editfont.c b/source/blender/editors/curve/editfont.c
index b759277572c..d78c543f94b 100644
--- a/source/blender/editors/curve/editfont.c
+++ b/source/blender/editors/curve/editfont.c
@@ -76,9 +76,9 @@ static int kill_selection(Object *obedit, int ins);
/** \name Internal Utilities
* \{ */
-static wchar_t findaccent(wchar_t char1, uint code)
+static char32_t findaccent(char32_t char1, uint code)
{
- wchar_t new = 0;
+ char32_t new = 0;
if (char1 == 'a') {
if (code == '`') {
@@ -682,7 +682,7 @@ static void txt_add_object(bContext *C, TextLine *firstline, int totline, const
cu->strinfo = MEM_callocN((nchars + 4) * sizeof(CharInfo), "strinfo");
cu->len = 0;
- cu->len_wchar = nchars - 1;
+ cu->len_char32 = nchars - 1;
cu->pos = 0;
s = cu->str;
@@ -703,7 +703,7 @@ static void txt_add_object(bContext *C, TextLine *firstline, int totline, const
}
}
- cu->pos = cu->len_wchar;
+ cu->pos = cu->len_char32;
*s = '\0';
WM_event_add_notifier(C, NC_OBJECT | NA_ADDED, obedit);
@@ -1661,7 +1661,7 @@ static int insert_text_invoke(bContext *C, wmOperator *op, const wmEvent *event)
uintptr_t ascii = event->ascii;
int alt = event->alt, shift = event->shift, ctrl = event->ctrl;
int event_type = event->type, event_val = event->val;
- wchar_t inserted_text[2] = {0};
+ char32_t inserted_text[2] = {0};
if (RNA_struct_property_is_set(op->ptr, "text")) {
return insert_text_exec(C, op);
@@ -1733,7 +1733,7 @@ static int insert_text_invoke(bContext *C, wmOperator *op, const wmEvent *event)
/* store as utf8 in RNA string */
char inserted_utf8[8] = {0};
- BLI_strncpy_wchar_as_utf8(inserted_utf8, inserted_text, sizeof(inserted_utf8));
+ BLI_str_utf32_as_utf8(inserted_utf8, inserted_text, sizeof(inserted_utf8));
RNA_string_set(op->ptr, "text", inserted_utf8);
}
@@ -1867,7 +1867,7 @@ void ED_curve_editfont_make(Object *obedit)
{
Curve *cu = obedit->data;
EditFont *ef = cu->editfont;
- int len_wchar;
+ int len_char32;
if (ef == NULL) {
ef = cu->editfont = MEM_callocN(sizeof(EditFont), "editfont");
@@ -1876,10 +1876,10 @@ void ED_curve_editfont_make(Object *obedit)
ef->textbufinfo = MEM_callocN((MAXTEXT + 4) * sizeof(CharInfo), "texteditbufinfo");
}
- /* Convert the original text to wchar_t */
- len_wchar = BLI_str_utf8_as_utf32(ef->textbuf, cu->str, MAXTEXT + 4);
- BLI_assert(len_wchar == cu->len_wchar);
- ef->len = len_wchar;
+ /* Convert the original text to chat32_t. */
+ len_char32 = BLI_str_utf8_as_utf32(ef->textbuf, cu->str, MAXTEXT + 4);
+ BLI_assert(len_char32 == cu->len_char32);
+ ef->len = len_char32;
BLI_assert(ef->len >= 0);
memcpy(ef->textbufinfo, cu->strinfo, ef->len * sizeof(CharInfo));
@@ -1908,7 +1908,7 @@ void ED_curve_editfont_load(Object *obedit)
MEM_freeN(cu->str);
/* Calculate the actual string length in UTF-8 variable characters */
- cu->len_wchar = ef->len;
+ cu->len_char32 = ef->len;
cu->len = BLI_str_utf32_as_utf8_len(ef->textbuf);
/* Alloc memory for UTF-8 variable char length string */
@@ -1920,8 +1920,8 @@ void ED_curve_editfont_load(Object *obedit)
if (cu->strinfo) {
MEM_freeN(cu->strinfo);
}
- cu->strinfo = MEM_callocN((cu->len_wchar + 4) * sizeof(CharInfo), "texteditinfo");
- memcpy(cu->strinfo, ef->textbufinfo, cu->len_wchar * sizeof(CharInfo));
+ cu->strinfo = MEM_callocN((cu->len_char32 + 4) * sizeof(CharInfo), "texteditinfo");
+ memcpy(cu->strinfo, ef->textbufinfo, cu->len_char32 * sizeof(CharInfo));
/* Other vars */
cu->pos = ef->pos;
diff --git a/source/blender/editors/gpencil/gpencil_interpolate.c b/source/blender/editors/gpencil/gpencil_interpolate.c
index 179f621205b..2d60d84bc19 100644
--- a/source/blender/editors/gpencil/gpencil_interpolate.c
+++ b/source/blender/editors/gpencil/gpencil_interpolate.c
@@ -136,15 +136,17 @@ static void gpencil_interpolate_free_temp_strokes(bGPDframe *gpf)
}
/* Helper: Untag all strokes. */
-static void gpencil_interpolate_untag_strokes(bGPDframe *gpf)
+static void gpencil_interpolate_untag_strokes(bGPDlayer *gpl)
{
- if (gpf == NULL) {
+ if (gpl == NULL) {
return;
}
- LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) {
- if (gps->flag & GP_STROKE_TAG) {
- gps->flag &= ~GP_STROKE_TAG;
+ LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) {
+ LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) {
+ if (gps->flag & GP_STROKE_TAG) {
+ gps->flag &= ~GP_STROKE_TAG;
+ }
}
}
}
@@ -263,15 +265,6 @@ static void gpencil_interpolate_set_points(bContext *C, tGPDinterpolate *tgpi)
/* set layers */
LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) {
tGPDinterpolate_layer *tgpil;
-
- /* Untag strokes to be sure nothing is pending. This must be done for
- * all layer because it could be anything tagged and it would be removed
- * at the end of the process when all tagged strokes are removed. */
- if (gpl->actframe != NULL) {
- gpencil_interpolate_untag_strokes(gpl->actframe);
- gpencil_interpolate_untag_strokes(gpl->actframe->next);
- }
-
/* all layers or only active */
if (!(tgpi->flag & GP_TOOLFLAG_INTERPOLATE_ALL_LAYERS) && (gpl != active_gpl)) {
continue;
@@ -483,6 +476,11 @@ static bool gpencil_interpolate_set_init_values(bContext *C, wmOperator *op, tGP
/* set layers */
gpencil_interpolate_set_points(C, tgpi);
+ /* Untag strokes to be sure nothing is pending due any canceled process. */
+ LISTBASE_FOREACH (bGPDlayer *, gpl, &tgpi->gpd->layers) {
+ gpencil_interpolate_untag_strokes(gpl);
+ }
+
return 1;
}
diff --git a/source/blender/editors/include/ED_buttons.h b/source/blender/editors/include/ED_buttons.h
index 2eaef5e82e0..455eee8580d 100644
--- a/source/blender/editors/include/ED_buttons.h
+++ b/source/blender/editors/include/ED_buttons.h
@@ -27,6 +27,10 @@
extern "C" {
#endif
+struct SpaceProperties;
+
+int ED_buttons_tabs_list(struct SpaceProperties *sbuts, int *context_tabs_array);
+
#ifdef __cplusplus
}
#endif
diff --git a/source/blender/editors/space_buttons/space_buttons.c b/source/blender/editors/space_buttons/space_buttons.c
index 88c2c6e82b6..8337c9b792a 100644
--- a/source/blender/editors/space_buttons/space_buttons.c
+++ b/source/blender/editors/space_buttons/space_buttons.c
@@ -35,6 +35,7 @@
#include "BKE_screen.h"
#include "BKE_shader_fx.h"
+#include "ED_buttons.h"
#include "ED_screen.h"
#include "ED_space_api.h"
#include "ED_view3d.h" /* To draw toolbar UI. */
@@ -139,6 +140,98 @@ static void buttons_main_region_init(wmWindowManager *wm, ARegion *region)
WM_event_add_keymap_handler(&region->handlers, keymap);
}
+/**
+ * Fills an array with the tab context values for the properties editor. -1 signals a separator.
+ *
+ * \return The total number of items in the array returned.
+ */
+int ED_buttons_tabs_list(SpaceProperties *sbuts, int *context_tabs_array)
+{
+ int length = 0;
+ if (sbuts->pathflag & (1 << BCONTEXT_TOOL)) {
+ context_tabs_array[length] = BCONTEXT_TOOL;
+ length++;
+ }
+ if (length != 0) {
+ context_tabs_array[length] = -1;
+ length++;
+ }
+ if (sbuts->pathflag & (1 << BCONTEXT_RENDER)) {
+ context_tabs_array[length] = BCONTEXT_RENDER;
+ length++;
+ }
+ if (sbuts->pathflag & (1 << BCONTEXT_OUTPUT)) {
+ context_tabs_array[length] = BCONTEXT_OUTPUT;
+ length++;
+ }
+ if (sbuts->pathflag & (1 << BCONTEXT_VIEW_LAYER)) {
+ context_tabs_array[length] = BCONTEXT_VIEW_LAYER;
+ length++;
+ }
+ if (sbuts->pathflag & (1 << BCONTEXT_SCENE)) {
+ context_tabs_array[length] = BCONTEXT_SCENE;
+ length++;
+ }
+ if (sbuts->pathflag & (1 << BCONTEXT_WORLD)) {
+ context_tabs_array[length] = BCONTEXT_WORLD;
+ length++;
+ }
+ if (length != 0) {
+ context_tabs_array[length] = -1;
+ length++;
+ }
+ if (sbuts->pathflag & (1 << BCONTEXT_OBJECT)) {
+ context_tabs_array[length] = BCONTEXT_OBJECT;
+ length++;
+ }
+ if (sbuts->pathflag & (1 << BCONTEXT_MODIFIER)) {
+ context_tabs_array[length] = BCONTEXT_MODIFIER;
+ length++;
+ }
+ if (sbuts->pathflag & (1 << BCONTEXT_SHADERFX)) {
+ context_tabs_array[length] = BCONTEXT_SHADERFX;
+ length++;
+ }
+ if (sbuts->pathflag & (1 << BCONTEXT_PARTICLE)) {
+ context_tabs_array[length] = BCONTEXT_PARTICLE;
+ length++;
+ }
+ if (sbuts->pathflag & (1 << BCONTEXT_PHYSICS)) {
+ context_tabs_array[length] = BCONTEXT_PHYSICS;
+ length++;
+ }
+ if (sbuts->pathflag & (1 << BCONTEXT_CONSTRAINT)) {
+ context_tabs_array[length] = BCONTEXT_CONSTRAINT;
+ length++;
+ }
+ if (sbuts->pathflag & (1 << BCONTEXT_DATA)) {
+ context_tabs_array[length] = BCONTEXT_DATA;
+ length++;
+ }
+ if (sbuts->pathflag & (1 << BCONTEXT_BONE)) {
+ context_tabs_array[length] = BCONTEXT_BONE;
+ length++;
+ }
+ if (sbuts->pathflag & (1 << BCONTEXT_BONE_CONSTRAINT)) {
+ context_tabs_array[length] = BCONTEXT_BONE_CONSTRAINT;
+ length++;
+ }
+ if (sbuts->pathflag & (1 << BCONTEXT_MATERIAL)) {
+ context_tabs_array[length] = BCONTEXT_MATERIAL;
+ length++;
+ }
+ if (length != 0) {
+ context_tabs_array[length] = -1;
+ length++;
+ }
+ if (sbuts->pathflag & (1 << BCONTEXT_TEXTURE)) {
+ context_tabs_array[length] = BCONTEXT_TEXTURE;
+ length++;
+ }
+
+ return length;
+}
+
static void buttons_main_region_layout_properties(const bContext *C,
SpaceProperties *sbuts,
ARegion *region)
diff --git a/source/blender/editors/space_node/node_edit.c b/source/blender/editors/space_node/node_edit.c
index 7af64e75656..3c927dbf25f 100644
--- a/source/blender/editors/space_node/node_edit.c
+++ b/source/blender/editors/space_node/node_edit.c
@@ -1697,6 +1697,8 @@ static int node_mute_exec(bContext *C, wmOperator *UNUSED(op))
}
}
+ do_tag_update |= ED_node_is_simulation(snode);
+
snode_notify(C, snode);
if (do_tag_update) {
snode_dag_update(C, snode);
@@ -1739,6 +1741,8 @@ static int node_delete_exec(bContext *C, wmOperator *UNUSED(op))
}
}
+ do_tag_update |= ED_node_is_simulation(snode);
+
ntreeUpdateTree(CTX_data_main(C), snode->edittree);
snode_notify(C, snode);
diff --git a/source/blender/editors/space_node/node_relationships.c b/source/blender/editors/space_node/node_relationships.c
index e5409271f7c..0a4607d2869 100644
--- a/source/blender/editors/space_node/node_relationships.c
+++ b/source/blender/editors/space_node/node_relationships.c
@@ -664,6 +664,8 @@ static void node_link_exit(bContext *C, wmOperator *op, bool apply_links)
}
ntree->is_updating = false;
+ do_tag_update |= ED_node_is_simulation(snode);
+
ntreeUpdateTree(bmain, ntree);
snode_notify(C, snode);
if (do_tag_update) {
@@ -1064,6 +1066,8 @@ static int cut_links_exec(bContext *C, wmOperator *op)
}
}
+ do_tag_update |= ED_node_is_simulation(snode);
+
if (found) {
ntreeUpdateTree(CTX_data_main(C), snode->edittree);
snode_notify(C, snode);
diff --git a/source/blender/editors/transform/transform.h b/source/blender/editors/transform/transform.h
index 2bda04ad811..bbd18b4bdaf 100644
--- a/source/blender/editors/transform/transform.h
+++ b/source/blender/editors/transform/transform.h
@@ -81,6 +81,7 @@ typedef struct TransSnap {
bool snap_self;
bool peel;
bool snap_spatial_grid;
+ bool use_backface_culling;
char status;
/* Snapped Element Type (currently for objects only). */
char snapElem;
diff --git a/source/blender/editors/transform/transform_snap.c b/source/blender/editors/transform/transform_snap.c
index 2943c3cb8ea..a700dd320b7 100644
--- a/source/blender/editors/transform/transform_snap.c
+++ b/source/blender/editors/transform/transform_snap.c
@@ -37,6 +37,7 @@
#include "BKE_editmesh.h"
#include "BKE_layer.h"
#include "BKE_object.h"
+#include "BKE_scene.h"
#include "BKE_sequencer.h"
#include "RNA_access.h"
@@ -100,6 +101,24 @@ int BIF_snappingSupported(Object *obedit)
}
#endif
+static bool snap_use_backface_culling(const TransInfo *t)
+{
+ BLI_assert(t->spacetype == SPACE_VIEW3D);
+ View3D *v3d = t->view;
+ if ((v3d->shading.type == OB_SOLID) && (v3d->shading.flag & V3D_SHADING_BACKFACE_CULLING)) {
+ return true;
+ }
+ if (v3d->shading.type == OB_RENDER &&
+ (t->scene->display.shading.flag & V3D_SHADING_BACKFACE_CULLING) &&
+ BKE_scene_uses_blender_workbench(t->scene)) {
+ return true;
+ }
+ if (t->settings->snap_flag & SCE_SNAP_BACKFACE_CULLING) {
+ return true;
+ }
+ return false;
+}
+
bool validSnap(const TransInfo *t)
{
return (t->tsnap.status & (POINT_INIT | TARGET_INIT)) == (POINT_INIT | TARGET_INIT) ||
@@ -315,8 +334,7 @@ void applyProject(TransInfo *t)
.snap_select = t->tsnap.modeSelect,
.use_object_edit_cage = (t->flag & T_EDIT) != 0,
.use_occlusion_test = false,
- .use_backface_culling = (t->scene->toolsettings->snap_flag &
- SCE_SNAP_BACKFACE_CULLING) != 0,
+ .use_backface_culling = t->tsnap.use_backface_culling,
},
mval_fl,
NULL,
@@ -601,6 +619,7 @@ static void initSnappingMode(TransInfo *t)
if (t->spacetype == SPACE_VIEW3D) {
if (t->tsnap.object_context == NULL) {
+ t->tsnap.use_backface_culling = snap_use_backface_culling(t);
t->tsnap.object_context = ED_transform_snap_object_context_create_view3d(
t->scene, 0, t->region, t->view);
@@ -1120,13 +1139,12 @@ short snapObjectsTransform(
return ED_transform_snap_object_project_view3d_ex(
t->tsnap.object_context,
t->depsgraph,
- t->scene->toolsettings->snap_mode,
+ t->settings->snap_mode,
&(const struct SnapObjectParams){
.snap_select = t->tsnap.modeSelect,
.use_object_edit_cage = (t->flag & T_EDIT) != 0,
- .use_occlusion_test = t->scene->toolsettings->snap_mode != SCE_SNAP_MODE_FACE,
- .use_backface_culling = (t->scene->toolsettings->snap_flag &
- SCE_SNAP_BACKFACE_CULLING) != 0,
+ .use_occlusion_test = t->settings->snap_mode != SCE_SNAP_MODE_FACE,
+ .use_backface_culling = t->tsnap.use_backface_culling,
},
mval,
target,
diff --git a/source/blender/functions/FN_array_spans.hh b/source/blender/functions/FN_array_spans.hh
index c362fef3630..5f976711e06 100644
--- a/source/blender/functions/FN_array_spans.hh
+++ b/source/blender/functions/FN_array_spans.hh
@@ -158,7 +158,7 @@ class GVArraySpan : public VArraySpanBase<void> {
this->type_ = &array.type();
this->virtual_size_ = virtual_size;
this->category_ = VArraySpanCategory::SingleArray;
- this->data_.single_array.start = array.buffer();
+ this->data_.single_array.start = array.data();
this->data_.single_array.size = array.size();
}
diff --git a/source/blender/functions/FN_multi_function_params.hh b/source/blender/functions/FN_multi_function_params.hh
index 93d7b47af83..ac4dca33cf0 100644
--- a/source/blender/functions/FN_multi_function_params.hh
+++ b/source/blender/functions/FN_multi_function_params.hh
@@ -50,52 +50,56 @@ class MFParamsBuilder {
MFParamsBuilder(const class MultiFunction &fn, int64_t min_array_size);
- template<typename T> void add_readonly_single_input(const T *value)
+ template<typename T> void add_readonly_single_input(const T *value, StringRef expected_name = "")
{
- this->add_readonly_single_input(GVSpan::FromSingle(CPPType::get<T>(), value, min_array_size_));
+ this->add_readonly_single_input(GVSpan::FromSingle(CPPType::get<T>(), value, min_array_size_),
+ expected_name);
}
- void add_readonly_single_input(GVSpan ref)
+ void add_readonly_single_input(GVSpan ref, StringRef expected_name = "")
{
- this->assert_current_param_type(MFParamType::ForSingleInput(ref.type()));
+ this->assert_current_param_type(MFParamType::ForSingleInput(ref.type()), expected_name);
BLI_assert(ref.size() >= min_array_size_);
virtual_spans_.append(ref);
}
- void add_readonly_vector_input(GVArraySpan ref)
+ void add_readonly_vector_input(GVArraySpan ref, StringRef expected_name = "")
{
- this->assert_current_param_type(MFParamType::ForVectorInput(ref.type()));
+ this->assert_current_param_type(MFParamType::ForVectorInput(ref.type()), expected_name);
BLI_assert(ref.size() >= min_array_size_);
virtual_array_spans_.append(ref);
}
- template<typename T> void add_uninitialized_single_output(T *value)
+ template<typename T> void add_uninitialized_single_output(T *value, StringRef expected_name = "")
{
- this->add_uninitialized_single_output(GMutableSpan(CPPType::get<T>(), value, 1));
+ this->add_uninitialized_single_output(GMutableSpan(CPPType::get<T>(), value, 1),
+ expected_name);
}
- void add_uninitialized_single_output(GMutableSpan ref)
+ void add_uninitialized_single_output(GMutableSpan ref, StringRef expected_name = "")
{
- this->assert_current_param_type(MFParamType::ForSingleOutput(ref.type()));
+ this->assert_current_param_type(MFParamType::ForSingleOutput(ref.type()), expected_name);
BLI_assert(ref.size() >= min_array_size_);
mutable_spans_.append(ref);
}
- void add_vector_output(GVectorArray &vector_array)
+ void add_vector_output(GVectorArray &vector_array, StringRef expected_name = "")
{
- this->assert_current_param_type(MFParamType::ForVectorOutput(vector_array.type()));
+ this->assert_current_param_type(MFParamType::ForVectorOutput(vector_array.type()),
+ expected_name);
BLI_assert(vector_array.size() >= min_array_size_);
vector_arrays_.append(&vector_array);
}
- void add_single_mutable(GMutableSpan ref)
+ void add_single_mutable(GMutableSpan ref, StringRef expected_name = "")
{
- this->assert_current_param_type(MFParamType::ForMutableSingle(ref.type()));
+ this->assert_current_param_type(MFParamType::ForMutableSingle(ref.type()), expected_name);
BLI_assert(ref.size() >= min_array_size_);
mutable_spans_.append(ref);
}
- void add_vector_mutable(GVectorArray &vector_array)
+ void add_vector_mutable(GVectorArray &vector_array, StringRef expected_name = "")
{
- this->assert_current_param_type(MFParamType::ForMutableVector(vector_array.type()));
+ this->assert_current_param_type(MFParamType::ForMutableVector(vector_array.type()),
+ expected_name);
BLI_assert(vector_array.size() >= min_array_size_);
vector_arrays_.append(&vector_array);
}
@@ -119,11 +123,17 @@ class MFParamsBuilder {
}
private:
- void assert_current_param_type(MFParamType param_type)
+ void assert_current_param_type(MFParamType param_type, StringRef expected_name = "")
{
- UNUSED_VARS_NDEBUG(param_type);
+ UNUSED_VARS_NDEBUG(param_type, expected_name);
#ifdef DEBUG
int param_index = this->current_param_index();
+
+ if (expected_name != "") {
+ StringRef actual_name = signature_->param_names[param_index];
+ BLI_assert(actual_name == expected_name);
+ }
+
MFParamType expected_type = signature_->param_types[param_index];
BLI_assert(expected_type == param_type);
#endif
diff --git a/source/blender/functions/FN_spans.hh b/source/blender/functions/FN_spans.hh
index d8b381199cc..c50c92cd16d 100644
--- a/source/blender/functions/FN_spans.hh
+++ b/source/blender/functions/FN_spans.hh
@@ -51,12 +51,12 @@ namespace blender::fn {
class GSpan {
private:
const CPPType *type_;
- const void *buffer_;
+ const void *data_;
int64_t size_;
public:
GSpan(const CPPType &type, const void *buffer, int64_t size)
- : type_(&type), buffer_(buffer), size_(size)
+ : type_(&type), data_(buffer), size_(size)
{
BLI_assert(size >= 0);
BLI_assert(buffer != nullptr || size == 0);
@@ -87,21 +87,21 @@ class GSpan {
return size_;
}
- const void *buffer() const
+ const void *data() const
{
- return buffer_;
+ return data_;
}
const void *operator[](int64_t index) const
{
BLI_assert(index < size_);
- return POINTER_OFFSET(buffer_, type_->size() * index);
+ return POINTER_OFFSET(data_, type_->size() * index);
}
template<typename T> Span<T> typed() const
{
BLI_assert(type_->is<T>());
- return Span<T>((const T *)buffer_, size_);
+ return Span<T>((const T *)data_, size_);
}
};
@@ -112,12 +112,12 @@ class GSpan {
class GMutableSpan {
private:
const CPPType *type_;
- void *buffer_;
+ void *data_;
int64_t size_;
public:
GMutableSpan(const CPPType &type, void *buffer, int64_t size)
- : type_(&type), buffer_(buffer), size_(size)
+ : type_(&type), data_(buffer), size_(size)
{
BLI_assert(size >= 0);
BLI_assert(buffer != nullptr || size == 0);
@@ -136,7 +136,7 @@ class GMutableSpan {
operator GSpan() const
{
- return GSpan(*type_, buffer_, size_);
+ return GSpan(*type_, data_, size_);
}
const CPPType &type() const
@@ -154,21 +154,21 @@ class GMutableSpan {
return size_;
}
- void *buffer()
+ void *data()
{
- return buffer_;
+ return data_;
}
void *operator[](int64_t index)
{
BLI_assert(index < size_);
- return POINTER_OFFSET(buffer_, type_->size() * index);
+ return POINTER_OFFSET(data_, type_->size() * index);
}
template<typename T> MutableSpan<T> typed()
{
BLI_assert(type_->is<T>());
- return MutableSpan<T>((T *)buffer_, size_);
+ return MutableSpan<T>((T *)data_, size_);
}
};
@@ -311,7 +311,7 @@ class GVSpan : public VSpanBase<void> {
this->type_ = &values.type();
this->virtual_size_ = values.size();
this->category_ = VSpanCategory::FullArray;
- this->data_.full_array.data = values.buffer();
+ this->data_.full_array.data = values.data();
}
GVSpan(GMutableSpan values) : GVSpan(GSpan(values))
diff --git a/source/blender/functions/intern/multi_function_builder.cc b/source/blender/functions/intern/multi_function_builder.cc
index 06084247e66..c9e8b88ba03 100644
--- a/source/blender/functions/intern/multi_function_builder.cc
+++ b/source/blender/functions/intern/multi_function_builder.cc
@@ -34,7 +34,7 @@ void CustomMF_GenericConstant::call(IndexMask mask,
MFContext UNUSED(context)) const
{
GMutableSpan output = params.uninitialized_single_output(0);
- type_.fill_uninitialized_indices(value_, output.buffer(), mask);
+ type_.fill_uninitialized_indices(value_, output.data(), mask);
}
uint64_t CustomMF_GenericConstant::hash() const
@@ -111,7 +111,7 @@ void CustomMF_DefaultOutput::call(IndexMask mask, MFParams params, MFContext UNU
if (param_type.data_type().is_single()) {
GMutableSpan span = params.uninitialized_single_output(param_index);
const CPPType &type = span.type();
- type.fill_uninitialized_indices(type.default_value(), span.buffer(), mask);
+ type.fill_uninitialized_indices(type.default_value(), span.data(), mask);
}
}
}
diff --git a/source/blender/functions/intern/multi_function_network_evaluation.cc b/source/blender/functions/intern/multi_function_network_evaluation.cc
index 58577e31c42..25e983d2eeb 100644
--- a/source/blender/functions/intern/multi_function_network_evaluation.cc
+++ b/source/blender/functions/intern/multi_function_network_evaluation.cc
@@ -390,7 +390,7 @@ BLI_NOINLINE void MFNetworkEvaluator::initialize_remaining_outputs(
case MFDataType::Single: {
GVSpan values = storage.get_single_input__full(*socket);
GMutableSpan output_values = params.uninitialized_single_output(param_index);
- values.materialize_to_uninitialized(storage.mask(), output_values.buffer());
+ values.materialize_to_uninitialized(storage.mask(), output_values.data());
break;
}
case MFDataType::Vector: {
@@ -524,11 +524,11 @@ MFNetworkEvaluationStorage::~MFNetworkEvaluationStorage()
GMutableSpan span = value->span;
const CPPType &type = span.type();
if (value->is_single_allocated) {
- type.destruct(span.buffer());
+ type.destruct(span.data());
}
else {
- type.destruct_indices(span.buffer(), mask_);
- MEM_freeN(span.buffer());
+ type.destruct_indices(span.data(), mask_);
+ MEM_freeN(span.data());
}
}
else if (any_value->type == ValueType::OwnVector) {
@@ -634,11 +634,11 @@ void MFNetworkEvaluationStorage::finish_input_socket(const MFInputSocket &socket
GMutableSpan span = value->span;
const CPPType &type = span.type();
if (value->is_single_allocated) {
- type.destruct(span.buffer());
+ type.destruct(span.data());
}
else {
- type.destruct_indices(span.buffer(), mask_);
- MEM_freeN(span.buffer());
+ type.destruct_indices(span.data(), mask_);
+ MEM_freeN(span.data());
}
value_per_output_id_[origin.id()] = nullptr;
}
@@ -791,7 +791,7 @@ GMutableSpan MFNetworkEvaluationStorage::get_mutable_single__full(const MFInputS
BLI_assert(to_any_value->type == ValueType::OutputSingle);
GMutableSpan span = ((OutputSingleValue *)to_any_value)->span;
GVSpan virtual_span = this->get_single_input__full(input);
- virtual_span.materialize_to_uninitialized(mask_, span.buffer());
+ virtual_span.materialize_to_uninitialized(mask_, span.data());
return span;
}
@@ -808,7 +808,7 @@ GMutableSpan MFNetworkEvaluationStorage::get_mutable_single__full(const MFInputS
GVSpan virtual_span = this->get_single_input__full(input);
void *new_buffer = MEM_mallocN_aligned(min_array_size_ * type.size(), type.alignment(), AT);
GMutableSpan new_array_ref(type, new_buffer, min_array_size_);
- virtual_span.materialize_to_uninitialized(mask_, new_array_ref.buffer());
+ virtual_span.materialize_to_uninitialized(mask_, new_array_ref.data());
OwnSingleValue *new_value = allocator_.construct<OwnSingleValue>(
new_array_ref, to.targets().size(), false);
@@ -953,7 +953,7 @@ GVSpan MFNetworkEvaluationStorage::get_single_input__full(const MFInputSocket &s
if (any_value->type == ValueType::OwnSingle) {
OwnSingleValue *value = (OwnSingleValue *)any_value;
if (value->is_single_allocated) {
- return GVSpan::FromSingle(value->span.type(), value->span.buffer(), min_array_size_);
+ return GVSpan::FromSingle(value->span.type(), value->span.data(), min_array_size_);
}
else {
return value->span;
diff --git a/source/blender/functions/intern/multi_function_network_optimization.cc b/source/blender/functions/intern/multi_function_network_optimization.cc
index f1e047f01a1..e8fd7afc2ee 100644
--- a/source/blender/functions/intern/multi_function_network_optimization.cc
+++ b/source/blender/functions/intern/multi_function_network_optimization.cc
@@ -265,7 +265,7 @@ static Array<MFOutputSocket *> add_constant_folded_sockets(const MultiFunction &
case MFDataType::Single: {
const CPPType &cpp_type = data_type.single_type();
GMutableSpan array = params.computed_array(param_index);
- void *buffer = array.buffer();
+ void *buffer = array.data();
resources.add(buffer, array.type().destruct_cb(), AT);
constant_fn = &resources.construct<CustomMF_GenericConstant>(AT, cpp_type, buffer);
diff --git a/source/blender/makesdna/DNA_curve_types.h b/source/blender/makesdna/DNA_curve_types.h
index b2902407a15..9f724973b6c 100644
--- a/source/blender/makesdna/DNA_curve_types.h
+++ b/source/blender/makesdna/DNA_curve_types.h
@@ -274,9 +274,12 @@ typedef struct Curve {
int selstart, selend;
/* text data */
- /** Number of characters (strinfo). */
- int len_wchar;
- /** Number of bytes (str - utf8). */
+ /**
+ * Number of characters (unicode code-points)
+ * This is the length of #Curve.strinfo and the result of `BLI_strlen_utf8(cu->str)`.
+ */
+ int len_char32;
+ /** Number of bytes: `strlen(Curve.str)`. */
int len;
char *str;
struct EditFont *editfont;
diff --git a/source/blender/makesdna/DNA_simulation_types.h b/source/blender/makesdna/DNA_simulation_types.h
index 5bb0e50e089..c0be66dcb30 100644
--- a/source/blender/makesdna/DNA_simulation_types.h
+++ b/source/blender/makesdna/DNA_simulation_types.h
@@ -72,7 +72,7 @@ typedef struct PersistentDataHandleItem {
struct PersistentDataHandleItem *prev;
struct ID *id;
int handle;
- char _pad[4];
+ int flag;
} PersistentDataHandleItem;
/* Simulation.flag */
@@ -80,6 +80,12 @@ enum {
SIM_DS_EXPAND = (1 << 0),
};
+/* PersistentDataHandleItem.flag */
+enum {
+ SIM_HANDLE_DEPENDS_ON_TRANSFORM = (1 << 0),
+ SIM_HANDLE_DEPENDS_ON_GEOMETRY = (1 << 1),
+};
+
#define SIM_TYPE_NAME_PARTICLE_SIMULATION "Particle Simulation"
#define SIM_TYPE_NAME_PARTICLE_MESH_EMITTER "Particle Mesh Emitter"
diff --git a/source/blender/makesdna/intern/dna_rename_defs.h b/source/blender/makesdna/intern/dna_rename_defs.h
index f2cf72843bd..49fa4f797bb 100644
--- a/source/blender/makesdna/intern/dna_rename_defs.h
+++ b/source/blender/makesdna/intern/dna_rename_defs.h
@@ -67,6 +67,7 @@ DNA_STRUCT_RENAME_ELEM(Bone, scaleOut, scale_out_x)
DNA_STRUCT_RENAME_ELEM(BrushGpencilSettings, gradient_f, hardeness)
DNA_STRUCT_RENAME_ELEM(BrushGpencilSettings, gradient_s, aspect_ratio)
DNA_STRUCT_RENAME_ELEM(Camera, YF_dofdist, dof_distance)
+DNA_STRUCT_RENAME_ELEM(Curve, len_wchar, len_char32)
DNA_STRUCT_RENAME_ELEM(Camera, clipend, clip_end)
DNA_STRUCT_RENAME_ELEM(Camera, clipsta, clip_start)
DNA_STRUCT_RENAME_ELEM(Collection, dupli_ofs, instance_offset)
diff --git a/source/blender/makesrna/intern/rna_curve.c b/source/blender/makesrna/intern/rna_curve.c
index 771235c85aa..8a3186ea7fe 100644
--- a/source/blender/makesrna/intern/rna_curve.c
+++ b/source/blender/makesrna/intern/rna_curve.c
@@ -570,7 +570,7 @@ static void rna_Curve_body_set(PointerRNA *ptr, const char *value)
Curve *cu = (Curve *)ptr->owner_id;
- cu->len_wchar = len_chars;
+ cu->len_char32 = len_chars;
cu->len = len_bytes;
cu->pos = len_chars;
@@ -1206,7 +1206,7 @@ static void rna_def_font(BlenderRNA *UNUSED(brna), StructRNA *srna)
RNA_def_property_update(prop, 0, "rna_Curve_update_data");
prop = RNA_def_property(srna, "body_format", PROP_COLLECTION, PROP_NONE);
- RNA_def_property_collection_sdna(prop, NULL, "strinfo", "len_wchar");
+ RNA_def_property_collection_sdna(prop, NULL, "strinfo", "len_char32");
RNA_def_property_struct_type(prop, "TextCharacterFormat");
RNA_def_property_ui_text(prop, "Character Info", "Stores the style of each character");
diff --git a/source/blender/makesrna/intern/rna_nodetree.c b/source/blender/makesrna/intern/rna_nodetree.c
index 6f0192bb810..0359b2ed959 100644
--- a/source/blender/makesrna/intern/rna_nodetree.c
+++ b/source/blender/makesrna/intern/rna_nodetree.c
@@ -38,6 +38,7 @@
#include "BKE_animsys.h"
#include "BKE_image.h"
#include "BKE_node.h"
+#include "BKE_simulation.h"
#include "BKE_texture.h"
#include "RNA_access.h"
@@ -2848,6 +2849,14 @@ static void rna_NodeSocketStandard_value_update(struct bContext *C, PointerRNA *
}
}
+static void rna_NodeSocketStandard_value_and_relation_update(struct bContext *C, PointerRNA *ptr)
+{
+ rna_NodeSocketStandard_value_update(C, ptr);
+ bNodeTree *ntree = (bNodeTree *)ptr->owner_id;
+ Main *bmain = CTX_data_main(C);
+ ntreeUpdateTree(bmain, ntree);
+}
+
/* ******** Node Types ******** */
static void rna_NodeInternalSocketTemplate_name_get(PointerRNA *ptr, char *value)
@@ -8862,7 +8871,8 @@ static void rna_def_node_socket_object(BlenderRNA *brna,
RNA_def_property_pointer_sdna(prop, NULL, "value");
RNA_def_property_struct_type(prop, "Object");
RNA_def_property_ui_text(prop, "Default Value", "Input value used for unconnected socket");
- RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_NodeSocketStandard_value_update");
+ RNA_def_property_update(
+ prop, NC_NODE | NA_EDITED, "rna_NodeSocketStandard_value_and_relation_update");
RNA_def_property_flag(prop, PROP_EDITABLE | PROP_ID_REFCOUNT | PROP_CONTEXT_UPDATE);
/* socket interface */
@@ -8896,7 +8906,8 @@ static void rna_def_node_socket_image(BlenderRNA *brna,
RNA_def_property_pointer_sdna(prop, NULL, "value");
RNA_def_property_struct_type(prop, "Image");
RNA_def_property_ui_text(prop, "Default Value", "Input value used for unconnected socket");
- RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_NodeSocketStandard_value_update");
+ RNA_def_property_update(
+ prop, NC_NODE | NA_EDITED, "rna_NodeSocketStandard_value_and_relation_update");
RNA_def_property_flag(prop, PROP_EDITABLE | PROP_ID_REFCOUNT | PROP_CONTEXT_UPDATE);
/* socket interface */
diff --git a/source/blender/makesrna/intern/rna_space.c b/source/blender/makesrna/intern/rna_space.c
index 49a21799d5b..466c1526893 100644
--- a/source/blender/makesrna/intern/rna_space.c
+++ b/source/blender/makesrna/intern/rna_space.c
@@ -1776,87 +1776,28 @@ static const EnumPropertyItem *rna_SpaceProperties_context_itemf(bContext *UNUSE
{
SpaceProperties *sbuts = (SpaceProperties *)(ptr->data);
EnumPropertyItem *item = NULL;
- int totitem = 0;
-
- if (sbuts->pathflag & (1 << BCONTEXT_TOOL)) {
- RNA_enum_items_add_value(&item, &totitem, buttons_context_items, BCONTEXT_TOOL);
- }
-
- if (totitem) {
- RNA_enum_item_add_separator(&item, &totitem);
- }
-
- if (sbuts->pathflag & (1 << BCONTEXT_RENDER)) {
- RNA_enum_items_add_value(&item, &totitem, buttons_context_items, BCONTEXT_RENDER);
- }
-
- if (sbuts->pathflag & (1 << BCONTEXT_OUTPUT)) {
- RNA_enum_items_add_value(&item, &totitem, buttons_context_items, BCONTEXT_OUTPUT);
- }
-
- if (sbuts->pathflag & (1 << BCONTEXT_VIEW_LAYER)) {
- RNA_enum_items_add_value(&item, &totitem, buttons_context_items, BCONTEXT_VIEW_LAYER);
- }
-
- if (sbuts->pathflag & (1 << BCONTEXT_SCENE)) {
- RNA_enum_items_add_value(&item, &totitem, buttons_context_items, BCONTEXT_SCENE);
- }
-
- if (sbuts->pathflag & (1 << BCONTEXT_WORLD)) {
- RNA_enum_items_add_value(&item, &totitem, buttons_context_items, BCONTEXT_WORLD);
- }
- if (totitem) {
- RNA_enum_item_add_separator(&item, &totitem);
- }
-
- if (sbuts->pathflag & (1 << BCONTEXT_OBJECT)) {
- RNA_enum_items_add_value(&item, &totitem, buttons_context_items, BCONTEXT_OBJECT);
- }
-
- if (sbuts->pathflag & (1 << BCONTEXT_MODIFIER)) {
- RNA_enum_items_add_value(&item, &totitem, buttons_context_items, BCONTEXT_MODIFIER);
- }
-
- if (sbuts->pathflag & (1 << BCONTEXT_SHADERFX)) {
- RNA_enum_items_add_value(&item, &totitem, buttons_context_items, BCONTEXT_SHADERFX);
- }
-
- if (sbuts->pathflag & (1 << BCONTEXT_PARTICLE)) {
- RNA_enum_items_add_value(&item, &totitem, buttons_context_items, BCONTEXT_PARTICLE);
- }
-
- if (sbuts->pathflag & (1 << BCONTEXT_PHYSICS)) {
- RNA_enum_items_add_value(&item, &totitem, buttons_context_items, BCONTEXT_PHYSICS);
- }
-
- if (sbuts->pathflag & (1 << BCONTEXT_CONSTRAINT)) {
- RNA_enum_items_add_value(&item, &totitem, buttons_context_items, BCONTEXT_CONSTRAINT);
- }
-
- if (sbuts->pathflag & (1 << BCONTEXT_DATA)) {
- RNA_enum_items_add_value(&item, &totitem, buttons_context_items, BCONTEXT_DATA);
- (item + totitem - 1)->icon = sbuts->dataicon;
- }
-
- if (sbuts->pathflag & (1 << BCONTEXT_BONE)) {
- RNA_enum_items_add_value(&item, &totitem, buttons_context_items, BCONTEXT_BONE);
- }
-
- if (sbuts->pathflag & (1 << BCONTEXT_BONE_CONSTRAINT)) {
- RNA_enum_items_add_value(&item, &totitem, buttons_context_items, BCONTEXT_BONE_CONSTRAINT);
- }
-
- if (sbuts->pathflag & (1 << BCONTEXT_MATERIAL)) {
- RNA_enum_items_add_value(&item, &totitem, buttons_context_items, BCONTEXT_MATERIAL);
- }
+ /* We use 32 tabs maximum here so a flag for each can fit into a 32 bit integer flag.
+ * A theoretical maximum would be BCONTEXT_TOT * 2, with every tab displayed and a spacer
+ * in every other item. But this size is currently limited by the size of integer
+ * supported by RNA enums. */
+ int context_tabs_array[32];
+ int totitem = ED_buttons_tabs_list(sbuts, context_tabs_array);
+ BLI_assert(totitem <= ARRAY_SIZE(context_tabs_array));
+
+ int totitem_added = 0;
+ for (int i = 0; i < totitem; i++) {
+ if (context_tabs_array[i] == -1) {
+ RNA_enum_item_add_separator(&item, &totitem_added);
+ continue;
+ }
- if (totitem) {
- RNA_enum_item_add_separator(&item, &totitem);
- }
+ RNA_enum_items_add_value(&item, &totitem_added, buttons_context_items, context_tabs_array[i]);
- if (sbuts->pathflag & (1 << BCONTEXT_TEXTURE)) {
- RNA_enum_items_add_value(&item, &totitem, buttons_context_items, BCONTEXT_TEXTURE);
+ /* Add the object data icon dynamically for the data tab. */
+ if (context_tabs_array[i] == BCONTEXT_DATA) {
+ (item + totitem_added - 1)->icon = sbuts->dataicon;
+ }
}
RNA_enum_item_end(&item, &totitem);
diff --git a/source/blender/modifiers/intern/MOD_simulation.cc b/source/blender/modifiers/intern/MOD_simulation.cc
index d9cc9840e08..85d9b47a861 100644
--- a/source/blender/modifiers/intern/MOD_simulation.cc
+++ b/source/blender/modifiers/intern/MOD_simulation.cc
@@ -131,6 +131,9 @@ static void panel_draw(const bContext *C, Panel *panel)
PointerRNA ob_ptr;
modifier_panel_get_property_pointers(C, panel, &ob_ptr, &ptr);
+ uiLayoutSetPropSep(layout, true);
+ uiLayoutSetPropDecorate(layout, false);
+
uiItemR(layout, &ptr, "simulation", 0, NULL, ICON_NONE);
uiItemR(layout, &ptr, "data_path", 0, NULL, ICON_NONE);
diff --git a/source/blender/nodes/CMakeLists.txt b/source/blender/nodes/CMakeLists.txt
index 80720f5206a..bb2aaf35e52 100644
--- a/source/blender/nodes/CMakeLists.txt
+++ b/source/blender/nodes/CMakeLists.txt
@@ -278,6 +278,7 @@ set(SRC
intern/node_common.c
intern/node_exec.c
intern/node_socket.cc
+ intern/node_tree_dependencies.cc
intern/node_tree_multi_function.cc
intern/node_tree_ref.cc
intern/node_util.c
@@ -292,6 +293,7 @@ set(SRC
NOD_composite.h
NOD_derived_node_tree.hh
NOD_function.h
+ NOD_node_tree_dependencies.hh
NOD_node_tree_multi_function.hh
NOD_node_tree_ref.hh
NOD_shader.h
diff --git a/source/blender/nodes/NOD_node_tree_dependencies.hh b/source/blender/nodes/NOD_node_tree_dependencies.hh
new file mode 100644
index 00000000000..ca7059caa5f
--- /dev/null
+++ b/source/blender/nodes/NOD_node_tree_dependencies.hh
@@ -0,0 +1,79 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __NOD_NODE_TREE_DEPENDENCIES_H__
+#define __NOD_NODE_TREE_DEPENDENCIES_H__
+
+#include "BLI_vector_set.hh"
+
+#include "DNA_ID.h"
+#include "DNA_object_types.h"
+
+struct bNodeTree;
+
+namespace blender::nodes {
+
+class NodeTreeDependencies {
+ private:
+ VectorSet<Object *> transform_deps_;
+ VectorSet<Object *> geometry_deps_;
+ VectorSet<ID *> id_deps_;
+
+ public:
+ void add_transform_dependency(Object *object)
+ {
+ if (object == nullptr) {
+ return;
+ }
+ transform_deps_.add(object);
+ id_deps_.add(&object->id);
+ }
+
+ void add_geometry_dependency(Object *object)
+ {
+ if (object == nullptr) {
+ return;
+ }
+ geometry_deps_.add(object);
+ id_deps_.add(&object->id);
+ }
+
+ bool depends_on(ID *id) const
+ {
+ return id_deps_.contains(id);
+ }
+
+ Span<Object *> transform_dependencies()
+ {
+ return transform_deps_;
+ }
+
+ Span<Object *> geometry_dependencies()
+ {
+ return geometry_deps_;
+ }
+
+ Span<ID *> id_dependencies()
+ {
+ return id_deps_;
+ }
+};
+
+NodeTreeDependencies find_node_tree_dependencies(bNodeTree &ntree);
+
+} // namespace blender::nodes
+
+#endif /* __NOD_NODE_TREE_DEPENDENCIES_H__ */
diff --git a/source/blender/nodes/intern/node_tree_dependencies.cc b/source/blender/nodes/intern/node_tree_dependencies.cc
new file mode 100644
index 00000000000..efe75a10f7e
--- /dev/null
+++ b/source/blender/nodes/intern/node_tree_dependencies.cc
@@ -0,0 +1,57 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include "NOD_node_tree_dependencies.hh"
+
+#include "DNA_node_types.h"
+
+#include "BKE_node.h"
+
+namespace blender::nodes {
+
+static void add_dependencies_of_node_tree(bNodeTree &ntree, NodeTreeDependencies &r_dependencies)
+{
+ /* TODO: Do a bit more sophisticated parsing to see which dependencies are really required. */
+ LISTBASE_FOREACH (bNode *, node, &ntree.nodes) {
+ LISTBASE_FOREACH (bNodeSocket *, socket, &node->inputs) {
+ if (socket->type == SOCK_OBJECT) {
+ Object *object = ((bNodeSocketValueObject *)socket->default_value)->value;
+ if (object != nullptr) {
+ r_dependencies.add_transform_dependency(object);
+ if (object->type == OB_MESH) {
+ r_dependencies.add_geometry_dependency(object);
+ }
+ }
+ }
+ }
+
+ if (node->type == NODE_GROUP) {
+ bNodeTree *group = (bNodeTree *)node->id;
+ if (group != nullptr) {
+ add_dependencies_of_node_tree(*group, r_dependencies);
+ }
+ }
+ }
+}
+
+NodeTreeDependencies find_node_tree_dependencies(bNodeTree &ntree)
+{
+ NodeTreeDependencies dependencies;
+ add_dependencies_of_node_tree(ntree, dependencies);
+ return dependencies;
+}
+
+} // namespace blender::nodes
diff --git a/source/blender/python/generic/py_capi_utils.c b/source/blender/python/generic/py_capi_utils.c
index 9c84a4bb824..f46588206ee 100644
--- a/source/blender/python/generic/py_capi_utils.c
+++ b/source/blender/python/generic/py_capi_utils.c
@@ -804,8 +804,12 @@ void PyC_MainModule_Restore(PyObject *main_mod)
Py_XDECREF(main_mod);
}
-/* Must be called before Py_Initialize,
- * expects output of BKE_appdir_folder_id(BLENDER_PYTHON, NULL). */
+/**
+ * - Must be called before #Py_Initialize.
+ * - Expects output of `BKE_appdir_folder_id(BLENDER_PYTHON, NULL)`.
+ * - Note that the `PYTHONPATH` environment variable isn't reliable, see T31506.
+ Use #Py_SetPythonHome instead.
+ */
void PyC_SetHomePath(const char *py_path_bundle)
{
if (py_path_bundle == NULL) {
@@ -824,24 +828,14 @@ void PyC_SetHomePath(const char *py_path_bundle)
/* OSX allow file/directory names to contain : character (represented as / in the Finder)
* but current Python lib (release 3.1.1) doesn't handle these correctly */
if (strchr(py_path_bundle, ':')) {
- printf(
- "Warning : Blender application is located in a path containing : or / chars\
- \nThis may make python import function fail\n");
- }
-# endif
-
-# if 0 /* disable for now [#31506] - campbell */
-# ifdef _WIN32
- /* cmake/MSVC debug build crashes without this, why only
- * in this case is unknown.. */
- {
- /*BLI_setenv("PYTHONPATH", py_path_bundle)*/;
+ fprintf(stderr,
+ "Warning! Blender application is located in a path containing ':' or '/' chars\n"
+ "This may make python import function fail\n");
}
-# endif
# endif
{
- static wchar_t py_path_bundle_wchar[1024];
+ wchar_t py_path_bundle_wchar[1024];
/* Can't use this, on linux gives bug: #23018,
* TODO: try LANG="en_US.UTF-8" /usr/bin/blender, suggested 2008 */
diff --git a/source/blender/python/intern/bpy_interface.c b/source/blender/python/intern/bpy_interface.c
index a880d2cd285..c40b2ded8ff 100644
--- a/source/blender/python/intern/bpy_interface.c
+++ b/source/blender/python/intern/bpy_interface.c
@@ -258,11 +258,13 @@ void BPY_python_start(int argc, const char **argv)
PyThreadState *py_tstate = NULL;
const char *py_path_bundle = BKE_appdir_folder_id(BLENDER_SYSTEM_PYTHON, NULL);
- /* not essential but nice to set our name */
- static wchar_t program_path_wchar[FILE_MAX]; /* python holds a reference */
- BLI_strncpy_wchar_from_utf8(
- program_path_wchar, BKE_appdir_program_path(), ARRAY_SIZE(program_path_wchar));
- Py_SetProgramName(program_path_wchar);
+ /* Not essential but nice to set our name. */
+ {
+ const char *program_path = BKE_appdir_program_path();
+ wchar_t program_path_wchar[FILE_MAX];
+ BLI_strncpy_wchar_from_utf8(program_path_wchar, program_path, ARRAY_SIZE(program_path_wchar));
+ Py_SetProgramName(program_path_wchar);
+ }
/* must run before python initializes */
PyImport_ExtendInittab(bpy_internal_modules);
diff --git a/source/blender/simulation/CMakeLists.txt b/source/blender/simulation/CMakeLists.txt
index 243b056db74..6466a6e67d4 100644
--- a/source/blender/simulation/CMakeLists.txt
+++ b/source/blender/simulation/CMakeLists.txt
@@ -43,6 +43,7 @@ set(SRC
intern/implicit_eigen.cpp
intern/particle_allocator.cc
intern/particle_function.cc
+ intern/particle_mesh_emitter.cc
intern/simulation_collect_influences.cc
intern/simulation_solver.cc
intern/simulation_update.cc
@@ -52,7 +53,9 @@ set(SRC
intern/implicit.h
intern/particle_allocator.hh
intern/particle_function.hh
+ intern/particle_mesh_emitter.hh
intern/simulation_collect_influences.hh
+ intern/simulation_solver_influences.hh
intern/simulation_solver.hh
intern/time_interval.hh
diff --git a/source/blender/simulation/SIM_simulation_update.hh b/source/blender/simulation/SIM_simulation_update.hh
index 40b62bfb58a..efdfde8a4de 100644
--- a/source/blender/simulation/SIM_simulation_update.hh
+++ b/source/blender/simulation/SIM_simulation_update.hh
@@ -27,6 +27,8 @@ void update_simulation_in_depsgraph(Depsgraph *depsgraph,
Scene *scene_cow,
Simulation *simulation_cow);
-}
+bool update_simulation_dependencies(Simulation *simulation);
+
+} // namespace blender::sim
#endif /* __SIM_SIMULATION_UPDATE_HH__ */
diff --git a/source/blender/simulation/intern/particle_allocator.cc b/source/blender/simulation/intern/particle_allocator.cc
index eb1e998e63a..5ff5909f506 100644
--- a/source/blender/simulation/intern/particle_allocator.cc
+++ b/source/blender/simulation/intern/particle_allocator.cc
@@ -68,7 +68,7 @@ fn::MutableAttributesRef ParticleAllocator::allocate(int size)
}
}
else {
- type.fill_uninitialized(info.default_of(i), attributes.get(i).buffer(), size);
+ type.fill_uninitialized(info.default_of(i), attributes.get(i).data(), size);
}
}
return attributes;
diff --git a/source/blender/simulation/intern/particle_mesh_emitter.cc b/source/blender/simulation/intern/particle_mesh_emitter.cc
new file mode 100644
index 00000000000..ab2ee0c81ed
--- /dev/null
+++ b/source/blender/simulation/intern/particle_mesh_emitter.cc
@@ -0,0 +1,147 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include "particle_mesh_emitter.hh"
+
+#include "BLI_float4x4.hh"
+#include "BLI_rand.hh"
+
+#include "DNA_mesh_types.h"
+#include "DNA_meshdata_types.h"
+#include "DNA_object_types.h"
+
+namespace blender::sim {
+
+struct EmitterSettings {
+ const Object *object;
+ float rate;
+};
+
+static void compute_birth_times(float rate,
+ TimeInterval emit_interval,
+ ParticleMeshEmitterSimulationState &state,
+ Vector<float> &r_birth_times)
+{
+ const float time_between_particles = 1.0f / rate;
+ int counter = 0;
+ while (true) {
+ counter++;
+ const float time_offset = counter * time_between_particles;
+ const float birth_time = state.last_birth_time + time_offset;
+ if (birth_time > emit_interval.end()) {
+ break;
+ }
+ if (birth_time <= emit_interval.start()) {
+ continue;
+ }
+ r_birth_times.append(birth_time);
+ }
+}
+
+static void compute_new_particle_attributes(EmitterSettings &settings,
+ TimeInterval emit_interval,
+ ParticleMeshEmitterSimulationState &state,
+ Vector<float3> &r_positions,
+ Vector<float3> &r_velocities,
+ Vector<float> &r_birth_times)
+{
+ if (settings.object == nullptr) {
+ return;
+ }
+ if (settings.rate <= 0.000001f) {
+ return;
+ }
+ if (settings.object->type != OB_MESH) {
+ return;
+ }
+ const Mesh &mesh = *(Mesh *)settings.object->data;
+ if (mesh.totvert == 0) {
+ return;
+ }
+
+ float4x4 local_to_world = settings.object->obmat;
+
+ const float start_time = emit_interval.start();
+ const uint32_t seed = DefaultHash<StringRef>{}(state.head.name);
+ RandomNumberGenerator rng{(*(uint32_t *)&start_time) ^ seed};
+
+ compute_birth_times(settings.rate, emit_interval, state, r_birth_times);
+ if (r_birth_times.is_empty()) {
+ return;
+ }
+
+ state.last_birth_time = r_birth_times.last();
+
+ for (int i : r_birth_times.index_range()) {
+ UNUSED_VARS(i);
+ const int vertex_index = rng.get_int32() % mesh.totvert;
+ float3 vertex_position = mesh.mvert[vertex_index].co;
+ r_positions.append(local_to_world * vertex_position);
+ r_velocities.append(rng.get_unit_float3());
+ }
+}
+
+static EmitterSettings compute_settings(const fn::MultiFunction &inputs_fn,
+ ParticleEmitterContext &context)
+{
+ EmitterSettings parameters;
+
+ fn::MFContextBuilder mf_context;
+ mf_context.add_global_context("PersistentDataHandleMap", &context.solve_context().handle_map());
+
+ fn::MFParamsBuilder mf_params{inputs_fn, 1};
+ bke::PersistentObjectHandle object_handle;
+ mf_params.add_uninitialized_single_output(&object_handle, "Object");
+ mf_params.add_uninitialized_single_output(&parameters.rate, "Rate");
+
+ inputs_fn.call(IndexRange(1), mf_params, mf_context);
+
+ parameters.object = context.solve_context().handle_map().lookup(object_handle);
+ return parameters;
+}
+
+void ParticleMeshEmitter::emit(ParticleEmitterContext &context) const
+{
+ auto *state = context.lookup_state<ParticleMeshEmitterSimulationState>(own_state_name_);
+ if (state == nullptr) {
+ return;
+ }
+
+ EmitterSettings settings = compute_settings(inputs_fn_, context);
+
+ Vector<float3> new_positions;
+ Vector<float3> new_velocities;
+ Vector<float> new_birth_times;
+
+ compute_new_particle_attributes(
+ settings, context.emit_interval(), *state, new_positions, new_velocities, new_birth_times);
+
+ for (StringRef name : particle_names_) {
+ ParticleAllocator *allocator = context.try_get_particle_allocator(name);
+ if (allocator == nullptr) {
+ continue;
+ }
+
+ int amount = new_positions.size();
+ fn::MutableAttributesRef attributes = allocator->allocate(amount);
+
+ attributes.get<float3>("Position").copy_from(new_positions);
+ attributes.get<float3>("Velocity").copy_from(new_velocities);
+ attributes.get<float>("Birth Time").copy_from(new_birth_times);
+ }
+}
+
+} // namespace blender::sim
diff --git a/source/blender/simulation/intern/particle_mesh_emitter.hh b/source/blender/simulation/intern/particle_mesh_emitter.hh
new file mode 100644
index 00000000000..8ad7b6f15f2
--- /dev/null
+++ b/source/blender/simulation/intern/particle_mesh_emitter.hh
@@ -0,0 +1,47 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __SIM_PARTICLE_MESH_EMITTER_HH__
+#define __SIM_PARTICLE_MESH_EMITTER_HH__
+
+#include "simulation_solver_influences.hh"
+
+#include "FN_multi_function.hh"
+
+namespace blender::sim {
+
+class ParticleMeshEmitter final : public ParticleEmitter {
+ private:
+ std::string own_state_name_;
+ Array<std::string> particle_names_;
+ const fn::MultiFunction &inputs_fn_;
+
+ public:
+ ParticleMeshEmitter(std::string own_state_name,
+ Array<std::string> particle_names,
+ const fn::MultiFunction &inputs_fn)
+ : own_state_name_(std::move(own_state_name)),
+ particle_names_(particle_names),
+ inputs_fn_(inputs_fn)
+ {
+ }
+
+ void emit(ParticleEmitterContext &context) const override;
+};
+
+} // namespace blender::sim
+
+#endif /* __SIM_PARTICLE_MESH_EMITTER_HH__ */
diff --git a/source/blender/simulation/intern/simulation_collect_influences.cc b/source/blender/simulation/intern/simulation_collect_influences.cc
index 764e587d157..eebfe8b864e 100644
--- a/source/blender/simulation/intern/simulation_collect_influences.cc
+++ b/source/blender/simulation/intern/simulation_collect_influences.cc
@@ -16,6 +16,7 @@
#include "simulation_collect_influences.hh"
#include "particle_function.hh"
+#include "particle_mesh_emitter.hh"
#include "FN_attributes_ref.hh"
#include "FN_multi_function_network_evaluation.hh"
@@ -23,6 +24,8 @@
#include "NOD_node_tree_multi_function.hh"
+#include "DEG_depsgraph_query.h"
+
#include "BLI_rand.hh"
namespace blender::sim {
@@ -277,83 +280,6 @@ static void collect_forces(nodes::MFNetworkTreeMap &network_map,
}
}
-class MyBasicEmitter : public ParticleEmitter {
- private:
- Array<std::string> names_;
- std::string my_state_;
- const fn::MultiFunction &inputs_fn_;
- uint32_t seed_;
-
- public:
- MyBasicEmitter(Array<std::string> names,
- std::string my_state,
- const fn::MultiFunction &inputs_fn,
- uint32_t seed)
- : names_(std::move(names)),
- my_state_(std::move(my_state)),
- inputs_fn_(inputs_fn),
- seed_(seed)
- {
- }
-
- void emit(ParticleEmitterContext &context) const override
- {
- auto *state = context.solve_context().state_map().lookup<ParticleMeshEmitterSimulationState>(
- my_state_);
- if (state == nullptr) {
- return;
- }
-
- fn::MFContextBuilder mf_context;
- mf_context.add_global_context("PersistentDataHandleMap",
- &context.solve_context().handle_map());
-
- fn::MFParamsBuilder mf_params{inputs_fn_, 1};
- bke::PersistentObjectHandle object_handle;
- float rate;
- mf_params.add_uninitialized_single_output(&object_handle);
- mf_params.add_uninitialized_single_output(&rate);
- inputs_fn_.call(IndexRange(1), mf_params, mf_context);
-
- const Object *object = context.solve_context().handle_map().lookup(object_handle);
- if (object == nullptr) {
- return;
- }
-
- Vector<float3> new_positions;
- Vector<float3> new_velocities;
- Vector<float> new_birth_times;
-
- TimeInterval time_interval = context.simulation_time_interval();
- float start_time = time_interval.start();
- RandomNumberGenerator rng{(*(uint32_t *)&start_time) ^ seed_};
-
- const float time_between_particles = 1.0f / rate;
- while (state->last_birth_time + time_between_particles < time_interval.end()) {
- new_positions.append(rng.get_unit_float3() * 0.3 + float3(object->loc));
- new_velocities.append(rng.get_unit_float3());
- const float birth_time = state->last_birth_time + time_between_particles;
- new_birth_times.append(birth_time);
- state->last_birth_time = birth_time;
- }
-
- for (StringRef name : names_) {
- ParticleAllocator *allocator = context.try_get_particle_allocator(name);
- if (allocator == nullptr) {
- return;
- }
-
- int amount = new_positions.size();
- fn::MutableAttributesRef attributes = allocator->allocate(amount);
-
- initialized_copy_n(new_positions.data(), amount, attributes.get<float3>("Position").data());
- initialized_copy_n(new_velocities.data(), amount, attributes.get<float3>("Velocity").data());
- initialized_copy_n(
- new_birth_times.data(), amount, attributes.get<float>("Birth Time").data());
- }
- }
-};
-
static Vector<const nodes::DNode *> find_linked_particle_simulations(
const nodes::DOutputSocket &output_socket)
{
@@ -394,11 +320,10 @@ static ParticleEmitter *create_particle_emitter(const nodes::DNode &dnode,
fn::MultiFunction &inputs_fn = resources.construct<fn::MFNetworkEvaluator>(
AT, Span<const fn::MFOutputSocket *>(), input_sockets.as_span());
- std::string my_state_name = dnode_to_path(dnode);
- r_required_states.add(my_state_name, SIM_TYPE_NAME_PARTICLE_MESH_EMITTER);
- uint32_t seed = DefaultHash<std::string>{}(my_state_name);
- ParticleEmitter &emitter = resources.construct<MyBasicEmitter>(
- AT, std::move(names), std::move(my_state_name), inputs_fn, seed);
+ std::string own_state_name = dnode_to_path(dnode);
+ r_required_states.add(own_state_name, SIM_TYPE_NAME_PARTICLE_MESH_EMITTER);
+ ParticleEmitter &emitter = resources.construct<ParticleMeshEmitter>(
+ AT, std::move(own_state_name), std::move(names), inputs_fn);
return &emitter;
}
@@ -434,23 +359,6 @@ static void prepare_particle_attribute_builders(nodes::MFNetworkTreeMap &network
}
}
-static void find_used_data_blocks(const nodes::DerivedNodeTree &tree,
- SimulationInfluences &r_influences)
-{
- const bNodeSocketType *socktype = nodeSocketTypeFind("NodeSocketObject");
- BLI_assert(socktype != nullptr);
-
- for (const nodes::DInputSocket *dsocket : tree.input_sockets()) {
- const bNodeSocket *bsocket = dsocket->bsocket();
- if (bsocket->typeinfo == socktype) {
- Object *value = ((const bNodeSocketValueObject *)bsocket->default_value)->value;
- if (value != nullptr) {
- r_influences.used_data_blocks.add(&value->id);
- }
- }
- }
-}
-
void collect_simulation_influences(Simulation &simulation,
ResourceCollector &resources,
SimulationInfluences &r_influences,
@@ -478,8 +386,6 @@ void collect_simulation_influences(Simulation &simulation,
for (const nodes::DNode *dnode : get_particle_simulation_nodes(tree)) {
r_required_states.add(dnode_to_path(*dnode), SIM_TYPE_NAME_PARTICLE_SIMULATION);
}
-
- find_used_data_blocks(tree, r_influences);
}
} // namespace blender::sim
diff --git a/source/blender/simulation/intern/simulation_collect_influences.hh b/source/blender/simulation/intern/simulation_collect_influences.hh
index 42cbea6977e..caf5a8c4ffa 100644
--- a/source/blender/simulation/intern/simulation_collect_influences.hh
+++ b/source/blender/simulation/intern/simulation_collect_influences.hh
@@ -21,7 +21,7 @@
#include "BLI_resource_collector.hh"
-#include "simulation_solver.hh"
+#include "simulation_solver_influences.hh"
namespace blender::sim {
diff --git a/source/blender/simulation/intern/simulation_solver.cc b/source/blender/simulation/intern/simulation_solver.cc
index ee7a8d40035..2bd9e72eeac 100644
--- a/source/blender/simulation/intern/simulation_solver.cc
+++ b/source/blender/simulation/intern/simulation_solver.cc
@@ -17,12 +17,13 @@
#include "simulation_solver.hh"
#include "BKE_customdata.h"
-#include "BKE_lib_id.h"
#include "BKE_persistent_data_handle.hh"
#include "BLI_rand.hh"
#include "BLI_set.hh"
+#include "DEG_depsgraph_query.h"
+
namespace blender::sim {
ParticleForce::~ParticleForce()
@@ -233,7 +234,7 @@ BLI_NOINLINE static void remove_dead_and_add_new_particles(ParticleSimulationSta
if (layer.data != nullptr) {
MEM_freeN(layer.data);
}
- layer.data = new_buffer.buffer();
+ layer.data = new_buffer.data();
}
BLI_assert(dead_layer != nullptr);
@@ -246,56 +247,10 @@ BLI_NOINLINE static void remove_dead_and_add_new_particles(ParticleSimulationSta
state.next_particle_id += allocator.total_allocated();
}
-static void update_persistent_data_handles(Simulation &simulation,
- const VectorSet<ID *> &used_data_blocks)
-{
- Set<ID *> contained_ids;
- Set<int> used_handles;
-
- /* Remove handles that have been invalidated. */
- LISTBASE_FOREACH_MUTABLE (
- PersistentDataHandleItem *, handle_item, &simulation.persistent_data_handles) {
- if (handle_item->id == nullptr) {
- BLI_remlink(&simulation.persistent_data_handles, handle_item);
- continue;
- }
- if (!used_data_blocks.contains(handle_item->id)) {
- id_us_min(handle_item->id);
- BLI_remlink(&simulation.persistent_data_handles, handle_item);
- MEM_freeN(handle_item);
- continue;
- }
- contained_ids.add_new(handle_item->id);
- used_handles.add_new(handle_item->handle);
- }
-
- /* Add new handles that are not in the list yet. */
- int next_handle = 0;
- for (ID *id : used_data_blocks) {
- if (contained_ids.contains(id)) {
- continue;
- }
-
- /* Find the next available handle. */
- while (used_handles.contains(next_handle)) {
- next_handle++;
- }
- used_handles.add_new(next_handle);
-
- PersistentDataHandleItem *handle_item = (PersistentDataHandleItem *)MEM_callocN(
- sizeof(*handle_item), AT);
- /* Cannot store const pointers in DNA. */
- id_us_plus(id);
- handle_item->id = id;
- handle_item->handle = next_handle;
-
- BLI_addtail(&simulation.persistent_data_handles, handle_item);
- }
-}
-
void initialize_simulation_states(Simulation &simulation,
Depsgraph &UNUSED(depsgraph),
- const SimulationInfluences &UNUSED(influences))
+ const SimulationInfluences &UNUSED(influences),
+ const bke::PersistentDataHandleMap &UNUSED(handle_map))
{
simulation.current_simulation_time = 0.0f;
}
@@ -303,15 +258,9 @@ void initialize_simulation_states(Simulation &simulation,
void solve_simulation_time_step(Simulation &simulation,
Depsgraph &depsgraph,
const SimulationInfluences &influences,
+ const bke::PersistentDataHandleMap &handle_map,
float time_step)
{
- update_persistent_data_handles(simulation, influences.used_data_blocks);
-
- bke::PersistentDataHandleMap handle_map;
- LISTBASE_FOREACH (PersistentDataHandleItem *, handle, &simulation.persistent_data_handles) {
- handle_map.add(handle->handle, *handle->id);
- }
-
SimulationStateMap state_map;
LISTBASE_FOREACH (SimulationState *, state, &simulation.states) {
state_map.add(state);
@@ -323,7 +272,6 @@ void solve_simulation_time_step(Simulation &simulation,
TimeInterval(simulation.current_simulation_time, time_step),
state_map,
handle_map};
- TimeInterval simulation_time_interval{simulation.current_simulation_time, time_step};
Span<ParticleSimulationState *> particle_simulation_states =
state_map.lookup<ParticleSimulationState>();
@@ -356,7 +304,7 @@ void solve_simulation_time_step(Simulation &simulation,
remove_dead_and_add_new_particles(*state, allocator);
}
- simulation.current_simulation_time = simulation_time_interval.end();
+ simulation.current_simulation_time = solve_context.solve_interval().end();
}
} // namespace blender::sim
diff --git a/source/blender/simulation/intern/simulation_solver.hh b/source/blender/simulation/intern/simulation_solver.hh
index 2dc0921ad9e..ccf4855a9a1 100644
--- a/source/blender/simulation/intern/simulation_solver.hh
+++ b/source/blender/simulation/intern/simulation_solver.hh
@@ -17,259 +17,21 @@
#ifndef __SIM_SIMULATION_SOLVER_HH__
#define __SIM_SIMULATION_SOLVER_HH__
-#include "BLI_float3.hh"
-#include "BLI_span.hh"
-
-#include "DNA_simulation_types.h"
-
-#include "FN_attributes_ref.hh"
-
-#include "BKE_persistent_data_handle.hh"
-#include "BKE_simulation.h"
-
-#include "particle_allocator.hh"
-#include "time_interval.hh"
+#include "simulation_collect_influences.hh"
struct Depsgraph;
namespace blender::sim {
-class ParticleEmitterContext;
-class ParticleForceContext;
-
-class ParticleEmitter {
- public:
- virtual ~ParticleEmitter();
- virtual void emit(ParticleEmitterContext &context) const = 0;
-};
-
-class ParticleForce {
- public:
- virtual ~ParticleForce();
- virtual void add_force(ParticleForceContext &context) const = 0;
-};
-
-struct SimulationInfluences {
- Map<std::string, Vector<const ParticleForce *>> particle_forces;
- Map<std::string, fn::AttributesInfoBuilder *> particle_attributes_builder;
- Vector<const ParticleEmitter *> particle_emitters;
- VectorSet<ID *> used_data_blocks;
-};
-
-class SimulationStateMap {
- private:
- Map<StringRefNull, SimulationState *> states_by_name_;
- Map<StringRefNull, Vector<SimulationState *>> states_by_type_;
-
- public:
- void add(SimulationState *state)
- {
- states_by_name_.add_new(state->name, state);
- states_by_type_.lookup_or_add_default(state->type).append(state);
- }
-
- template<typename StateType> StateType *lookup(StringRef name) const
- {
- const char *type = BKE_simulation_get_state_type_name<StateType>();
- return (StateType *)this->lookup_name_type(name, type);
- }
-
- template<typename StateType> Span<StateType *> lookup() const
- {
- const char *type = BKE_simulation_get_state_type_name<StateType>();
- return this->lookup_type(type).cast<StateType *>();
- }
-
- SimulationState *lookup_name_type(StringRef name, StringRef type) const
- {
- SimulationState *state = states_by_name_.lookup_default_as(name, nullptr);
- if (state == nullptr) {
- return nullptr;
- }
- if (state->type == type) {
- return state;
- }
- return nullptr;
- }
-
- Span<SimulationState *> lookup_type(StringRef type) const
- {
- const Vector<SimulationState *> *states = states_by_type_.lookup_ptr_as(type);
- if (states == nullptr) {
- return {};
- }
- else {
- return states->as_span();
- }
- }
-};
-
-class SimulationSolveContext {
- private:
- Simulation &simulation_;
- Depsgraph &depsgraph_;
- const SimulationInfluences &influences_;
- TimeInterval solve_interval_;
- const SimulationStateMap &state_map_;
- const bke::PersistentDataHandleMap &id_handle_map_;
-
- public:
- SimulationSolveContext(Simulation &simulation,
- Depsgraph &depsgraph,
- const SimulationInfluences &influences,
- TimeInterval solve_interval,
- const SimulationStateMap &state_map,
- const bke::PersistentDataHandleMap &handle_map)
- : simulation_(simulation),
- depsgraph_(depsgraph),
- influences_(influences),
- solve_interval_(solve_interval),
- state_map_(state_map),
- id_handle_map_(handle_map)
- {
- }
-
- TimeInterval solve_interval() const
- {
- return solve_interval_;
- }
-
- const SimulationInfluences &influences() const
- {
- return influences_;
- }
-
- const bke::PersistentDataHandleMap &handle_map() const
- {
- return id_handle_map_;
- }
-
- const SimulationStateMap &state_map() const
- {
- return state_map_;
- }
-};
-
-class ParticleAllocators {
- private:
- Map<std::string, std::unique_ptr<ParticleAllocator>> &allocators_;
-
- public:
- ParticleAllocators(Map<std::string, std::unique_ptr<ParticleAllocator>> &allocators)
- : allocators_(allocators)
- {
- }
-
- ParticleAllocator *try_get_allocator(StringRef particle_simulation_name)
- {
- auto *ptr = allocators_.lookup_ptr_as(particle_simulation_name);
- if (ptr != nullptr) {
- return ptr->get();
- }
- else {
- return nullptr;
- }
- }
-};
-
-class ParticleChunkContext {
- private:
- IndexMask index_mask_;
- fn::MutableAttributesRef attributes_;
-
- public:
- ParticleChunkContext(IndexMask index_mask, fn::MutableAttributesRef attributes)
- : index_mask_(index_mask), attributes_(attributes)
- {
- }
-
- IndexMask index_mask() const
- {
- return index_mask_;
- }
-
- fn::MutableAttributesRef attributes()
- {
- return attributes_;
- }
-
- fn::AttributesRef attributes() const
- {
- return attributes_;
- }
-};
-
-class ParticleEmitterContext {
- private:
- SimulationSolveContext &solve_context_;
- ParticleAllocators &particle_allocators_;
- TimeInterval simulation_time_interval_;
-
- public:
- ParticleEmitterContext(SimulationSolveContext &solve_context,
- ParticleAllocators &particle_allocators,
- TimeInterval simulation_time_interval)
- : solve_context_(solve_context),
- particle_allocators_(particle_allocators),
- simulation_time_interval_(simulation_time_interval)
- {
- }
-
- SimulationSolveContext &solve_context()
- {
- return solve_context_;
- }
-
- ParticleAllocator *try_get_particle_allocator(StringRef particle_simulation_name)
- {
- return particle_allocators_.try_get_allocator(particle_simulation_name);
- }
-
- TimeInterval simulation_time_interval() const
- {
- return simulation_time_interval_;
- }
-};
-
-class ParticleForceContext {
- private:
- SimulationSolveContext &solve_context_;
- const ParticleChunkContext &particle_chunk_context_;
- MutableSpan<float3> force_dst_;
-
- public:
- ParticleForceContext(SimulationSolveContext &solve_context,
- const ParticleChunkContext &particle_chunk_context,
- MutableSpan<float3> force_dst)
- : solve_context_(solve_context),
- particle_chunk_context_(particle_chunk_context),
- force_dst_(force_dst)
- {
- }
-
- SimulationSolveContext &solve_context()
- {
- return solve_context_;
- }
-
- const ParticleChunkContext &particle_chunk() const
- {
- return particle_chunk_context_;
- }
-
- MutableSpan<float3> force_dst()
- {
- return force_dst_;
- }
-};
-
void initialize_simulation_states(Simulation &simulation,
Depsgraph &depsgraph,
- const SimulationInfluences &influences);
+ const SimulationInfluences &influences,
+ const bke::PersistentDataHandleMap &handle_map);
void solve_simulation_time_step(Simulation &simulation,
Depsgraph &depsgraph,
const SimulationInfluences &influences,
+ const bke::PersistentDataHandleMap &handle_map,
float time_step);
} // namespace blender::sim
diff --git a/source/blender/simulation/intern/simulation_solver_influences.hh b/source/blender/simulation/intern/simulation_solver_influences.hh
new file mode 100644
index 00000000000..8d5e171bea0
--- /dev/null
+++ b/source/blender/simulation/intern/simulation_solver_influences.hh
@@ -0,0 +1,270 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __SIM_SIMULATION_SOLVER_INFLUENCES_HH__
+#define __SIM_SIMULATION_SOLVER_INFLUENCES_HH__
+
+#include "BLI_float3.hh"
+#include "BLI_span.hh"
+
+#include "DNA_simulation_types.h"
+
+#include "FN_attributes_ref.hh"
+
+#include "BKE_persistent_data_handle.hh"
+#include "BKE_simulation.h"
+
+#include "particle_allocator.hh"
+#include "time_interval.hh"
+
+namespace blender::sim {
+
+class ParticleEmitterContext;
+class ParticleForceContext;
+
+class ParticleEmitter {
+ public:
+ virtual ~ParticleEmitter();
+ virtual void emit(ParticleEmitterContext &context) const = 0;
+};
+
+class ParticleForce {
+ public:
+ virtual ~ParticleForce();
+ virtual void add_force(ParticleForceContext &context) const = 0;
+};
+
+struct SimulationInfluences {
+ Map<std::string, Vector<const ParticleForce *>> particle_forces;
+ Map<std::string, fn::AttributesInfoBuilder *> particle_attributes_builder;
+ Vector<const ParticleEmitter *> particle_emitters;
+};
+
+class SimulationStateMap {
+ private:
+ Map<StringRefNull, SimulationState *> states_by_name_;
+ Map<StringRefNull, Vector<SimulationState *>> states_by_type_;
+
+ public:
+ void add(SimulationState *state)
+ {
+ states_by_name_.add_new(state->name, state);
+ states_by_type_.lookup_or_add_default(state->type).append(state);
+ }
+
+ template<typename StateType> StateType *lookup(StringRef name) const
+ {
+ const char *type = BKE_simulation_get_state_type_name<StateType>();
+ return (StateType *)this->lookup_name_type(name, type);
+ }
+
+ template<typename StateType> Span<StateType *> lookup() const
+ {
+ const char *type = BKE_simulation_get_state_type_name<StateType>();
+ return this->lookup_type(type).cast<StateType *>();
+ }
+
+ SimulationState *lookup_name_type(StringRef name, StringRef type) const
+ {
+ SimulationState *state = states_by_name_.lookup_default_as(name, nullptr);
+ if (state == nullptr) {
+ return nullptr;
+ }
+ if (state->type == type) {
+ return state;
+ }
+ return nullptr;
+ }
+
+ Span<SimulationState *> lookup_type(StringRef type) const
+ {
+ const Vector<SimulationState *> *states = states_by_type_.lookup_ptr_as(type);
+ if (states == nullptr) {
+ return {};
+ }
+ else {
+ return states->as_span();
+ }
+ }
+};
+
+class SimulationSolveContext {
+ private:
+ Simulation &simulation_;
+ Depsgraph &depsgraph_;
+ const SimulationInfluences &influences_;
+ TimeInterval solve_interval_;
+ const SimulationStateMap &state_map_;
+ const bke::PersistentDataHandleMap &id_handle_map_;
+
+ public:
+ SimulationSolveContext(Simulation &simulation,
+ Depsgraph &depsgraph,
+ const SimulationInfluences &influences,
+ TimeInterval solve_interval,
+ const SimulationStateMap &state_map,
+ const bke::PersistentDataHandleMap &handle_map)
+ : simulation_(simulation),
+ depsgraph_(depsgraph),
+ influences_(influences),
+ solve_interval_(solve_interval),
+ state_map_(state_map),
+ id_handle_map_(handle_map)
+ {
+ }
+
+ TimeInterval solve_interval() const
+ {
+ return solve_interval_;
+ }
+
+ const SimulationInfluences &influences() const
+ {
+ return influences_;
+ }
+
+ const bke::PersistentDataHandleMap &handle_map() const
+ {
+ return id_handle_map_;
+ }
+
+ const SimulationStateMap &state_map() const
+ {
+ return state_map_;
+ }
+};
+
+class ParticleAllocators {
+ private:
+ Map<std::string, std::unique_ptr<ParticleAllocator>> &allocators_;
+
+ public:
+ ParticleAllocators(Map<std::string, std::unique_ptr<ParticleAllocator>> &allocators)
+ : allocators_(allocators)
+ {
+ }
+
+ ParticleAllocator *try_get_allocator(StringRef particle_simulation_name)
+ {
+ auto *ptr = allocators_.lookup_ptr_as(particle_simulation_name);
+ if (ptr != nullptr) {
+ return ptr->get();
+ }
+ else {
+ return nullptr;
+ }
+ }
+};
+
+class ParticleChunkContext {
+ private:
+ IndexMask index_mask_;
+ fn::MutableAttributesRef attributes_;
+
+ public:
+ ParticleChunkContext(IndexMask index_mask, fn::MutableAttributesRef attributes)
+ : index_mask_(index_mask), attributes_(attributes)
+ {
+ }
+
+ IndexMask index_mask() const
+ {
+ return index_mask_;
+ }
+
+ fn::MutableAttributesRef attributes()
+ {
+ return attributes_;
+ }
+
+ fn::AttributesRef attributes() const
+ {
+ return attributes_;
+ }
+};
+
+class ParticleEmitterContext {
+ private:
+ SimulationSolveContext &solve_context_;
+ ParticleAllocators &particle_allocators_;
+ TimeInterval emit_interval_;
+
+ public:
+ ParticleEmitterContext(SimulationSolveContext &solve_context,
+ ParticleAllocators &particle_allocators,
+ TimeInterval emit_interval)
+ : solve_context_(solve_context),
+ particle_allocators_(particle_allocators),
+ emit_interval_(emit_interval)
+ {
+ }
+
+ template<typename StateType> StateType *lookup_state(StringRef name)
+ {
+ return solve_context_.state_map().lookup<StateType>(name);
+ }
+
+ SimulationSolveContext &solve_context()
+ {
+ return solve_context_;
+ }
+
+ ParticleAllocator *try_get_particle_allocator(StringRef particle_simulation_name)
+ {
+ return particle_allocators_.try_get_allocator(particle_simulation_name);
+ }
+
+ TimeInterval emit_interval() const
+ {
+ return emit_interval_;
+ }
+};
+
+class ParticleForceContext {
+ private:
+ SimulationSolveContext &solve_context_;
+ const ParticleChunkContext &particle_chunk_context_;
+ MutableSpan<float3> force_dst_;
+
+ public:
+ ParticleForceContext(SimulationSolveContext &solve_context,
+ const ParticleChunkContext &particle_chunk_context,
+ MutableSpan<float3> force_dst)
+ : solve_context_(solve_context),
+ particle_chunk_context_(particle_chunk_context),
+ force_dst_(force_dst)
+ {
+ }
+
+ SimulationSolveContext &solve_context()
+ {
+ return solve_context_;
+ }
+
+ const ParticleChunkContext &particle_chunk() const
+ {
+ return particle_chunk_context_;
+ }
+
+ MutableSpan<float3> force_dst()
+ {
+ return force_dst_;
+ }
+};
+
+} // namespace blender::sim
+
+#endif /* __SIM_SIMULATION_SOLVER_INFLUENCES_HH__ */
diff --git a/source/blender/simulation/intern/simulation_update.cc b/source/blender/simulation/intern/simulation_update.cc
index 09219e0238f..3303145b891 100644
--- a/source/blender/simulation/intern/simulation_update.cc
+++ b/source/blender/simulation/intern/simulation_update.cc
@@ -17,6 +17,7 @@
#include "SIM_simulation_update.hh"
#include "BKE_customdata.h"
+#include "BKE_lib_id.h"
#include "BKE_simulation.h"
#include "DNA_scene_types.h"
@@ -29,8 +30,11 @@
#include "BLI_listbase.h"
#include "BLI_map.hh"
#include "BLI_rand.h"
+#include "BLI_set.hh"
#include "BLI_vector.hh"
+#include "NOD_node_tree_dependencies.hh"
+
#include "particle_function.hh"
#include "simulation_collect_influences.hh"
#include "simulation_solver.hh"
@@ -108,13 +112,19 @@ void update_simulation_in_depsgraph(Depsgraph *depsgraph,
SimulationInfluences influences;
RequiredStates required_states;
- /* TODO: Use simulation_cow, but need to add depsgraph relations before that. */
- collect_simulation_influences(*simulation_orig, resources, influences, required_states);
+ collect_simulation_influences(*simulation_cow, resources, influences, required_states);
+
+ bke::PersistentDataHandleMap handle_map;
+ LISTBASE_FOREACH (
+ PersistentDataHandleItem *, handle_item, &simulation_orig->persistent_data_handles) {
+ ID *id_cow = DEG_get_evaluated_id(depsgraph, handle_item->id);
+ handle_map.add(handle_item->handle, *id_cow);
+ }
if (current_frame == 1) {
reinitialize_empty_simulation_states(simulation_orig, required_states);
- initialize_simulation_states(*simulation_orig, *depsgraph, influences);
+ initialize_simulation_states(*simulation_orig, *depsgraph, influences, handle_map);
simulation_orig->current_frame = 1;
copy_states_to_cow(simulation_orig, simulation_cow);
@@ -123,11 +133,88 @@ void update_simulation_in_depsgraph(Depsgraph *depsgraph,
update_simulation_state_list(simulation_orig, required_states);
float time_step = 1.0f / 24.0f;
- solve_simulation_time_step(*simulation_orig, *depsgraph, influences, time_step);
+ solve_simulation_time_step(*simulation_orig, *depsgraph, influences, handle_map, time_step);
simulation_orig->current_frame = current_frame;
copy_states_to_cow(simulation_orig, simulation_cow);
}
}
+/* Returns true when dependencies have changed. */
+bool update_simulation_dependencies(Simulation *simulation)
+{
+ nodes::NodeTreeDependencies dependencies = nodes::find_node_tree_dependencies(
+ *simulation->nodetree);
+
+ ListBase *handle_list = &simulation->persistent_data_handles;
+
+ bool dependencies_changed = false;
+
+ Map<ID *, PersistentDataHandleItem *> handle_item_by_id;
+ Map<PersistentDataHandleItem *, int> old_flag_by_handle_item;
+ Set<int> used_handles;
+
+ /* Remove unused handle items and clear flags that are reinitialized later. */
+ LISTBASE_FOREACH_MUTABLE (PersistentDataHandleItem *, handle_item, handle_list) {
+ if (dependencies.depends_on(handle_item->id)) {
+ handle_item_by_id.add_new(handle_item->id, handle_item);
+ used_handles.add_new(handle_item->handle);
+ old_flag_by_handle_item.add_new(handle_item, handle_item->flag);
+ handle_item->flag &= ~(SIM_HANDLE_DEPENDS_ON_TRANSFORM | SIM_HANDLE_DEPENDS_ON_GEOMETRY);
+ }
+ else {
+ if (handle_item->id != nullptr) {
+ id_us_min(handle_item->id);
+ }
+ BLI_remlink(handle_list, handle_item);
+ MEM_freeN(handle_item);
+ dependencies_changed = true;
+ }
+ }
+
+ /* Add handle items for new id dependencies. */
+ int next_handle = 0;
+ for (ID *id : dependencies.id_dependencies()) {
+ handle_item_by_id.lookup_or_add_cb(id, [&]() {
+ while (used_handles.contains(next_handle)) {
+ next_handle++;
+ }
+ used_handles.add_new(next_handle);
+
+ PersistentDataHandleItem *handle_item = (PersistentDataHandleItem *)MEM_callocN(
+ sizeof(*handle_item), AT);
+ id_us_plus(id);
+ handle_item->id = id;
+ handle_item->handle = next_handle;
+ BLI_addtail(handle_list, handle_item);
+
+ return handle_item;
+ });
+ }
+
+ /* Set appropriate dependency flags. */
+ for (Object *object : dependencies.transform_dependencies()) {
+ PersistentDataHandleItem *handle_item = handle_item_by_id.lookup(&object->id);
+ handle_item->flag |= SIM_HANDLE_DEPENDS_ON_TRANSFORM;
+ }
+ for (Object *object : dependencies.geometry_dependencies()) {
+ PersistentDataHandleItem *handle_item = handle_item_by_id.lookup(&object->id);
+ handle_item->flag |= SIM_HANDLE_DEPENDS_ON_GEOMETRY;
+ }
+
+ if (!dependencies_changed) {
+ /* Check if any flags have changed. */
+ LISTBASE_FOREACH (PersistentDataHandleItem *, handle_item, handle_list) {
+ int old_flag = old_flag_by_handle_item.lookup_default(handle_item, 0);
+ int new_flag = handle_item->flag;
+ if (old_flag != new_flag) {
+ dependencies_changed = true;
+ break;
+ }
+ }
+ }
+
+ return dependencies_changed;
+}
+
} // namespace blender::sim
diff --git a/source/blender/simulation/intern/time_interval.hh b/source/blender/simulation/intern/time_interval.hh
index 49600dd10de..75ef4b21c33 100644
--- a/source/blender/simulation/intern/time_interval.hh
+++ b/source/blender/simulation/intern/time_interval.hh
@@ -22,7 +22,7 @@
namespace blender::sim {
/**
- * The start time is inclusive and the end time is exclusive. The duration is zero, the interval
+ * The start time is exclusive and the end time is inclusive. If the duration is zero, the interval
* describes a single point in time.
*/
class TimeInterval {
diff --git a/source/blender/windowmanager/xr/intern/wm_xr_session.c b/source/blender/windowmanager/xr/intern/wm_xr_session.c
index 88b7e589a9c..8e7101ddcd5 100644
--- a/source/blender/windowmanager/xr/intern/wm_xr_session.c
+++ b/source/blender/windowmanager/xr/intern/wm_xr_session.c
@@ -177,8 +177,8 @@ static bool wm_xr_session_draw_data_needs_reset_to_base_pose(const wmXrSessionSt
(state->prev_base_pose_object != settings->base_pose_object));
}
-wmXrSessionStateEvent wm_xr_session_state_to_event(const wmXrSessionState *state,
- const XrSessionSettings *settings)
+static wmXrSessionStateEvent wm_xr_session_state_to_event(const wmXrSessionState *state,
+ const XrSessionSettings *settings)
{
if (!state->is_view_data_set) {
return SESSION_STATE_EVENT_START;