/* 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. * - #DummyNode: 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; }; 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 targets_; friend Graph; public: Span targets(); Span targets() const; }; /** * A #Node has input and output sockets. Every node is either a #FunctionNode or a #DummyNode. */ class Node : NonCopyable, NonMovable { protected: /** * The function this node corresponds to. If this is null, the node is a #DummyNode. * The function is not owned by this #Node nor by the #Graph. */ const LazyFunction *fn_ = nullptr; /** * Input sockets of the node. */ Span inputs_; /** * Output sockets of the node. */ Span 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_dummy() const; bool is_function() const; int index_in_graph() const; Span inputs() const; Span outputs() const; Span inputs(); Span 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 : public Node { public: const LazyFunction &function() const; }; class DummyDebugInfo { public: virtual ~DummyDebugInfo() = default; virtual std::string node_name() const; virtual std::string input_name(const int i) const; virtual std::string output_name(const int i) const; }; /** * Just stores a string per socket in a dummy node. */ class SimpleDummyDebugInfo : public DummyDebugInfo { public: std::string name; Vector input_names; Vector output_names; std::string node_name() const override; std::string input_name(const int i) const override; std::string output_name(const int i) const override; }; /** * 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 DummyNode : public Node { private: const DummyDebugInfo *debug_info_ = nullptr; friend Node; friend Socket; friend Graph; }; /** * 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. */ Vector nodes_; /** * 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(); /** * Get all nodes in the graph. The index in the span corresponds to #Node::index_in_graph. */ Span nodes() const; Span nodes(); /** * Add a new function node with sockets that match the passed in #LazyFunction. */ FunctionNode &add_function(const LazyFunction &fn); /** * Add a new dummy node with the given socket types. */ DummyNode &add_dummy(Span input_types, Span output_types, const DummyDebugInfo *debug_info = nullptr); /** * 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 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(this); } inline OutputSocket &Socket::as_output() { BLI_assert(this->is_output()); return *static_cast(this); } inline const InputSocket &Socket::as_input() const { BLI_assert(this->is_input()); return *static_cast(this); } inline const OutputSocket &Socket::as_output() const { BLI_assert(this->is_output()); return *static_cast(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 OutputSocket::targets() const { return targets_; } inline Span OutputSocket::targets() { return targets_; } /** \} */ /* -------------------------------------------------------------------- */ /** \name #Node Inline Methods * \{ */ inline bool Node::is_dummy() 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 Node::inputs() const { return inputs_; } inline Span Node::outputs() const { return outputs_; } inline Span Node::inputs() { return inputs_; } inline Span 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 Graph::nodes() const { return nodes_; } inline Span Graph::nodes() { return nodes_; } inline int Graph::socket_num() const { return socket_num_; } /** \} */ } // namespace blender::fn::lazy_function