Core: new blenlib library for implicit-sharing #105994
|
@ -54,7 +54,7 @@ class AnonymousAttributeID : public ImplicitSharingMixin {
|
|||
};
|
||||
|
||||
/** Wrapper for #AnonymousAttributeID that avoids manual reference counting. */
|
||||
using AutoAnonymousAttributeID = ImplicitSharePtr<const AnonymousAttributeID>;
|
||||
using AutoAnonymousAttributeID = ImplicitSharingPtr<const AnonymousAttributeID>;
|
||||
|
||||
/**
|
||||
* A set of anonymous attribute names that is passed around in geometry nodes.
|
||||
|
|
|
@ -101,7 +101,7 @@ inline constexpr bool is_geometry_component_v = std::is_base_of_v<GeometryCompon
|
|||
*/
|
||||
struct GeometrySet {
|
||||
private:
|
||||
using GeometryComponentPtr = blender::ImplicitSharePtr<class GeometryComponent>;
|
||||
using GeometryComponentPtr = blender::ImplicitSharingPtr<class GeometryComponent>;
|
||||
/* Indexed by #GeometryComponentType. */
|
||||
std::array<GeometryComponentPtr, GEO_COMPONENT_TYPE_ENUM_SIZE> components_;
|
||||
|
||||
|
|
|
@ -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<int> users_;
|
||||
|
||||
|
@ -74,14 +75,14 @@ struct ImplicitSharingInfo : blender::NonCopyable, blender::NonMovable {
|
|||
}
|
||||
|
||||
brecht marked this conversation as resolved
Outdated
|
||||
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.
|
||||
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).
|
||||
*/
|
||||
struct ImplicitSharingMixin : public ImplicitSharingInfo {
|
||||
class ImplicitSharingMixin : public ImplicitSharingInfo {
|
||||
public:
|
||||
ImplicitSharingMixin() : ImplicitSharingInfo(1)
|
||||
{
|
||||
|
|
|
@ -10,33 +10,38 @@
|
|||
|
||||
namespace blender {
|
||||
|
||||
template<typename T> 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.
|
||||
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 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<typename T> class ImplicitSharePtr {
|
|||
return *this;
|
||||
}
|
||||
|
||||
ImplicitSharePtr &operator=(ImplicitSharePtr &&other)
|
||||
ImplicitSharingPtr &operator=(ImplicitSharingPtr &&other)
|
||||
{
|
||||
if (this == &other) {
|
||||
return *this;
|
||||
|
@ -122,7 +127,7 @@ template<typename T> 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_;
|
||||
}
|
||||
|
|
|
@ -226,8 +226,8 @@ struct GatherTasks {
|
|||
|
||||
/* Volumes only have very simple support currently. Only the first found volume is put into the
|
||||
* output. */
|
||||
ImplicitSharePtr<const VolumeComponent> first_volume;
|
||||
ImplicitSharePtr<const GeometryComponentEditData> first_edit_data;
|
||||
ImplicitSharingPtr<const VolumeComponent> first_volume;
|
||||
ImplicitSharingPtr<const GeometryComponentEditData> first_edit_data;
|
||||
};
|
||||
|
||||
/** Current offsets while during the gather operation. */
|
||||
|
|
Loading…
Reference in New Issue
My understanding is that user count decrement requires
std::memory_order_acq_rel
, while increment can usestd::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