Nodes: add forward compatibility for dynamic node declarations

The goal of this patch is to allow us to use dynamic node declarations
for various nodes in a future version without breaking forward compatibility.
The same kind of change should be added to Blender 3.6.

The code added here is not supposed to end up in `main`, so it has to be
removed when merging the release branch.

Pull Request: https://projects.blender.org/blender/blender/pulls/113497
This commit is contained in:
Jacques Lucke
2023-10-13 14:39:51 +02:00
parent a9bf212de0
commit 0ea193bdb3
2 changed files with 218 additions and 4 deletions

View File

@@ -296,6 +296,9 @@ class bNodeRuntime : NonCopyable, NonMovable {
/** Eagerly maintained cache of the node's index in the tree. */
int index_in_tree = -1;
/** Used to avoid running forward compatibility code more often than necessary. */
bool forward_compatible_versioning_done = false;
/** Only valid if #topology_cache_is_dirty is false. */
Vector<bNodeSocket *> inputs;
Vector<bNodeSocket *> outputs;

View File

@@ -35,6 +35,7 @@
#include "NOD_node_declaration.hh"
#include "NOD_socket.hh"
#include "NOD_socket_declarations.hh"
#include "FN_field.hh"
@@ -275,14 +276,224 @@ static void refresh_node_panel(const PanelDeclaration &panel_decl,
}
}
/**
* Not great to have this here, but this is only for forward compatibility, so this code shouldn't
* in the `main` branch.
*/
static std::optional<eNodeSocketDatatype> decl_to_data_type(const SocketDeclaration &socket_decl)
{
if (dynamic_cast<const decl::Float *>(&socket_decl)) {
return SOCK_FLOAT;
}
else if (dynamic_cast<const decl::Int *>(&socket_decl)) {
return SOCK_INT;
}
else if (dynamic_cast<const decl::Bool *>(&socket_decl)) {
return SOCK_BOOLEAN;
}
else if (dynamic_cast<const decl::Vector *>(&socket_decl)) {
return SOCK_VECTOR;
}
else if (dynamic_cast<const decl::Color *>(&socket_decl)) {
return SOCK_RGBA;
}
else if (dynamic_cast<const decl::Rotation *>(&socket_decl)) {
return SOCK_ROTATION;
}
else if (dynamic_cast<const decl::String *>(&socket_decl)) {
return SOCK_STRING;
}
else if (dynamic_cast<const decl::Image *>(&socket_decl)) {
return SOCK_IMAGE;
}
else if (dynamic_cast<const decl::Texture *>(&socket_decl)) {
return SOCK_TEXTURE;
}
else if (dynamic_cast<const decl::Material *>(&socket_decl)) {
return SOCK_MATERIAL;
}
else if (dynamic_cast<const decl::Shader *>(&socket_decl)) {
return SOCK_SHADER;
}
else if (dynamic_cast<const decl::Collection *>(&socket_decl)) {
return SOCK_COLLECTION;
}
else if (dynamic_cast<const decl::Object *>(&socket_decl)) {
return SOCK_OBJECT;
}
return std::nullopt;
}
static const char *get_identifier_from_decl(const char *identifier_prefix,
const bNodeSocket &socket,
const Span<const SocketDeclaration *> socket_decls)
{
if (!BLI_str_startswith(socket.identifier, identifier_prefix)) {
return nullptr;
}
for (const SocketDeclaration *socket_decl : socket_decls) {
if (BLI_str_startswith(socket_decl->identifier.c_str(), identifier_prefix)) {
if (socket.type == decl_to_data_type(*socket_decl)) {
return socket_decl->identifier.c_str();
}
}
}
return nullptr;
}
static const char *get_identifier_from_decl(const Span<const char *> identifier_prefixes,
const bNodeSocket &socket,
const Span<const SocketDeclaration *> socket_decls)
{
for (const char *identifier_prefix : identifier_prefixes) {
if (const char *identifier = get_identifier_from_decl(identifier_prefix, socket, socket_decls))
{
return identifier;
}
}
return nullptr;
}
/**
* Currently, nodes that support different socket types have sockets for all supported types with
* different identifiers (e.g. `Attribute`, `Attribute_001`, `Attribute_002`, ...). In the future,
* we will hopefully have a better way to handle this that does not require all the sockets of
* different types to exist at the same time. Instead we want that there is only a single socket
* that can change its type while the identifier stays the same.
*
* This function prepares us for that future. It returns the identifier that we use for a socket
* now based on the "base socket name" (e.g. `Attribute`) and its socket type. It allows us to
* change the socket identifiers in the future without breaking forward compatibility for the nodes
* handled here.
*/
static const char *get_current_socket_identifier_for_future_socket(
const bNode &node,
const bNodeSocket &socket,
const Span<const SocketDeclaration *> socket_decls)
{
switch (node.type) {
case GEO_NODE_SWITCH: {
const NodeSwitch &storage = *static_cast<const NodeSwitch *>(node.storage);
const bool use_field_socket = ELEM(storage.input_type,
SOCK_FLOAT,
SOCK_INT,
SOCK_BOOLEAN,
SOCK_VECTOR,
SOCK_RGBA,
SOCK_STRING,
SOCK_ROTATION);
if (BLI_str_startswith(socket.identifier, "Switch")) {
if (use_field_socket) {
return "Switch";
}
return "Switch_001";
}
return get_identifier_from_decl({"False", "True", "Output"}, socket, socket_decls);
}
case GEO_NODE_ACCUMULATE_FIELD: {
return get_identifier_from_decl(
{"Value", "Leading", "Trailing", "Total"}, socket, socket_decls);
}
case GEO_NODE_CAPTURE_ATTRIBUTE: {
return get_identifier_from_decl({"Value", "Attribute"}, socket, socket_decls);
}
case GEO_NODE_ATTRIBUTE_STATISTIC: {
return get_identifier_from_decl({"Attribute",
"Mean",
"Median",
"Sum",
"Min",
"Max",
"Range",
"Standard Deviation",
"Variance"},
socket,
socket_decls);
}
case GEO_NODE_BLUR_ATTRIBUTE: {
return get_identifier_from_decl({"Value"}, socket, socket_decls);
}
case GEO_NODE_SAMPLE_CURVE: {
return get_identifier_from_decl({"Value"}, socket, socket_decls);
}
case GEO_NODE_EVALUATE_AT_INDEX: {
return get_identifier_from_decl({"Value"}, socket, socket_decls);
}
case GEO_NODE_EVALUATE_ON_DOMAIN: {
return get_identifier_from_decl({"Value"}, socket, socket_decls);
}
case GEO_NODE_INPUT_NAMED_ATTRIBUTE: {
return get_identifier_from_decl({"Attribute"}, socket, socket_decls);
}
case GEO_NODE_RAYCAST: {
return get_identifier_from_decl({"Attribute"}, socket, socket_decls);
}
case GEO_NODE_SAMPLE_INDEX: {
return get_identifier_from_decl({"Value"}, socket, socket_decls);
}
case GEO_NODE_SAMPLE_NEAREST_SURFACE: {
return get_identifier_from_decl({"Value"}, socket, socket_decls);
}
case FN_NODE_RANDOM_VALUE: {
return get_identifier_from_decl({"Min", "Max", "Value"}, socket, socket_decls);
}
case GEO_NODE_SAMPLE_UV_SURFACE: {
return get_identifier_from_decl({"Value"}, socket, socket_decls);
}
case GEO_NODE_STORE_NAMED_ATTRIBUTE: {
return get_identifier_from_decl({"Value"}, socket, socket_decls);
}
case GEO_NODE_VIEWER: {
return get_identifier_from_decl({"Value"}, socket, socket_decls);
}
case SH_NODE_MIX: {
return get_identifier_from_decl({"A", "B", "Result"}, socket, socket_decls);
}
case FN_NODE_COMPARE: {
return get_identifier_from_decl({"A", "B"}, socket, socket_decls);
}
}
return nullptr;
}
/**
* Try to update identifiers of sockets created in the future to match identifiers that exist now.
*/
static void do_forward_compat_versioning(bNode &node, const NodeDeclaration &node_decl)
{
LISTBASE_FOREACH (bNodeSocket *, socket, &node.inputs) {
if (socket->is_available()) {
if (const char *new_identifier = get_current_socket_identifier_for_future_socket(
node, *socket, node_decl.inputs))
{
STRNCPY(socket->identifier, new_identifier);
}
}
}
LISTBASE_FOREACH (bNodeSocket *, socket, &node.outputs) {
if (socket->is_available()) {
if (const char *new_identifier = get_current_socket_identifier_for_future_socket(
node, *socket, node_decl.outputs))
{
STRNCPY(socket->identifier, new_identifier);
}
}
}
}
static void refresh_node_sockets_and_panels(bNodeTree &ntree,
bNode &node,
Span<ItemDeclarationPtr> item_decls,
const NodeDeclaration &node_decl,
const bool do_id_user)
{
if (!node.runtime->forward_compatible_versioning_done) {
do_forward_compat_versioning(node, node_decl);
node.runtime->forward_compatible_versioning_done = true;
}
/* Count panels */
int new_num_panels = 0;
for (const ItemDeclarationPtr &item_decl : item_decls) {
for (const ItemDeclarationPtr &item_decl : node_decl.items) {
if (dynamic_cast<const PanelDeclaration *>(item_decl.get())) {
++new_num_panels;
}
@@ -309,7 +520,7 @@ static void refresh_node_sockets_and_panels(bNodeTree &ntree,
VectorSet<bNodeSocket *> new_inputs;
VectorSet<bNodeSocket *> new_outputs;
bNodePanelState *new_panel = node.panel_states_array;
for (const ItemDeclarationPtr &item_decl : item_decls) {
for (const ItemDeclarationPtr &item_decl : node_decl.items) {
if (const SocketDeclaration *socket_decl = dynamic_cast<const SocketDeclaration *>(
item_decl.get()))
{
@@ -360,7 +571,7 @@ static void refresh_node(bNodeTree &ntree,
return;
}
if (!node_decl.matches(node)) {
refresh_node_sockets_and_panels(ntree, node, node_decl.items, do_id_user);
refresh_node_sockets_and_panels(ntree, node, node_decl, do_id_user);
}
blender::bke::nodeSocketDeclarationsUpdate(&node);
}