tornavis/source/blender/blenlib/BLI_implicit_sharing.hh

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

316 lines
11 KiB
C++
Raw Normal View History

/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bli
*/
#include <atomic>
#include "BLI_assert.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:
BLI: support weak users and version in implicit sharing info The main goal of these changes is to support checking if some data has been changed over time. This is used by the WIP simulation nodes during baking to detect which attributes have to be stored in every frame because they have changed. By using a combination of a weak user count and a version counter, it is possible to detect that an attribute (or any data controlled by implicit sharing) has not been changed with O(1) memory and time. It's still possible that the data has been changed multiple times and is the same in the end and beginning of course. That wouldn't be detected using this mechanism. The `ImplicitSharingInfo` struct has a new weak user count. A weak reference is one that does not keep the referenced data alive, but makes sure that the `ImplicitSharingInfo` itself is not deleted. If some piece of data has one strong and multiple weak users, it is still mutable. If the strong user count goes down to zero, the referenced data is freed. Remaining weak users can check for this condition using `is_expired`. This is a bit similar to `std::weak_ptr` but there is an important difference: a weak user can not become a strong user while one can create a `shared_ptr` from a `weak_ptr`. This restriction is necessary, because some code might be changing the referenced data assuming that it is the only owner. If another thread suddenly adds a new owner, the data would be shared again and the first thread would not have been allowed to modify the data in the first place. There is also a new integer version counter in `ImplicitSharingInfo`. It is incremented whenever some code wants to modify the referenced data. Obviously, this can only be done when the data is not shared because then it would be immutable. By comparing an old and new version number of the same sharing info, one can check if the data has been modified. One has to keep a weak reference to the sharing info together with the old version number to ensure that the new sharing info is still the same as the old one. Without this, it can happen that the sharing info was freed and a new one was allocated at the same pointer address. Using a strong reference for this purpose does not work, because then the data would never be modified because it's shared.
2023-04-28 12:03:42 +02:00
/**
* 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: support weak users and version in implicit sharing info The main goal of these changes is to support checking if some data has been changed over time. This is used by the WIP simulation nodes during baking to detect which attributes have to be stored in every frame because they have changed. By using a combination of a weak user count and a version counter, it is possible to detect that an attribute (or any data controlled by implicit sharing) has not been changed with O(1) memory and time. It's still possible that the data has been changed multiple times and is the same in the end and beginning of course. That wouldn't be detected using this mechanism. The `ImplicitSharingInfo` struct has a new weak user count. A weak reference is one that does not keep the referenced data alive, but makes sure that the `ImplicitSharingInfo` itself is not deleted. If some piece of data has one strong and multiple weak users, it is still mutable. If the strong user count goes down to zero, the referenced data is freed. Remaining weak users can check for this condition using `is_expired`. This is a bit similar to `std::weak_ptr` but there is an important difference: a weak user can not become a strong user while one can create a `shared_ptr` from a `weak_ptr`. This restriction is necessary, because some code might be changing the referenced data assuming that it is the only owner. If another thread suddenly adds a new owner, the data would be shared again and the first thread would not have been allowed to modify the data in the first place. There is also a new integer version counter in `ImplicitSharingInfo`. It is incremented whenever some code wants to modify the referenced data. Obviously, this can only be done when the data is not shared because then it would be immutable. By comparing an old and new version number of the same sharing info, one can check if the data has been modified. One has to keep a weak reference to the sharing info together with the old version number to ensure that the new sharing info is still the same as the old one. Without this, it can happen that the sharing info was freed and a new one was allocated at the same pointer address. Using a strong reference for this purpose does not work, because then the data would never be modified because it's shared.
2023-04-28 12:03:42 +02:00
BLI_assert(strong_users_ == 0);
BLI_assert(weak_users_ == 0);
}
/** Whether the resource can be modified in place because there is only one owner. */
BLI: support weak users and version in implicit sharing info The main goal of these changes is to support checking if some data has been changed over time. This is used by the WIP simulation nodes during baking to detect which attributes have to be stored in every frame because they have changed. By using a combination of a weak user count and a version counter, it is possible to detect that an attribute (or any data controlled by implicit sharing) has not been changed with O(1) memory and time. It's still possible that the data has been changed multiple times and is the same in the end and beginning of course. That wouldn't be detected using this mechanism. The `ImplicitSharingInfo` struct has a new weak user count. A weak reference is one that does not keep the referenced data alive, but makes sure that the `ImplicitSharingInfo` itself is not deleted. If some piece of data has one strong and multiple weak users, it is still mutable. If the strong user count goes down to zero, the referenced data is freed. Remaining weak users can check for this condition using `is_expired`. This is a bit similar to `std::weak_ptr` but there is an important difference: a weak user can not become a strong user while one can create a `shared_ptr` from a `weak_ptr`. This restriction is necessary, because some code might be changing the referenced data assuming that it is the only owner. If another thread suddenly adds a new owner, the data would be shared again and the first thread would not have been allowed to modify the data in the first place. There is also a new integer version counter in `ImplicitSharingInfo`. It is incremented whenever some code wants to modify the referenced data. Obviously, this can only be done when the data is not shared because then it would be immutable. By comparing an old and new version number of the same sharing info, one can check if the data has been modified. One has to keep a weak reference to the sharing info together with the old version number to ensure that the new sharing info is still the same as the old one. Without this, it can happen that the sharing info was freed and a new one was allocated at the same pointer address. Using a strong reference for this purpose does not work, because then the data would never be modified because it's shared.
2023-04-28 12:03:42 +02:00
bool is_mutable() const
{
BLI: support weak users and version in implicit sharing info The main goal of these changes is to support checking if some data has been changed over time. This is used by the WIP simulation nodes during baking to detect which attributes have to be stored in every frame because they have changed. By using a combination of a weak user count and a version counter, it is possible to detect that an attribute (or any data controlled by implicit sharing) has not been changed with O(1) memory and time. It's still possible that the data has been changed multiple times and is the same in the end and beginning of course. That wouldn't be detected using this mechanism. The `ImplicitSharingInfo` struct has a new weak user count. A weak reference is one that does not keep the referenced data alive, but makes sure that the `ImplicitSharingInfo` itself is not deleted. If some piece of data has one strong and multiple weak users, it is still mutable. If the strong user count goes down to zero, the referenced data is freed. Remaining weak users can check for this condition using `is_expired`. This is a bit similar to `std::weak_ptr` but there is an important difference: a weak user can not become a strong user while one can create a `shared_ptr` from a `weak_ptr`. This restriction is necessary, because some code might be changing the referenced data assuming that it is the only owner. If another thread suddenly adds a new owner, the data would be shared again and the first thread would not have been allowed to modify the data in the first place. There is also a new integer version counter in `ImplicitSharingInfo`. It is incremented whenever some code wants to modify the referenced data. Obviously, this can only be done when the data is not shared because then it would be immutable. By comparing an old and new version number of the same sharing info, one can check if the data has been modified. One has to keep a weak reference to the sharing info together with the old version number to ensure that the new sharing info is still the same as the old one. Without this, it can happen that the sharing info was freed and a new one was allocated at the same pointer address. Using a strong reference for this purpose does not work, because then the data would never be modified because it's shared.
2023-04-28 12:03:42 +02:00
return strong_users_.load(std::memory_order_relaxed) == 1;
}
BLI: support weak users and version in implicit sharing info The main goal of these changes is to support checking if some data has been changed over time. This is used by the WIP simulation nodes during baking to detect which attributes have to be stored in every frame because they have changed. By using a combination of a weak user count and a version counter, it is possible to detect that an attribute (or any data controlled by implicit sharing) has not been changed with O(1) memory and time. It's still possible that the data has been changed multiple times and is the same in the end and beginning of course. That wouldn't be detected using this mechanism. The `ImplicitSharingInfo` struct has a new weak user count. A weak reference is one that does not keep the referenced data alive, but makes sure that the `ImplicitSharingInfo` itself is not deleted. If some piece of data has one strong and multiple weak users, it is still mutable. If the strong user count goes down to zero, the referenced data is freed. Remaining weak users can check for this condition using `is_expired`. This is a bit similar to `std::weak_ptr` but there is an important difference: a weak user can not become a strong user while one can create a `shared_ptr` from a `weak_ptr`. This restriction is necessary, because some code might be changing the referenced data assuming that it is the only owner. If another thread suddenly adds a new owner, the data would be shared again and the first thread would not have been allowed to modify the data in the first place. There is also a new integer version counter in `ImplicitSharingInfo`. It is incremented whenever some code wants to modify the referenced data. Obviously, this can only be done when the data is not shared because then it would be immutable. By comparing an old and new version number of the same sharing info, one can check if the data has been modified. One has to keep a weak reference to the sharing info together with the old version number to ensure that the new sharing info is still the same as the old one. Without this, it can happen that the sharing info was freed and a new one was allocated at the same pointer address. Using a strong reference for this purpose does not work, because then the data would never be modified because it's shared.
2023-04-28 12:03:42 +02:00
/**
* 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
{
BLI: support weak users and version in implicit sharing info The main goal of these changes is to support checking if some data has been changed over time. This is used by the WIP simulation nodes during baking to detect which attributes have to be stored in every frame because they have changed. By using a combination of a weak user count and a version counter, it is possible to detect that an attribute (or any data controlled by implicit sharing) has not been changed with O(1) memory and time. It's still possible that the data has been changed multiple times and is the same in the end and beginning of course. That wouldn't be detected using this mechanism. The `ImplicitSharingInfo` struct has a new weak user count. A weak reference is one that does not keep the referenced data alive, but makes sure that the `ImplicitSharingInfo` itself is not deleted. If some piece of data has one strong and multiple weak users, it is still mutable. If the strong user count goes down to zero, the referenced data is freed. Remaining weak users can check for this condition using `is_expired`. This is a bit similar to `std::weak_ptr` but there is an important difference: a weak user can not become a strong user while one can create a `shared_ptr` from a `weak_ptr`. This restriction is necessary, because some code might be changing the referenced data assuming that it is the only owner. If another thread suddenly adds a new owner, the data would be shared again and the first thread would not have been allowed to modify the data in the first place. There is also a new integer version counter in `ImplicitSharingInfo`. It is incremented whenever some code wants to modify the referenced data. Obviously, this can only be done when the data is not shared because then it would be immutable. By comparing an old and new version number of the same sharing info, one can check if the data has been modified. One has to keep a weak reference to the sharing info together with the old version number to ensure that the new sharing info is still the same as the old one. Without this, it can happen that the sharing info was freed and a new one was allocated at the same pointer address. Using a strong reference for this purpose does not work, because then the data would never be modified because it's shared.
2023-04-28 12:03:42 +02:00
return strong_users_.load(std::memory_order_acquire) == 0;
}
/** Call when a the data has a new additional owner. */
void add_user() const
{
BLI: support weak users and version in implicit sharing info The main goal of these changes is to support checking if some data has been changed over time. This is used by the WIP simulation nodes during baking to detect which attributes have to be stored in every frame because they have changed. By using a combination of a weak user count and a version counter, it is possible to detect that an attribute (or any data controlled by implicit sharing) has not been changed with O(1) memory and time. It's still possible that the data has been changed multiple times and is the same in the end and beginning of course. That wouldn't be detected using this mechanism. The `ImplicitSharingInfo` struct has a new weak user count. A weak reference is one that does not keep the referenced data alive, but makes sure that the `ImplicitSharingInfo` itself is not deleted. If some piece of data has one strong and multiple weak users, it is still mutable. If the strong user count goes down to zero, the referenced data is freed. Remaining weak users can check for this condition using `is_expired`. This is a bit similar to `std::weak_ptr` but there is an important difference: a weak user can not become a strong user while one can create a `shared_ptr` from a `weak_ptr`. This restriction is necessary, because some code might be changing the referenced data assuming that it is the only owner. If another thread suddenly adds a new owner, the data would be shared again and the first thread would not have been allowed to modify the data in the first place. There is also a new integer version counter in `ImplicitSharingInfo`. It is incremented whenever some code wants to modify the referenced data. Obviously, this can only be done when the data is not shared because then it would be immutable. By comparing an old and new version number of the same sharing info, one can check if the data has been modified. One has to keep a weak reference to the sharing info together with the old version number to ensure that the new sharing info is still the same as the old one. Without this, it can happen that the sharing info was freed and a new one was allocated at the same pointer address. Using a strong reference for this purpose does not work, because then the data would never be modified because it's shared.
2023-04-28 12:03:42 +02:00
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);
}
Undo: support implicit-sharing in memfile undo step This adds implicit sharing support for the `MemFile` undo-step. This decreases memory usage and increases performance. Implicit sharing allows the undo system to take (shared) ownership of some data. Previously, the data would always be serialized and compared to the previous undo-step. So this turns an O(n) operation into O(1) (in terms of memory usage and time). Read/write code that wants to make use of this has to use the new `BLO_read_shared` and `BLO_write_shared` functions respectively. Those either make use of implicit-sharing internally or do the "full" read/write based on a passed-in function. It seems possible to use the same API in the future to store shared data to .blend files. Improvements: * Much faster undo step creation in many cases by avoiding the majority data copies and equality checks. This fixes #98574. I found undo step creation and undo step decoding to be 2-5 times faster in some demo files from the blender website and in some production files from the Heist project. * Reduced memory usage when there is large data in `bmain`. For example, when loading the same highly subdivided mesh that I used in #106228 the memory usage is 1.03 GB now (compared to 1.62 GB in `main` currently). The main remaining copy of the data now is done by rendering code. * Some significant performance improvements were also measured for the new grease pencil type (#105540). There is one main downside of using implicit-sharing as implemented here: `MemFile` undo steps can't be written as .blend files anymore. This has a few consequences: * Auto-save becomes slower (up to 3x), because it can't just save the previous undo step anymore and does a normal save instead. This has been discussed in more detail here: https://devtalk.blender.org/t/remove-support-for-saving-memfile-undo-steps-as-blend-files-proposal/33544 It would be nice to work towards asynchronous auto-save to alleviate this problem. Some previous work has been done to reduce the impact of this change in 41b10424c7e0 and f0f304e240. This has been committed separately in efb511a76d98a. * Writing `quit.blend` has to do a normal file save now. So it's a bit slower too, but it's less of a problem in practice. * The `USE_WRITE_CRASH_BLEND` functionality does not work anymore. It doesn't seem to be used by anyone (removed in e90f5d03c4a382) There are also benefits to not writing `MemFile` from undo steps to disk. It allows us to more safely do undo-specific optimizations without risking corrupted .blend files. This is especially useful when we want to preserve forward compatibility in some cases. This requires converting data before writing the .blend files, but this conversion is not necessary for undo steps. Trying to implement this kind of optimization in the past has often lead to bugs (e.g. 43b37fbc93). Another new problem is that it is harder to know the size of each undo step. Currently, a heuristic is used to approximate the memory usage, but better solutions could be found if necessary. Pull Request: https://projects.blender.org/blender/blender/pulls/106903
2024-02-29 17:14:58 +01:00
int strong_users() const
{
return strong_users_.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
{
BLI: support weak users and version in implicit sharing info The main goal of these changes is to support checking if some data has been changed over time. This is used by the WIP simulation nodes during baking to detect which attributes have to be stored in every frame because they have changed. By using a combination of a weak user count and a version counter, it is possible to detect that an attribute (or any data controlled by implicit sharing) has not been changed with O(1) memory and time. It's still possible that the data has been changed multiple times and is the same in the end and beginning of course. That wouldn't be detected using this mechanism. The `ImplicitSharingInfo` struct has a new weak user count. A weak reference is one that does not keep the referenced data alive, but makes sure that the `ImplicitSharingInfo` itself is not deleted. If some piece of data has one strong and multiple weak users, it is still mutable. If the strong user count goes down to zero, the referenced data is freed. Remaining weak users can check for this condition using `is_expired`. This is a bit similar to `std::weak_ptr` but there is an important difference: a weak user can not become a strong user while one can create a `shared_ptr` from a `weak_ptr`. This restriction is necessary, because some code might be changing the referenced data assuming that it is the only owner. If another thread suddenly adds a new owner, the data would be shared again and the first thread would not have been allowed to modify the data in the first place. There is also a new integer version counter in `ImplicitSharingInfo`. It is incremented whenever some code wants to modify the referenced data. Obviously, this can only be done when the data is not shared because then it would be immutable. By comparing an old and new version number of the same sharing info, one can check if the data has been modified. One has to keep a weak reference to the sharing info together with the old version number to ensure that the new sharing info is still the same as the old one. Without this, it can happen that the sharing info was freed and a new one was allocated at the same pointer address. Using a strong reference for this purpose does not work, because then the data would never be modified because it's shared.
2023-04-28 12:03:42 +02:00
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) {
BLI: support weak users and version in implicit sharing info The main goal of these changes is to support checking if some data has been changed over time. This is used by the WIP simulation nodes during baking to detect which attributes have to be stored in every frame because they have changed. By using a combination of a weak user count and a version counter, it is possible to detect that an attribute (or any data controlled by implicit sharing) has not been changed with O(1) memory and time. It's still possible that the data has been changed multiple times and is the same in the end and beginning of course. That wouldn't be detected using this mechanism. The `ImplicitSharingInfo` struct has a new weak user count. A weak reference is one that does not keep the referenced data alive, but makes sure that the `ImplicitSharingInfo` itself is not deleted. If some piece of data has one strong and multiple weak users, it is still mutable. If the strong user count goes down to zero, the referenced data is freed. Remaining weak users can check for this condition using `is_expired`. This is a bit similar to `std::weak_ptr` but there is an important difference: a weak user can not become a strong user while one can create a `shared_ptr` from a `weak_ptr`. This restriction is necessary, because some code might be changing the referenced data assuming that it is the only owner. If another thread suddenly adds a new owner, the data would be shared again and the first thread would not have been allowed to modify the data in the first place. There is also a new integer version counter in `ImplicitSharingInfo`. It is incremented whenever some code wants to modify the referenced data. Obviously, this can only be done when the data is not shared because then it would be immutable. By comparing an old and new version number of the same sharing info, one can check if the data has been modified. One has to keep a weak reference to the sharing info together with the old version number to ensure that the new sharing info is still the same as the old one. Without this, it can happen that the sharing info was freed and a new one was allocated at the same pointer address. Using a strong reference for this purpose does not work, because then the data would never be modified because it's shared.
2023-04-28 12:03:42 +02:00
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. */
BLI: support weak users and version in implicit sharing info The main goal of these changes is to support checking if some data has been changed over time. This is used by the WIP simulation nodes during baking to detect which attributes have to be stored in every frame because they have changed. By using a combination of a weak user count and a version counter, it is possible to detect that an attribute (or any data controlled by implicit sharing) has not been changed with O(1) memory and time. It's still possible that the data has been changed multiple times and is the same in the end and beginning of course. That wouldn't be detected using this mechanism. The `ImplicitSharingInfo` struct has a new weak user count. A weak reference is one that does not keep the referenced data alive, but makes sure that the `ImplicitSharingInfo` itself is not deleted. If some piece of data has one strong and multiple weak users, it is still mutable. If the strong user count goes down to zero, the referenced data is freed. Remaining weak users can check for this condition using `is_expired`. This is a bit similar to `std::weak_ptr` but there is an important difference: a weak user can not become a strong user while one can create a `shared_ptr` from a `weak_ptr`. This restriction is necessary, because some code might be changing the referenced data assuming that it is the only owner. If another thread suddenly adds a new owner, the data would be shared again and the first thread would not have been allowed to modify the data in the first place. There is also a new integer version counter in `ImplicitSharingInfo`. It is incremented whenever some code wants to modify the referenced data. Obviously, this can only be done when the data is not shared because then it would be immutable. By comparing an old and new version number of the same sharing info, one can check if the data has been modified. One has to keep a weak reference to the sharing info together with the old version number to ensure that the new sharing info is still the same as the old one. Without this, it can happen that the sharing info was freed and a new one was allocated at the same pointer address. Using a strong reference for this purpose does not work, because then the data would never be modified because it's shared.
2023-04-28 12:03:42 +02:00
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:
BLI: support weak users and version in implicit sharing info The main goal of these changes is to support checking if some data has been changed over time. This is used by the WIP simulation nodes during baking to detect which attributes have to be stored in every frame because they have changed. By using a combination of a weak user count and a version counter, it is possible to detect that an attribute (or any data controlled by implicit sharing) has not been changed with O(1) memory and time. It's still possible that the data has been changed multiple times and is the same in the end and beginning of course. That wouldn't be detected using this mechanism. The `ImplicitSharingInfo` struct has a new weak user count. A weak reference is one that does not keep the referenced data alive, but makes sure that the `ImplicitSharingInfo` itself is not deleted. If some piece of data has one strong and multiple weak users, it is still mutable. If the strong user count goes down to zero, the referenced data is freed. Remaining weak users can check for this condition using `is_expired`. This is a bit similar to `std::weak_ptr` but there is an important difference: a weak user can not become a strong user while one can create a `shared_ptr` from a `weak_ptr`. This restriction is necessary, because some code might be changing the referenced data assuming that it is the only owner. If another thread suddenly adds a new owner, the data would be shared again and the first thread would not have been allowed to modify the data in the first place. There is also a new integer version counter in `ImplicitSharingInfo`. It is incremented whenever some code wants to modify the referenced data. Obviously, this can only be done when the data is not shared because then it would be immutable. By comparing an old and new version number of the same sharing info, one can check if the data has been modified. One has to keep a weak reference to the sharing info together with the old version number to ensure that the new sharing info is still the same as the old one. Without this, it can happen that the sharing info was freed and a new one was allocated at the same pointer address. Using a strong reference for this purpose does not work, because then the data would never be modified because it's shared.
2023-04-28 12:03:42 +02:00
/** 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;
BLI: support weak users and version in implicit sharing info The main goal of these changes is to support checking if some data has been changed over time. This is used by the WIP simulation nodes during baking to detect which attributes have to be stored in every frame because they have changed. By using a combination of a weak user count and a version counter, it is possible to detect that an attribute (or any data controlled by implicit sharing) has not been changed with O(1) memory and time. It's still possible that the data has been changed multiple times and is the same in the end and beginning of course. That wouldn't be detected using this mechanism. The `ImplicitSharingInfo` struct has a new weak user count. A weak reference is one that does not keep the referenced data alive, but makes sure that the `ImplicitSharingInfo` itself is not deleted. If some piece of data has one strong and multiple weak users, it is still mutable. If the strong user count goes down to zero, the referenced data is freed. Remaining weak users can check for this condition using `is_expired`. This is a bit similar to `std::weak_ptr` but there is an important difference: a weak user can not become a strong user while one can create a `shared_ptr` from a `weak_ptr`. This restriction is necessary, because some code might be changing the referenced data assuming that it is the only owner. If another thread suddenly adds a new owner, the data would be shared again and the first thread would not have been allowed to modify the data in the first place. There is also a new integer version counter in `ImplicitSharingInfo`. It is incremented whenever some code wants to modify the referenced data. Obviously, this can only be done when the data is not shared because then it would be immutable. By comparing an old and new version number of the same sharing info, one can check if the data has been modified. One has to keep a weak reference to the sharing info together with the old version number to ensure that the new sharing info is still the same as the old one. Without this, it can happen that the sharing info was freed and a new one was allocated at the same pointer address. Using a strong reference for this purpose does not work, because then the data would never be modified because it's shared.
2023-04-28 12:03:42 +02:00
/** 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 for creating an allocated shared resource, to be used like:
* `new ImplicitSharedValue<T>(args);`
*/
template<typename T> class ImplicitSharedValue : public ImplicitSharingInfo {
public:
T data;
template<typename... Args>
ImplicitSharedValue(Args &&...args) : data(std::forward<Args>(args)...)
{
}
#ifdef WITH_CXX_GUARDEDALLOC
MEM_CXX_CLASS_ALLOC_FUNCS("ImplicitSharedValue");
#endif
private:
void delete_self_with_data() override
{
delete this;
}
};
Geometry Nodes: add simulation support This adds support for building simulations with geometry nodes. A new `Simulation Input` and `Simulation Output` node allow maintaining a simulation state across multiple frames. Together these two nodes form a `simulation zone` which contains all the nodes that update the simulation state from one frame to the next. A new simulation zone can be added via the menu (`Simulation > Simulation Zone`) or with the node add search. The simulation state contains a geometry by default. However, it is possible to add multiple geometry sockets as well as other socket types. Currently, field inputs are evaluated and stored for the preceding geometry socket in the order that the sockets are shown. Simulation state items can be added by linking one of the empty sockets to something else. In the sidebar, there is a new panel that allows adding, removing and reordering these sockets. The simulation nodes behave as follows: * On the first frame, the inputs of the `Simulation Input` node are evaluated to initialize the simulation state. In later frames these sockets are not evaluated anymore. The `Delta Time` at the first frame is zero, but the simulation zone is still evaluated. * On every next frame, the `Simulation Input` node outputs the simulation state of the previous frame. Nodes in the simulation zone can edit that data in arbitrary ways, also taking into account the `Delta Time`. The new simulation state has to be passed to the `Simulation Output` node where it is cached and forwarded. * On a frame that is already cached or baked, the nodes in the simulation zone are not evaluated, because the `Simulation Output` node can return the previously cached data directly. It is not allowed to connect sockets from inside the simulation zone to the outside without going through the `Simulation Output` node. This is a necessary restriction to make caching and sub-frame interpolation work. Links can go into the simulation zone without problems though. Anonymous attributes are not propagated by the simulation nodes unless they are explicitly stored in the simulation state. This is unfortunate, but currently there is no practical and reliable alternative. The core problem is detecting which anonymous attributes will be required for the simulation and afterwards. While we can detect this for the current evaluation, we can't look into the future in time to see what data will be necessary. We intend to make it easier to explicitly pass data through a simulation in the future, even if the simulation is in a nested node group. There is a new `Simulation Nodes` panel in the physics tab in the properties editor. It allows baking all simulation zones on the selected objects. The baking options are intentially kept at a minimum for this MVP. More features for simulation baking as well as baking in general can be expected to be added separately. All baked data is stored on disk in a folder next to the .blend file. #106937 describes how baking is implemented in more detail. Volumes can not be baked yet and materials are lost during baking for now. Packing the baked data into the .blend file is not yet supported. The timeline indicates which frames are currently cached, baked or cached but invalidated by user-changes. Simulation input and output nodes are internally linked together by their `bNode.identifier` which stays the same even if the node name changes. They are generally added and removed together. However, there are still cases where "dangling" simulation nodes can be created currently. Those generally don't cause harm, but would be nice to avoid this in more cases in the future. Co-authored-by: Hans Goudey <h.goudey@me.com> Co-authored-by: Lukas Tönne <lukas@blender.org> Pull Request: https://projects.blender.org/blender/blender/pulls/104924
2023-05-03 13:18:51 +02:00
/**
* 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