How to Debug Flow Graphs with Breakpoints and Step-by-Step Execution in Ceres

Use the editor's Debug toolbar for visual breakpoint debugging, or implement FlowGraphTracker for programmatic control over node execution, logging, and profiling.

Ceres provides a robust visual scripting system called Flow graphs that requires sophisticated debugging capabilities. Whether you prefer interactive debugging in the Unity Editor or automated analysis at runtime, you can debug Flow graphs with breakpoints and step-by-step execution using two complementary approaches that share the same underlying execution engine.

Editor-Based Debugging

The Flow editor provides a visual debugging interface accessible directly from the toolbar. This method requires no code and allows you to inspect node execution in real-time.

Enabling Debug Mode

To begin debugging, click the Debug button in the upper-right toolbar of the Flow editor. This activates the debugging session and pauses execution after each node evaluation, allowing you to control the flow manually.

Stepping Through Execution

Once Debug Mode is active, use the Next Frame (►) button to execute the current node and pause before the next one. This step-by-step execution lets you observe how data transforms through each node in the graph.

Setting Breakpoints

For targeted debugging, right-click any node and select Add Breakpoint. After setting breakpoints, use the Next Breakpoint (⏭) button on the toolbar to run the graph until it hits the first breakpoint, then continue stepping from that point.

Inspecting Port Values

While the graph is paused at a breakpoint, hover over any input port to view a tooltip displaying the current value. This allows you to verify data integrity at specific execution points without modifying the graph.

Hot-Reload Support

Enable Hot Reload in the toolbar to edit FlowGraphObject assets while the game is playing. Changes apply instantly without restarting the scene, enabling rapid iteration during debugging sessions.

The complete editor debugging guide is available in the repository documentation at Documentation~/docs/flow_debugging.md.

Runtime Debugging with FlowGraphTracker

When you need programmatic control, automated logging, or conditional breakpoints, implement the FlowGraphTracker API. This approach is defined in Runtime/Flow/Models/FlowGraphTracker.cs and provides hooks into every node execution.

Basic Tracker Pattern

The FlowGraphTracker uses an auto-scope pattern to ensure proper cleanup. Wrap your graph execution in a using statement with tracker.Auto() to automatically handle tracker lifecycle:

using Ceres.Graph.Flow;
using Cysharp.Threading.Tasks;

public async UniTask RunWithTracker(FlowGraph graph)
{
    // Create a concrete tracker (e.g., the built-in dependency logger)
    var tracker = new FlowGraphDependencyTracker(graph);

    // Auto-scope guarantees Dispose() runs even on exceptions
    using (tracker.Auto())
    {
        // Trigger the event you want to observe
        await graph.ExecuteEventAsync(context: null, eventName: "Start", evt: null);
    }   // <- tracker.Dispose() called here; prints log summary
}

The FlowGraphDependencyTracker logs each node entry/exit and identifies missing dependencies, helping you trace execution flow programmatically.

Custom Breakpoint Tracker

You can halt Unity's editor by implementing a custom tracker that calls Debug.Break() when specific conditions are met:

using Ceres.Graph.Flow;
using Cysharp.Threading.Tasks;
using UnityEngine;

public class ConditionalBreakpointTracker : FlowGraphTracker
{
    private readonly Func<ExecutableNode, bool> _condition;

    public ConditionalBreakpointTracker(Func<ExecutableNode, bool> condition)
    {
        _condition = condition;
    }

    public override UniTask EnterNode(ExecutableNode node)
    {
        if (_condition(node))
        {
            Debug.Break();   // Pops up Unity's debugger pause
        }
        return UniTask.CompletedTask;
    }
}

// Usage:
using (new ConditionalBreakpointTracker(n => n.GetTypeName().Contains("Log")).Auto())
{
    await graph.ExecuteEventAsync(null, "Start", null);
}

This approach allows you to set conditional breakpoints based on node type, GUID, or custom metadata without modifying the visual graph.

Performance Profiling

Build a lightweight profiler by tracking timing data in EnterNode and ExitNode:

