diff options
Diffstat (limited to 'source/blender')
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(®ion->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(¶meters.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; |