This repository has been archived on 2023-10-09. You can view files and clone it, but cannot push or open issues or pull requests.
Files
blender-archive/source/blender/functions/FN_lazy_function_graph.hh
Jacques Lucke 2ffd08e952 Geometry Nodes: deterministic anonymous attribute lifetimes
Previously, the lifetimes of anonymous attributes were determined by
reference counts which were non-deterministic when multiple threads
are used. Now the lifetimes of anonymous attributes are handled
more explicitly and deterministically. This is a prerequisite for any kind
of caching, because caching the output of nodes that do things
non-deterministically and have "invisible inputs" (reference counts)
doesn't really work.

For more details for how deterministic lifetimes are achieved, see D16858.

No functional changes are expected. Small performance changes are expected
as well (within few percent, anything larger regressions should be reported as
bugs).

Differential Revision: https://developer.blender.org/D16858
2023-01-05 14:05:30 +01:00

505 lines
11 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::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<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;
};
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<std::string> input_names;
Vector<std::string> 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<Node *> 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<const Node *> nodes() const;
Span<Node *> 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<const CPPType *> input_types,
Span<const CPPType *> 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<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_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_;
}
inline Span<Node *> Graph::nodes()
{
return nodes_;
}
inline int Graph::socket_num() const
{
return socket_num_;
}
/** \} */
} // namespace blender::fn::lazy_function