Search Results for

    Show / Hide 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
    {
        [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:

    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; }
    

    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
    • Improve this Doc
    In This Article
    Back to top Copyright © 2025 AkiKurisu
    Generated with DocFX