Dynamic node declaration

I explored the idea of storing the runtime data as a field in the DNA of the node. So in DNA_node_types.h, I add:

typedef struct NodeGeometryPizza {
  // [...]
  NodeGeometryPizzaRuntimeHandle *runtime;
} NodeGeometryPizza;

In order to define NodeGeometryPizzaRuntimeHandle, I use the same workaround than for NodeDeclarationHandle, at the beginning of the file (but I guess I could have simply set the type to void*):

/** Same workaround than for NodeDeclarationHandle. */
#ifdef __cplusplus
namespace blender::nodes::node_geo_pizza_cc{
class RuntimeData;
}  // namespace blender::nodes::node_geo_pizza_cc
using NodeGeometryOpenMfxRuntimeHandle = blender::nodes::node_geo_pizza_cc::RuntimeData;
#else
typedef struct NodeGeometryOpenMfxRuntimeHandle NodeGeometryOpenMfxRuntimeHandle;
#endif

Then, in my node_geo_pizza.cc file I can define the runtime data:

namespace blender::nodes::node_geo_pizza_cc {
class RuntimeData { /* [...] */ };
}

And regarding memory allocation, this requires me to change 3 callbacks, namely (i) init, (ii) free and (iii) copy:

static void node_init(bNodeTree *UNUSED(tree), bNode *node)
{
  NodeGeometryPizza *data = MEM_cnew<NodeGeometryPizza>(__func__);
  data->olive_count = 5;
  data->runtime = MEM_new<RuntimeData>(__func__); // Alloc runtime data
  node->storage = data;
}

static void node_free_storage(struct bNode *node)
{
  NodeGeometryPizza &storage = node_storage(*node);
  // If there is some runtime data, delete it
  if (storage.runtime != nullptr) {
    MEM_delete<RuntimeData>(storage.runtime);
    storage.runtime = nullptr;
  }
  // Then call the regular free callback
  node_free_standard_storage(node);
}

static void node_copy_storage(struct bNodeTree *dest_ntree,
                       struct bNode *dest_node,
                       const struct bNode *src_node)
{
  // First copy the storage data using the standard callback
  node_copy_standard_storage(dest_ntree, dest_node, src_node);

  // Then duplicate runtime memory and call RuntimeData::operator=()
  NodeGeometryOpenMfx &dest_storage = node_storage(*dest_node);
  const NodeGeometryOpenMfx &src_storage = node_storage(*src_node);
  dest_storage.runtime = MEM_new<RuntimeData>(__func__);
  *dest_storage.runtime = *src_storage.runtime;
}

and in register_node_type_geo_pizza():

node_type_storage(&ntype,
                  "NodeGeometryPizza",
                  file_ns::node_free_storage,
                  file_ns::node_copy_storage);

PS The pizza-related naming assumes that this tutorial was used as a base.