Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'source/blender/modifiers/intern/MOD_nodes_evaluator.cc')
-rw-r--r--source/blender/modifiers/intern/MOD_nodes_evaluator.cc290
1 files changed, 141 insertions, 149 deletions
diff --git a/source/blender/modifiers/intern/MOD_nodes_evaluator.cc b/source/blender/modifiers/intern/MOD_nodes_evaluator.cc
index 5f82cd9ba27..e8677c7ce1a 100644
--- a/source/blender/modifiers/intern/MOD_nodes_evaluator.cc
+++ b/source/blender/modifiers/intern/MOD_nodes_evaluator.cc
@@ -264,13 +264,10 @@ struct NodeWithState {
class GeometryNodesEvaluator;
/**
- * Utility class that locks the state of a node. Having this is a separate class is useful because
- * it allows methods to communicate that they expect the node to be locked.
+ * Utility class that wraps a node whose state is locked. Having this is a separate class is useful
+ * because it allows methods to communicate that they expect the node to be locked.
*/
class LockedNode : NonCopyable, NonMovable {
- private:
- GeometryNodesEvaluator &evaluator_;
-
public:
/**
* This is the node that is currently locked.
@@ -290,13 +287,9 @@ class LockedNode : NonCopyable, NonMovable {
Vector<DOutputSocket> delayed_unused_outputs;
Vector<DNode> delayed_scheduled_nodes;
- LockedNode(GeometryNodesEvaluator &evaluator, const DNode node, NodeState &node_state)
- : evaluator_(evaluator), node(node), node_state(node_state)
+ LockedNode(const DNode node, NodeState &node_state) : node(node), node_state(node_state)
{
- node_state.mutex.lock();
}
-
- ~LockedNode();
};
static const CPPType *get_socket_cpp_type(const DSocket socket)
@@ -352,7 +345,7 @@ class GeometryNodesEvaluator {
* on cache line boundaries. Note, just because a value is allocated in one specific thread,
* does not mean that it will only be used by that thread.
*/
- EnumerableThreadSpecific<LinearAllocator<>> local_allocators_;
+ threading::EnumerableThreadSpecific<LinearAllocator<>> local_allocators_;
/**
* Every node that is reachable from the output gets its own state. Once all states have been
@@ -380,7 +373,7 @@ class GeometryNodesEvaluator {
void execute()
{
- task_pool_ = BLI_task_pool_create(this, TASK_PRIORITY_HIGH, TASK_ISOLATION_OFF);
+ task_pool_ = BLI_task_pool_create(this, TASK_PRIORITY_HIGH);
this->create_states_for_reachable_nodes();
this->forward_group_inputs();
@@ -426,12 +419,13 @@ class GeometryNodesEvaluator {
/* Initialize the more complex parts of the node states in parallel. At this point no new
* node states are added anymore, so it is safe to lookup states from `node_states_` from
* multiple threads. */
- parallel_for(IndexRange(node_states_.size()), 50, [&, this](const IndexRange range) {
- LinearAllocator<> &allocator = this->local_allocators_.local();
- for (const NodeWithState &item : node_states_.as_span().slice(range)) {
- this->initialize_node_state(item.node, *item.state, allocator);
- }
- });
+ threading::parallel_for(
+ IndexRange(node_states_.size()), 50, [&, this](const IndexRange range) {
+ LinearAllocator<> &allocator = this->local_allocators_.local();
+ for (const NodeWithState &item : node_states_.as_span().slice(range)) {
+ this->initialize_node_state(item.node, *item.state, allocator);
+ }
+ });
}
void initialize_node_state(const DNode node, NodeState &node_state, LinearAllocator<> &allocator)
@@ -507,11 +501,12 @@ class GeometryNodesEvaluator {
void destruct_node_states()
{
- parallel_for(IndexRange(node_states_.size()), 50, [&, this](const IndexRange range) {
- for (const NodeWithState &item : node_states_.as_span().slice(range)) {
- this->destruct_node_state(item.node, *item.state);
- }
- });
+ threading::parallel_for(
+ IndexRange(node_states_.size()), 50, [&, this](const IndexRange range) {
+ for (const NodeWithState &item : node_states_.as_span().slice(range)) {
+ this->destruct_node_state(item.node, *item.state);
+ }
+ });
}
void destruct_node_state(const DNode node, NodeState &node_state)
@@ -568,9 +563,10 @@ class GeometryNodesEvaluator {
for (const DInputSocket &socket : params_.output_sockets) {
const DNode node = socket.node();
NodeState &node_state = this->get_node_state(node);
- LockedNode locked_node{*this, node, node_state};
- /* Setting an input as required will schedule any linked node. */
- this->set_input_required(locked_node, socket);
+ this->with_locked_node(node, node_state, [&](LockedNode &locked_node) {
+ /* Setting an input as required will schedule any linked node. */
+ this->set_input_required(locked_node, socket);
+ });
}
}
@@ -632,30 +628,33 @@ class GeometryNodesEvaluator {
bool node_task_preprocessing(const DNode node, NodeState &node_state)
{
- LockedNode locked_node{*this, node, node_state};
- BLI_assert(node_state.schedule_state == NodeScheduleState::Scheduled);
- node_state.schedule_state = NodeScheduleState::Running;
+ bool do_execute_node = false;
+ this->with_locked_node(node, node_state, [&](LockedNode &locked_node) {
+ BLI_assert(node_state.schedule_state == NodeScheduleState::Scheduled);
+ node_state.schedule_state = NodeScheduleState::Running;
- /* Early return if the node has finished already. */
- if (locked_node.node_state.node_has_finished) {
- return false;
- }
- /* Prepare outputs and check if actually any new outputs have to be computed. */
- if (!this->prepare_node_outputs_for_execution(locked_node)) {
- return false;
- }
- /* Initialize nodes that don't support laziness. This is done after at least one output is
- * required and before we check that all required inputs are provided. This reduces the
- * number of "round-trips" through the task pool by one for most nodes. */
- if (!node_state.non_lazy_node_is_initialized && !node_supports_laziness(node)) {
- this->initialize_non_lazy_node(locked_node);
- node_state.non_lazy_node_is_initialized = true;
- }
- /* Prepare inputs and check if all required inputs are provided. */
- if (!this->prepare_node_inputs_for_execution(locked_node)) {
- return false;
- }
- return true;
+ /* Early return if the node has finished already. */
+ if (locked_node.node_state.node_has_finished) {
+ return;
+ }
+ /* Prepare outputs and check if actually any new outputs have to be computed. */
+ if (!this->prepare_node_outputs_for_execution(locked_node)) {
+ return;
+ }
+ /* Initialize nodes that don't support laziness. This is done after at least one output is
+ * required and before we check that all required inputs are provided. This reduces the
+ * number of "round-trips" through the task pool by one for most nodes. */
+ if (!node_state.non_lazy_node_is_initialized && !node_supports_laziness(node)) {
+ this->initialize_non_lazy_node(locked_node);
+ node_state.non_lazy_node_is_initialized = true;
+ }
+ /* Prepare inputs and check if all required inputs are provided. */
+ if (!this->prepare_node_inputs_for_execution(locked_node)) {
+ return;
+ }
+ do_execute_node = true;
+ });
+ return do_execute_node;
}
/* A node is finished when it has computed all outputs that may be used. */
@@ -896,18 +895,18 @@ class GeometryNodesEvaluator {
void node_task_postprocessing(const DNode node, NodeState &node_state)
{
- LockedNode locked_node{*this, node, node_state};
-
- const bool node_has_finished = this->finish_node_if_possible(locked_node);
- const bool reschedule_requested = node_state.schedule_state ==
- NodeScheduleState::RunningAndRescheduled;
- node_state.schedule_state = NodeScheduleState::NotScheduled;
- if (reschedule_requested && !node_has_finished) {
- /* Either the node rescheduled itself or another node tried to schedule it while it ran. */
- this->schedule_node(locked_node);
- }
+ this->with_locked_node(node, node_state, [&](LockedNode &locked_node) {
+ const bool node_has_finished = this->finish_node_if_possible(locked_node);
+ const bool reschedule_requested = node_state.schedule_state ==
+ NodeScheduleState::RunningAndRescheduled;
+ node_state.schedule_state = NodeScheduleState::NotScheduled;
+ if (reschedule_requested && !node_has_finished) {
+ /* Either the node rescheduled itself or another node tried to schedule it while it ran. */
+ this->schedule_node(locked_node);
+ }
- this->assert_expected_outputs_have_been_computed(locked_node);
+ this->assert_expected_outputs_have_been_computed(locked_node);
+ });
}
void assert_expected_outputs_have_been_computed(LockedNode &locked_node)
@@ -1042,13 +1041,14 @@ class GeometryNodesEvaluator {
this->load_unlinked_input_value(locked_node, input_socket, input_state, origin_socket);
locked_node.node_state.missing_required_inputs -= 1;
this->schedule_node(locked_node);
- return;
}
- /* The value has not been computed yet, so when it will be forwarded by another node, this
- * node will be triggered. */
- will_be_triggered_by_other_node = true;
+ else {
+ /* The value has not been computed yet, so when it will be forwarded by another node, this
+ * node will be triggered. */
+ will_be_triggered_by_other_node = true;
- locked_node.delayed_required_outputs.append(DOutputSocket(origin_socket));
+ locked_node.delayed_required_outputs.append(DOutputSocket(origin_socket));
+ }
}
/* If this node will be triggered by another node, we don't have to schedule it now. */
if (!will_be_triggered_by_other_node) {
@@ -1095,15 +1095,16 @@ class GeometryNodesEvaluator {
NodeState &node_state = this->get_node_state(node);
OutputState &output_state = node_state.outputs[socket->index()];
- LockedNode locked_node{*this, node, node_state};
- if (output_state.output_usage == ValueUsage::Required) {
- /* Output is marked as required already. So the node is scheduled already. */
- return;
- }
- /* The origin node needs to be scheduled so that it provides the requested input
- * eventually. */
- output_state.output_usage = ValueUsage::Required;
- this->schedule_node(locked_node);
+ this->with_locked_node(node, node_state, [&](LockedNode &locked_node) {
+ if (output_state.output_usage == ValueUsage::Required) {
+ /* Output is marked as required already. So the node is scheduled already. */
+ return;
+ }
+ /* The origin node needs to be scheduled so that it provides the requested input
+ * eventually. */
+ output_state.output_usage = ValueUsage::Required;
+ this->schedule_node(locked_node);
+ });
}
void send_output_unused_notification(const DOutputSocket socket)
@@ -1112,14 +1113,15 @@ class GeometryNodesEvaluator {
NodeState &node_state = this->get_node_state(node);
OutputState &output_state = node_state.outputs[socket->index()];
- LockedNode locked_node{*this, node, node_state};
- output_state.potential_users -= 1;
- if (output_state.potential_users == 0) {
- /* The output socket has no users anymore. */
- output_state.output_usage = ValueUsage::Unused;
- /* Schedule the origin node in case it wants to set its inputs as unused as well. */
- this->schedule_node(locked_node);
- }
+ this->with_locked_node(node, node_state, [&](LockedNode &locked_node) {
+ output_state.potential_users -= 1;
+ if (output_state.potential_users == 0) {
+ /* The output socket has no users anymore. */
+ output_state.output_usage = ValueUsage::Unused;
+ /* Schedule the origin node in case it wants to set its inputs as unused as well. */
+ this->schedule_node(locked_node);
+ }
+ });
}
void add_node_to_task_pool(const DNode node)
@@ -1247,28 +1249,27 @@ class GeometryNodesEvaluator {
NodeState &node_state = this->get_node_state(node);
InputState &input_state = node_state.inputs[socket->index()];
- /* Lock the node because we want to change its state. */
- LockedNode locked_node{*this, node, node_state};
-
- if (socket->is_multi_input_socket()) {
- /* Add a new value to the multi-input. */
- MultiInputValue &multi_value = *input_state.value.multi;
- multi_value.items.append({origin, value.get()});
- }
- else {
- /* Assign the value to the input. */
- SingleInputValue &single_value = *input_state.value.single;
- BLI_assert(single_value.value == nullptr);
- single_value.value = value.get();
- }
+ this->with_locked_node(node, node_state, [&](LockedNode &locked_node) {
+ if (socket->is_multi_input_socket()) {
+ /* Add a new value to the multi-input. */
+ MultiInputValue &multi_value = *input_state.value.multi;
+ multi_value.items.append({origin, value.get()});
+ }
+ else {
+ /* Assign the value to the input. */
+ SingleInputValue &single_value = *input_state.value.single;
+ BLI_assert(single_value.value == nullptr);
+ single_value.value = value.get();
+ }
- if (input_state.usage == ValueUsage::Required) {
- node_state.missing_required_inputs--;
- if (node_state.missing_required_inputs == 0) {
- /* Schedule node if all the required inputs have been provided. */
- this->schedule_node(locked_node);
+ if (input_state.usage == ValueUsage::Required) {
+ node_state.missing_required_inputs--;
+ if (node_state.missing_required_inputs == 0) {
+ /* Schedule node if all the required inputs have been provided. */
+ this->schedule_node(locked_node);
+ }
}
- }
+ });
}
void load_unlinked_input_value(LockedNode &locked_node,
@@ -1363,44 +1364,33 @@ class GeometryNodesEvaluator {
{
this->log_socket_value(socket, Span<GPointer>(&value, 1));
}
-};
-LockedNode::~LockedNode()
-{
- /* First unlock the current node. */
- node_state.mutex.unlock();
- /* Then send notifications to the other nodes. */
- for (const DOutputSocket &socket : delayed_required_outputs) {
- evaluator_.send_output_required_notification(socket);
- }
- for (const DOutputSocket &socket : delayed_unused_outputs) {
- evaluator_.send_output_unused_notification(socket);
- }
- for (const DNode &node : delayed_scheduled_nodes) {
- evaluator_.add_node_to_task_pool(node);
- }
-}
+ /* In most cases when `NodeState` is accessed, the node has to be locked first to avoid race
+ * conditions. */
+ template<typename Function>
+ void with_locked_node(const DNode node, NodeState &node_state, const Function &function)
+ {
+ LockedNode locked_node{node, node_state};
-/* TODO: Use a map data structure or so to make this faster. */
-static DInputSocket get_input_by_identifier(const DNode node, const StringRef identifier)
-{
- for (const InputSocketRef *socket : node->inputs()) {
- if (socket->identifier() == identifier) {
- return {node.context(), socket};
- }
- }
- return {};
-}
+ node_state.mutex.lock();
+ /* Isolate this thread because we don't want it to start executing another node. This other
+ * node might want to lock the same mutex leading to a deadlock. */
+ threading::isolate_task([&] { function(locked_node); });
+ node_state.mutex.unlock();
-static DOutputSocket get_output_by_identifier(const DNode node, const StringRef identifier)
-{
- for (const OutputSocketRef *socket : node->outputs()) {
- if (socket->identifier() == identifier) {
- return {node.context(), socket};
+ /* Then send notifications to the other nodes after the node state is unlocked. This avoids
+ * locking two nodes at the same time on this thread and helps to prevent deadlocks. */
+ for (const DOutputSocket &socket : locked_node.delayed_required_outputs) {
+ this->send_output_required_notification(socket);
+ }
+ for (const DOutputSocket &socket : locked_node.delayed_unused_outputs) {
+ this->send_output_unused_notification(socket);
+ }
+ for (const DNode &node : locked_node.delayed_scheduled_nodes) {
+ this->add_node_to_task_pool(node);
}
}
- return {};
-}
+};
NodeParamsProvider::NodeParamsProvider(GeometryNodesEvaluator &evaluator,
DNode dnode,
@@ -1415,7 +1405,7 @@ NodeParamsProvider::NodeParamsProvider(GeometryNodesEvaluator &evaluator,
bool NodeParamsProvider::can_get_input(StringRef identifier) const
{
- const DInputSocket socket = get_input_by_identifier(this->dnode, identifier);
+ const DInputSocket socket = this->dnode.input_by_identifier(identifier);
BLI_assert(socket);
InputState &input_state = node_state_.inputs[socket->index()];
@@ -1433,7 +1423,7 @@ bool NodeParamsProvider::can_get_input(StringRef identifier) const
bool NodeParamsProvider::can_set_output(StringRef identifier) const
{
- const DOutputSocket socket = get_output_by_identifier(this->dnode, identifier);
+ const DOutputSocket socket = this->dnode.output_by_identifier(identifier);
BLI_assert(socket);
OutputState &output_state = node_state_.outputs[socket->index()];
@@ -1442,7 +1432,7 @@ bool NodeParamsProvider::can_set_output(StringRef identifier) const
GMutablePointer NodeParamsProvider::extract_input(StringRef identifier)
{
- const DInputSocket socket = get_input_by_identifier(this->dnode, identifier);
+ const DInputSocket socket = this->dnode.input_by_identifier(identifier);
BLI_assert(socket);
BLI_assert(!socket->is_multi_input_socket());
BLI_assert(this->can_get_input(identifier));
@@ -1456,7 +1446,7 @@ GMutablePointer NodeParamsProvider::extract_input(StringRef identifier)
Vector<GMutablePointer> NodeParamsProvider::extract_multi_input(StringRef identifier)
{
- const DInputSocket socket = get_input_by_identifier(this->dnode, identifier);
+ const DInputSocket socket = this->dnode.input_by_identifier(identifier);
BLI_assert(socket);
BLI_assert(socket->is_multi_input_socket());
BLI_assert(this->can_get_input(identifier));
@@ -1487,7 +1477,7 @@ Vector<GMutablePointer> NodeParamsProvider::extract_multi_input(StringRef identi
GPointer NodeParamsProvider::get_input(StringRef identifier) const
{
- const DInputSocket socket = get_input_by_identifier(this->dnode, identifier);
+ const DInputSocket socket = this->dnode.input_by_identifier(identifier);
BLI_assert(socket);
BLI_assert(!socket->is_multi_input_socket());
BLI_assert(this->can_get_input(identifier));
@@ -1505,7 +1495,7 @@ GMutablePointer NodeParamsProvider::alloc_output_value(const CPPType &type)
void NodeParamsProvider::set_output(StringRef identifier, GMutablePointer value)
{
- const DOutputSocket socket = get_output_by_identifier(this->dnode, identifier);
+ const DOutputSocket socket = this->dnode.output_by_identifier(identifier);
BLI_assert(socket);
evaluator_.log_socket_value(socket, value);
@@ -1519,30 +1509,32 @@ void NodeParamsProvider::set_output(StringRef identifier, GMutablePointer value)
bool NodeParamsProvider::lazy_require_input(StringRef identifier)
{
BLI_assert(node_supports_laziness(this->dnode));
- const DInputSocket socket = get_input_by_identifier(this->dnode, identifier);
+ const DInputSocket socket = this->dnode.input_by_identifier(identifier);
BLI_assert(socket);
InputState &input_state = node_state_.inputs[socket->index()];
if (input_state.was_ready_for_execution) {
return false;
}
- LockedNode locked_node{evaluator_, this->dnode, node_state_};
- evaluator_.set_input_required(locked_node, socket);
+ evaluator_.with_locked_node(this->dnode, node_state_, [&](LockedNode &locked_node) {
+ evaluator_.set_input_required(locked_node, socket);
+ });
return true;
}
void NodeParamsProvider::set_input_unused(StringRef identifier)
{
- const DInputSocket socket = get_input_by_identifier(this->dnode, identifier);
+ const DInputSocket socket = this->dnode.input_by_identifier(identifier);
BLI_assert(socket);
- LockedNode locked_node{evaluator_, this->dnode, node_state_};
- evaluator_.set_input_unused(locked_node, socket);
+ evaluator_.with_locked_node(this->dnode, node_state_, [&](LockedNode &locked_node) {
+ evaluator_.set_input_unused(locked_node, socket);
+ });
}
bool NodeParamsProvider::output_is_required(StringRef identifier) const
{
- const DOutputSocket socket = get_output_by_identifier(this->dnode, identifier);
+ const DOutputSocket socket = this->dnode.output_by_identifier(identifier);
BLI_assert(socket);
OutputState &output_state = node_state_.outputs[socket->index()];
@@ -1555,7 +1547,7 @@ bool NodeParamsProvider::output_is_required(StringRef identifier) const
bool NodeParamsProvider::lazy_output_is_required(StringRef identifier) const
{
BLI_assert(node_supports_laziness(this->dnode));
- const DOutputSocket socket = get_output_by_identifier(this->dnode, identifier);
+ const DOutputSocket socket = this->dnode.output_by_identifier(identifier);
BLI_assert(socket);
OutputState &output_state = node_state_.outputs[socket->index()];