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
319 lines
7.2 KiB
C++
319 lines
7.2 KiB
C++
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
|
*
|
|
* SPDX-License-Identifier: GPL-2.0-or-later */
|
|
|
|
#include <iomanip>
|
|
|
|
#include "BLI_dot_export.hh"
|
|
|
|
#include <sstream>
|
|
|
|
namespace blender::dot {
|
|
|
|
/* Graph Building
|
|
************************************************/
|
|
|
|
Node &Graph::new_node(StringRef label)
|
|
{
|
|
Node *node = new Node(*this);
|
|
nodes_.append(std::unique_ptr<Node>(node));
|
|
top_level_nodes_.add_new(node);
|
|
node->attributes.set("label", label);
|
|
return *node;
|
|
}
|
|
|
|
Cluster &Graph::new_cluster(StringRef label)
|
|
{
|
|
Cluster *cluster = new Cluster(*this);
|
|
clusters_.append(std::unique_ptr<Cluster>(cluster));
|
|
top_level_clusters_.add_new(cluster);
|
|
cluster->attributes.set("label", label);
|
|
return *cluster;
|
|
}
|
|
|
|
UndirectedEdge &UndirectedGraph::new_edge(NodePort a, NodePort b)
|
|
{
|
|
UndirectedEdge *edge = new UndirectedEdge(a, b);
|
|
edges_.append(std::unique_ptr<UndirectedEdge>(edge));
|
|
return *edge;
|
|
}
|
|
|
|
DirectedEdge &DirectedGraph::new_edge(NodePort from, NodePort to)
|
|
{
|
|
DirectedEdge *edge = new DirectedEdge(from, to);
|
|
edges_.append(std::unique_ptr<DirectedEdge>(edge));
|
|
return *edge;
|
|
}
|
|
|
|
void Cluster::set_parent_cluster(Cluster *new_parent)
|
|
{
|
|
if (parent_ == new_parent) {
|
|
return;
|
|
}
|
|
if (parent_ == nullptr) {
|
|
graph_.top_level_clusters_.remove(this);
|
|
new_parent->children_.add_new(this);
|
|
}
|
|
else if (new_parent == nullptr) {
|
|
parent_->children_.remove(this);
|
|
graph_.top_level_clusters_.add_new(this);
|
|
}
|
|
else {
|
|
parent_->children_.remove(this);
|
|
new_parent->children_.add_new(this);
|
|
}
|
|
parent_ = new_parent;
|
|
}
|
|
|
|
void Node::set_parent_cluster(Cluster *cluster)
|
|
{
|
|
if (cluster_ == cluster) {
|
|
return;
|
|
}
|
|
if (cluster_ == nullptr) {
|
|
graph_.top_level_nodes_.remove(this);
|
|
cluster->nodes_.add_new(this);
|
|
}
|
|
else if (cluster == nullptr) {
|
|
cluster_->nodes_.remove(this);
|
|
graph_.top_level_nodes_.add_new(this);
|
|
}
|
|
else {
|
|
cluster_->nodes_.remove(this);
|
|
cluster->nodes_.add_new(this);
|
|
}
|
|
cluster_ = cluster;
|
|
}
|
|
|
|
/* Utility methods
|
|
**********************************************/
|
|
|
|
void Graph::set_random_cluster_bgcolors()
|
|
{
|
|
for (Cluster *cluster : top_level_clusters_) {
|
|
cluster->set_random_cluster_bgcolors();
|
|
}
|
|
}
|
|
|
|
void Cluster::set_random_cluster_bgcolors()
|
|
{
|
|
float hue = rand() / float(RAND_MAX);
|
|
float staturation = 0.3f;
|
|
float value = 0.8f;
|
|
this->attributes.set("bgcolor", color_attr_from_hsv(hue, staturation, value));
|
|
|
|
for (Cluster *cluster : children_) {
|
|
cluster->set_random_cluster_bgcolors();
|
|
}
|
|
}
|
|
|
|
bool Cluster::contains(Node &node) const
|
|
{
|
|
Cluster *current = node.parent_cluster();
|
|
while (current != nullptr) {
|
|
if (current == this) {
|
|
return true;
|
|
}
|
|
current = current->parent_;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/* Dot Generation
|
|
**********************************************/
|
|
|
|
std::string DirectedGraph::to_dot_string() const
|
|
{
|
|
std::stringstream ss;
|
|
ss << "digraph {\n";
|
|
this->export__declare_nodes_and_clusters(ss);
|
|
ss << "\n";
|
|
|
|
for (const std::unique_ptr<DirectedEdge> &edge : edges_) {
|
|
edge->export__as_edge_statement(ss);
|
|
ss << "\n";
|
|
}
|
|
|
|
ss << "}\n";
|
|
return ss.str();
|
|
}
|
|
|
|
std::string UndirectedGraph::to_dot_string() const
|
|
{
|
|
std::stringstream ss;
|
|
ss << "graph {\n";
|
|
this->export__declare_nodes_and_clusters(ss);
|
|
ss << "\n";
|
|
|
|
for (const std::unique_ptr<UndirectedEdge> &edge : edges_) {
|
|
edge->export__as_edge_statement(ss);
|
|
ss << "\n";
|
|
}
|
|
|
|
ss << "}\n";
|
|
return ss.str();
|
|
}
|
|
|
|
void Graph::export__declare_nodes_and_clusters(std::stringstream &ss) const
|
|
{
|
|
ss << "graph ";
|
|
attributes.export__as_bracket_list(ss);
|
|
ss << "\n\n";
|
|
|
|
for (Node *node : top_level_nodes_) {
|
|
node->export__as_declaration(ss);
|
|
}
|
|
|
|
for (Cluster *cluster : top_level_clusters_) {
|
|
cluster->export__declare_nodes_and_clusters(ss);
|
|
}
|
|
}
|
|
|
|
void Cluster::export__declare_nodes_and_clusters(std::stringstream &ss) const
|
|
{
|
|
ss << "subgraph " << this->name() << " {\n";
|
|
|
|
ss << "graph ";
|
|
attributes.export__as_bracket_list(ss);
|
|
ss << "\n\n";
|
|
|
|
for (Node *node : nodes_) {
|
|
node->export__as_declaration(ss);
|
|
}
|
|
|
|
for (Cluster *cluster : children_) {
|
|
cluster->export__declare_nodes_and_clusters(ss);
|
|
}
|
|
|
|
ss << "}\n";
|
|
}
|
|
|
|
void DirectedEdge::export__as_edge_statement(std::stringstream &ss) const
|
|
{
|
|
a_.to_dot_string(ss);
|
|
ss << " -> ";
|
|
b_.to_dot_string(ss);
|
|
ss << " ";
|
|
attributes.export__as_bracket_list(ss);
|
|
}
|
|
|
|
void UndirectedEdge::export__as_edge_statement(std::stringstream &ss) const
|
|
{
|
|
a_.to_dot_string(ss);
|
|
ss << " -- ";
|
|
b_.to_dot_string(ss);
|
|
ss << " ";
|
|
attributes.export__as_bracket_list(ss);
|
|
}
|
|
|
|
void Attributes::export__as_bracket_list(std::stringstream &ss) const
|
|
{
|
|
ss << "[";
|
|
attributes_.foreach_item([&](StringRef key, StringRef value) {
|
|
if (StringRef(value).startswith("<")) {
|
|
/* Don't draw the quotes, this is an HTML-like value. */
|
|
ss << key << "=" << value << ", ";
|
|
}
|
|
else {
|
|
ss << key << "=\"";
|
|
for (char c : value) {
|
|
if (c == '\"') {
|
|
/* Escape double quotes. */
|
|
ss << '\\';
|
|
}
|
|
ss << c;
|
|
}
|
|
ss << "\", ";
|
|
}
|
|
});
|
|
ss << "]";
|
|
}
|
|
|
|
void Node::export__as_id(std::stringstream &ss) const
|
|
{
|
|
ss << '"' << uintptr_t(this) << '"';
|
|
}
|
|
|
|
void Node::export__as_declaration(std::stringstream &ss) const
|
|
{
|
|
this->export__as_id(ss);
|
|
ss << " ";
|
|
attributes.export__as_bracket_list(ss);
|
|
ss << "\n";
|
|
}
|
|
|
|
void NodePort::to_dot_string(std::stringstream &ss) const
|
|
{
|
|
node_->export__as_id(ss);
|
|
if (port_name_.has_value()) {
|
|
ss << ":" << *port_name_;
|
|
}
|
|
if (port_position_.has_value()) {
|
|
ss << ":" << *port_position_;
|
|
}
|
|
}
|
|
|
|
std::string color_attr_from_hsv(float h, float s, float v)
|
|
{
|
|
std::stringstream ss;
|
|
ss << std::setprecision(4) << h << ' ' << s << ' ' << v;
|
|
return ss.str();
|
|
}
|
|
|
|
NodeWithSocketsRef::NodeWithSocketsRef(Node &node, const NodeWithSockets &data) : node_(&node)
|
|
{
|
|
std::stringstream ss;
|
|
|
|
ss << R"(<<table border="0" cellspacing="3">)";
|
|
|
|
/* Header */
|
|
ss << R"(<tr><td colspan="3" align="center"><b>)";
|
|
ss << (data.node_name.empty() ? "No Name" : data.node_name);
|
|
ss << "</b></td></tr>";
|
|
|
|
/* Sockets */
|
|
int socket_max_amount = std::max(data.inputs.size(), data.outputs.size());
|
|
for (int i = 0; i < socket_max_amount; i++) {
|
|
ss << "<tr>";
|
|
if (i < data.inputs.size()) {
|
|
const NodeWithSockets::Input &input = data.inputs[i];
|
|
ss << R"(<td align="left" port="in)" << i << "\">";
|
|
if (input.fontcolor) {
|
|
ss << R"(<font color=")" << *input.fontcolor << "\">";
|
|
}
|
|
ss << (input.name.empty() ? "No Name" : input.name);
|
|
if (input.fontcolor) {
|
|
ss << "</font>";
|
|
}
|
|
ss << "</td>";
|
|
}
|
|
else {
|
|
ss << "<td></td>";
|
|
}
|
|
ss << "<td></td>";
|
|
if (i < data.outputs.size()) {
|
|
const NodeWithSockets::Output &output = data.outputs[i];
|
|
ss << R"(<td align="right" port="out)" << i << "\">";
|
|
if (output.fontcolor) {
|
|
ss << R"(<font color=")" << *output.fontcolor << "\">";
|
|
}
|
|
ss << (output.name.empty() ? "No Name" : output.name);
|
|
if (output.fontcolor) {
|
|
ss << "</font>";
|
|
}
|
|
ss << "</td>";
|
|
}
|
|
else {
|
|
ss << "<td></td>";
|
|
}
|
|
ss << "</tr>";
|
|
}
|
|
|
|
ss << "</table>>";
|
|
|
|
node_->attributes.set("label", ss.str());
|
|
node_->set_shape(Attr_shape::Rectangle);
|
|
}
|
|
|
|
} // namespace blender::dot
|