Table of Contents

ExecutableFunctionLibrary Guide

ExecutableFunctionLibrary is a powerful system for exposing API to Flow graphs with optimal performance. This guide explains how it works and best practices for creating your own function libraries.

Basic Usage

Create a partial class that inherits from ExecutableFunctionLibrary:

using Ceres.Graph.Flow;
using Ceres.Graph.Flow.Annotations;
using Ceres.Annotations;
using UnityEngine;

public partial class GameplayFunctionLibrary : ExecutableFunctionLibrary
{
    /// <summary>
    /// Calculates the distance between two world-space points.
    /// </summary>
    /// <param name="pointA">The first point.</param>
    /// <param name="pointB">The second point.</param>
    /// <returns>The distance between the two points.</returns>
    [ExecutableFunction]
    public static float CalculateDistance(Vector3 pointA, Vector3 pointB)
    {
        return Vector3.Distance(pointA, pointB);
    }
    
    [ExecutableFunction]
    public static string GetGameObjectName(GameObject gameObject)
    {
        return gameObject.name;
    }
}

Critical: You must add the partial modifier to your class. The source generator needs it to inject the registration code.

Tooltips: Add XML documentation comments to each exposed method. The Flow graph editor reads the method <summary>, <param>, and <returns> text and uses it as the node tooltip.

How Source Generator Works

When you compile your code, the source generator analyzes your ExecutableFunctionLibrary class and generates a partial implementation that registers all static methods marked with [ExecutableFunction].

What Gets Generated

For the example above, the source generator creates code like this:

/// <auto-generated>
/// This file is auto-generated by Ceres.SourceGenerator. 
/// All changes will be discarded.
/// </auto-generated>
[System.Runtime.CompilerServices.CompilerGenerated]
public partial class GameplayFunctionLibrary
{
    protected override unsafe void CollectExecutableFunctions()
    {
        RegisterExecutableFunctionPtr<GameplayFunctionLibrary>(
            nameof(CalculateDistance), 
            2, 
            (delegate* <Vector3, Vector3, float>)&CalculateDistance);
        
        RegisterExecutableFunctionPtr<GameplayFunctionLibrary>(
            nameof(GetGameObjectName), 
            1, 
            (delegate* <GameObject, string>)&GetGameObjectName);
    }
}

This generated code:

  1. Overrides CollectExecutableFunctions() method
  2. Registers each function using function pointers
  3. Includes parameter count for efficient lookup
  4. Registers file info for debugging (editor/dev builds only)

Complete Example: Game Logic Library

Here's a complete example of a game logic function library:

using Ceres.Graph.Flow;
using Ceres.Graph.Flow.Annotations;
using Ceres.Annotations;
using UnityEngine;

[CeresGroup("Gameplay")]
public partial class GameplayFunctionLibrary : ExecutableFunctionLibrary
{
    // Simple calculation function
    [ExecutableFunction, CeresLabel("Calculate Distance")]
    public static float Flow_CalculateDistance(Vector3 pointA, Vector3 pointB)
    {
        return Vector3.Distance(pointA, pointB);
    }
    
    // Function with ExecuteInDependency flag (runs in dependency path)
    [ExecutableFunction(ExecuteInDependency = true), CeresLabel("Get Player Position")]
    public static Vector3 Flow_GetPlayerPosition()
    {
        var player = GameObject.FindGameObjectWithTag("Player");
        return player != null ? player.transform.position : Vector3.zero;
    }
    
    // Function with IsScriptMethod and IsSelfTarget flags
    [ExecutableFunction(IsScriptMethod = true, IsSelfTarget = true), CeresLabel("Get Component")]
    public static Component Flow_GetComponent(GameObject target, Component component)
    {
        // IsSelfTarget automatically passes the target as first parameter
        return target.GetComponent(component.GetType());
    }
    
    // Function with ResolveReturn for dynamic return types
    [ExecutableFunction]
    public static Component Flow_FindComponent(
        GameObject target,
        [ResolveReturn] SerializedType<Component> componentType)
    {
        return target.GetComponent(componentType);
    }
    
    // Function with custom label to distinguish overloads
    [ExecutableFunction, CeresLabel("Log Message")]
    public static void Flow_Log(string message)
    {
        Debug.Log(message);
    }
    
