Including <iostream> or similar headers is quite expensive, since it also pulls in things like <locale> and so on. In many BLI headers, iostreams are only used to implement some sort of "debug print", or an operator<< for ostream. Change some of the commonly used places to instead include <iosfwd>, which is the standard way of forward-declaring iostreams related classes, and move the actual debug-print / operator<< implementations into .cc files. This is not done for templated classes though (it would be possible to provide explicit operator<< instantiations somewhere in the source file, but that would lead to hard-to-figure-out linker error whenever someone would add a different template type). There, where possible, I changed from full <iostream> include to only the needed <ostream> part. For Span<T>, I just removed print_as_lines since it's not used by anything. It could be moved into a .cc file using a similar approach as above if needed. Doing full blender build changes include counts this way: - <iostream> 1986 -> 978 - <sstream> 2880 -> 925 It does not affect the total build time much though, mostly because towards the end of it there's just several CPU cores finishing compiling OpenVDB related source files. Pull Request: https://projects.blender.org/blender/blender/pulls/111046
284 lines
7.8 KiB
C++
284 lines
7.8 KiB
C++
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
|
*
|
|
* SPDX-License-Identifier: GPL-2.0-or-later */
|
|
|
|
#include "BLI_dot_export.hh"
|
|
|
|
#include "FN_lazy_function_graph.hh"
|
|
|
|
#include <sstream>
|
|
|
|
namespace blender::fn::lazy_function {
|
|
|
|
Graph::~Graph()
|
|
{
|
|
for (Node *node : nodes_) {
|
|
for (InputSocket *socket : node->inputs_) {
|
|
std::destroy_at(socket);
|
|
}
|
|
for (OutputSocket *socket : node->outputs_) {
|
|
std::destroy_at(socket);
|
|
}
|
|
std::destroy_at(node);
|
|
}
|
|
}
|
|
|
|
FunctionNode &Graph::add_function(const LazyFunction &fn)
|
|
{
|
|
const Span<Input> inputs = fn.inputs();
|
|
const Span<Output> outputs = fn.outputs();
|
|
|
|
FunctionNode &node = *allocator_.construct<FunctionNode>().release();
|
|
node.fn_ = &fn;
|
|
node.inputs_ = allocator_.construct_elements_and_pointer_array<InputSocket>(inputs.size());
|
|
node.outputs_ = allocator_.construct_elements_and_pointer_array<OutputSocket>(outputs.size());
|
|
|
|
for (const int i : inputs.index_range()) {
|
|
InputSocket &socket = *node.inputs_[i];
|
|
socket.index_in_node_ = i;
|
|
socket.is_input_ = true;
|
|
socket.node_ = &node;
|
|
socket.type_ = inputs[i].type;
|
|
}
|
|
for (const int i : outputs.index_range()) {
|
|
OutputSocket &socket = *node.outputs_[i];
|
|
socket.index_in_node_ = i;
|
|
socket.is_input_ = false;
|
|
socket.node_ = &node;
|
|
socket.type_ = outputs[i].type;
|
|
}
|
|
|
|
nodes_.append(&node);
|
|
return node;
|
|
}
|
|
|
|
DummyNode &Graph::add_dummy(Span<const CPPType *> input_types,
|
|
Span<const CPPType *> output_types,
|
|
const DummyDebugInfo *debug_info)
|
|
{
|
|
DummyNode &node = *allocator_.construct<DummyNode>().release();
|
|
node.fn_ = nullptr;
|
|
node.inputs_ = allocator_.construct_elements_and_pointer_array<InputSocket>(input_types.size());
|
|
node.outputs_ = allocator_.construct_elements_and_pointer_array<OutputSocket>(
|
|
output_types.size());
|
|
node.debug_info_ = debug_info;
|
|
|
|
for (const int i : input_types.index_range()) {
|
|
InputSocket &socket = *node.inputs_[i];
|
|
socket.index_in_node_ = i;
|
|
socket.is_input_ = true;
|
|
socket.node_ = &node;
|
|
socket.type_ = input_types[i];
|
|
}
|
|
for (const int i : output_types.index_range()) {
|
|
OutputSocket &socket = *node.outputs_[i];
|
|
socket.index_in_node_ = i;
|
|
socket.is_input_ = false;
|
|
socket.node_ = &node;
|
|
socket.type_ = output_types[i];
|
|
}
|
|
|
|
nodes_.append(&node);
|
|
return node;
|
|
}
|
|
|
|
void Graph::add_link(OutputSocket &from, InputSocket &to)
|
|
{
|
|
BLI_assert(to.origin_ == nullptr);
|
|
BLI_assert(from.type_ == to.type_);
|
|
to.origin_ = &from;
|
|
from.targets_.append(&to);
|
|
}
|
|
|
|
void Graph::clear_origin(InputSocket &socket)
|
|
{
|
|
if (socket.origin_ != nullptr) {
|
|
socket.origin_->targets_.remove_first_occurrence_and_reorder(&socket);
|
|
socket.origin_ = nullptr;
|
|
}
|
|
}
|
|
|
|
void Graph::update_node_indices()
|
|
{
|
|
for (const int i : nodes_.index_range()) {
|
|
nodes_[i]->index_in_graph_ = i;
|
|
}
|
|
}
|
|
|
|
void Graph::update_socket_indices()
|
|
{
|
|
int socket_counter = 0;
|
|
for (const int i : nodes_.index_range()) {
|
|
for (InputSocket *socket : nodes_[i]->inputs()) {
|
|
socket->index_in_graph_ = socket_counter++;
|
|
}
|
|
for (OutputSocket *socket : nodes_[i]->outputs()) {
|
|
socket->index_in_graph_ = socket_counter++;
|
|
}
|
|
}
|
|
socket_num_ = socket_counter;
|
|
}
|
|
|
|
bool Graph::node_indices_are_valid() const
|
|
{
|
|
for (const int i : nodes_.index_range()) {
|
|
if (nodes_[i]->index_in_graph_ != i) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static const char *fallback_name = "No Name";
|
|
|
|
std::string Socket::name() const
|
|
{
|
|
if (node_->is_function()) {
|
|
const FunctionNode &fn_node = static_cast<const FunctionNode &>(*node_);
|
|
const LazyFunction &fn = fn_node.function();
|
|
if (is_input_) {
|
|
return fn.input_name(index_in_node_);
|
|
}
|
|
return fn.output_name(index_in_node_);
|
|
}
|
|
const DummyNode &dummy_node = *static_cast<const DummyNode *>(node_);
|
|
if (dummy_node.debug_info_) {
|
|
if (is_input_) {
|
|
return dummy_node.debug_info_->input_name(index_in_node_);
|
|
}
|
|
return dummy_node.debug_info_->output_name(index_in_node_);
|
|
}
|
|
return fallback_name;
|
|
}
|
|
|
|
std::string Socket::detailed_name() const
|
|
{
|
|
std::stringstream ss;
|
|
ss << node_->name() << ":" << (is_input_ ? "IN" : "OUT") << ":" << index_in_node_ << ":"
|
|
<< this->name();
|
|
return ss.str();
|
|
}
|
|
|
|
std::string Node::name() const
|
|
{
|
|
if (this->is_function()) {
|
|
return fn_->name();
|
|
}
|
|
const DummyNode &dummy_node = *static_cast<const DummyNode *>(this);
|
|
if (dummy_node.debug_info_) {
|
|
return dummy_node.debug_info_->node_name();
|
|
}
|
|
return fallback_name;
|
|
}
|
|
|
|
std::string DummyDebugInfo::node_name() const
|
|
{
|
|
return fallback_name;
|
|
}
|
|
|
|
std::string DummyDebugInfo::input_name(const int /*i*/) const
|
|
{
|
|
return fallback_name;
|
|
}
|
|
|
|
std::string DummyDebugInfo::output_name(const int /*i*/) const
|
|
{
|
|
return fallback_name;
|
|
}
|
|
|
|
std::string SimpleDummyDebugInfo::node_name() const
|
|
{
|
|
return this->name;
|
|
}
|
|
|
|
std::string SimpleDummyDebugInfo::input_name(const int i) const
|
|
{
|
|
return this->input_names[i];
|
|
}
|
|
|
|
std::string SimpleDummyDebugInfo::output_name(const int i) const
|
|
{
|
|
return this->output_names[i];
|
|
}
|
|
|
|
std::string Graph::ToDotOptions::socket_name(const Socket &socket) const
|
|
{
|
|
return socket.name();
|
|
}
|
|
|
|
std::optional<std::string> Graph::ToDotOptions::socket_font_color(const Socket & /*socket*/) const
|
|
{
|
|
return std::nullopt;
|
|
}
|
|
|
|
void Graph::ToDotOptions::add_edge_attributes(const OutputSocket & /*from*/,
|
|
const InputSocket & /*to*/,
|
|
dot::DirectedEdge & /*dot_edge*/) const
|
|
{
|
|
}
|
|
|
|
std::string Graph::to_dot(const ToDotOptions &options) const
|
|
{
|
|
dot::DirectedGraph digraph;
|
|
digraph.set_rankdir(dot::Attr_rankdir::LeftToRight);
|
|
|
|
Map<const Node *, dot::NodeWithSocketsRef> dot_nodes;
|
|
|
|
for (const Node *node : nodes_) {
|
|
dot::Node &dot_node = digraph.new_node("");
|
|
if (node->is_dummy()) {
|
|
dot_node.set_background_color("lightblue");
|
|
}
|
|
else {
|
|
dot_node.set_background_color("white");
|
|
}
|
|
|
|
dot::NodeWithSockets dot_node_with_sockets;
|
|
dot_node_with_sockets.node_name = node->name();
|
|
for (const InputSocket *socket : node->inputs()) {
|
|
dot::NodeWithSockets::Input &dot_input = dot_node_with_sockets.add_input(
|
|
options.socket_name(*socket));
|
|
dot_input.fontcolor = options.socket_font_color(*socket);
|
|
}
|
|
for (const OutputSocket *socket : node->outputs()) {
|
|
dot::NodeWithSockets::Output &dot_output = dot_node_with_sockets.add_output(
|
|
options.socket_name(*socket));
|
|
dot_output.fontcolor = options.socket_font_color(*socket);
|
|
}
|
|
|
|
dot_nodes.add_new(node, dot::NodeWithSocketsRef(dot_node, dot_node_with_sockets));
|
|
}
|
|
|
|
for (const Node *node : nodes_) {
|
|
for (const InputSocket *socket : node->inputs()) {
|
|
const dot::NodeWithSocketsRef &to_dot_node = dot_nodes.lookup(&socket->node());
|
|
const dot::NodePort to_dot_port = to_dot_node.input(socket->index());
|
|
|
|
if (const OutputSocket *origin = socket->origin()) {
|
|
dot::NodeWithSocketsRef &from_dot_node = dot_nodes.lookup(&origin->node());
|
|
dot::DirectedEdge &dot_edge = digraph.new_edge(from_dot_node.output(origin->index()),
|
|
to_dot_port);
|
|
options.add_edge_attributes(*origin, *socket, dot_edge);
|
|
}
|
|
else if (const void *default_value = socket->default_value()) {
|
|
const CPPType &type = socket->type();
|
|
std::string value_string;
|
|
if (type.is_printable()) {
|
|
value_string = type.to_string(default_value);
|
|
}
|
|
else {
|
|
value_string = type.name();
|
|
}
|
|
dot::Node &default_value_dot_node = digraph.new_node(value_string);
|
|
default_value_dot_node.set_shape(dot::Attr_shape::Ellipse);
|
|
default_value_dot_node.attributes.set("color", "#00000055");
|
|
digraph.new_edge(default_value_dot_node, to_dot_port);
|
|
}
|
|
}
|
|
}
|
|
|
|
return digraph.to_dot_string();
|
|
}
|
|
|
|
} // namespace blender::fn::lazy_function
|