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/functions/intern/multi_function_procedure.cc')
-rw-r--r--source/blender/functions/intern/multi_function_procedure.cc794
1 files changed, 794 insertions, 0 deletions
diff --git a/source/blender/functions/intern/multi_function_procedure.cc b/source/blender/functions/intern/multi_function_procedure.cc
new file mode 100644
index 00000000000..6eff7bc09f8
--- /dev/null
+++ b/source/blender/functions/intern/multi_function_procedure.cc
@@ -0,0 +1,794 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include "FN_multi_function_procedure.hh"
+
+#include "BLI_dot_export.hh"
+#include "BLI_stack.hh"
+
+namespace blender::fn {
+
+void MFVariable::set_name(std::string name)
+{
+ name_ = std::move(name);
+}
+
+void MFCallInstruction::set_next(MFInstruction *instruction)
+{
+ if (next_ != nullptr) {
+ next_->prev_.remove_first_occurrence_and_reorder(this);
+ }
+ if (instruction != nullptr) {
+ instruction->prev_.append(this);
+ }
+ next_ = instruction;
+}
+
+void MFCallInstruction::set_param_variable(int param_index, MFVariable *variable)
+{
+ if (params_[param_index] != nullptr) {
+ params_[param_index]->users_.remove_first_occurrence_and_reorder(this);
+ }
+ if (variable != nullptr) {
+ BLI_assert(fn_->param_type(param_index).data_type() == variable->data_type());
+ variable->users_.append(this);
+ }
+ params_[param_index] = variable;
+}
+
+void MFCallInstruction::set_params(Span<MFVariable *> variables)
+{
+ BLI_assert(variables.size() == params_.size());
+ for (const int i : variables.index_range()) {
+ this->set_param_variable(i, variables[i]);
+ }
+}
+
+void MFBranchInstruction::set_condition(MFVariable *variable)
+{
+ if (condition_ != nullptr) {
+ condition_->users_.remove_first_occurrence_and_reorder(this);
+ }
+ if (variable != nullptr) {
+ variable->users_.append(this);
+ }
+ condition_ = variable;
+}
+
+void MFBranchInstruction::set_branch_true(MFInstruction *instruction)
+{
+ if (branch_true_ != nullptr) {
+ branch_true_->prev_.remove_first_occurrence_and_reorder(this);
+ }
+ if (instruction != nullptr) {
+ instruction->prev_.append(this);
+ }
+ branch_true_ = instruction;
+}
+
+void MFBranchInstruction::set_branch_false(MFInstruction *instruction)
+{
+ if (branch_false_ != nullptr) {
+ branch_false_->prev_.remove_first_occurrence_and_reorder(this);
+ }
+ if (instruction != nullptr) {
+ instruction->prev_.append(this);
+ }
+ branch_false_ = instruction;
+}
+
+void MFDestructInstruction::set_variable(MFVariable *variable)
+{
+ if (variable_ != nullptr) {
+ variable_->users_.remove_first_occurrence_and_reorder(this);
+ }
+ if (variable != nullptr) {
+ variable->users_.append(this);
+ }
+ variable_ = variable;
+}
+
+void MFDestructInstruction::set_next(MFInstruction *instruction)
+{
+ if (next_ != nullptr) {
+ next_->prev_.remove_first_occurrence_and_reorder(this);
+ }
+ if (instruction != nullptr) {
+ instruction->prev_.append(this);
+ }
+ next_ = instruction;
+}
+
+void MFDummyInstruction::set_next(MFInstruction *instruction)
+{
+ if (next_ != nullptr) {
+ next_->prev_.remove_first_occurrence_and_reorder(this);
+ }
+ if (instruction != nullptr) {
+ instruction->prev_.append(this);
+ }
+ next_ = instruction;
+}
+
+MFVariable &MFProcedure::new_variable(MFDataType data_type, std::string name)
+{
+ MFVariable &variable = *allocator_.construct<MFVariable>().release();
+ variable.name_ = std::move(name);
+ variable.data_type_ = data_type;
+ variable.id_ = variables_.size();
+ variables_.append(&variable);
+ return variable;
+}
+
+MFCallInstruction &MFProcedure::new_call_instruction(const MultiFunction &fn)
+{
+ MFCallInstruction &instruction = *allocator_.construct<MFCallInstruction>().release();
+ instruction.type_ = MFInstructionType::Call;
+ instruction.fn_ = &fn;
+ instruction.params_ = allocator_.allocate_array<MFVariable *>(fn.param_amount());
+ instruction.params_.fill(nullptr);
+ call_instructions_.append(&instruction);
+ return instruction;
+}
+
+MFBranchInstruction &MFProcedure::new_branch_instruction()
+{
+ MFBranchInstruction &instruction = *allocator_.construct<MFBranchInstruction>().release();
+ instruction.type_ = MFInstructionType::Branch;
+ branch_instructions_.append(&instruction);
+ return instruction;
+}
+
+MFDestructInstruction &MFProcedure::new_destruct_instruction()
+{
+ MFDestructInstruction &instruction = *allocator_.construct<MFDestructInstruction>().release();
+ instruction.type_ = MFInstructionType::Destruct;
+ destruct_instructions_.append(&instruction);
+ return instruction;
+}
+
+MFDummyInstruction &MFProcedure::new_dummy_instruction()
+{
+ MFDummyInstruction &instruction = *allocator_.construct<MFDummyInstruction>().release();
+ instruction.type_ = MFInstructionType::Dummy;
+ dummy_instructions_.append(&instruction);
+ return instruction;
+}
+
+MFReturnInstruction &MFProcedure::new_return_instruction()
+{
+ MFReturnInstruction &instruction = *allocator_.construct<MFReturnInstruction>().release();
+ instruction.type_ = MFInstructionType::Return;
+ return_instructions_.append(&instruction);
+ return instruction;
+}
+
+void MFProcedure::add_parameter(MFParamType::InterfaceType interface_type, MFVariable &variable)
+{
+ params_.append({interface_type, &variable});
+}
+
+void MFProcedure::set_entry(MFInstruction &entry)
+{
+ entry_ = &entry;
+}
+
+MFProcedure::~MFProcedure()
+{
+ for (MFCallInstruction *instruction : call_instructions_) {
+ instruction->~MFCallInstruction();
+ }
+ for (MFBranchInstruction *instruction : branch_instructions_) {
+ instruction->~MFBranchInstruction();
+ }
+ for (MFDestructInstruction *instruction : destruct_instructions_) {
+ instruction->~MFDestructInstruction();
+ }
+ for (MFDummyInstruction *instruction : dummy_instructions_) {
+ instruction->~MFDummyInstruction();
+ }
+ for (MFReturnInstruction *instruction : return_instructions_) {
+ instruction->~MFReturnInstruction();
+ }
+ for (MFVariable *variable : variables_) {
+ variable->~MFVariable();
+ }
+}
+
+bool MFProcedure::validate() const
+{
+ if (entry_ == nullptr) {
+ return false;
+ }
+ if (!this->validate_all_instruction_pointers_set()) {
+ return false;
+ }
+ if (!this->validate_all_params_provided()) {
+ return false;
+ }
+ if (!this->validate_same_variables_in_one_call()) {
+ return false;
+ }
+ if (!this->validate_parameters()) {
+ return false;
+ }
+ if (!this->validate_initialization()) {
+ return false;
+ }
+ return true;
+}
+
+bool MFProcedure::validate_all_instruction_pointers_set() const
+{
+ for (const MFCallInstruction *instruction : call_instructions_) {
+ if (instruction->next_ == nullptr) {
+ return false;
+ }
+ }
+ for (const MFDestructInstruction *instruction : destruct_instructions_) {
+ if (instruction->next_ == nullptr) {
+ return false;
+ }
+ }
+ for (const MFBranchInstruction *instruction : branch_instructions_) {
+ if (instruction->branch_true_ == nullptr) {
+ return false;
+ }
+ if (instruction->branch_false_ == nullptr) {
+ return false;
+ }
+ }
+ for (const MFDummyInstruction *instruction : dummy_instructions_) {
+ if (instruction->next_ == nullptr) {
+ return false;
+ }
+ }
+ return true;
+}
+
+bool MFProcedure::validate_all_params_provided() const
+{
+ for (const MFCallInstruction *instruction : call_instructions_) {
+ for (const MFVariable *variable : instruction->params_) {
+ if (variable == nullptr) {
+ return false;
+ }
+ }
+ }
+ for (const MFBranchInstruction *instruction : branch_instructions_) {
+ if (instruction->condition_ == nullptr) {
+ return false;
+ }
+ }
+ for (const MFDestructInstruction *instruction : destruct_instructions_) {
+ if (instruction->variable_ == nullptr) {
+ return false;
+ }
+ }
+ return true;
+}
+
+bool MFProcedure::validate_same_variables_in_one_call() const
+{
+ for (const MFCallInstruction *instruction : call_instructions_) {
+ const MultiFunction &fn = *instruction->fn_;
+ for (const int param_index : fn.param_indices()) {
+ const MFParamType param_type = fn.param_type(param_index);
+ const MFVariable *variable = instruction->params_[param_index];
+ for (const int other_param_index : fn.param_indices()) {
+ if (other_param_index == param_index) {
+ continue;
+ }
+ const MFVariable *other_variable = instruction->params_[other_param_index];
+ if (other_variable != variable) {
+ continue;
+ }
+ if (ELEM(param_type.interface_type(), MFParamType::Mutable, MFParamType::Output)) {
+ /* When a variable is used as mutable or output parameter, it can only be used once. */
+ return false;
+ }
+ const MFParamType other_param_type = fn.param_type(other_param_index);
+ /* A variable is allowed to be used as input more than once. */
+ if (other_param_type.interface_type() != MFParamType::Input) {
+ return false;
+ }
+ }
+ }
+ }
+ return true;
+}
+
+bool MFProcedure::validate_parameters() const
+{
+ Set<const MFVariable *> variables;
+ for (const MFParameter &param : params_) {
+ /* One variable cannot be used as multiple parameters. */
+ if (!variables.add(param.variable)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+bool MFProcedure::validate_initialization() const
+{
+ /* TODO: Issue warning when it maybe wrongly initialized. */
+ for (const MFDestructInstruction *instruction : destruct_instructions_) {
+ const MFVariable &variable = *instruction->variable_;
+ const InitState state = this->find_initialization_state_before_instruction(*instruction,
+ variable);
+ if (!state.can_be_initialized) {
+ return false;
+ }
+ }
+ for (const MFBranchInstruction *instruction : branch_instructions_) {
+ const MFVariable &variable = *instruction->condition_;
+ const InitState state = this->find_initialization_state_before_instruction(*instruction,
+ variable);
+ if (!state.can_be_initialized) {
+ return false;
+ }
+ }
+ for (const MFCallInstruction *instruction : call_instructions_) {
+ const MultiFunction &fn = *instruction->fn_;
+ for (const int param_index : fn.param_indices()) {
+ const MFParamType param_type = fn.param_type(param_index);
+ const MFVariable &variable = *instruction->params_[param_index];
+ const InitState state = this->find_initialization_state_before_instruction(*instruction,
+ variable);
+ switch (param_type.interface_type()) {
+ case MFParamType::Input:
+ case MFParamType::Mutable: {
+ if (!state.can_be_initialized) {
+ return false;
+ }
+ break;
+ }
+ case MFParamType::Output: {
+ if (!state.can_be_uninitialized) {
+ return false;
+ }
+ break;
+ }
+ }
+ }
+ }
+ Set<const MFVariable *> variables_that_should_be_initialized_on_return;
+ for (const MFParameter &param : params_) {
+ if (ELEM(param.type, MFParamType::Mutable, MFParamType::Output)) {
+ variables_that_should_be_initialized_on_return.add_new(param.variable);
+ }
+ }
+ for (const MFReturnInstruction *instruction : return_instructions_) {
+ for (const MFVariable *variable : variables_) {
+ const InitState init_state = this->find_initialization_state_before_instruction(*instruction,
+ *variable);
+ if (variables_that_should_be_initialized_on_return.contains(variable)) {
+ if (!init_state.can_be_initialized) {
+ return false;
+ }
+ }
+ else {
+ if (!init_state.can_be_uninitialized) {
+ return false;
+ }
+ }
+ }
+ }
+ return true;
+}
+
+MFProcedure::InitState MFProcedure::find_initialization_state_before_instruction(
+ const MFInstruction &target_instruction, const MFVariable &target_variable) const
+{
+ InitState state;
+
+ auto check_entry_instruction = [&]() {
+ bool caller_initialized_variable = false;
+ for (const MFParameter &param : params_) {
+ if (param.variable == &target_variable) {
+ if (ELEM(param.type, MFParamType::Input, MFParamType::Mutable)) {
+ caller_initialized_variable = true;
+ break;
+ }
+ }
+ }
+ if (caller_initialized_variable) {
+ state.can_be_initialized = true;
+ }
+ else {
+ state.can_be_uninitialized = true;
+ }
+ };
+
+ if (&target_instruction == entry_) {
+ check_entry_instruction();
+ }
+
+ Set<const MFInstruction *> checked_instructions;
+ Stack<const MFInstruction *> instructions_to_check;
+ instructions_to_check.push_multiple(target_instruction.prev_);
+
+ while (!instructions_to_check.is_empty()) {
+ const MFInstruction &instruction = *instructions_to_check.pop();
+ if (!checked_instructions.add(&instruction)) {
+ /* Skip if the instruction has been checked already. */
+ continue;
+ }
+ bool state_modified = false;
+ switch (instruction.type_) {
+ case MFInstructionType::Call: {
+ const MFCallInstruction &call_instruction = static_cast<const MFCallInstruction &>(
+ instruction);
+ const MultiFunction &fn = *call_instruction.fn_;
+ for (const int param_index : fn.param_indices()) {
+ if (call_instruction.params_[param_index] == &target_variable) {
+ const MFParamType param_type = fn.param_type(param_index);
+ if (param_type.interface_type() == MFParamType::Output) {
+ state.can_be_initialized = true;
+ state_modified = true;
+ break;
+ }
+ }
+ }
+ break;
+ }
+ case MFInstructionType::Destruct: {
+ const MFDestructInstruction &destruct_instruction =
+ static_cast<const MFDestructInstruction &>(instruction);
+ if (destruct_instruction.variable_ == &target_variable) {
+ state.can_be_uninitialized = true;
+ state_modified = true;
+ }
+ break;
+ }
+ case MFInstructionType::Branch:
+ case MFInstructionType::Dummy:
+ case MFInstructionType::Return: {
+ /* These instruction types don't change the initialization state of variables. */
+ break;
+ }
+ }
+
+ if (!state_modified) {
+ if (&instruction == entry_) {
+ check_entry_instruction();
+ }
+ instructions_to_check.push_multiple(instruction.prev_);
+ }
+ }
+
+ return state;
+}
+
+class MFProcedureDotExport {
+ private:
+ const MFProcedure &procedure_;
+ dot::DirectedGraph digraph_;
+ Map<const MFInstruction *, dot::Node *> dot_nodes_by_begin_;
+ Map<const MFInstruction *, dot::Node *> dot_nodes_by_end_;
+
+ public:
+ MFProcedureDotExport(const MFProcedure &procedure) : procedure_(procedure)
+ {
+ }
+
+ std::string generate()
+ {
+ this->create_nodes();
+ this->create_edges();
+ return digraph_.to_dot_string();
+ }
+
+ void create_nodes()
+ {
+ Vector<const MFInstruction *> all_instructions;
+ auto add_instructions = [&](auto instructions) {
+ all_instructions.extend(instructions.begin(), instructions.end());
+ };
+ add_instructions(procedure_.call_instructions_);
+ add_instructions(procedure_.branch_instructions_);
+ add_instructions(procedure_.destruct_instructions_);
+ add_instructions(procedure_.dummy_instructions_);
+ add_instructions(procedure_.return_instructions_);
+
+ Set<const MFInstruction *> handled_instructions;
+
+ for (const MFInstruction *representative : all_instructions) {
+ if (handled_instructions.contains(representative)) {
+ continue;
+ }
+ Vector<const MFInstruction *> block_instructions = this->get_instructions_in_block(
+ *representative);
+ std::stringstream ss;
+ ss << "<";
+
+ for (const MFInstruction *current : block_instructions) {
+ handled_instructions.add_new(current);
+ switch (current->type()) {
+ case MFInstructionType::Call: {
+ this->instruction_to_string(*static_cast<const MFCallInstruction *>(current), ss);
+ break;
+ }
+ case MFInstructionType::Destruct: {
+ this->instruction_to_string(*static_cast<const MFDestructInstruction *>(current), ss);
+ break;
+ }
+ case MFInstructionType::Dummy: {
+ this->instruction_to_string(*static_cast<const MFDummyInstruction *>(current), ss);
+ break;
+ }
+ case MFInstructionType::Return: {
+ this->instruction_to_string(*static_cast<const MFReturnInstruction *>(current), ss);
+ break;
+ }
+ case MFInstructionType::Branch: {
+ this->instruction_to_string(*static_cast<const MFBranchInstruction *>(current), ss);
+ break;
+ }
+ }
+ ss << R"(<br align="left" />)";
+ }
+ ss << ">";
+
+ dot::Node &dot_node = digraph_.new_node(ss.str());
+ dot_node.set_shape(dot::Attr_shape::Rectangle);
+ dot_nodes_by_begin_.add_new(block_instructions.first(), &dot_node);
+ dot_nodes_by_end_.add_new(block_instructions.last(), &dot_node);
+ }
+ }
+
+ void create_edges()
+ {
+ auto create_edge = [&](dot::Node &from_node,
+ const MFInstruction *to_instruction) -> dot::DirectedEdge & {
+ if (to_instruction == nullptr) {
+ dot::Node &to_node = digraph_.new_node("missing");
+ to_node.set_shape(dot::Attr_shape::Diamond);
+ return digraph_.new_edge(from_node, to_node);
+ }
+ dot::Node &to_node = *dot_nodes_by_begin_.lookup(to_instruction);
+ return digraph_.new_edge(from_node, to_node);
+ };
+
+ for (auto item : dot_nodes_by_end_.items()) {
+ const MFInstruction &from_instruction = *item.key;
+ dot::Node &from_node = *item.value;
+ switch (from_instruction.type()) {
+ case MFInstructionType::Call: {
+ const MFInstruction *to_instruction =
+ static_cast<const MFCallInstruction &>(from_instruction).next();
+ create_edge(from_node, to_instruction);
+ break;
+ }
+ case MFInstructionType::Destruct: {
+ const MFInstruction *to_instruction =
+ static_cast<const MFDestructInstruction &>(from_instruction).next();
+ create_edge(from_node, to_instruction);
+ break;
+ }
+ case MFInstructionType::Dummy: {
+ const MFInstruction *to_instruction =
+ static_cast<const MFDummyInstruction &>(from_instruction).next();
+ create_edge(from_node, to_instruction);
+ break;
+ }
+ case MFInstructionType::Return: {
+ break;
+ }
+ case MFInstructionType::Branch: {
+ const MFBranchInstruction &branch_instruction = static_cast<const MFBranchInstruction &>(
+ from_instruction);
+ const MFInstruction *to_true_instruction = branch_instruction.branch_true();
+ const MFInstruction *to_false_instruction = branch_instruction.branch_false();
+ create_edge(from_node, to_true_instruction).attributes.set("color", "#118811");
+ create_edge(from_node, to_false_instruction).attributes.set("color", "#881111");
+ break;
+ }
+ }
+ }
+
+ dot::Node &entry_node = this->create_entry_node();
+ create_edge(entry_node, procedure_.entry());
+ }
+
+ bool has_to_be_block_begin(const MFInstruction &instruction)
+ {
+ if (procedure_.entry() == &instruction) {
+ return true;
+ }
+ if (instruction.prev().size() != 1) {
+ return true;
+ }
+ if (instruction.prev()[0]->type() == MFInstructionType::Branch) {
+ return true;
+ }
+ return false;
+ }
+
+ const MFInstruction &get_first_instruction_in_block(const MFInstruction &representative)
+ {
+ const MFInstruction *current = &representative;
+ while (!this->has_to_be_block_begin(*current)) {
+ current = current->prev()[0];
+ if (current == &representative) {
+ /* There is a loop without entry or exit, just break it up here. */
+ break;
+ }
+ }
+ return *current;
+ }
+
+ const MFInstruction *get_next_instruction_in_block(const MFInstruction &instruction,
+ const MFInstruction &block_begin)
+ {
+ const MFInstruction *next = nullptr;
+ switch (instruction.type()) {
+ case MFInstructionType::Call: {
+ next = static_cast<const MFCallInstruction &>(instruction).next();
+ break;
+ }
+ case MFInstructionType::Destruct: {
+ next = static_cast<const MFDestructInstruction &>(instruction).next();
+ break;
+ }
+ case MFInstructionType::Dummy: {
+ next = static_cast<const MFDummyInstruction &>(instruction).next();
+ break;
+ }
+ case MFInstructionType::Return:
+ case MFInstructionType::Branch: {
+ break;
+ }
+ }
+ if (next == nullptr) {
+ return nullptr;
+ }
+ if (next == &block_begin) {
+ return nullptr;
+ }
+ if (this->has_to_be_block_begin(*next)) {
+ return nullptr;
+ }
+ return next;
+ }
+
+ Vector<const MFInstruction *> get_instructions_in_block(const MFInstruction &representative)
+ {
+ Vector<const MFInstruction *> instructions;
+ const MFInstruction &begin = this->get_first_instruction_in_block(representative);
+ for (const MFInstruction *current = &begin; current != nullptr;
+ current = this->get_next_instruction_in_block(*current, begin)) {
+ instructions.append(current);
+ }
+ return instructions;
+ }
+
+ void variable_to_string(const MFVariable *variable, std::stringstream &ss)
+ {
+ if (variable == nullptr) {
+ ss << "null";
+ }
+ else {
+ ss << "$" << variable->id();
+ if (!variable->name().is_empty()) {
+ ss << "(" << variable->name() << ")";
+ }
+ }
+ }
+
+ void instruction_name_format(StringRef name, std::stringstream &ss)
+ {
+ ss << name;
+ }
+
+ void instruction_to_string(const MFCallInstruction &instruction, std::stringstream &ss)
+ {
+ const MultiFunction &fn = instruction.fn();
+ this->instruction_name_format(fn.name() + ": ", ss);
+ for (const int param_index : fn.param_indices()) {
+ const MFParamType param_type = fn.param_type(param_index);
+ const MFVariable *variable = instruction.params()[param_index];
+ ss << R"(<font color="grey30">)";
+ switch (param_type.interface_type()) {
+ case MFParamType::Input: {
+ ss << "in";
+ break;
+ }
+ case MFParamType::Mutable: {
+ ss << "mut";
+ break;
+ }
+ case MFParamType::Output: {
+ ss << "out";
+ break;
+ }
+ }
+ ss << " </font> ";
+ variable_to_string(variable, ss);
+ if (param_index < fn.param_amount() - 1) {
+ ss << ", ";
+ }
+ }
+ }
+
+ void instruction_to_string(const MFDestructInstruction &instruction, std::stringstream &ss)
+ {
+ instruction_name_format("Destruct ", ss);
+ variable_to_string(instruction.variable(), ss);
+ }
+
+ void instruction_to_string(const MFDummyInstruction &UNUSED(instruction), std::stringstream &ss)
+ {
+ instruction_name_format("Dummy ", ss);
+ }
+
+ void instruction_to_string(const MFReturnInstruction &UNUSED(instruction), std::stringstream &ss)
+ {
+ instruction_name_format("Return ", ss);
+
+ Vector<ConstMFParameter> outgoing_parameters;
+ for (const ConstMFParameter &param : procedure_.params()) {
+ if (ELEM(param.type, MFParamType::Mutable, MFParamType::Output)) {
+ outgoing_parameters.append(param);
+ }
+ }
+ for (const int param_index : outgoing_parameters.index_range()) {
+ const ConstMFParameter &param = outgoing_parameters[param_index];
+ variable_to_string(param.variable, ss);
+ if (param_index < outgoing_parameters.size() - 1) {
+ ss << ", ";
+ }
+ }
+ }
+
+ void instruction_to_string(const MFBranchInstruction &instruction, std::stringstream &ss)
+ {
+ instruction_name_format("Branch ", ss);
+ variable_to_string(instruction.condition(), ss);
+ }
+
+ dot::Node &create_entry_node()
+ {
+ std::stringstream ss;
+ ss << "Entry: ";
+ Vector<ConstMFParameter> incoming_parameters;
+ for (const ConstMFParameter &param : procedure_.params()) {
+ if (ELEM(param.type, MFParamType::Input, MFParamType::Mutable)) {
+ incoming_parameters.append(param);
+ }
+ }
+ for (const int param_index : incoming_parameters.index_range()) {
+ const ConstMFParameter &param = incoming_parameters[param_index];
+ variable_to_string(param.variable, ss);
+ if (param_index < incoming_parameters.size() - 1) {
+ ss << ", ";
+ }
+ }
+
+ dot::Node &node = digraph_.new_node(ss.str());
+ node.set_shape(dot::Attr_shape::Ellipse);
+ return node;
+ }
+};
+
+std::string MFProcedure::to_dot() const
+{
+ MFProcedureDotExport dot_export{*this};
+ return dot_export.generate();
+}
+
+} // namespace blender::fn