    [ExecutableFunction, CeresLabel("Log Message with Color")]
    public static void Flow_Log(string message, Color color)
    {
        Debug.Log($"<color=#{ColorUtility.ToHtmlStringRGB(color)}>{message}</color>");
    }
}

ExecutableFunction Attribute Options

ExecuteInDependency

Marks the function to run in the dependency execution path instead of forward path. Useful for data retrieval functions that should run before dependent nodes.

[ExecutableFunction(ExecuteInDependency = true)]
public static float GetValue() { return 42.0f; }

IsScriptMethod

Treats the function as if it's an instance method on the target object. Useful for GameObject/Component operations.

[ExecutableFunction(IsScriptMethod = true, IsSelfTarget = true)]
public static string GetName(GameObject target) { return target.name; }

IsSelfTarget

Automatically passes the graph's container object as the first parameter. Only works with IsScriptMethod = true.

ResolveReturn

Used with SerializedType<T> parameters to dynamically resolve return types at runtime. Useful for generic operations.

[ExecutableFunction]
public static Component FindComponent(
    GameObject target,
    [ResolveReturn] SerializedType<Component> type)
{
    return target.GetComponent(type);
}

Best Practices

1. Use Meaningful Function Names

Prefix your functions with a namespace identifier (like Flow_) to avoid conflicts:

[ExecutableFunction]
public static void Flow_DoSomething() { }

2. Use CeresLabel for Overloads

When you have multiple functions with the same name but different parameters, use CeresLabel to distinguish them:

[ExecutableFunction, CeresLabel("Log Message")]
public static void Flow_Log(string message) { }

[ExecutableFunction, CeresLabel("Log Message with Color")]
public static void Flow_Log(string message, Color color) { }

3. Keep Parameter Count ≤ 6

Functions with more than 6 parameters use "Uber nodes" which have greater runtime overhead. If you need more parameters, consider using a struct or class.

// Good: 6 parameters
[ExecutableFunction]
public static void DoSomething(int a, int b, int c, int d, int e, int f) { }

// Avoid: 7+ parameters (uses Uber node)
[ExecutableFunction]
public static void DoSomething(int a, int b, int c, int d, int e, int f, int g) { }

4. Use ExecuteInDependency for Data Functions

Functions that retrieve data should use ExecuteInDependency = true:

[ExecutableFunction(ExecuteInDependency = true)]
public static float GetHealth() { return currentHealth; }

Use CeresGroup attribute to organize functions in the search window:

[CeresGroup("Math")]
public partial class MathFunctionLibrary : ExecutableFunctionLibrary { }

[CeresGroup("Gameplay")]
public partial class GameplayFunctionLibrary : ExecutableFunctionLibrary { }

6. Handle Null References

Always check for null when working with Unity objects:

[ExecutableFunction]
public static string GetName(GameObject obj)
{
    return obj != null ? obj.name : "Null";
}

Common Patterns

Pattern 1: Utility Functions

public partial class UtilityLibrary : ExecutableFunctionLibrary
{
    [ExecutableFunction]
    public static float Clamp(float value, float min, float max)
    {
        return Mathf.Clamp(value, min, max);
    }
}

Pattern 2: Game State Access

public partial class GameStateLibrary : ExecutableFunctionLibrary
{
    [ExecutableFunction(ExecuteInDependency = true)]
    public static int GetPlayerScore()
    {
        return GameManager.Instance.PlayerScore;
    }
}

Pattern 3: Component Operations

public partial class ComponentLibrary : ExecutableFunctionLibrary
{
    [ExecutableFunction(IsScriptMethod = true, IsSelfTarget = true)]
    public static Rigidbody GetRigidbody(GameObject target)
    {
        return target.GetComponent<Rigidbody>();
    }
}

Advanced: Custom Function Libraries

You can create multiple function libraries for different purposes:

// Math operations
[CeresGroup("Math")]
public partial class MathLibrary : ExecutableFunctionLibrary
{
    [ExecutableFunction]
    public static float Add(float a, float b) => a + b;
}

// String operations
[CeresGroup("String")]
public partial class StringLibrary : ExecutableFunctionLibrary
{
    [ExecutableFunction]
    public static string Concat(string a, string b) => a + b;
}

// Gameplay operations
[CeresGroup("Gameplay")]
public partial class GameplayLibrary : ExecutableFunctionLibrary
{
    [ExecutableFunction]
    public static void SpawnEnemy(Vector3 position) { }
}

Next Steps