From bbd7a7f6d334992eec8baefce843b02a9f58c9e7 Mon Sep 17 00:00:00 2001 From: Jacques Lucke Date: Wed, 22 Mar 2023 12:52:30 +0100 Subject: [PATCH 01/13] initial commit --- .../blenkernel/BKE_anonymous_attribute_id.hh | 22 ++--- source/blender/blenkernel/BKE_geometry_set.hh | 24 ++--- .../blender/blenkernel/intern/customdata.cc | 6 +- .../blender/blenkernel/intern/geometry_set.cc | 27 ++---- source/blender/blenlib/BLI_copy_on_write.hh | 96 +++++++++++++++++++ ...r_counter.hh => BLI_copy_on_write_user.hh} | 50 ++++------ source/blender/blenlib/CMakeLists.txt | 3 +- .../geometry/intern/mesh_split_edges.cc | 7 +- .../geometry/intern/realize_instances.cc | 10 +- 9 files changed, 149 insertions(+), 96 deletions(-) create mode 100644 source/blender/blenlib/BLI_copy_on_write.hh rename source/blender/blenlib/{BLI_user_counter.hh => BLI_copy_on_write_user.hh} (57%) diff --git a/source/blender/blenkernel/BKE_anonymous_attribute_id.hh b/source/blender/blenkernel/BKE_anonymous_attribute_id.hh index 7e4ca4b7afd..ab0a5c2ac05 100644 --- a/source/blender/blenkernel/BKE_anonymous_attribute_id.hh +++ b/source/blender/blenkernel/BKE_anonymous_attribute_id.hh @@ -4,9 +4,9 @@ #include +#include "BLI_copy_on_write_user.hh" #include "BLI_set.hh" #include "BLI_string_ref.hh" -#include "BLI_user_counter.hh" namespace blender::bke { @@ -32,10 +32,7 @@ namespace blender::bke { * because that is not available in C code. If possible, the #AutoAnonymousAttributeID wrapper * should be used to avoid manual reference counting in C++ code. */ -class AnonymousAttributeID { - private: - mutable std::atomic users_ = 1; - +class AnonymousAttributeID : public bCopyOnWriteMixin { protected: std::string name_; @@ -49,22 +46,15 @@ class AnonymousAttributeID { virtual std::string user_name() const; - void user_add() const + private: + void delete_self() override { - users_.fetch_add(1); - } - - void user_remove() const - { - const int new_users = users_.fetch_sub(1) - 1; - if (new_users == 0) { - MEM_delete(this); - } + MEM_delete(this); } }; /** Wrapper for #AnonymousAttributeID that avoids manual reference counting. */ -using AutoAnonymousAttributeID = UserCounter; +using AutoAnonymousAttributeID = COWUser; /** * A set of anonymous attribute names that is passed around in geometry nodes. diff --git a/source/blender/blenkernel/BKE_geometry_set.hh b/source/blender/blenkernel/BKE_geometry_set.hh index 72f77c889c1..ba47943140e 100644 --- a/source/blender/blenkernel/BKE_geometry_set.hh +++ b/source/blender/blenkernel/BKE_geometry_set.hh @@ -12,8 +12,6 @@ #include "BLI_function_ref.hh" #include "BLI_map.hh" -#include "BLI_math_vector_types.hh" -#include "BLI_user_counter.hh" #include "BLI_vector_set.hh" #include "BKE_attribute.hh" @@ -40,18 +38,13 @@ class CurvesEditHints; class Instances; } // namespace blender::bke -class GeometryComponent; - /** - * This is the base class for specialized geometry component types. A geometry component handles - * a user count to allow avoiding duplication when it is wrapped with #UserCounter. It also handles - * the attribute API, which generalizes storing and modifying generic information on a geometry. + * This is the base class for specialized geometry component types. A geometry component uses + * copy-on-write behavior to avoid read-only copies. It also integrates with attribute API, which + * generalizes storing and modifying generic information on a geometry. */ -class GeometryComponent { +class GeometryComponent : public bCopyOnWriteMixin { private: - /* The reference count has two purposes. When it becomes zero, the component is freed. When it is - * larger than one, the component becomes immutable. */ - mutable std::atomic users_ = 1; GeometryComponentType type_; public: @@ -77,13 +70,12 @@ class GeometryComponent { virtual bool owns_direct_data() const = 0; virtual void ensure_owns_direct_data() = 0; - void user_add() const; - void user_remove() const; - bool is_mutable() const; - GeometryComponentType type() const; virtual bool is_empty() const; + + private: + void delete_self() override; }; template @@ -109,7 +101,7 @@ inline constexpr bool is_geometry_component_v = std::is_base_of_v; + using GeometryComponentPtr = blender::COWUser; /* Indexed by #GeometryComponentType. */ std::array components_; diff --git a/source/blender/blenkernel/intern/customdata.cc b/source/blender/blenkernel/intern/customdata.cc index 0d7f55a6f26..b9bc5b79533 100644 --- a/source/blender/blenkernel/intern/customdata.cc +++ b/source/blender/blenkernel/intern/customdata.cc @@ -2273,7 +2273,7 @@ bool CustomData_merge(const CustomData *source, layer->anonymous_id = nullptr; } else { - layer->anonymous_id->user_add(); + layer->anonymous_id->add_user(); } } if (alloctype == CD_ASSIGN) { @@ -2365,7 +2365,7 @@ static void customData_free_layer__internal(CustomDataLayer *layer, const int to const LayerTypeInfo *typeInfo; if (layer->anonymous_id != nullptr) { - layer->anonymous_id->user_remove(); + layer->anonymous_id->remove_user_and_delete_if_last(); layer->anonymous_id = nullptr; } if (!(layer->flag & CD_FLAG_NOFREE) && layer->data) { @@ -2924,7 +2924,7 @@ void *CustomData_add_layer_anonymous(CustomData *data, return nullptr; } - anonymous_id->user_add(); + anonymous_id->add_user(); layer->anonymous_id = anonymous_id; return layer->data; } diff --git a/source/blender/blenkernel/intern/geometry_set.cc b/source/blender/blenkernel/intern/geometry_set.cc index 3f5fadbe636..713034e4f16 100644 --- a/source/blender/blenkernel/intern/geometry_set.cc +++ b/source/blender/blenkernel/intern/geometry_set.cc @@ -84,26 +84,6 @@ std::optional GeometryComponent::attribu return std::nullopt; } -void GeometryComponent::user_add() const -{ - users_.fetch_add(1); -} - -void GeometryComponent::user_remove() const -{ - const int new_users = users_.fetch_sub(1) - 1; - if (new_users == 0) { - delete this; - } -} - -bool GeometryComponent::is_mutable() const -{ - /* If the item is shared, it is read-only. */ - /* The user count can be 0, when this is called from the destructor. */ - return users_ <= 1; -} - GeometryComponentType GeometryComponent::type() const { return type_; @@ -114,6 +94,11 @@ bool GeometryComponent::is_empty() const return false; } +void GeometryComponent::delete_self() +{ + delete this; +} + /** \} */ /* -------------------------------------------------------------------- */ @@ -198,7 +183,7 @@ void GeometrySet::remove_geometry_during_modify() void GeometrySet::add(const GeometryComponent &component) { BLI_assert(!components_[component.type()]); - component.user_add(); + component.add_user(); components_[component.type()] = const_cast(&component); } diff --git a/source/blender/blenlib/BLI_copy_on_write.hh b/source/blender/blenlib/BLI_copy_on_write.hh new file mode 100644 index 00000000000..6754b60f6d4 --- /dev/null +++ b/source/blender/blenlib/BLI_copy_on_write.hh @@ -0,0 +1,96 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#pragma once + +/** \file + * \ingroup bli + */ + +#include + +#include "BLI_compiler_attrs.h" +#include "BLI_utildefines.h" +#include "BLI_utility_mixins.hh" + +/** + * #bCopyOnWrite allows implementing copy-on-write behavior, i.e. it allows sharing read-only data + * between multiple independend systems (e.g. meshes). The data is only copied when it is shared + * and is about to be modified. This is in contrast to making copies before it is actually known + * that it is necessary. + * + * Internally, this is mostly just a glorified reference count. If the reference count is 1, the + * data only has a single owner and is mutable. If it is larger than 1, it is shared and must be + * logically const. + * + * On top of containing a reference count, #bCopyOnWrite also knows how to destruct the referenced + * data. This is important because the code freeing the data in the end might not know how it was + * allocated (for example, it doesn't know whether an array was allocated using the system or + * guarded allocator). + * + * #bCopyOnWrite is used in two ways: + * - It can be allocated separately from the referenced data as is typically the case with raw + * arrays (e.g. for mesh attributes). + * - It can be embedded into another struct. For that it's best to use #bCopyOnWriteMixin. + */ +struct bCopyOnWrite : blender::NonCopyable, blender::NonMovable { + private: + mutable std::atomic users_; + + public: + bCopyOnWrite(const int initial_users) : users_(initial_users) + { + } + + virtual ~bCopyOnWrite() + { + BLI_assert(this->is_mutable()); + } + + bool is_shared() const + { + return users_.load(std::memory_order_relaxed) >= 2; + } + + bool is_mutable() const + { + return !this->is_shared(); + } + + void add_user() const + { + users_.fetch_add(1, std::memory_order_relaxed); + } + + void remove_user_and_delete_if_last() const + { + const int old_user_count = users_.fetch_sub(1, std::memory_order_relaxed); + BLI_assert(old_user_count >= 1); + const bool was_last_user = old_user_count == 1; + if (was_last_user) { + const_cast(this)->delete_self_with_data(); + } + } + + private: + /** Has to free the #bCopyOnWrite and the referenced data. */ + virtual void delete_self_with_data() = 0; +}; + +/** + * Makes it easy to embed copy-on-write behavior into a struct. + */ +struct bCopyOnWriteMixin : public bCopyOnWrite { + public: + bCopyOnWriteMixin() : bCopyOnWrite(1) + { + } + + private: + void delete_self_with_data() override + { + /* Can't use `delete this` here, because we don't know what allocator was used. */ + this->delete_self(); + } + + virtual void delete_self() = 0; +}; diff --git a/source/blender/blenlib/BLI_user_counter.hh b/source/blender/blenlib/BLI_copy_on_write_user.hh similarity index 57% rename from source/blender/blenlib/BLI_user_counter.hh rename to source/blender/blenlib/BLI_copy_on_write_user.hh index 11cd845ff53..9a2260ad87b 100644 --- a/source/blender/blenlib/BLI_user_counter.hh +++ b/source/blender/blenlib/BLI_copy_on_write_user.hh @@ -6,59 +6,55 @@ * \ingroup bli */ -#include +#include "BLI_copy_on_write.hh" namespace blender { -/** - * A simple automatic reference counter. It is similar to std::shared_ptr, but expects that the - * reference count is inside the object. - */ -template class UserCounter { +template class COWUser { private: T *data_ = nullptr; public: - UserCounter() = default; + COWUser() = default; - UserCounter(T *data) : data_(data) + COWUser(T *data) : data_(data) { } - UserCounter(const UserCounter &other) : data_(other.data_) + COWUser(const COWUser &other) : data_(other.data_) { - this->user_add(data_); + this->add_user(data_); } - UserCounter(UserCounter &&other) : data_(other.data_) + COWUser(COWUser &&other) : data_(other.data_) { other.data_ = nullptr; } - ~UserCounter() + ~COWUser() { - this->user_remove(data_); + this->remove_user_and_delete_if_last(data_); } - UserCounter &operator=(const UserCounter &other) + COWUser &operator=(const COWUser &other) { if (this == &other) { return *this; } - this->user_remove(data_); + this->remove_user_and_delete_if_last(data_); data_ = other.data_; - this->user_add(data_); + this->add_user(data_); return *this; } - UserCounter &operator=(UserCounter &&other) + COWUser &operator=(COWUser &&other) { if (this == &other) { return *this; } - this->user_remove(data_); + this->remove_user_and_delete_if_last(data_); data_ = other.data_; other.data_ = nullptr; return *this; @@ -112,7 +108,7 @@ template class UserCounter { void reset() { - this->user_remove(data_); + this->remove_user_and_delete_if_last(data_); data_ = nullptr; } @@ -126,29 +122,23 @@ template class UserCounter { return get_default_hash(data_); } - friend bool operator==(const UserCounter &a, const UserCounter &b) + friend bool operator==(const COWUser &a, const COWUser &b) { return a.data_ == b.data_; } - friend std::ostream &operator<<(std::ostream &stream, const UserCounter &value) - { - stream << value.data_; - return stream; - } - private: - static void user_add(T *data) + static void add_user(T *data) { if (data != nullptr) { - data->user_add(); + data->add_user(); } } - static void user_remove(T *data) + static void remove_user_and_delete_if_last(T *data) { if (data != nullptr) { - data->user_remove(); + data->remove_user_and_delete_if_last(); } } }; diff --git a/source/blender/blenlib/CMakeLists.txt b/source/blender/blenlib/CMakeLists.txt index bee0b4e1264..eea8a968619 100644 --- a/source/blender/blenlib/CMakeLists.txt +++ b/source/blender/blenlib/CMakeLists.txt @@ -198,6 +198,8 @@ set(SRC BLI_compute_context.hh BLI_console.h BLI_convexhull_2d.h + BLI_copy_on_write.hh + BLI_copy_on_write_user.hh BLI_cpp_type.hh BLI_cpp_type_make.hh BLI_cpp_types.hh @@ -351,7 +353,6 @@ set(SRC BLI_timecode.h BLI_timeit.hh BLI_timer.h - BLI_user_counter.hh BLI_utildefines.h BLI_utildefines_iter.h BLI_utildefines_stack.h diff --git a/source/blender/geometry/intern/mesh_split_edges.cc b/source/blender/geometry/intern/mesh_split_edges.cc index 8148f9a7fc5..74190ce6633 100644 --- a/source/blender/geometry/intern/mesh_split_edges.cc +++ b/source/blender/geometry/intern/mesh_split_edges.cc @@ -2,7 +2,6 @@ #include "BLI_array_utils.hh" #include "BLI_index_mask.hh" -#include "BLI_user_counter.hh" #include "BKE_attribute.hh" #include "BKE_attribute_math.hh" @@ -69,7 +68,7 @@ static void add_new_edges(Mesh &mesh, /* Store a copy of the IDs locally since we will remove the existing attributes which * can also free the names, since the API does not provide pointer stability. */ Vector named_ids; - Vector> anonymous_ids; + Vector anonymous_ids; for (const bke::AttributeIDRef &id : attributes.all_ids()) { if (attributes.lookup_meta_data(id)->domain != ATTR_DOMAIN_EDGE) { continue; @@ -82,14 +81,14 @@ static void add_new_edges(Mesh &mesh, } else { anonymous_ids.append(&id.anonymous_id()); - id.anonymous_id().user_add(); + id.anonymous_id().add_user(); } } Vector local_edge_ids; for (const StringRef name : named_ids) { local_edge_ids.append(name); } - for (const UserCounter &id : anonymous_ids) { + for (const bke::AutoAnonymousAttributeID &id : anonymous_ids) { local_edge_ids.append(*id); } diff --git a/source/blender/geometry/intern/realize_instances.cc b/source/blender/geometry/intern/realize_instances.cc index 9ff6eacd1cb..6da3a76fde1 100644 --- a/source/blender/geometry/intern/realize_instances.cc +++ b/source/blender/geometry/intern/realize_instances.cc @@ -226,8 +226,8 @@ struct GatherTasks { /* Volumes only have very simple support currently. Only the first found volume is put into the * output. */ - UserCounter first_volume; - UserCounter first_edit_data; + COWUser first_volume; + COWUser first_edit_data; }; /** Current offsets while during the gather operation. */ @@ -608,8 +608,8 @@ static void gather_realize_tasks_recursive(GatherTasksInfo &gather_info, case GEO_COMPONENT_TYPE_VOLUME: { const VolumeComponent *volume_component = static_cast(component); if (!gather_info.r_tasks.first_volume) { - volume_component->user_add(); - gather_info.r_tasks.first_volume = volume_component; + volume_component->add_user(); + gather_info.r_tasks.first_volume = const_cast(volume_component); } break; } @@ -617,7 +617,7 @@ static void gather_realize_tasks_recursive(GatherTasksInfo &gather_info, const GeometryComponentEditData *edit_component = static_cast(component); if (!gather_info.r_tasks.first_edit_data) { - edit_component->user_add(); + edit_component->add_user(); gather_info.r_tasks.first_edit_data = edit_component; } break; -- 2.30.2 From e2fea7bed6f6dfe2dfc786a129ddad386731f7eb Mon Sep 17 00:00:00 2001 From: Jacques Lucke Date: Wed, 22 Mar 2023 12:55:11 +0100 Subject: [PATCH 02/13] rename files --- source/blender/blenkernel/BKE_anonymous_attribute_id.hh | 2 +- .../blenlib/{BLI_copy_on_write.hh => BLI_implicit_sharing.hh} | 0 ...BLI_copy_on_write_user.hh => BLI_implicit_sharing_user.hh} | 2 +- source/blender/blenlib/CMakeLists.txt | 4 ++-- 4 files changed, 4 insertions(+), 4 deletions(-) rename source/blender/blenlib/{BLI_copy_on_write.hh => BLI_implicit_sharing.hh} (100%) rename source/blender/blenlib/{BLI_copy_on_write_user.hh => BLI_implicit_sharing_user.hh} (98%) diff --git a/source/blender/blenkernel/BKE_anonymous_attribute_id.hh b/source/blender/blenkernel/BKE_anonymous_attribute_id.hh index ab0a5c2ac05..dfe8cc824a0 100644 --- a/source/blender/blenkernel/BKE_anonymous_attribute_id.hh +++ b/source/blender/blenkernel/BKE_anonymous_attribute_id.hh @@ -4,7 +4,7 @@ #include -#include "BLI_copy_on_write_user.hh" +#include "BLI_implicit_sharing_user.hh" #include "BLI_set.hh" #include "BLI_string_ref.hh" diff --git a/source/blender/blenlib/BLI_copy_on_write.hh b/source/blender/blenlib/BLI_implicit_sharing.hh similarity index 100% rename from source/blender/blenlib/BLI_copy_on_write.hh rename to source/blender/blenlib/BLI_implicit_sharing.hh diff --git a/source/blender/blenlib/BLI_copy_on_write_user.hh b/source/blender/blenlib/BLI_implicit_sharing_user.hh similarity index 98% rename from source/blender/blenlib/BLI_copy_on_write_user.hh rename to source/blender/blenlib/BLI_implicit_sharing_user.hh index 9a2260ad87b..2fc6476f2b6 100644 --- a/source/blender/blenlib/BLI_copy_on_write_user.hh +++ b/source/blender/blenlib/BLI_implicit_sharing_user.hh @@ -6,7 +6,7 @@ * \ingroup bli */ -#include "BLI_copy_on_write.hh" +#include "BLI_implicit_sharing.hh" namespace blender { diff --git a/source/blender/blenlib/CMakeLists.txt b/source/blender/blenlib/CMakeLists.txt index eea8a968619..5ceed93da5c 100644 --- a/source/blender/blenlib/CMakeLists.txt +++ b/source/blender/blenlib/CMakeLists.txt @@ -198,8 +198,8 @@ set(SRC BLI_compute_context.hh BLI_console.h BLI_convexhull_2d.h - BLI_copy_on_write.hh - BLI_copy_on_write_user.hh + BLI_implicit_sharing.hh + BLI_implicit_sharing_user.hh BLI_cpp_type.hh BLI_cpp_type_make.hh BLI_cpp_types.hh -- 2.30.2 From ab3fda1c586327a23650206b47ea71d7306f9ba8 Mon Sep 17 00:00:00 2001 From: Jacques Lucke Date: Wed, 22 Mar 2023 13:04:35 +0100 Subject: [PATCH 03/13] initial renames --- .../blenkernel/BKE_anonymous_attribute_id.hh | 4 ++-- source/blender/blenkernel/BKE_geometry_set.hh | 4 ++-- source/blender/blenlib/BLI_implicit_sharing.hh | 18 +++++++++++------- .../blenlib/BLI_implicit_sharing_user.hh | 18 +++++++++--------- .../geometry/intern/realize_instances.cc | 4 ++-- 5 files changed, 26 insertions(+), 22 deletions(-) diff --git a/source/blender/blenkernel/BKE_anonymous_attribute_id.hh b/source/blender/blenkernel/BKE_anonymous_attribute_id.hh index dfe8cc824a0..8bb751c54bf 100644 --- a/source/blender/blenkernel/BKE_anonymous_attribute_id.hh +++ b/source/blender/blenkernel/BKE_anonymous_attribute_id.hh @@ -32,7 +32,7 @@ namespace blender::bke { * because that is not available in C code. If possible, the #AutoAnonymousAttributeID wrapper * should be used to avoid manual reference counting in C++ code. */ -class AnonymousAttributeID : public bCopyOnWriteMixin { +class AnonymousAttributeID : public ImplicitShareMixin { protected: std::string name_; @@ -54,7 +54,7 @@ class AnonymousAttributeID : public bCopyOnWriteMixin { }; /** Wrapper for #AnonymousAttributeID that avoids manual reference counting. */ -using AutoAnonymousAttributeID = COWUser; +using AutoAnonymousAttributeID = ImplicitSharePtr; /** * A set of anonymous attribute names that is passed around in geometry nodes. diff --git a/source/blender/blenkernel/BKE_geometry_set.hh b/source/blender/blenkernel/BKE_geometry_set.hh index ba47943140e..ad20594cd9e 100644 --- a/source/blender/blenkernel/BKE_geometry_set.hh +++ b/source/blender/blenkernel/BKE_geometry_set.hh @@ -43,7 +43,7 @@ class Instances; * copy-on-write behavior to avoid read-only copies. It also integrates with attribute API, which * generalizes storing and modifying generic information on a geometry. */ -class GeometryComponent : public bCopyOnWriteMixin { +class GeometryComponent : public blender::ImplicitShareMixin { private: GeometryComponentType type_; @@ -101,7 +101,7 @@ inline constexpr bool is_geometry_component_v = std::is_base_of_v; + using GeometryComponentPtr = blender::ImplicitSharePtr; /* Indexed by #GeometryComponentType. */ std::array components_; diff --git a/source/blender/blenlib/BLI_implicit_sharing.hh b/source/blender/blenlib/BLI_implicit_sharing.hh index 6754b60f6d4..6c2a0425ac9 100644 --- a/source/blender/blenlib/BLI_implicit_sharing.hh +++ b/source/blender/blenlib/BLI_implicit_sharing.hh @@ -12,6 +12,8 @@ #include "BLI_utildefines.h" #include "BLI_utility_mixins.hh" +namespace blender { + /** * #bCopyOnWrite allows implementing copy-on-write behavior, i.e. it allows sharing read-only data * between multiple independend systems (e.g. meshes). The data is only copied when it is shared @@ -30,18 +32,18 @@ * #bCopyOnWrite is used in two ways: * - It can be allocated separately from the referenced data as is typically the case with raw * arrays (e.g. for mesh attributes). - * - It can be embedded into another struct. For that it's best to use #bCopyOnWriteMixin. + * - It can be embedded into another struct. For that it's best to use #ImplicitShareMixin. */ -struct bCopyOnWrite : blender::NonCopyable, blender::NonMovable { +struct ImplicitShareInfo : blender::NonCopyable, blender::NonMovable { private: mutable std::atomic users_; public: - bCopyOnWrite(const int initial_users) : users_(initial_users) + ImplicitShareInfo(const int initial_users) : users_(initial_users) { } - virtual ~bCopyOnWrite() + virtual ~ImplicitShareInfo() { BLI_assert(this->is_mutable()); } @@ -67,7 +69,7 @@ struct bCopyOnWrite : blender::NonCopyable, blender::NonMovable { BLI_assert(old_user_count >= 1); const bool was_last_user = old_user_count == 1; if (was_last_user) { - const_cast(this)->delete_self_with_data(); + const_cast(this)->delete_self_with_data(); } } @@ -79,9 +81,9 @@ struct bCopyOnWrite : blender::NonCopyable, blender::NonMovable { /** * Makes it easy to embed copy-on-write behavior into a struct. */ -struct bCopyOnWriteMixin : public bCopyOnWrite { +struct ImplicitShareMixin : public ImplicitShareInfo { public: - bCopyOnWriteMixin() : bCopyOnWrite(1) + ImplicitShareMixin() : ImplicitShareInfo(1) { } @@ -94,3 +96,5 @@ struct bCopyOnWriteMixin : public bCopyOnWrite { virtual void delete_self() = 0; }; + +} // namespace blender diff --git a/source/blender/blenlib/BLI_implicit_sharing_user.hh b/source/blender/blenlib/BLI_implicit_sharing_user.hh index 2fc6476f2b6..8f043c4d6b6 100644 --- a/source/blender/blenlib/BLI_implicit_sharing_user.hh +++ b/source/blender/blenlib/BLI_implicit_sharing_user.hh @@ -10,33 +10,33 @@ namespace blender { -template class COWUser { +template class ImplicitSharePtr { private: T *data_ = nullptr; public: - COWUser() = default; + ImplicitSharePtr() = default; - COWUser(T *data) : data_(data) + ImplicitSharePtr(T *data) : data_(data) { } - COWUser(const COWUser &other) : data_(other.data_) + ImplicitSharePtr(const ImplicitSharePtr &other) : data_(other.data_) { this->add_user(data_); } - COWUser(COWUser &&other) : data_(other.data_) + ImplicitSharePtr(ImplicitSharePtr &&other) : data_(other.data_) { other.data_ = nullptr; } - ~COWUser() + ~ImplicitSharePtr() { this->remove_user_and_delete_if_last(data_); } - COWUser &operator=(const COWUser &other) + ImplicitSharePtr &operator=(const ImplicitSharePtr &other) { if (this == &other) { return *this; @@ -48,7 +48,7 @@ template class COWUser { return *this; } - COWUser &operator=(COWUser &&other) + ImplicitSharePtr &operator=(ImplicitSharePtr &&other) { if (this == &other) { return *this; @@ -122,7 +122,7 @@ template class COWUser { return get_default_hash(data_); } - friend bool operator==(const COWUser &a, const COWUser &b) + friend bool operator==(const ImplicitSharePtr &a, const ImplicitSharePtr &b) { return a.data_ == b.data_; } diff --git a/source/blender/geometry/intern/realize_instances.cc b/source/blender/geometry/intern/realize_instances.cc index 6da3a76fde1..bc72fe72919 100644 --- a/source/blender/geometry/intern/realize_instances.cc +++ b/source/blender/geometry/intern/realize_instances.cc @@ -226,8 +226,8 @@ struct GatherTasks { /* Volumes only have very simple support currently. Only the first found volume is put into the * output. */ - COWUser first_volume; - COWUser first_edit_data; + ImplicitSharePtr first_volume; + ImplicitSharePtr first_edit_data; }; /** Current offsets while during the gather operation. */ -- 2.30.2 From d63aca8865b0c8ebddf9a8e5eee79ba14ca9ac29 Mon Sep 17 00:00:00 2001 From: Jacques Lucke Date: Wed, 22 Mar 2023 13:06:18 +0100 Subject: [PATCH 04/13] more renames --- .../blenkernel/BKE_anonymous_attribute_id.hh | 4 ++-- source/blender/blenkernel/BKE_geometry_set.hh | 2 +- source/blender/blenlib/BLI_implicit_sharing.hh | 14 +++++++------- ...sharing_user.hh => BLI_implicit_sharing_ptr.hh} | 0 source/blender/blenlib/CMakeLists.txt | 2 +- 5 files changed, 11 insertions(+), 11 deletions(-) rename source/blender/blenlib/{BLI_implicit_sharing_user.hh => BLI_implicit_sharing_ptr.hh} (100%) diff --git a/source/blender/blenkernel/BKE_anonymous_attribute_id.hh b/source/blender/blenkernel/BKE_anonymous_attribute_id.hh index 8bb751c54bf..7132e23c535 100644 --- a/source/blender/blenkernel/BKE_anonymous_attribute_id.hh +++ b/source/blender/blenkernel/BKE_anonymous_attribute_id.hh @@ -4,7 +4,7 @@ #include -#include "BLI_implicit_sharing_user.hh" +#include "BLI_implicit_sharing_ptr.hh" #include "BLI_set.hh" #include "BLI_string_ref.hh" @@ -32,7 +32,7 @@ namespace blender::bke { * because that is not available in C code. If possible, the #AutoAnonymousAttributeID wrapper * should be used to avoid manual reference counting in C++ code. */ -class AnonymousAttributeID : public ImplicitShareMixin { +class AnonymousAttributeID : public ImplicitSharingMixin { protected: std::string name_; diff --git a/source/blender/blenkernel/BKE_geometry_set.hh b/source/blender/blenkernel/BKE_geometry_set.hh index ad20594cd9e..4f751ca14ce 100644 --- a/source/blender/blenkernel/BKE_geometry_set.hh +++ b/source/blender/blenkernel/BKE_geometry_set.hh @@ -43,7 +43,7 @@ class Instances; * copy-on-write behavior to avoid read-only copies. It also integrates with attribute API, which * generalizes storing and modifying generic information on a geometry. */ -class GeometryComponent : public blender::ImplicitShareMixin { +class GeometryComponent : public blender::ImplicitSharingMixin { private: GeometryComponentType type_; diff --git a/source/blender/blenlib/BLI_implicit_sharing.hh b/source/blender/blenlib/BLI_implicit_sharing.hh index 6c2a0425ac9..fe46706c788 100644 --- a/source/blender/blenlib/BLI_implicit_sharing.hh +++ b/source/blender/blenlib/BLI_implicit_sharing.hh @@ -32,18 +32,18 @@ namespace blender { * #bCopyOnWrite is used in two ways: * - It can be allocated separately from the referenced data as is typically the case with raw * arrays (e.g. for mesh attributes). - * - It can be embedded into another struct. For that it's best to use #ImplicitShareMixin. + * - It can be embedded into another struct. For that it's best to use #ImplicitSharingMixin. */ -struct ImplicitShareInfo : blender::NonCopyable, blender::NonMovable { +struct ImplicitSharingInfo : blender::NonCopyable, blender::NonMovable { private: mutable std::atomic users_; public: - ImplicitShareInfo(const int initial_users) : users_(initial_users) + ImplicitSharingInfo(const int initial_users) : users_(initial_users) { } - virtual ~ImplicitShareInfo() + virtual ~ImplicitSharingInfo() { BLI_assert(this->is_mutable()); } @@ -69,7 +69,7 @@ struct ImplicitShareInfo : blender::NonCopyable, blender::NonMovable { BLI_assert(old_user_count >= 1); const bool was_last_user = old_user_count == 1; if (was_last_user) { - const_cast(this)->delete_self_with_data(); + const_cast(this)->delete_self_with_data(); } } @@ -81,9 +81,9 @@ struct ImplicitShareInfo : blender::NonCopyable, blender::NonMovable { /** * Makes it easy to embed copy-on-write behavior into a struct. */ -struct ImplicitShareMixin : public ImplicitShareInfo { +struct ImplicitSharingMixin : public ImplicitSharingInfo { public: - ImplicitShareMixin() : ImplicitShareInfo(1) + ImplicitSharingMixin() : ImplicitSharingInfo(1) { } diff --git a/source/blender/blenlib/BLI_implicit_sharing_user.hh b/source/blender/blenlib/BLI_implicit_sharing_ptr.hh similarity index 100% rename from source/blender/blenlib/BLI_implicit_sharing_user.hh rename to source/blender/blenlib/BLI_implicit_sharing_ptr.hh diff --git a/source/blender/blenlib/CMakeLists.txt b/source/blender/blenlib/CMakeLists.txt index 5ceed93da5c..7086f7822fc 100644 --- a/source/blender/blenlib/CMakeLists.txt +++ b/source/blender/blenlib/CMakeLists.txt @@ -199,7 +199,7 @@ set(SRC BLI_console.h BLI_convexhull_2d.h BLI_implicit_sharing.hh - BLI_implicit_sharing_user.hh + BLI_implicit_sharing_ptr.hh BLI_cpp_type.hh BLI_cpp_type_make.hh BLI_cpp_types.hh -- 2.30.2 From d5b14539e3116bffff211b297e3f217abd62960d Mon Sep 17 00:00:00 2001 From: Jacques Lucke Date: Wed, 22 Mar 2023 13:59:19 +0100 Subject: [PATCH 05/13] cleanup --- .../blenkernel/BKE_anonymous_attribute_id.hh | 2 +- source/blender/blenkernel/BKE_geometry_set.hh | 2 +- .../blender/blenlib/BLI_implicit_sharing.hh | 37 ++++++++++--------- .../blenlib/BLI_implicit_sharing_ptr.hh | 23 +++++++----- .../geometry/intern/realize_instances.cc | 4 +- 5 files changed, 37 insertions(+), 31 deletions(-) diff --git a/source/blender/blenkernel/BKE_anonymous_attribute_id.hh b/source/blender/blenkernel/BKE_anonymous_attribute_id.hh index 7132e23c535..d9024ed9c05 100644 --- a/source/blender/blenkernel/BKE_anonymous_attribute_id.hh +++ b/source/blender/blenkernel/BKE_anonymous_attribute_id.hh @@ -54,7 +54,7 @@ class AnonymousAttributeID : public ImplicitSharingMixin { }; /** Wrapper for #AnonymousAttributeID that avoids manual reference counting. */ -using AutoAnonymousAttributeID = ImplicitSharePtr; +using AutoAnonymousAttributeID = ImplicitSharingPtr; /** * A set of anonymous attribute names that is passed around in geometry nodes. diff --git a/source/blender/blenkernel/BKE_geometry_set.hh b/source/blender/blenkernel/BKE_geometry_set.hh index 4f751ca14ce..203657361e9 100644 --- a/source/blender/blenkernel/BKE_geometry_set.hh +++ b/source/blender/blenkernel/BKE_geometry_set.hh @@ -101,7 +101,7 @@ inline constexpr bool is_geometry_component_v = std::is_base_of_v; + using GeometryComponentPtr = blender::ImplicitSharingPtr; /* Indexed by #GeometryComponentType. */ std::array components_; diff --git a/source/blender/blenlib/BLI_implicit_sharing.hh b/source/blender/blenlib/BLI_implicit_sharing.hh index fe46706c788..4c9331bfe0e 100644 --- a/source/blender/blenlib/BLI_implicit_sharing.hh +++ b/source/blender/blenlib/BLI_implicit_sharing.hh @@ -15,26 +15,27 @@ namespace blender { /** - * #bCopyOnWrite allows implementing copy-on-write behavior, i.e. it allows sharing read-only data - * between multiple independend systems (e.g. meshes). The data is only copied when it is shared - * and is about to be modified. This is in contrast to making copies before it is actually known - * that it is necessary. + * #ImplicitSharingInfo is the core data structure for implicit sharing in Blender. Implicit + * sharing is a technique that avoids copying data when it is not necessary. This results in better + * memory usage and performance. Only read-only data can be shared, because otherwise multiple + * owners might want to change the data in conflicting ways. * - * Internally, this is mostly just a glorified reference count. If the reference count is 1, the - * data only has a single owner and is mutable. If it is larger than 1, it is shared and must be - * logically const. + * To determine whether data is shared, #ImplicitSharingInfo keeps a user count. If the count is 1, + * the data only has a single owner and is therefore mutable. If some code wants to modify data + * that is currently shared, it has to make a copy first. + * This behavior is also called "copy on write". * - * On top of containing a reference count, #bCopyOnWrite also knows how to destruct the referenced - * data. This is important because the code freeing the data in the end might not know how it was - * allocated (for example, it doesn't know whether an array was allocated using the system or - * guarded allocator). + * On top of containing a reference count, #ImplicitSharingInfo also knows how to destruct the + * referenced data. This is important because the code freeing the data in the end might not know + * how it was allocated (for example, it doesn't know whether an array was allocated using the + * system or guarded allocator). * - * #bCopyOnWrite is used in two ways: - * - It can be allocated separately from the referenced data as is typically the case with raw - * arrays (e.g. for mesh attributes). + * #ImplicitSharingInfo can be used in two ways: + * - It can be allocated separately from the referenced data. This is used when the shared data is + * e.g. a plain data array. * - It can be embedded into another struct. For that it's best to use #ImplicitSharingMixin. */ -struct ImplicitSharingInfo : blender::NonCopyable, blender::NonMovable { +class ImplicitSharingInfo : blender::NonCopyable, blender::NonMovable { private: mutable std::atomic users_; @@ -74,14 +75,14 @@ struct ImplicitSharingInfo : blender::NonCopyable, blender::NonMovable { } private: - /** Has to free the #bCopyOnWrite and the referenced data. */ + /** Has to free the #ImplicitSharingInfo and the referenced data. */ virtual void delete_self_with_data() = 0; }; /** - * Makes it easy to embed copy-on-write behavior into a struct. + * Makes it easy to embed implicit-sharing behavior into a struct. */ -struct ImplicitSharingMixin : public ImplicitSharingInfo { +class ImplicitSharingMixin : public ImplicitSharingInfo { public: ImplicitSharingMixin() : ImplicitSharingInfo(1) { diff --git a/source/blender/blenlib/BLI_implicit_sharing_ptr.hh b/source/blender/blenlib/BLI_implicit_sharing_ptr.hh index 8f043c4d6b6..2c95855b553 100644 --- a/source/blender/blenlib/BLI_implicit_sharing_ptr.hh +++ b/source/blender/blenlib/BLI_implicit_sharing_ptr.hh @@ -10,33 +10,38 @@ namespace blender { -template class ImplicitSharePtr { +/** + * #ImplicitSharingPtr is a smart pointer that manages implicit sharing. It's designed to work with + * types that derive from #ImplicitSharingMixin. It is fairly similar to #std::shared_ptr but + * expects the reference count to be embedded in the data. + */ +template class ImplicitSharingPtr { private: T *data_ = nullptr; public: - ImplicitSharePtr() = default; + ImplicitSharingPtr() = default; - ImplicitSharePtr(T *data) : data_(data) + ImplicitSharingPtr(T *data) : data_(data) { } - ImplicitSharePtr(const ImplicitSharePtr &other) : data_(other.data_) + ImplicitSharingPtr(const ImplicitSharingPtr &other) : data_(other.data_) { this->add_user(data_); } - ImplicitSharePtr(ImplicitSharePtr &&other) : data_(other.data_) + ImplicitSharingPtr(ImplicitSharingPtr &&other) : data_(other.data_) { other.data_ = nullptr; } - ~ImplicitSharePtr() + ~ImplicitSharingPtr() { this->remove_user_and_delete_if_last(data_); } - ImplicitSharePtr &operator=(const ImplicitSharePtr &other) + ImplicitSharingPtr &operator=(const ImplicitSharingPtr &other) { if (this == &other) { return *this; @@ -48,7 +53,7 @@ template class ImplicitSharePtr { return *this; } - ImplicitSharePtr &operator=(ImplicitSharePtr &&other) + ImplicitSharingPtr &operator=(ImplicitSharingPtr &&other) { if (this == &other) { return *this; @@ -122,7 +127,7 @@ template class ImplicitSharePtr { return get_default_hash(data_); } - friend bool operator==(const ImplicitSharePtr &a, const ImplicitSharePtr &b) + friend bool operator==(const ImplicitSharingPtr &a, const ImplicitSharingPtr &b) { return a.data_ == b.data_; } diff --git a/source/blender/geometry/intern/realize_instances.cc b/source/blender/geometry/intern/realize_instances.cc index bc72fe72919..57c2de72537 100644 --- a/source/blender/geometry/intern/realize_instances.cc +++ b/source/blender/geometry/intern/realize_instances.cc @@ -226,8 +226,8 @@ struct GatherTasks { /* Volumes only have very simple support currently. Only the first found volume is put into the * output. */ - ImplicitSharePtr first_volume; - ImplicitSharePtr first_edit_data; + ImplicitSharingPtr first_volume; + ImplicitSharingPtr first_edit_data; }; /** Current offsets while during the gather operation. */ -- 2.30.2 From 6f965991caef29508edc8c51a05b015308d4c877 Mon Sep 17 00:00:00 2001 From: Jacques Lucke Date: Wed, 22 Mar 2023 14:01:09 +0100 Subject: [PATCH 06/13] cleanup --- source/blender/blenkernel/BKE_geometry_set.hh | 2 +- source/blender/blenlib/CMakeLists.txt | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/source/blender/blenkernel/BKE_geometry_set.hh b/source/blender/blenkernel/BKE_geometry_set.hh index 203657361e9..a70fa8cfb4e 100644 --- a/source/blender/blenkernel/BKE_geometry_set.hh +++ b/source/blender/blenkernel/BKE_geometry_set.hh @@ -40,7 +40,7 @@ class Instances; /** * This is the base class for specialized geometry component types. A geometry component uses - * copy-on-write behavior to avoid read-only copies. It also integrates with attribute API, which + * implicit sharing to avoid read-only copies. It also integrates with attribute API, which * generalizes storing and modifying generic information on a geometry. */ class GeometryComponent : public blender::ImplicitSharingMixin { diff --git a/source/blender/blenlib/CMakeLists.txt b/source/blender/blenlib/CMakeLists.txt index 7086f7822fc..a4ba0114913 100644 --- a/source/blender/blenlib/CMakeLists.txt +++ b/source/blender/blenlib/CMakeLists.txt @@ -198,8 +198,6 @@ set(SRC BLI_compute_context.hh BLI_console.h BLI_convexhull_2d.h - BLI_implicit_sharing.hh - BLI_implicit_sharing_ptr.hh BLI_cpp_type.hh BLI_cpp_type_make.hh BLI_cpp_types.hh @@ -243,6 +241,8 @@ set(SRC BLI_hash_tables.hh BLI_heap.h BLI_heap_simple.h + BLI_implicit_sharing.hh + BLI_implicit_sharing_ptr.hh BLI_index_mask.hh BLI_index_mask_ops.hh BLI_index_range.hh -- 2.30.2 From 6810f4c4896552f602e5831a7b01ba20d657bc8b Mon Sep 17 00:00:00 2001 From: Jacques Lucke Date: Wed, 22 Mar 2023 14:09:42 +0100 Subject: [PATCH 07/13] cleanup --- source/blender/blenlib/BLI_implicit_sharing.hh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/source/blender/blenlib/BLI_implicit_sharing.hh b/source/blender/blenlib/BLI_implicit_sharing.hh index 4c9331bfe0e..b54852e2be3 100644 --- a/source/blender/blenlib/BLI_implicit_sharing.hh +++ b/source/blender/blenlib/BLI_implicit_sharing.hh @@ -25,9 +25,9 @@ namespace blender { * that is currently shared, it has to make a copy first. * This behavior is also called "copy on write". * - * On top of containing a reference count, #ImplicitSharingInfo also knows how to destruct the - * referenced data. This is important because the code freeing the data in the end might not know - * how it was allocated (for example, it doesn't know whether an array was allocated using the + * In addition to containing the reference count, #ImplicitSharingInfo also knows how to destruct + * the referenced data. This is important because the code freeing the data in the end might not + * know how it was allocated (for example, it doesn't know whether an array was allocated using the * system or guarded allocator). * * #ImplicitSharingInfo can be used in two ways: -- 2.30.2 From d1541dec17e72b6cb2ac06a93fcfe282eae87998 Mon Sep 17 00:00:00 2001 From: Jacques Lucke Date: Wed, 22 Mar 2023 14:11:10 +0100 Subject: [PATCH 08/13] improve comment --- source/blender/blenlib/BLI_implicit_sharing.hh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/source/blender/blenlib/BLI_implicit_sharing.hh b/source/blender/blenlib/BLI_implicit_sharing.hh index b54852e2be3..9bcc1857a63 100644 --- a/source/blender/blenlib/BLI_implicit_sharing.hh +++ b/source/blender/blenlib/BLI_implicit_sharing.hh @@ -80,7 +80,8 @@ class ImplicitSharingInfo : blender::NonCopyable, blender::NonMovable { }; /** - * Makes it easy to embed implicit-sharing behavior into a struct. + * Makes it easy to embed implicit-sharing behavior into a struct. This also allows the subclass to + * be used with #ImplicitSharingPtr. */ class ImplicitSharingMixin : public ImplicitSharingInfo { public: -- 2.30.2 From 5607c08dbd7cc9fd568c163a76b0eafec8d289df Mon Sep 17 00:00:00 2001 From: Jacques Lucke Date: Wed, 22 Mar 2023 14:12:38 +0100 Subject: [PATCH 09/13] remove const cast --- source/blender/geometry/intern/realize_instances.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/blender/geometry/intern/realize_instances.cc b/source/blender/geometry/intern/realize_instances.cc index 57c2de72537..e2b6a47fac5 100644 --- a/source/blender/geometry/intern/realize_instances.cc +++ b/source/blender/geometry/intern/realize_instances.cc @@ -609,7 +609,7 @@ static void gather_realize_tasks_recursive(GatherTasksInfo &gather_info, const VolumeComponent *volume_component = static_cast(component); if (!gather_info.r_tasks.first_volume) { volume_component->add_user(); - gather_info.r_tasks.first_volume = const_cast(volume_component); + gather_info.r_tasks.first_volume = volume_component; } break; } -- 2.30.2 From a126d3bd486a0e13b28af0bc78d4e3cb34faec35 Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Wed, 22 Mar 2023 09:41:42 -0400 Subject: [PATCH 10/13] Cleanup: Remove unnecessary namespace specification --- source/blender/blenlib/BLI_implicit_sharing.hh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/blender/blenlib/BLI_implicit_sharing.hh b/source/blender/blenlib/BLI_implicit_sharing.hh index 9bcc1857a63..1ec01858515 100644 --- a/source/blender/blenlib/BLI_implicit_sharing.hh +++ b/source/blender/blenlib/BLI_implicit_sharing.hh @@ -35,7 +35,7 @@ namespace blender { * e.g. a plain data array. * - It can be embedded into another struct. For that it's best to use #ImplicitSharingMixin. */ -class ImplicitSharingInfo : blender::NonCopyable, blender::NonMovable { +class ImplicitSharingInfo : NonCopyable, NonMovable { private: mutable std::atomic users_; -- 2.30.2 From ddc0ba893484b6cb4b18238523a477f159e5bf4e Mon Sep 17 00:00:00 2001 From: Jacques Lucke Date: Wed, 22 Mar 2023 18:13:30 +0100 Subject: [PATCH 11/13] improve comments --- source/blender/blenlib/BLI_implicit_sharing.hh | 11 +++++++++-- source/blender/blenlib/BLI_implicit_sharing_ptr.hh | 2 +- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/source/blender/blenlib/BLI_implicit_sharing.hh b/source/blender/blenlib/BLI_implicit_sharing.hh index 9bcc1857a63..8bd330a6935 100644 --- a/source/blender/blenlib/BLI_implicit_sharing.hh +++ b/source/blender/blenlib/BLI_implicit_sharing.hh @@ -49,21 +49,28 @@ class ImplicitSharingInfo : blender::NonCopyable, blender::NonMovable { BLI_assert(this->is_mutable()); } + /** True if there are other const references to the resource, meaning it cannot be modified. */ bool is_shared() const { return users_.load(std::memory_order_relaxed) >= 2; } + /** Whether the resource can be modified without a copy because there is only one owner. */ bool is_mutable() const { return !this->is_shared(); } + /** Call when a the data has a new additional owner. */ void add_user() const { users_.fetch_add(1, std::memory_order_relaxed); } + /** + * Call when the data is no longer needed. This might just decrement the user count, or it might + * also delete the data if this was the last user. + */ void remove_user_and_delete_if_last() const { const int old_user_count = users_.fetch_sub(1, std::memory_order_relaxed); @@ -80,8 +87,8 @@ class ImplicitSharingInfo : blender::NonCopyable, blender::NonMovable { }; /** - * Makes it easy to embed implicit-sharing behavior into a struct. This also allows the subclass to - * be used with #ImplicitSharingPtr. + * Makes it easy to embed implicit-sharing behavior into a struct. Structs that derive from this + * class can be used with #ImplicitSharingPtr. */ class ImplicitSharingMixin : public ImplicitSharingInfo { public: diff --git a/source/blender/blenlib/BLI_implicit_sharing_ptr.hh b/source/blender/blenlib/BLI_implicit_sharing_ptr.hh index 2c95855b553..8e726ac97ce 100644 --- a/source/blender/blenlib/BLI_implicit_sharing_ptr.hh +++ b/source/blender/blenlib/BLI_implicit_sharing_ptr.hh @@ -13,7 +13,7 @@ namespace blender { /** * #ImplicitSharingPtr is a smart pointer that manages implicit sharing. It's designed to work with * types that derive from #ImplicitSharingMixin. It is fairly similar to #std::shared_ptr but - * expects the reference count to be embedded in the data. + * requires the reference count to be embedded in the data. */ template class ImplicitSharingPtr { private: -- 2.30.2 From 65024c99af68c41b19df29bd9fa73f8f2a7b254b Mon Sep 17 00:00:00 2001 From: Jacques Lucke Date: Mon, 27 Mar 2023 13:30:51 +0200 Subject: [PATCH 12/13] use memory_order_acq_rel when decrementing counter --- source/blender/blenlib/BLI_implicit_sharing.hh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/blender/blenlib/BLI_implicit_sharing.hh b/source/blender/blenlib/BLI_implicit_sharing.hh index af535ed8320..7b47830aadf 100644 --- a/source/blender/blenlib/BLI_implicit_sharing.hh +++ b/source/blender/blenlib/BLI_implicit_sharing.hh @@ -73,7 +73,7 @@ class ImplicitSharingInfo : NonCopyable, NonMovable { */ void remove_user_and_delete_if_last() const { - const int old_user_count = users_.fetch_sub(1, std::memory_order_relaxed); + const int old_user_count = users_.fetch_sub(1, std::memory_order_acq_rel); BLI_assert(old_user_count >= 1); const bool was_last_user = old_user_count == 1; if (was_last_user) { -- 2.30.2 From aff843b7f1548db5be50aaace656d5bded2a8d2d Mon Sep 17 00:00:00 2001 From: Jacques Lucke Date: Tue, 28 Mar 2023 13:24:14 +0200 Subject: [PATCH 13/13] add unit test --- source/blender/blenlib/CMakeLists.txt | 1 + .../tests/BLI_implicit_sharing_test.cc | 85 +++++++++++++++++++ 2 files changed, 86 insertions(+) create mode 100644 source/blender/blenlib/tests/BLI_implicit_sharing_test.cc diff --git a/source/blender/blenlib/CMakeLists.txt b/source/blender/blenlib/CMakeLists.txt index 4f67ba8e73e..04ba4a672ee 100644 --- a/source/blender/blenlib/CMakeLists.txt +++ b/source/blender/blenlib/CMakeLists.txt @@ -494,6 +494,7 @@ if(WITH_GTESTS) tests/BLI_hash_mm2a_test.cc tests/BLI_heap_simple_test.cc tests/BLI_heap_test.cc + tests/BLI_implicit_sharing_test.cc tests/BLI_index_mask_test.cc tests/BLI_index_range_test.cc tests/BLI_inplace_priority_queue_test.cc diff --git a/source/blender/blenlib/tests/BLI_implicit_sharing_test.cc b/source/blender/blenlib/tests/BLI_implicit_sharing_test.cc new file mode 100644 index 00000000000..ebb341d7a1b --- /dev/null +++ b/source/blender/blenlib/tests/BLI_implicit_sharing_test.cc @@ -0,0 +1,85 @@ +/* SPDX-License-Identifier: Apache-2.0 */ + +#include "MEM_guardedalloc.h" + +#include "BLI_implicit_sharing_ptr.hh" + +#include "testing/testing.h" + +namespace blender::tests { + +class ImplicitlySharedData : public ImplicitSharingMixin { + public: + ImplicitSharingPtr copy() const + { + return MEM_new(__func__); + } + + void delete_self() override + { + MEM_delete(this); + } +}; + +class SharedDataContainer { + private: + ImplicitSharingPtr data_; + + public: + SharedDataContainer() : data_(MEM_new(__func__)) + { + } + + const ImplicitlySharedData *get_for_read() const + { + return data_.get(); + } + + ImplicitlySharedData *get_for_write() + { + if (!data_) { + return nullptr; + } + if (data_->is_mutable()) { + return data_.get(); + } + data_ = data_->copy(); + return data_.get(); + } +}; + +TEST(implicit_sharing, CopyOnWriteAccess) +{ + /* Create the initial data. */ + SharedDataContainer a; + EXPECT_NE(a.get_for_read(), nullptr); + + /* a and b share the same underlying data now. */ + SharedDataContainer b = a; + EXPECT_EQ(a.get_for_read(), b.get_for_read()); + + /* c now shares the data with a and b. */ + SharedDataContainer c = a; + EXPECT_EQ(b.get_for_read(), c.get_for_read()); + + /* Retrieving write access on b should make a copy because the data is shared. */ + ImplicitlySharedData *data_b1 = b.get_for_write(); + EXPECT_NE(data_b1, nullptr); + EXPECT_EQ(data_b1, b.get_for_read()); + EXPECT_NE(data_b1, a.get_for_read()); + EXPECT_NE(data_b1, c.get_for_read()); + + /* Retrieving the same write access again should *not* make another copy. */ + ImplicitlySharedData *data_b2 = b.get_for_write(); + EXPECT_EQ(data_b1, data_b2); + + /* Moving b should also move the data. b then does not have ownership anymore. Since the data in + * b only had one owner, the data is still mutable now that d is the owner. */ + SharedDataContainer d = std::move(b); + EXPECT_EQ(b.get_for_read(), nullptr); + EXPECT_EQ(b.get_for_write(), nullptr); + EXPECT_EQ(d.get_for_read(), data_b1); + EXPECT_EQ(d.get_for_write(), data_b1); +} + +} // namespace blender::tests -- 2.30.2