public class PerfTracker : FlowGraphTracker
{
    private readonly Dictionary<string, NodeProfile> _profiles = new();
    private readonly Stack<NodeProfile> _stack = new();

    public override UniTask EnterNode(ExecutableNode node)
    {
        if (!_profiles.TryGetValue(node.Guid, out var p))
        {
            p = new NodeProfile { NodeName = node.GetTypeName(), NodeGuid = node.Guid };
            _profiles[node.Guid] = p;
        }
        p.Stopwatch.Restart();
        _stack.Push(p);
        return UniTask.CompletedTask;
    }

    public override UniTask ExitNode(ExecutableNode node)
    {
        var p = _stack.Pop();
        p.Stopwatch.Stop();
        p.TotalTicks += p.Stopwatch.ElapsedTicks;
        return UniTask.CompletedTask;
    }

    public override void Dispose()
    {
        foreach (var kv in _profiles)
        {
            var avgMs = (kv.Value.TotalTicks / (double)Stopwatch.Frequency) /
                        kv.Value.ExecutionCount * 1000;
            Debug.Log($"{kv.Value.NodeName}: {kv.Value.ExecutionCount} runs, Avg {avgMs:F3} ms");
        }
        base.Dispose();
    }
}

This profiler tracks average execution time per node, helping you identify bottlenecks in complex Flow graphs.

How Execution Tracking Works

The debugging system relies on a unified execution model defined in Runtime/Flow/FlowGraphObject.cs and Runtime/Flow/Models/FlowGraphTracker.cs.

When a graph executes via FlowGraphObjectBase, the runtime queries FlowGraphTracker.GetActiveTracker() to retrieve the current tracker. If no tracker is active, a no-op Empty tracker is returned, ensuring zero overhead in production builds.

The active tracker receives EnterNode and ExitNode callbacks for every ExecutableNode in the graph. This design allows the editor's visual debugger to function as a specialized tracker that pauses execution and renders port values, while custom code implementations can perform logging, profiling, or conditional breakpoints using the same callbacks.

Summary

  • Editor debugging provides visual breakpoints, step-by-step execution, and port inspection through the Flow editor toolbar without writing code.
  • Runtime debugging uses the FlowGraphTracker API in Runtime/Flow/Models/FlowGraphTracker.cs to implement custom breakpoints, logging, and profiling.
  • Auto-scope pattern with tracker.Auto() ensures proper cleanup and exception safety when using trackers programmatically.
  • Unified execution model means both editor and code debugging use the same EnterNode/ExitNode callbacks, ensuring consistent state representation.

Frequently Asked Questions

How do I set a breakpoint on a specific node in the Flow editor?

Right-click the target node in the Flow editor and select Add Breakpoint from the context menu. Once set, use the Next Breakpoint (⏭) button in the toolbar to execute the graph until it reaches that node, or step through manually using Next Frame (►) to observe execution flow.

Can I debug Flow graphs at runtime without using the editor?

Yes. Implement the FlowGraphTracker class from Runtime/Flow/Models/FlowGraphTracker.cs and override the EnterNode and ExitNode methods to add logging, profiling, or conditional breakpoints. Wrap your graph execution in a using statement with tracker.Auto() to ensure proper lifecycle management and automatic cleanup.

What is the difference between FlowGraphDependencyTracker and a custom FlowGraphTracker?

FlowGraphDependencyTracker is a built-in implementation that logs node execution entry/exit points and identifies missing dependencies between nodes. A custom FlowGraphTracker allows you to implement specialized behavior such as performance profiling, conditional breakpoints using Debug.Break(), or integration with external logging systems by overriding the virtual EnterNode and ExitNode methods.

Does enabling debug mode affect game performance?

The editor's debug mode pauses execution after each node, which inherently slows down execution to human-interactive speeds. For runtime debugging, the FlowGraphTracker system uses a no-op Empty tracker when no tracker is active, ensuring zero overhead in production builds. Only when a tracker is explicitly instantiated and registered via GetActiveTracker() does the runtime incur the cost of the EnterNode/ExitNode callbacks.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →