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:
- Overrides
CollectExecutableFunctions()method - Registers each function using function pointers
- Includes parameter count for efficient lookup
- 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; }
5. Group Related Functions
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
- Learn about Custom Nodes for more complex logic
- Explore Generic Nodes for type-safe generic operations
- Check Executable Functions for instance method patterns
- See Code Generation for technical details