Listing the "Blender Foundation" as copyright holder implied the Blender Foundation holds copyright to files which may include work from many developers. While keeping copyright on headers makes sense for isolated libraries, Blender's own code may be refactored or moved between files in a way that makes the per file copyright holders less meaningful. Copyright references to the "Blender Foundation" have been replaced with "Blender Authors", with the exception of `./extern/` since these this contains libraries which are more isolated, any changed to license headers there can be handled on a case-by-case basis. Some directories in `./intern/` have also been excluded: - `./intern/cycles/` it's own `AUTHORS` file is planned. - `./intern/opensubdiv/`. An "AUTHORS" file has been added, using the chromium projects authors file as a template. Design task: #110784 Ref !110783.
288 lines
10 KiB
C++
288 lines
10 KiB
C++
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
|
*
|
|
* 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:
|
|
/**
|
|
* Number of users that want to own the shared data. This can be in multiple states:
|
|
* - 0: The data is expired and likely freed. It must not be accessed anymore. The
|
|
* #ImplicitSharingInfo may still be alive when there are weak users.
|
|
* - 1: The data is mutable by the single owner.
|
|
* - >1: The data is shared and therefore immutable.
|
|
*/
|
|
mutable std::atomic<int> strong_users_ = 1;
|
|
/**
|
|
* Number of users that only keep a reference to the `ImplicitSharingInfo` but don't need to own
|
|
* the shared data. One additional weak user is added as long as there is at least one strong
|
|
* user. Together with the `version_` below this adds an efficient way to detect if data has been
|
|
* changed.
|
|
*/
|
|
mutable std::atomic<int> weak_users_ = 1;
|
|
/**
|
|
* The data referenced by an #ImplicitSharingInfo can change over time. This version is
|
|
* incremented whenever the referenced data is about to be changed. This allows checking if the
|
|
* data has been changed between points in time.
|
|
*/
|
|
mutable std::atomic<int64_t> version_ = 0;
|
|
|
|
public:
|
|
virtual ~ImplicitSharingInfo()
|
|
{
|
|
BLI_assert(strong_users_ == 0);
|
|
BLI_assert(weak_users_ == 0);
|
|
}
|
|
|
|
/** Whether the resource can be modified in place because there is only one owner. */
|
|
bool is_mutable() const
|
|
{
|
|
return strong_users_.load(std::memory_order_relaxed) == 1;
|
|
}
|
|
|
|
/**
|
|
* Weak users don't protect the referenced data from being freed. If the data is freed while
|
|
* there is still a weak referenced, this returns true.
|
|
*/
|
|
bool is_expired() const
|
|
{
|
|
return strong_users_.load(std::memory_order_acquire) == 0;
|
|
}
|
|
|
|
/** Call when a the data has a new additional owner. */
|
|
void add_user() const
|
|
{
|
|
BLI_assert(!this->is_expired());
|
|
strong_users_.fetch_add(1, std::memory_order_relaxed);
|
|
}
|
|
|
|
/**
|
|
* Adding a weak owner prevents the #ImplicitSharingInfo from being freed but not the referenced
|
|
* data.
|
|
*
|
|
* \note Unlike std::shared_ptr a weak user cannot be turned into a strong user. This is
|
|
* because some code might change the referenced data assuming that there is only one strong user
|
|
* while a new strong user is added by another thread.
|
|
*/
|
|
void add_weak_user() const
|
|
{
|
|
weak_users_.fetch_add(1, std::memory_order_relaxed);
|
|
}
|
|
|
|
/**
|
|
* Call this when making sure that the referenced data is mutable, which also implies that it is
|
|
* about to be modified. This allows other code to detect whether data has not been changed very
|
|
* efficiently.
|
|
*/
|
|
void tag_ensured_mutable() const
|
|
{
|
|
BLI_assert(this->is_mutable());
|
|
/* This might not need an atomic increment when the #version method below is only called when
|
|
* the code calling it is a strong user of this sharing info. Better be safe and use an atomic
|
|
* for now. */
|
|
version_.fetch_add(1, std::memory_order_acq_rel);
|
|
}
|
|
|
|
/**
|
|
* Get a version number that is increased when the data is modified. It can be used to detect if
|
|
* data has been changed.
|
|
*/
|
|
int64_t version() const
|
|
{
|
|
return version_.load(std::memory_order_acquire);
|
|
}
|
|
|
|
/**
|
|
* 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 = strong_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) {
|
|
const int old_weak_user_count = weak_users_.load(std::memory_order_acquire);
|
|
BLI_assert(old_weak_user_count >= 1);
|
|
if (old_weak_user_count == 1) {
|
|
/* If the weak user count is 1 it means that there is no actual weak user. The 1 just
|
|
* indicates that there was still at least one strong user. */
|
|
weak_users_ = 0;
|
|
const_cast<ImplicitSharingInfo *>(this)->delete_self_with_data();
|
|
}
|
|
else {
|
|
/* There is still at least one actual weak user, so don't free the sharing info yet. The
|
|
* data can be freed though. */
|
|
const_cast<ImplicitSharingInfo *>(this)->delete_data_only();
|
|
/* Also remove the "fake" weak user that indicated that there was at least one strong
|
|
* user. */
|
|
this->remove_weak_user_and_delete_if_last();
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This might just decrement the weak user count or might delete the data. Should be used in
|
|
* conjunction with #add_weak_user.
|
|
*/
|
|
void remove_weak_user_and_delete_if_last() const
|
|
{
|
|
const int old_weak_user_count = weak_users_.fetch_sub(1, std::memory_order_acq_rel);
|
|
BLI_assert(old_weak_user_count >= 1);
|
|
const bool was_last_weak_user = old_weak_user_count == 1;
|
|
if (was_last_weak_user) {
|
|
/* It's possible that the data has been freed before already, but now it is definitely freed
|
|
* together with the sharing info. */
|
|
const_cast<ImplicitSharingInfo *>(this)->delete_self_with_data();
|
|
}
|
|
}
|
|
|
|
private:
|
|
/** Has to free the #ImplicitSharingInfo and the referenced data. The data might have been freed
|
|
* before by #delete_data_only already. This case should be handled here. */
|
|
virtual void delete_self_with_data() = 0;
|
|
/** Can free the referenced data but the #ImplicitSharingInfo still has to be kept alive. */
|
|
virtual void delete_data_only() {}
|
|
};
|
|
|
|
/**
|
|
* 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 {
|
|
|
|
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;
|
|
};
|
|
|
|
/**
|
|
* Utility that contains sharing information and the data that is shared.
|
|
*/
|
|
struct ImplicitSharingInfoAndData {
|
|
const ImplicitSharingInfo *sharing_info = nullptr;
|
|
const void *data = nullptr;
|
|
};
|
|
|
|
namespace implicit_sharing {
|
|
|
|
namespace detail {
|
|
|
|
void *resize_trivial_array_impl(void *old_data,
|
|
int64_t old_size,
|
|
int64_t new_size,
|
|
int64_t alignment,
|
|
const ImplicitSharingInfo **sharing_info);
|
|
void *make_trivial_data_mutable_impl(void *old_data,
|
|
int64_t size,
|
|
int64_t alignment,
|
|
const ImplicitSharingInfo **sharing_info);
|
|
|
|
} // namespace detail
|
|
|
|
/**
|
|
* Copy shared data from the source to the destination, adding a user count.
|
|
* \note Does not free any existing data in the destination.
|
|
*/
|
|
template<typename T>
|
|
void copy_shared_pointer(T *src_ptr,
|
|
const ImplicitSharingInfo *src_sharing_info,
|
|
T **r_dst_ptr,
|
|
const ImplicitSharingInfo **r_dst_sharing_info)
|
|
{
|
|
*r_dst_ptr = src_ptr;
|
|
*r_dst_sharing_info = src_sharing_info;
|
|
if (*r_dst_ptr) {
|
|
BLI_assert(*r_dst_sharing_info != nullptr);
|
|
(*r_dst_sharing_info)->add_user();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Remove this reference to the shared data and remove dangling pointers.
|
|
*/
|
|
template<typename T> void free_shared_data(T **data, const ImplicitSharingInfo **sharing_info)
|
|
{
|
|
if (*sharing_info) {
|
|
BLI_assert(*data != nullptr);
|
|
(*sharing_info)->remove_user_and_delete_if_last();
|
|
}
|
|
*data = nullptr;
|
|
*sharing_info = nullptr;
|
|
}
|
|
|
|
/**
|
|
* Create an implicit sharing object that takes ownership of the data, allowing it to be shared.
|
|
* When it is no longer used, the data is freed with #MEM_freeN, so it must be a trivial type.
|
|
*/
|
|
const ImplicitSharingInfo *info_for_mem_free(void *data);
|
|
|
|
/**
|
|
* Make data mutable (single-user) if it is shared. For trivially-copyable data only.
|
|
*/
|
|
template<typename T>
|
|
void make_trivial_data_mutable(T **data,
|
|
const ImplicitSharingInfo **sharing_info,
|
|
const int64_t size)
|
|
{
|
|
*data = static_cast<T *>(
|
|
detail::make_trivial_data_mutable_impl(*data, sizeof(T) * size, alignof(T), sharing_info));
|
|
}
|
|
|
|
/**
|
|
* Resize an array of shared data. For trivially-copyable data only. Any new values are not
|
|
* initialized.
|
|
*/
|
|
template<typename T>
|
|
void resize_trivial_array(T **data,
|
|
const ImplicitSharingInfo **sharing_info,
|
|
int64_t old_size,
|
|
int64_t new_size)
|
|
{
|
|
*data = static_cast<T *>(detail::resize_trivial_array_impl(
|
|
*data, sizeof(T) * old_size, sizeof(T) * new_size, alignof(T), sharing_info));
|
|
}
|
|
|
|
} // namespace implicit_sharing
|
|
|
|
} // namespace blender
|