Core: new blenlib library for implicit-sharing #105994
|
@ -4,9 +4,9 @@
|
||||||
|
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
|
|
||||||
|
#include "BLI_implicit_sharing_ptr.hh"
|
||||||
#include "BLI_set.hh"
|
#include "BLI_set.hh"
|
||||||
#include "BLI_string_ref.hh"
|
#include "BLI_string_ref.hh"
|
||||||
#include "BLI_user_counter.hh"
|
|
||||||
|
|
||||||
namespace blender::bke {
|
namespace blender::bke {
|
||||||
|
|
||||||
|
@ -32,10 +32,7 @@ namespace blender::bke {
|
||||||
* because that is not available in C code. If possible, the #AutoAnonymousAttributeID wrapper
|
* because that is not available in C code. If possible, the #AutoAnonymousAttributeID wrapper
|
||||||
* should be used to avoid manual reference counting in C++ code.
|
* should be used to avoid manual reference counting in C++ code.
|
||||||
*/
|
*/
|
||||||
class AnonymousAttributeID {
|
class AnonymousAttributeID : public ImplicitSharingMixin {
|
||||||
private:
|
|
||||||
mutable std::atomic<int> users_ = 1;
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
std::string name_;
|
std::string name_;
|
||||||
|
|
||||||
|
@ -49,22 +46,15 @@ class AnonymousAttributeID {
|
||||||
|
|
||||||
virtual std::string user_name() const;
|
virtual std::string user_name() const;
|
||||||
|
|
||||||
void user_add() const
|
private:
|
||||||
|
void delete_self() override
|
||||||
{
|
{
|
||||||
users_.fetch_add(1);
|
MEM_delete(this);
|
||||||
}
|
|
||||||
|
|
||||||
void user_remove() const
|
|
||||||
{
|
|
||||||
const int new_users = users_.fetch_sub(1) - 1;
|
|
||||||
if (new_users == 0) {
|
|
||||||
MEM_delete(this);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Wrapper for #AnonymousAttributeID that avoids manual reference counting. */
|
/** Wrapper for #AnonymousAttributeID that avoids manual reference counting. */
|
||||||
using AutoAnonymousAttributeID = UserCounter<const AnonymousAttributeID>;
|
using AutoAnonymousAttributeID = ImplicitSharingPtr<const AnonymousAttributeID>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A set of anonymous attribute names that is passed around in geometry nodes.
|
* A set of anonymous attribute names that is passed around in geometry nodes.
|
||||||
|
|
|
@ -12,8 +12,6 @@
|
||||||
|
|
||||||
#include "BLI_function_ref.hh"
|
#include "BLI_function_ref.hh"
|
||||||
#include "BLI_map.hh"
|
#include "BLI_map.hh"
|
||||||
#include "BLI_math_vector_types.hh"
|
|
||||||
#include "BLI_user_counter.hh"
|
|
||||||
#include "BLI_vector_set.hh"
|
#include "BLI_vector_set.hh"
|
||||||
|
|
||||||
#include "BKE_attribute.hh"
|
#include "BKE_attribute.hh"
|
||||||
|
@ -40,18 +38,13 @@ class CurvesEditHints;
|
||||||
class Instances;
|
class Instances;
|
||||||
} // namespace blender::bke
|
} // namespace blender::bke
|
||||||
|
|
||||||
class GeometryComponent;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is the base class for specialized geometry component types. A geometry component handles
|
* This is the base class for specialized geometry component types. A geometry component uses
|
||||||
* a user count to allow avoiding duplication when it is wrapped with #UserCounter. It also handles
|
* implicit sharing to avoid read-only copies. It also integrates with attribute API, which
|
||||||
* the attribute API, which generalizes storing and modifying generic information on a geometry.
|
* generalizes storing and modifying generic information on a geometry.
|
||||||
*/
|
*/
|
||||||
class GeometryComponent {
|
class GeometryComponent : public blender::ImplicitSharingMixin {
|
||||||
private:
|
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<int> users_ = 1;
|
|
||||||
GeometryComponentType type_;
|
GeometryComponentType type_;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
@ -77,13 +70,12 @@ class GeometryComponent {
|
||||||
virtual bool owns_direct_data() const = 0;
|
virtual bool owns_direct_data() const = 0;
|
||||||
virtual void ensure_owns_direct_data() = 0;
|
virtual void ensure_owns_direct_data() = 0;
|
||||||
|
|
||||||
void user_add() const;
|
|
||||||
void user_remove() const;
|
|
||||||
bool is_mutable() const;
|
|
||||||
|
|
||||||
GeometryComponentType type() const;
|
GeometryComponentType type() const;
|
||||||
|
|
||||||
virtual bool is_empty() const;
|
virtual bool is_empty() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void delete_self() override;
|
||||||
};
|
};
|
||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
|
@ -109,7 +101,7 @@ inline constexpr bool is_geometry_component_v = std::is_base_of_v<GeometryCompon
|
||||||
*/
|
*/
|
||||||
struct GeometrySet {
|
struct GeometrySet {
|
||||||
private:
|
private:
|
||||||
using GeometryComponentPtr = blender::UserCounter<class GeometryComponent>;
|
using GeometryComponentPtr = blender::ImplicitSharingPtr<class GeometryComponent>;
|
||||||
/* Indexed by #GeometryComponentType. */
|
/* Indexed by #GeometryComponentType. */
|
||||||
std::array<GeometryComponentPtr, GEO_COMPONENT_TYPE_ENUM_SIZE> components_;
|
std::array<GeometryComponentPtr, GEO_COMPONENT_TYPE_ENUM_SIZE> components_;
|
||||||
|
|
||||||
|
|
|
@ -2273,7 +2273,7 @@ bool CustomData_merge(const CustomData *source,
|
||||||
layer->anonymous_id = nullptr;
|
layer->anonymous_id = nullptr;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
layer->anonymous_id->user_add();
|
layer->anonymous_id->add_user();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (alloctype == CD_ASSIGN) {
|
if (alloctype == CD_ASSIGN) {
|
||||||
|
@ -2365,7 +2365,7 @@ static void customData_free_layer__internal(CustomDataLayer *layer, const int to
|
||||||
const LayerTypeInfo *typeInfo;
|
const LayerTypeInfo *typeInfo;
|
||||||
|
|
||||||
if (layer->anonymous_id != nullptr) {
|
if (layer->anonymous_id != nullptr) {
|
||||||
layer->anonymous_id->user_remove();
|
layer->anonymous_id->remove_user_and_delete_if_last();
|
||||||
layer->anonymous_id = nullptr;
|
layer->anonymous_id = nullptr;
|
||||||
}
|
}
|
||||||
if (!(layer->flag & CD_FLAG_NOFREE) && layer->data) {
|
if (!(layer->flag & CD_FLAG_NOFREE) && layer->data) {
|
||||||
|
@ -2956,7 +2956,7 @@ void *CustomData_add_layer_anonymous(CustomData *data,
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
anonymous_id->user_add();
|
anonymous_id->add_user();
|
||||||
layer->anonymous_id = anonymous_id;
|
layer->anonymous_id = anonymous_id;
|
||||||
return layer->data;
|
return layer->data;
|
||||||
}
|
}
|
||||||
|
|
|
@ -84,26 +84,6 @@ std::optional<blender::bke::MutableAttributeAccessor> GeometryComponent::attribu
|
||||||
return std::nullopt;
|
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
|
GeometryComponentType GeometryComponent::type() const
|
||||||
{
|
{
|
||||||
return type_;
|
return type_;
|
||||||
|
@ -114,6 +94,11 @@ bool GeometryComponent::is_empty() const
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GeometryComponent::delete_self()
|
||||||
|
{
|
||||||
|
delete this;
|
||||||
|
}
|
||||||
|
|
||||||
/** \} */
|
/** \} */
|
||||||
|
|
||||||
/* -------------------------------------------------------------------- */
|
/* -------------------------------------------------------------------- */
|
||||||
|
@ -198,7 +183,7 @@ void GeometrySet::remove_geometry_during_modify()
|
||||||
void GeometrySet::add(const GeometryComponent &component)
|
void GeometrySet::add(const GeometryComponent &component)
|
||||||
{
|
{
|
||||||
BLI_assert(!components_[component.type()]);
|
BLI_assert(!components_[component.type()]);
|
||||||
component.user_add();
|
component.add_user();
|
||||||
components_[component.type()] = const_cast<GeometryComponent *>(&component);
|
components_[component.type()] = const_cast<GeometryComponent *>(&component);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,109 @@
|
||||||
|
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
/** \file
|
||||||
|
* \ingroup bli
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
|
||||||
|
#include "BLI_compiler_attrs.h"
|
||||||
|
#include "BLI_utildefines.h"
|
||||||
|
#include "BLI_utility_mixins.hh"
|
||||||
|
|
||||||
|
namespace blender {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* #ImplicitSharingInfo is the core data structure for implicit sharing in Blender. Implicit
|
||||||
|
* sharing is a technique that avoids copying data when it is not necessary. This results in better
|
||||||
|
* memory usage and performance. Only read-only data can be shared, because otherwise multiple
|
||||||
|
* owners might want to change the data in conflicting ways.
|
||||||
|
*
|
||||||
|
* To determine whether data is shared, #ImplicitSharingInfo keeps a user count. If the count is 1,
|
||||||
|
* the data only has a single owner and is therefore mutable. If some code wants to modify data
|
||||||
|
* that is currently shared, it has to make a copy first.
|
||||||
|
* This behavior is also called "copy on write".
|
||||||
|
*
|
||||||
|
* In addition to containing the reference count, #ImplicitSharingInfo also knows how to destruct
|
||||||
|
* the referenced data. This is important because the code freeing the data in the end might not
|
||||||
|
* know how it was allocated (for example, it doesn't know whether an array was allocated using the
|
||||||
|
* system or guarded allocator).
|
||||||
|
*
|
||||||
|
* #ImplicitSharingInfo can be used in two ways:
|
||||||
|
* - It can be allocated separately from the referenced data. This is used when the shared data is
|
||||||
|
* e.g. a plain data array.
|
||||||
|
* - It can be embedded into another struct. For that it's best to use #ImplicitSharingMixin.
|
||||||
|
*/
|
||||||
|
class ImplicitSharingInfo : NonCopyable, NonMovable {
|
||||||
|
private:
|
||||||
|
mutable std::atomic<int> users_;
|
||||||
|
|
||||||
|
public:
|
||||||
|
ImplicitSharingInfo(const int initial_users) : users_(initial_users)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual ~ImplicitSharingInfo()
|
||||||
|
{
|
||||||
|
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;
|
||||||
JacquesLucke marked this conversation as resolved
|
|||||||
|
}
|
||||||
|
|
||||||
|
/** 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_acq_rel);
|
||||||
brecht marked this conversation as resolved
Outdated
Brecht Van Lommel
commented
My understanding is that user count decrement requires For example libc++ does this for shared_ptr. More details: My understanding is that user count decrement requires `std::memory_order_acq_rel`, while increment can use `std::memory_order_relaxed`.
For example libc++ does this for shared_ptr. More details:
https://stackoverflow.com/questions/48124031/stdmemory-order-relaxed-atomicity-with-respect-to-the-same-atomic-variable
|
|||||||
|
BLI_assert(old_user_count >= 1);
|
||||||
|
const bool was_last_user = old_user_count == 1;
|
||||||
|
if (was_last_user) {
|
||||||
|
const_cast<ImplicitSharingInfo *>(this)->delete_self_with_data();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
JacquesLucke marked this conversation as resolved
Outdated
Hans Goudey
commented
This comment is a bit hard to follow, maybe replacing This comment is a bit hard to follow, maybe replacing `the subclass` with something more specific would help (like saying the ptr class can be used with a class that derives from the mixin class).
|
|||||||
|
private:
|
||||||
|
/** Has to free the #ImplicitSharingInfo and the referenced data. */
|
||||||
|
virtual void delete_self_with_data() = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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:
|
||||||
|
ImplicitSharingMixin() : ImplicitSharingInfo(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;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace blender
|
|
@ -6,59 +6,60 @@
|
||||||
* \ingroup bli
|
* \ingroup bli
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <atomic>
|
#include "BLI_implicit_sharing.hh"
|
||||||
|
|
||||||
namespace blender {
|
namespace blender {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A simple automatic reference counter. It is similar to std::shared_ptr, but expects that the
|
* #ImplicitSharingPtr is a smart pointer that manages implicit sharing. It's designed to work with
|
||||||
* reference count is inside the object.
|
* types that derive from #ImplicitSharingMixin. It is fairly similar to #std::shared_ptr but
|
||||||
|
* requires the reference count to be embedded in the data.
|
||||||
JacquesLucke marked this conversation as resolved
Outdated
Hans Goudey
commented
`expects` -> `requires`, right? `std::make_shared` will allocate the use count with the object so it's helpful to clarify this a bit.
|
|||||||
*/
|
*/
|
||||||
template<typename T> class UserCounter {
|
template<typename T> class ImplicitSharingPtr {
|
||||||
private:
|
private:
|
||||||
T *data_ = nullptr;
|
T *data_ = nullptr;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
UserCounter() = default;
|
ImplicitSharingPtr() = default;
|
||||||
|
|
||||||
UserCounter(T *data) : data_(data)
|
ImplicitSharingPtr(T *data) : data_(data)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
UserCounter(const UserCounter &other) : data_(other.data_)
|
ImplicitSharingPtr(const ImplicitSharingPtr &other) : data_(other.data_)
|
||||||
{
|
{
|
||||||
this->user_add(data_);
|
this->add_user(data_);
|
||||||
}
|
}
|
||||||
|
|
||||||
UserCounter(UserCounter &&other) : data_(other.data_)
|
ImplicitSharingPtr(ImplicitSharingPtr &&other) : data_(other.data_)
|
||||||
{
|
{
|
||||||
other.data_ = nullptr;
|
other.data_ = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
~UserCounter()
|
~ImplicitSharingPtr()
|
||||||
{
|
{
|
||||||
this->user_remove(data_);
|
this->remove_user_and_delete_if_last(data_);
|
||||||
}
|
}
|
||||||
|
|
||||||
UserCounter &operator=(const UserCounter &other)
|
ImplicitSharingPtr &operator=(const ImplicitSharingPtr &other)
|
||||||
{
|
{
|
||||||
if (this == &other) {
|
if (this == &other) {
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
this->user_remove(data_);
|
this->remove_user_and_delete_if_last(data_);
|
||||||
data_ = other.data_;
|
data_ = other.data_;
|
||||||
this->user_add(data_);
|
this->add_user(data_);
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
UserCounter &operator=(UserCounter &&other)
|
ImplicitSharingPtr &operator=(ImplicitSharingPtr &&other)
|
||||||
{
|
{
|
||||||
if (this == &other) {
|
if (this == &other) {
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
this->user_remove(data_);
|
this->remove_user_and_delete_if_last(data_);
|
||||||
data_ = other.data_;
|
data_ = other.data_;
|
||||||
other.data_ = nullptr;
|
other.data_ = nullptr;
|
||||||
return *this;
|
return *this;
|
||||||
|
@ -112,7 +113,7 @@ template<typename T> class UserCounter {
|
||||||
|
|
||||||
void reset()
|
void reset()
|
||||||
{
|
{
|
||||||
this->user_remove(data_);
|
this->remove_user_and_delete_if_last(data_);
|
||||||
data_ = nullptr;
|
data_ = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -126,29 +127,23 @@ template<typename T> class UserCounter {
|
||||||
return get_default_hash(data_);
|
return get_default_hash(data_);
|
||||||
}
|
}
|
||||||
|
|
||||||
friend bool operator==(const UserCounter &a, const UserCounter &b)
|
friend bool operator==(const ImplicitSharingPtr &a, const ImplicitSharingPtr &b)
|
||||||
{
|
{
|
||||||
return a.data_ == b.data_;
|
return a.data_ == b.data_;
|
||||||
}
|
}
|
||||||
|
|
||||||
friend std::ostream &operator<<(std::ostream &stream, const UserCounter &value)
|
|
||||||
{
|
|
||||||
stream << value.data_;
|
|
||||||
return stream;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static void user_add(T *data)
|
static void add_user(T *data)
|
||||||
{
|
{
|
||||||
if (data != nullptr) {
|
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) {
|
if (data != nullptr) {
|
||||||
data->user_remove();
|
data->remove_user_and_delete_if_last();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
|
@ -243,6 +243,8 @@ set(SRC
|
||||||
BLI_hash_tables.hh
|
BLI_hash_tables.hh
|
||||||
BLI_heap.h
|
BLI_heap.h
|
||||||
BLI_heap_simple.h
|
BLI_heap_simple.h
|
||||||
|
BLI_implicit_sharing.hh
|
||||||
|
BLI_implicit_sharing_ptr.hh
|
||||||
BLI_index_mask.hh
|
BLI_index_mask.hh
|
||||||
BLI_index_mask_ops.hh
|
BLI_index_mask_ops.hh
|
||||||
BLI_index_range.hh
|
BLI_index_range.hh
|
||||||
|
@ -355,7 +357,6 @@ set(SRC
|
||||||
BLI_timecode.h
|
BLI_timecode.h
|
||||||
BLI_timeit.hh
|
BLI_timeit.hh
|
||||||
BLI_timer.h
|
BLI_timer.h
|
||||||
BLI_user_counter.hh
|
|
||||||
BLI_utildefines.h
|
BLI_utildefines.h
|
||||||
BLI_utildefines_iter.h
|
BLI_utildefines_iter.h
|
||||||
BLI_utildefines_stack.h
|
BLI_utildefines_stack.h
|
||||||
|
@ -493,6 +494,7 @@ if(WITH_GTESTS)
|
||||||
tests/BLI_hash_mm2a_test.cc
|
tests/BLI_hash_mm2a_test.cc
|
||||||
tests/BLI_heap_simple_test.cc
|
tests/BLI_heap_simple_test.cc
|
||||||
tests/BLI_heap_test.cc
|
tests/BLI_heap_test.cc
|
||||||
|
tests/BLI_implicit_sharing_test.cc
|
||||||
tests/BLI_index_mask_test.cc
|
tests/BLI_index_mask_test.cc
|
||||||
tests/BLI_index_range_test.cc
|
tests/BLI_index_range_test.cc
|
||||||
tests/BLI_inplace_priority_queue_test.cc
|
tests/BLI_inplace_priority_queue_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<ImplicitlySharedData> copy() const
|
||||||
|
{
|
||||||
|
return MEM_new<ImplicitlySharedData>(__func__);
|
||||||
|
}
|
||||||
|
|
||||||
|
void delete_self() override
|
||||||
|
{
|
||||||
|
MEM_delete(this);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class SharedDataContainer {
|
||||||
|
private:
|
||||||
|
ImplicitSharingPtr<ImplicitlySharedData> data_;
|
||||||
|
|
||||||
|
public:
|
||||||
|
SharedDataContainer() : data_(MEM_new<ImplicitlySharedData>(__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,7 +2,6 @@
|
||||||
|
|
||||||
#include "BLI_array_utils.hh"
|
#include "BLI_array_utils.hh"
|
||||||
#include "BLI_index_mask.hh"
|
#include "BLI_index_mask.hh"
|
||||||
#include "BLI_user_counter.hh"
|
|
||||||
|
|
||||||
#include "BKE_attribute.hh"
|
#include "BKE_attribute.hh"
|
||||||
#include "BKE_attribute_math.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
|
/* 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. */
|
* can also free the names, since the API does not provide pointer stability. */
|
||||||
Vector<std::string> named_ids;
|
Vector<std::string> named_ids;
|
||||||
Vector<UserCounter<const bke::AnonymousAttributeID>> anonymous_ids;
|
Vector<bke::AutoAnonymousAttributeID> anonymous_ids;
|
||||||
for (const bke::AttributeIDRef &id : attributes.all_ids()) {
|
for (const bke::AttributeIDRef &id : attributes.all_ids()) {
|
||||||
if (attributes.lookup_meta_data(id)->domain != ATTR_DOMAIN_EDGE) {
|
if (attributes.lookup_meta_data(id)->domain != ATTR_DOMAIN_EDGE) {
|
||||||
continue;
|
continue;
|
||||||
|
@ -82,14 +81,14 @@ static void add_new_edges(Mesh &mesh,
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
anonymous_ids.append(&id.anonymous_id());
|
anonymous_ids.append(&id.anonymous_id());
|
||||||
id.anonymous_id().user_add();
|
id.anonymous_id().add_user();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Vector<bke::AttributeIDRef> local_edge_ids;
|
Vector<bke::AttributeIDRef> local_edge_ids;
|
||||||
for (const StringRef name : named_ids) {
|
for (const StringRef name : named_ids) {
|
||||||
local_edge_ids.append(name);
|
local_edge_ids.append(name);
|
||||||
}
|
}
|
||||||
for (const UserCounter<const bke::AnonymousAttributeID> &id : anonymous_ids) {
|
for (const bke::AutoAnonymousAttributeID &id : anonymous_ids) {
|
||||||
local_edge_ids.append(*id);
|
local_edge_ids.append(*id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -229,8 +229,8 @@ struct GatherTasks {
|
||||||
|
|
||||||
/* Volumes only have very simple support currently. Only the first found volume is put into the
|
/* Volumes only have very simple support currently. Only the first found volume is put into the
|
||||||
* output. */
|
* output. */
|
||||||
UserCounter<const VolumeComponent> first_volume;
|
ImplicitSharingPtr<const VolumeComponent> first_volume;
|
||||||
UserCounter<const GeometryComponentEditData> first_edit_data;
|
ImplicitSharingPtr<const GeometryComponentEditData> first_edit_data;
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Current offsets while during the gather operation. */
|
/** Current offsets while during the gather operation. */
|
||||||
|
@ -611,7 +611,7 @@ static void gather_realize_tasks_recursive(GatherTasksInfo &gather_info,
|
||||||
case GEO_COMPONENT_TYPE_VOLUME: {
|
case GEO_COMPONENT_TYPE_VOLUME: {
|
||||||
const VolumeComponent *volume_component = static_cast<const VolumeComponent *>(component);
|
const VolumeComponent *volume_component = static_cast<const VolumeComponent *>(component);
|
||||||
if (!gather_info.r_tasks.first_volume) {
|
if (!gather_info.r_tasks.first_volume) {
|
||||||
volume_component->user_add();
|
volume_component->add_user();
|
||||||
gather_info.r_tasks.first_volume = volume_component;
|
gather_info.r_tasks.first_volume = volume_component;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
@ -620,7 +620,7 @@ static void gather_realize_tasks_recursive(GatherTasksInfo &gather_info,
|
||||||
const GeometryComponentEditData *edit_component =
|
const GeometryComponentEditData *edit_component =
|
||||||
static_cast<const GeometryComponentEditData *>(component);
|
static_cast<const GeometryComponentEditData *>(component);
|
||||||
if (!gather_info.r_tasks.first_edit_data) {
|
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;
|
gather_info.r_tasks.first_edit_data = edit_component;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
Loading…
Reference in New Issue
This could be over-documenting and redundant, I wonder what you think, but possibly some comments like this could help people answer their questions faster:
/** True if there are other const references to the resource, meaning it cannot be modified. */
/** Whether the resource can be modified without a copy. */
/** Call when creating another const reference/pointer to the data. */
etc.