Blender V5.0
deg_builder_relations_drivers.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2013 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
10
12
13#include <cstring>
14#include <deque>
15
16#include "BLI_listbase.h"
17
18#include "DNA_anim_types.h"
19
20#include "RNA_access.hh"
21#include "RNA_path.hh"
22
23#include "BKE_anim_data.hh"
24
28
29namespace blender::deg {
30
32 : id_ptr_(id_ptr),
33 fcu_(fcu),
34 driver_relations_needed_(false),
35 pointer_rna_(),
36 property_rna_(nullptr),
37 is_array_(false)
38{
39 driver_relations_needed_ = determine_relations_needed();
40 split_rna_path();
41}
42
43bool DriverDescriptor::determine_relations_needed()
44{
45 if (fcu_->array_index > 0) {
46 /* Drivers on array elements always need relations. */
47 is_array_ = true;
48 return true;
49 }
50
51 if (!resolve_rna()) {
52 /* Properties that don't exist can't cause threading issues either. */
53 return false;
54 }
55
56 if (RNA_property_array_check(property_rna_)) {
57 /* Drivers on array elements always need relations. */
58 is_array_ = true;
59 return true;
60 }
61
62 /* Drivers on Booleans and Enums (when used as bit-flags) can write to the same memory location,
63 * so they need relations between each other. */
64 return ELEM(RNA_property_type(property_rna_), PROP_BOOLEAN, PROP_ENUM);
65}
66
68{
69 return driver_relations_needed_;
70}
71
73{
74 return is_array_;
75}
76
78{
79 if (!is_array_ || !other.is_array_) {
80 return false;
81 }
82 return rna_suffix == other.rna_suffix;
83}
84
86{
87 return OperationKey(id_ptr_->owner_id,
90 fcu_->rna_path,
91 fcu_->array_index);
92}
93
94void DriverDescriptor::split_rna_path()
95{
96 const char *last_dot = strrchr(fcu_->rna_path, '.');
97 if (last_dot == nullptr || last_dot[1] == '\0') {
100 return;
101 }
102
103 rna_prefix = StringRef(fcu_->rna_path, last_dot);
104 rna_suffix = StringRef(last_dot + 1);
105}
106
107bool DriverDescriptor::resolve_rna()
108{
109 return RNA_path_resolve_property(id_ptr_, fcu_->rna_path, &pointer_rna_, &property_rna_);
110}
111
112static bool is_reachable(const Node *const from, const Node *const to)
113{
114 if (from == to) {
115 return true;
116 }
117
118 /* Perform a graph walk from 'to' towards its incoming connections.
119 * Walking from 'from' towards its outgoing connections is 10x slower on the Spring rig. */
120 std::deque<const Node *> queue;
122 queue.push_back(to);
123 while (!queue.empty()) {
124 /* Visit the next node to inspect. */
125 const Node *visit = queue.back();
126 queue.pop_back();
127
128 if (visit == from) {
129 return true;
130 }
131
132 /* Queue all incoming relations that we haven't seen before. */
133 for (Relation *relation : visit->inlinks) {
134 const Node *prev_node = relation->from;
135 if (seen.add(prev_node)) {
136 queue.push_back(prev_node);
137 }
138 }
139 }
140 return false;
141}
142
143/* **** DepsgraphRelationBuilder functions **** */
144
146{
147 for (IDNode *id_node : graph_->id_nodes) {
148 build_driver_relations(id_node);
149 }
150}
151
153{
154 /* Add relations between drivers that write to the same datablock.
155 *
156 * This prevents threading issues when two separate RNA properties write to
157 * the same memory address. For example:
158 * - Drivers on individual array elements, as the animation system will write
159 * the whole array back to RNA even when changing individual array value.
160 * - Drivers on RNA properties that map to a single bit flag. Changing the RNA
161 * value will write the entire int containing the bit, in a non-thread-safe
162 * way.
163 */
164 ID *id_orig = id_node->id_orig;
165 AnimData *adt = BKE_animdata_from_id(id_orig);
166 if (adt == nullptr) {
167 return;
168 }
169
170 /* Mapping from RNA prefix -> set of driver descriptors: */
172
173 PointerRNA id_ptr = RNA_id_pointer_create(id_orig);
174
175 LISTBASE_FOREACH (FCurve *, fcu, &adt->drivers) {
176 if (fcu->rna_path == nullptr) {
177 continue;
178 }
179
180 DriverDescriptor driver_desc(&id_ptr, fcu);
181 if (!driver_desc.driver_relations_needed()) {
182 continue;
183 }
184
185 driver_groups.lookup_or_add_default_as(driver_desc.rna_prefix).append(driver_desc);
186 }
187
188 for (Span<DriverDescriptor> prefix_group : driver_groups.values()) {
189 /* For each node in the driver group, try to connect it to another node
190 * in the same group without creating any cycles. */
191 int num_drivers = prefix_group.size();
192 if (num_drivers < 2) {
193 /* A relation requires two drivers. */
194 continue;
195 }
196 for (int from_index = 0; from_index < num_drivers; ++from_index) {
197 const DriverDescriptor &driver_from = prefix_group[from_index];
198 Node *op_from = get_node(driver_from.depsgraph_key());
199
200 /* Start by trying the next node in the group. */
201 for (int to_offset = 1; to_offset < num_drivers; ++to_offset) {
202 const int to_index = (from_index + to_offset) % num_drivers;
203 const DriverDescriptor &driver_to = prefix_group[to_index];
204 Node *op_to = get_node(driver_to.depsgraph_key());
205
206 /* Duplicate drivers can exist (see #78615), but cannot be distinguished by OperationKey
207 * and thus have the same depsgraph node. Relations between those drivers should not be
208 * created. This not something that is expected to happen (both the UI and the Python API
209 * prevent duplicate drivers), it did happen in a file and it is easy to deal with here. */
210 if (op_from == op_to) {
211 continue;
212 }
213
214 if (from_index < to_index && driver_from.is_same_array_as(driver_to)) {
215 /* This is for adding a relation like `color[0]` -> `color[1]`.
216 * When the search for another driver wraps around,
217 * we cannot blindly add relations any more. */
218 }
219 else {
220 /* Investigate whether this relation would create a dependency cycle.
221 * Example graph:
222 * A -> B -> C
223 * and investigating a potential connection C->A. Because A->C is an
224 * existing transitive connection, adding C->A would create a cycle. */
225 if (is_reachable(op_to, op_from)) {
226 continue;
227 }
228
229 /* No need to directly connect this node if there is already a transitive connection. */
230 if (is_reachable(op_from, op_to)) {
231 break;
232 }
233 }
234
236 op_from->get_exit_operation(), op_to->get_entry_operation(), "Driver Serialization");
237 break;
238 }
239 }
240 }
241}
242
243bool data_path_maybe_shared(const ID &id, const StringRef data_path)
244{
245 /* As it is hard to generally detect implicit sharing, this is implemented as
246 * a 'known to not share' list. */
247
248 /* Allow concurrent writes to custom properties. #140706 shows that this
249 * shouldn't be a problem in practice. */
250 if (data_path.startswith("[\"") && data_path.endswith("\"]")) {
251 return false;
252 }
253
254 if (GS(id.name) == ID_OB) {
255 const Object &ob = *reinterpret_cast<const Object *>(&id);
256 const bool is_thread_safe = (ob.type == OB_ARMATURE && data_path.startswith("pose.bones["));
257 return !is_thread_safe;
258 }
259
260 /* Allow concurrent writes to shape-key values. #140706 shows that this
261 * shouldn't be a problem in practice. */
262 if (GS(id.name) == ID_KE) {
263 const bool is_thread_safe = data_path.startswith("key_blocks[") &&
264 data_path.endswith("].value");
265 return !is_thread_safe;
266 }
267
268 return true;
269}
270
271} // namespace blender::deg
AnimData * BKE_animdata_from_id(const ID *id)
Definition anim_data.cc:83
#define LISTBASE_FOREACH(type, var, list)
#define ELEM(...)
@ ID_KE
@ ID_OB
@ OB_ARMATURE
@ PROP_BOOLEAN
Definition RNA_types.hh:162
@ PROP_ENUM
Definition RNA_types.hh:166
ValueIterator values() const &
Definition BLI_map.hh:884
Value & lookup_or_add_default_as(ForwardKey &&key)
Definition BLI_map.hh:647
bool add(const Key &key)
Definition BLI_set.hh:248
constexpr bool startswith(StringRef prefix) const
constexpr bool endswith(StringRef suffix) const
TimeSourceNode * get_node(const TimeSourceKey &key) const
Relation * add_operation_relation(OperationNode *node_from, OperationNode *node_to, const char *description, int flags=0)
DriverDescriptor(PointerRNA *id_ptr, FCurve *fcu)
bool is_same_array_as(const DriverDescriptor &other) const
#define GS(x)
static bool is_reachable(const Node *const from, const Node *const to)
bool data_path_maybe_shared(const ID &id, const StringRef data_path)
const char * name
bool RNA_property_array_check(PropertyRNA *prop)
PropertyType RNA_property_type(PropertyRNA *prop)
PointerRNA RNA_id_pointer_create(ID *id)
bool RNA_path_resolve_property(const PointerRNA *ptr, const char *path, PointerRNA *r_ptr, PropertyRNA **r_prop)
Definition rna_path.cc:560
ListBase drivers
char * rna_path
int array_index
Definition DNA_ID.h:414
virtual OperationNode * get_entry_operation()
Definition deg_node.hh:203
Relations inlinks
Definition deg_node.hh:182
virtual OperationNode * get_exit_operation()
Definition deg_node.hh:207