422 lines
9.0 KiB
C++
422 lines
9.0 KiB
C++
|
/* 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<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 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<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_dummy() 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 : 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<Node *> nodes_;
|
||
|
|
||
|
public:
|
||
|
~Graph();
|
||
|
|
||
|
/**
|
||
|
* Get all nodes in the graph. The index in the span corresponds to #Node::index_in_graph.
|
||
|
*/
|
||
|
Span<const Node *> 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<const CPPType *> input_types, Span<const CPPType *> 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<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_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<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_;
|
||
|
}
|
||
|
|
||
|
/** \} */
|
||
|
|
||
|
} // namespace blender::fn::lazy_function
|