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
{
[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.
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