Extending the Entity Graph

This walkthrough shows you how to extend the Entity Graph and use the generate-graph-nodes.exe. You will learn about:

  • How to develop the Entity Graph with your nodes.
  • When to run generate-graph-nodes.exe

How to extend the Entity Graph

You can extend the visual scripting language with your nodes. All you need to do is write the code that implements the node's action, together with some macros that specify how to create a visual scripting node from that code. Then you run the generate-graph-nodes.exe executable to generate an *.inl file with glue code.

  1. Create the file

Our goal is to create a node that computes the square of a floating-point number. We can either add to an existing plugin a new file or create a new plugin.

Important is it to make sure that the filename contains the graph_nodes string. Otherwise, the node generator will ignore the file.

For example: my_nodes.c will be ignored by the tool, while my_graph_nodes.c wont be ignored.

  1. Write the code
GGN_BEGIN("Sample/Math/Float");
GGN_GEN_REGISTER_FUNCTION();
GGN_NODE_QUERY();
static inline void sample_float_square(float a, float *res)
{
    *res = a * a;
}
GGN_END();

Let us digest the code example above. There are some things to note here:

  • Node functions always return their results in pointer parameters(the reason is that they can have more than one result). Pointer parameters are seen as out parameters if they are mutable by the node-generator.
  • The function parameter names are also used for the naming of the input/output wires.
  • Two special macros surround all the code for the node(s): GGN_BEGIN() and GGN_END().
  • The Engine will use the function name sample_float_square later: Sample Float Square, and you will find it in the defined category: Sample/Math/Float
  • The GGN_NODE_QUERY() macro marks this node as a Query node. Query nodes are triggered automatically when their output is requested. Nodes that don't just purely modify data need to be triggered by an explicit event. Such as Tick or Init.
  • The GGN_GEN_REGISTER_FUNCTION() macro automatically creates a register function for registering the node with the visual scripting system. Otherwise, you need to write this function yourself.
  • The #include "example_graph_nodes.inl" will include the autogenerated graph node implementations. It needs to be somewhere in the file. (before the tm_load_plugin function)
  • The generate-graph-nodes.exe auto-generates the my_graph_nodes.inl for you. It would be best if you do not edit this file. Otherwise, the generator will overwrite your changes next time.

For a full documentation of all GGN_* macros see plugins/graph_interpreter/graph_node_macros.h. The code we write is relatively slight. We need to ensure that we have some header files, including the graph_node_macros.h, which we are using to tell the generator to generate code for us.

The following list makes sure that the nodes work on their own.

#include <plugins/editor_views/graph.h>
#include <plugins/graph_interpreter/graph_node_helpers.inl>
#include <plugins/graph_interpreter/graph_node_macros.h>

If we now think 'yeah, we can compile', we are wrong; We need some other header files to ensure that the generated magic in the my_graph_nodes.inl file works.

We need to include the following files as well:

static struct tm_graph_interpreter_api *tm_graph_interpreter_api;
#include <foundation/api_registry.h> //Is needed for `GGN_GEN_REGISTER_FUNCTION()`
#include <foundation/localizer.h> // it automatically localizes your category name
#include <foundation/macros.h>
#include <foundation/the_truth_types.h> // is needed for the description of the wire input types

The next question is, Are we done now? The answer is yes nearly. What's left is registering our nodes in the plugin load function. It may look than like this:

#include "example_graph_nodes.inl"

TM_DLL_EXPORT void tm_load_plugin(struct tm_api_registry_api *reg, bool load)
{
    tm_graph_interpreter_api = tm_get_api(reg, tm_graph_interpreter_api);
    // This is auto generated from the graph node generator, you just need to call it.
    generated__register_example_graph_nodes(reg, load);
}

Here we just call the generated__register_example_graph_nodes function, defined in my_graph_nodes.inl and auto-generated. You can find the complete example here samples/plugins/graph_nodes.

  1. Run the node generator & tmbuild

The last step before we can compile is to make sure that we run the generate-graph-nodes.exe application. This helper utility processes all GGN_* macros. This program generates glue code that ties your function into the graph system -- creating connectors that correspond to your function parameters, etc. The glue code is stored in an .inl file with the same name as the .c file that contains the graph nodes. Do not forget that if you have used some new TM_STATIC_HASH values, run hash.exe to make sure that those get hashed. Then you are ready to run tmbuild. It will compile your plugin, and then when you start the Engine or have it run in hot-reload mode it will show you your new nodes in the editor.

Note: You can also run tmbuild with a argument: tmbuild --gen-nodes . This will make sure that tmbuild runs generate-graph-nodes.exe before it builds.

Created nodes in the entity graph view

Full sample code:

static struct tm_graph_interpreter_api *tm_graph_interpreter_api;
#include <foundation/api_registry.h> //Is needed for `GGN_GEN_REGISTER_FUNCTION()`
#include <foundation/localizer.h> // it automatically localizes your category name
#include <foundation/macros.h>
#include <foundation/the_truth_types.h> // is needed for the description of the wire input types
#include <plugins/editor_views/graph.h>
#include <plugins/graph_interpreter/graph_node_helpers.inl>
#include <plugins/graph_interpreter/graph_node_macros.h>
GGN_BEGIN("Sample/Math/Float");
GGN_GEN_REGISTER_FUNCTION();
GGN_NODE_QUERY();
static inline void sample_float_square(float a, float *res)
{
    *res = a * a;
}
GGN_END();
#include "example_graph_nodes.inl"

TM_DLL_EXPORT void tm_load_plugin(struct tm_api_registry_api *reg, bool load)
{
    tm_graph_interpreter_api = tm_get_api(reg, tm_graph_interpreter_api);
    // This is auto generated from the graph node generator, you just need to call it.
    generated__register_example_graph_nodes(reg, load);
}

Contributors

Simon Renger Simon Renger