↓ Skip to main content

Graph system of TinkerFlow

·1266 words·6 mins
Author
Sebastian Pötter
Software Engineer, Researcher and Tinkerer.
Author
Aron Schaub
Software Engineer, Researcher and Open Source Enthusiast.
Table of Contents

HEY!

We are currently building the graph editor for the process view in TinkerFlow.

As you may have read in our Process Inspector, we discussed how UI drawers were developed for different steps, behaviors, and conditions. There we went into technical detail and explained how the source code itself works. Another topic was the component system described in our Component System inside TinkerFlow, which bridges scenes and TinkerFlow.

graph-view.webp
The graph editor inside godot to create the process.

Now we are focusing on visually representing and drawing a process through the Godot graph system and showing how the parts mentioned before are connected into this system. For that we built a chapter overview with a graph system with an additional UI. The main question is to think about how to represent this visually first and how to keep the overall process readable second (the second part is the hard one…).

graph-view-with-inspector.webp
Drawer with open node graph and a selected node

Processes and chapters
#

A process is split into chapters, each chapter can hold maaaaaaany steps. Chapters can also hold other processes or nested chapters. Take a look at our older post about the process structure if you are curious about the process itself.

However, there are two different cases here. A Step Group is a simple collection of steps inside a subprocess. It keeps related steps together.

Into the rabbit hole of subprocesses!

Parallel execution is the other usage for additional subprocesses with independent paths that run side by side. When a parallel chapter runs, the parent still waits for all required paths to finish. Optional paths can be aborted until all other required paths are finished. A common use is highlighting an object while waiting for user input, or playing a delayed sound while the main process part waits.

PlantUML diagram

Developers can see this in ExecuteChaptersBehavior.Update. It waits until every non-optional subchapter reaches Active, then it aborts the optional ones that are still in Activating.

// ExecuteChaptersBehavior: wait for required paths, then abort optional ones
while (Data.SubChapters.Any(sc => sc.IsOptional == false && sc.Chapter.LifeCycle.Stage != Stage.Active))
{
    foreach (SubChapter sc in Data.SubChapters.Where(sc => sc.Chapter.LifeCycle.Stage == Stage.Activating))
    {
        sc.Chapter.Update(); // keep required paths ticking
    }
    yield return null;
}
foreach (SubChapter subChapter in Data.SubChapters.Where(sc => sc.IsOptional && sc.Chapter.LifeCycle.Stage == Stage.Activating))
{
    subChapter.Chapter.LifeCycle.Abort();
}

Nodes in the graph view
#

Right now this builds on native Godot elements with custom code. We reuse Godot components where possible. The editor window combines ProcessEditor with ChaptersView, Breadcrumb, and StepWindow. The ChaptersView shows which level you are on, the main chapter or a nested one.

graph-view-with-subprocess.webp
Process view with an open subprocess

Every chapter has a start and an end node, plus as normal steps as you need. A normal step starts with one input and output. It grows to N rows for N transitions, so a step with three transitions shows three rows with three output ports.

The End Chapter node can jump to other chapters. Its dropdown shows the label “Next chapter selection is not implemented yet”, so picking a target chapter stays an idea for now. The start step is the entry point of the chapter or process, with a pointer going outward.

graph-view-nodes.webp
Chapter start node with one connected step

All of this happens at editor time. The full graph view at runtime is technically possible (and this is interesting for a lot of use-cases like debugging and analytics!).

TinkerFlow nodes and Godot connection
#

On the Godot side GraphEdit, GraphNode are the main class for the graph system. In TinkerFlow those map to concrete classes. ProcessGraph is the GraphEdit that handles connections, layout, and dirty tracking. GraphNodes plus StepNodeRow implement the visible elements like start and end nodes, group nodes, and parallel nodes. ProcessEditor with ChaptersView, Breadcrumb, and StepWindow render the full window so you see a clean editor.

When you drag a node, ProcessGraph writes the new position back to metadata and marks the graph modified inside the metadata.

// ProcessGraph: keep node position in metadata
node.EntryPoint = step;
if (step.StepMetadata.Position != null)
    node.PositionOffset = step.StepMetadata.Position.ToGodot();
node.PositionOffsetChanged += () =>
{
    // save every drag so reopening restores layout
    step.StepMetadata.Position = node.PositionOffset.ToVector2Data();
    OnModified();
};

All graph nodes inherit from a shared ProcessGraphNode, which itself extends Godot GraphNode. StepGraphNode adds step data, rows, and ports on top of it. StepGroupNode adds the Expand button for drilling into a nested chapter.

EntryPointNode is a direct child of ProcessGraphNode. It does not go through StepGraphNode, because it has no step data or transitions.

PlantUML diagram

From click to inspector
#

The Process Inspector is part of the editor element. Clicking a node selects the step in ProcessGraph. ProcessEditor forwards it to StepWindow. StepWindow asks DrawerLocator for the matching factory, as described in the inspector post.

The graph itself refreshes through a Dirty flag, so only changed nodes redraw. We want to reduce the reload times as good as possible (like redrawing of other UI elements we had problems with)

PlantUML diagram

Future ideas for the graph view
#

We have tons of ideas for the whole graph view and system here. Nothing is scheduled, and order will depend on feedback.

Stronger visual feedback or debug functionality while the app runs is a feature we’re thinking about. The active path could use color coding, so you see at once which step is active and where the process heads next. A green border could mark the active step.

graph-view-ideas-validation.webp
Validation highlighting if something critical is missing

A missing behavior could show a warning triangle or a red mark, so validation errors show in the graph instead of only in logs. Or using icons for the different types of behaviors and nodes is also an option.

graph-view-ideas.webp
Colored outputs with highlighting the current node with additional running time

The most likely path as a color of the step connection is interesting for analytics reasons or later data mining. Based on pre-existing data or live evaluation while the application is running.

A cleaner layout might be nice as well. Steps could snap to a self-aligned grid. Edges could use angular lines instead of curves. This placement could try to reduce crossings. Godot already gives us good building blocks for this, so we would add it case by case.

Additional graph elements like swimlanes inspired by BPMN or creating notes are also an interesting idea for us. Swimlanes could group steps by role or for a better overview or help in multiplayer setups for the role mapping. We don’t want to copy the full standard, but visual helpers like these:

bpmn-swimmlanes.webp
Swimlanes, source: https://docs.eraser.io/bpmn
Makes it easier to understand the process better.

Typing in comments directly on the graph might help to understand some more complex process parts or for remembering important information around the process:

graph-view-ideas-notes.webp
First concept of notes inside the process

Navigation across levels already has a working core through Expand and the breadcrumb. The remaining work is polish, like faster jumps between nested layers and a calmer presentation of depth.

All these kinds of additional data would be saved as metadata inside the process data (for now).

BUT, currently, we focus on getting the core to work well. Once that is solid, extensions like the ones above can follow. At this point we are very satisfied with what Godot graphs give us out of the box.


We hope you enjoyed this blog post, see you next time!