/* 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::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_; friend Graph; public: bool is_input() const; bool is_output() const; int index() 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; }; /** * 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: std::string name_; friend Node; }; /** * 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_; public: ~Graph(); /** * Get all nodes in the graph. The index in the span corresponds to #Node::index_in_graph. */ Span nodes() const; /** * 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); /** * 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); /** * Make sure that #Node::index_in_graph is up to date. */ void update_node_indices(); /** * Can be used to assert that #update_node_indices has been called. */ bool node_indices_are_valid() const; /** * Utility to generate a dot graph string for the graph. This can be used for debugging. */ std::string to_dot() 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 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_; } /** \} */ } // namespace blender::fn::lazy_function