tornavis/source/blender/functions/FN_lazy_function_graph.hh

540 lines
12 KiB
C++

/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup fn
*
* This file contains a graph data structure that allows composing multiple lazy-functions into a
* combined lazy-function.
*
* There are two types of nodes in the graph:
* - #FunctionNode: Corresponds to a #LazyFunction. The inputs and outputs of the function become
* input and output sockets of the node.
* - #InterfaceNode: Is used to indicate inputs and outputs of the entire graph. It can have an
* arbitrary number of sockets.
*/
#include "BLI_linear_allocator.hh"
#include "FN_lazy_function.hh"
namespace blender::dot {
class DirectedEdge;
}
namespace blender::fn::lazy_function {
class Socket;
class InputSocket;
class OutputSocket;
class Node;
class Graph;
/**
* A #Socket is the interface of a #Node. Every #Socket is either an #InputSocket or #OutputSocket.
* Links can be created from output sockets to input sockets.
*/
class Socket : NonCopyable, NonMovable {
protected:
/**
* The node the socket belongs to.
*/
Node *node_;
/**
* Data type of the socket. Only sockets with the same type can be linked.
*/
const CPPType *type_;
/**
* Indicates whether this is an #InputSocket or #OutputSocket.
*/
bool is_input_;
/**
* Index of the socket. E.g. 0 for the first input and the first output socket.
*/
int index_in_node_;
/**
* Index of the socket in the entire graph. Every socket has a different index.
*/
int index_in_graph_;
friend Graph;
public:
bool is_input() const;
bool is_output() const;
int index() const;
int index_in_graph() const;
InputSocket &as_input();
OutputSocket &as_output();
const InputSocket &as_input() const;
const OutputSocket &as_output() const;
const Node &node() const;
Node &node();
const CPPType &type() const;
std::string name() const;
std::string detailed_name() const;
};
class InputSocket : public Socket {
private:
/**
* An input can have at most one link connected to it. The linked socket is the "origin" because
* it's where the data is coming from. The type of the origin must be the same as the type of
* this socket.
*/
OutputSocket *origin_;
/**
* Can be null or a non-owning pointer to a value of the type of the socket. This value will be
* used when the input is used but not linked.
*
* This is technically not needed, because one could just create a separate node that just
* outputs the value, but that would have more overhead. Especially because it's commonly the
* case that most inputs are unlinked.
*/
const void *default_value_ = nullptr;
friend Graph;
public:
OutputSocket *origin();
const OutputSocket *origin() const;
const void *default_value() const;
void set_default_value(const void *value);
};
class OutputSocket : public Socket {
private:
/**
* An output can be linked to an arbitrary number of inputs of the same type.
*/
Vector<InputSocket *> targets_;
friend Graph;
public:
Span<InputSocket *> targets();
Span<const InputSocket *> targets() const;
};
/**
* A #Node has input and output sockets. Every node is either a #FunctionNode or an #InterfaceNode.
*/
class Node : NonCopyable, NonMovable {
protected:
/**
* The function this node corresponds to. If this is null, the node is an #InterfaceNode.
* The function is not owned by this #Node nor by the #Graph.
*/
const LazyFunction *fn_ = nullptr;
/**
* Input sockets of the node.
*/
Span<InputSocket *> inputs_;
/**
* Output sockets of the node.
*/
Span<OutputSocket *> outputs_;
/**
* An index that is set when calling #Graph::update_node_indices. This can be used to create
* efficient mappings from nodes to other data using just an array instead of a hash map.
*
* This is technically not necessary but has better performance than always using hash maps.
*/
int index_in_graph_ = -1;
friend Graph;
public:
bool is_interface() const;
bool is_function() const;
int index_in_graph() const;
Span<const InputSocket *> inputs() const;
Span<const OutputSocket *> outputs() const;
Span<InputSocket *> inputs();
Span<OutputSocket *> outputs();
const InputSocket &input(int index) const;
const OutputSocket &output(int index) const;
InputSocket &input(int index);
OutputSocket &output(int index);
std::string name() const;
};
/**
* A #Node that corresponds to a specific #LazyFunction.
*/
class FunctionNode final : public Node {
public:
const LazyFunction &function() const;
};
/**
* A #Node that does *not* correspond to a #LazyFunction. Instead it can be used to indicate inputs
* and outputs of the entire graph. It can have an arbitrary number of inputs and outputs.
*/
class InterfaceNode final : public Node {
private:
friend Node;
friend Socket;
friend Graph;
Vector<std::string> socket_names_;
};
/**
* Interface input sockets are actually output sockets on the input node. This renaming makes the
* code less confusing.
*/
using GraphInputSocket = OutputSocket;
using GraphOutputSocket = InputSocket;
/**
* A container for an arbitrary number of nodes and links between their sockets.
*/
class Graph : NonCopyable, NonMovable {
private:
/**
* Used to allocate nodes and sockets in the graph.
*/
LinearAllocator<> allocator_;
/**
* Contains all nodes in the graph so that it is efficient to iterate over them.
* The first two nodes are the interface input and output nodes.
*/
Vector<Node *> nodes_;
InterfaceNode *graph_input_node_ = nullptr;
InterfaceNode *graph_output_node_ = nullptr;
Vector<GraphInputSocket *> graph_inputs_;
Vector<GraphOutputSocket *> graph_outputs_;
/**
* Number of sockets in the graph. Can be used as array size when indexing using
* `Socket::index_in_graph`.
*/
int socket_num_ = 0;
public:
Graph();
~Graph();
/**
* Get all nodes in the graph. The index in the span corresponds to #Node::index_in_graph.
*/
Span<const Node *> nodes() const;
Span<Node *> nodes();
Span<const FunctionNode *> function_nodes() const;
Span<FunctionNode *> function_nodes();
Span<GraphInputSocket *> graph_inputs();
Span<GraphOutputSocket *> graph_outputs();
Span<const GraphInputSocket *> graph_inputs() const;
Span<const GraphOutputSocket *> graph_outputs() const;
/**
* Add a new function node with sockets that match the passed in #LazyFunction.
*/
FunctionNode &add_function(const LazyFunction &fn);
/**
* Add inputs and outputs to the graph.
*/
GraphInputSocket &add_input(const CPPType &type, std::string name = "");
GraphOutputSocket &add_output(const CPPType &type, std::string name = "");
/**
* Add a link between the two given sockets.
* This has undefined behavior when the input is linked to something else already.
*/
void add_link(OutputSocket &from, InputSocket &to);
/**
* If the socket is linked, remove the link.
*/
void clear_origin(InputSocket &socket);
/**
* Make sure that #Node::index_in_graph is up to date.
*/
void update_node_indices();
/**
* Make sure that #Socket::index_in_graph is up to date.
*/
void update_socket_indices();
/**
* Number of sockets in the graph.
*/
int socket_num() const;
/**
* Can be used to assert that #update_node_indices has been called.
*/
bool node_indices_are_valid() const;
/**
* Optional configuration options for the dot graph generation. This allows creating
* visualizations for specific purposes.
*/
class ToDotOptions {
public:
virtual std::string socket_name(const Socket &socket) const;
virtual std::optional<std::string> socket_font_color(const Socket &socket) const;
virtual void add_edge_attributes(const OutputSocket &from,
const InputSocket &to,
dot::DirectedEdge &dot_edge) const;
};
/**
* Utility to generate a dot graph string for the graph. This can be used for debugging.
*/
std::string to_dot(const ToDotOptions &options = {}) const;
};
/* -------------------------------------------------------------------- */
/** \name #Socket Inline Methods
* \{ */
inline bool Socket::is_input() const
{
return is_input_;
}
inline bool Socket::is_output() const
{
return !is_input_;
}
inline int Socket::index() const
{
return index_in_node_;
}
inline int Socket::index_in_graph() const
{
return index_in_graph_;
}
inline InputSocket &Socket::as_input()
{
BLI_assert(this->is_input());
return *static_cast<InputSocket *>(this);
}
inline OutputSocket &Socket::as_output()
{
BLI_assert(this->is_output());
return *static_cast<OutputSocket *>(this);
}
inline const InputSocket &Socket::as_input() const
{
BLI_assert(this->is_input());
return *static_cast<const InputSocket *>(this);
}
inline const OutputSocket &Socket::as_output() const
{
BLI_assert(this->is_output());
return *static_cast<const OutputSocket *>(this);
}
inline const Node &Socket::node() const
{
return *node_;
}
inline Node &Socket::node()
{
return *node_;
}
inline const CPPType &Socket::type() const
{
return *type_;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name #InputSocket Inline Methods
* \{ */
inline const OutputSocket *InputSocket::origin() const
{
return origin_;
}
inline OutputSocket *InputSocket::origin()
{
return origin_;
}
inline const void *InputSocket::default_value() const
{
return default_value_;
}
inline void InputSocket::set_default_value(const void *value)
{
default_value_ = value;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name #OutputSocket Inline Methods
* \{ */
inline Span<const InputSocket *> OutputSocket::targets() const
{
return targets_;
}
inline Span<InputSocket *> OutputSocket::targets()
{
return targets_;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name #Node Inline Methods
* \{ */
inline bool Node::is_interface() const
{
return fn_ == nullptr;
}
inline bool Node::is_function() const
{
return fn_ != nullptr;
}
inline int Node::index_in_graph() const
{
return index_in_graph_;
}
inline Span<const InputSocket *> Node::inputs() const
{
return inputs_;
}
inline Span<const OutputSocket *> Node::outputs() const
{
return outputs_;
}
inline Span<InputSocket *> Node::inputs()
{
return inputs_;
}
inline Span<OutputSocket *> Node::outputs()
{
return outputs_;
}
inline const InputSocket &Node::input(const int index) const
{
return *inputs_[index];
}
inline const OutputSocket &Node::output(const int index) const
{
return *outputs_[index];
}
inline InputSocket &Node::input(const int index)
{
return *inputs_[index];
}
inline OutputSocket &Node::output(const int index)
{
return *outputs_[index];
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name #FunctionNode Inline Methods
* \{ */
inline const LazyFunction &FunctionNode::function() const
{
BLI_assert(fn_ != nullptr);
return *fn_;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name #Graph Inline Methods
* \{ */
inline Span<const Node *> Graph::nodes() const
{
return nodes_;
}
inline Span<Node *> Graph::nodes()
{
return nodes_;
}
inline Span<const FunctionNode *> Graph::function_nodes() const
{
return nodes_.as_span().drop_front(2).cast<const FunctionNode *>();
}
inline Span<FunctionNode *> Graph::function_nodes()
{
return nodes_.as_span().drop_front(2).cast<FunctionNode *>();
}
inline Span<GraphInputSocket *> Graph::graph_inputs()
{
return graph_inputs_;
}
inline Span<GraphOutputSocket *> Graph::graph_outputs()
{
return graph_outputs_;
}
inline Span<const GraphInputSocket *> Graph::graph_inputs() const
{
return graph_inputs_;
}
inline Span<const GraphOutputSocket *> Graph::graph_outputs() const
{
return graph_outputs_;
}
inline int Graph::socket_num() const
{
return socket_num_;
}
/** \} */
} // namespace blender::fn::lazy_function